/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* See LICENSE.txt included in this distribution for the specific
* language governing permissions and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at LICENSE.txt.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
*/
package org.opensolaris.opengrok.authorization;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.http.HttpServletRequest;
import org.opensolaris.opengrok.configuration.Group;
import org.opensolaris.opengrok.configuration.Nameable;
import org.opensolaris.opengrok.configuration.Project;
import org.opensolaris.opengrok.logger.LoggerFactory;
/**
* This is a subclass of {@link AuthorizationEntity} and is a wrapper to a
* {@link IAuthorizationPlugin} delegating the decision methods to the contained
* plugin.
*
* @author Krystof Tulinger
*/
public class AuthorizationPlugin extends AuthorizationStack {
private static final Logger LOGGER = LoggerFactory.getLogger(AuthorizationPlugin.class);
private static final long serialVersionUID = 2L;
private transient IAuthorizationPlugin plugin;
public AuthorizationPlugin() {
}
/**
* Clone the plugin:
* <ul>
* <li>copy the superclass {@link AuthorizationStack}</li>
* <li>sets the plugin to {@code null}</li>
* </ul>
*
* @param x the plugin to be copied
*/
public AuthorizationPlugin(AuthorizationPlugin x) {
super(x);
plugin = null;
}
public AuthorizationPlugin(AuthControlFlag flag, IAuthorizationPlugin plugin) {
this(flag, plugin.getClass().getCanonicalName() == null ? plugin.getClass().getName() : plugin.getClass().getCanonicalName(), plugin);
}
public AuthorizationPlugin(AuthControlFlag flag, String name) {
this(flag, name, null);
}
public AuthorizationPlugin(AuthControlFlag flag, String name, IAuthorizationPlugin plugin) {
super(flag, name);
this.plugin = plugin;
}
/**
* Call the load method on the underlying plugin if the plugin exists. Note
* that the load method can throw any throwable from its body and it should
* not stop the application.
*
* <p>
* If the method is unable to load the plugin because of any reason (mostly
* the class is not found, not instantiable or the load method throws an
* exception) then any authorization check should fail for this plugin in
* the future.
* </p>
*
* @param parameters parameters given in the configuration
*
* @see IAuthorizationPlugin#load(java.util.Map)
*/
@Override
public synchronized void load(Map<String, Object> parameters) {
// fill properly the "forGroups" and "forProjects" fields
processTargetGroupsAndProjects();
if (!hasPlugin()) {
LOGGER.log(Level.SEVERE, "Configured plugin \"{0}\" has not been loaded into JVM (missing file?). "
+ "This can cause the authorization to fail always.",
getName());
setFailed();
LOGGER.log(Level.INFO, "[{0}] Plugin \"{1}\" {2} and is {3}.",
new Object[]{
getFlag().toString().toUpperCase(),
getName(),
hasPlugin() ? "found" : "not found",
isWorking() ? "working" : "failed"});
return;
}
Map<String, Object> s = new TreeMap<>();
s.putAll(parameters);
s.putAll(getSetup());
try {
plugin.load(s);
setWorking();
} catch (Throwable ex) {
LOGGER.log(Level.SEVERE, "Plugin \"" + getName() + "\" has failed while loading with exception:", ex);
setFailed();
}
LOGGER.log(Level.INFO, "[{0}] Plugin \"{1}\" {2} and is {3}.",
new Object[]{
getFlag().toString().toUpperCase(),
getName(),
hasPlugin() ? "found" : "not found",
isWorking() ? "working" : "failed"});
}
/**
* Call the unload method on the underlying plugin if the plugin exists.
* Note that the unload method can throw any throwable from its body and it
* should not stop the application.
*
* @see IAuthorizationPlugin#unload()
*/
@Override
public synchronized void unload() {
if (hasPlugin()) {
try {
plugin.unload();
plugin = null;
} catch (Throwable ex) {
LOGGER.log(Level.SEVERE, "Plugin \"" + getName() + "\" has failed while unloading with exception:", ex);
}
}
}
/**
* Test the underlying plugin with the predicate if and only if the plugin
* is not marked as failed.
*
* @param entity the given entity - this is either group or project and is
* passed just for the logging purposes.
* @param pluginPredicate predicate returning true or false for the given
* entity which determines if the authorization for such entity is
* successful or failed for particular request and plugin
* @param skippingPredicate predicate returning true if this authorization
* entity should be omitted from the authorization process
* @return true if the plugin is not failed and the project is allowed;
* false otherwise
*
* @see #isFailed()
* @see IAuthorizationPlugin#isAllowed(HttpServletRequest, Project)
* @see IAuthorizationPlugin#isAllowed(HttpServletRequest, Group)
*/
@Override
public boolean isAllowed(Nameable entity,
AuthorizationEntity.PluginDecisionPredicate pluginPredicate,
AuthorizationEntity.PluginSkippingPredicate skippingPredicate) {
/**
* We don't check the skippingPredicate here as this instance is
* <b>always</b> a part of some stack (may be the default stack) and the
* stack checks the skipping predicate before invoking this method.
*
* @see AuthorizationStack#processStack
*/
if (isFailed()) {
return false;
}
return pluginPredicate.decision(this.plugin);
}
/**
* Set the plugin to this entity if this entity requires this plugin class
* in the configuration. This creates a new instance of the plugin for each
* class which needs it.
*
* @param plugin the new instance of a plugin
* @return true if there is the class names are equal and the plugin is not
* null; false otherwise
*/
@Override
public boolean setPlugin(IAuthorizationPlugin plugin) {
if (!getName().equals(plugin.getClass().getCanonicalName())
|| !getName().equals(plugin.getClass().getName())) {
return false;
}
if (hasPlugin()) {
unload();
}
try {
/**
* The exception should not happen here as we already have an
* instance of IAuthoriazationPlugin. But it is required by the
* compiler.
*
* NOTE: If we were to add a throws clause here we would interrupt
* the whole stack walk through and prevent the other authorization
* entities to work properly.
*/
return (this.plugin = plugin.getClass().newInstance()) != null;
} catch (InstantiationException ex) {
LOGGER.log(Level.INFO, "Class could not be instantiated: ", ex);
} catch (IllegalAccessException ex) {
LOGGER.log(Level.INFO, "Class loader threw an exception: ", ex);
} catch (Throwable ex) {
LOGGER.log(Level.INFO, "Class loader threw an uknown error: ", ex);
}
return false;
}
/**
* Get the authorization plugin
*
* @return the underlying plugin
*/
protected IAuthorizationPlugin getPlugin() {
return plugin;
}
/**
* Check if the plugin exists and has not failed while loading.
*
* @return true if working, false otherwise
*/
@Override
public boolean isWorking() {
return working && hasPlugin();
}
/**
* Check if the plugin class was found for this plugin.
*
* @return true if was; false otherwise
*/
public boolean hasPlugin() {
return plugin != null;
}
/**
* Clone the plugin:
* <ul>
* <li>copy the superclass {@link AuthorizationStack}</li>
* <li>sets the plugin to {@code null}</li>
* </ul>
*
* @return new instance of {@link AuthorizationPlugin}
*/
@Override
public AuthorizationPlugin clone() {
return new AuthorizationPlugin(this);
}
}