package org.dcache.gplazma.strategies;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collections;
import java.util.List;
import org.dcache.util.NDC;
import org.dcache.gplazma.AuthenticationException;
import org.dcache.gplazma.configuration.ConfigurationItemControl;
import org.dcache.gplazma.plugins.GPlazmaPlugin;
import static org.dcache.gplazma.configuration.ConfigurationItemControl.SUFFICIENT;
/**
* This class provides a common mechanism to iterate through a list of
* plugins that have been defined with a PAM-like configuration. Calling
* #callPlugins will iterate through the list, honouring the control settings
* (REQUIRED, REQUISIT, OPTIONAL, etc).
* <p/>
* The exact invocation is chain-specific: an AUTH plugin is called differently
* from how a MAP plugin is called. Therefore, the task of calling each plugin
* is delegated to the supplied PluginCaller object.
*/
public class PAMStyleStrategy<T extends GPlazmaPlugin>
{
private static final Logger logger =
LoggerFactory.getLogger(PAMStyleStrategy.class);
public List<GPlazmaPluginService<T>> pluginElements;
/**
* creates a new instance of the PAMStyleStrategy
* @param pluginElements
*/
public PAMStyleStrategy(List<GPlazmaPluginService<T>> pluginElements)
{
this.pluginElements = Collections.unmodifiableList(pluginElements);
}
/**
* Execute the the
* {@link PluginCaller#call(GPlazmaPluginService)}
* methods of the plugins supplied in
* {@link PAMStyleStrategy(List<T>) constructor}
* in the order of the plugin elements in the list.
* The implementation attempts to mimic the following PAM standard execution
* policies based on the contol flag.
* <br>
* Source:
* <i href="http://www.redhat.com/docs/manuals/linux/RHL-8.0-Manual/ref-guide/s1-pam-control-flags.html">
* Red Hat Manual, PAM Module Control Flags </i>
* <br>
* Four types of control flags are defined by the PAM standard:
* <br>
* <ul>
* <li>
* required - the module must be successfully checked in order to allow
* authentication. If a required module check fails, the user is not
* notified until all other modules of the same module type
* have been checked.
* </li>
* <li>
* requisite - the module must be successfully checked in order for the
* authentication to be successful. However, if a requisite module check
* fails, the user is notified immediately with a message reflecting the
* first failed required or requisite module.
* </li>
* <li>
* sufficient - the module checks are ignored if it fails. But, if a
* sufficient flagged module is successfully checked and no required
* flagged modules above it have failed, then no other modules of this
* module type are checked and the user is authenticated.
* </li>
* <li>
* optional - the module checks are ignored if it fails. If the module
* check is successful, it does not play a role in the overall success
* or failure for that module type. The only time a module flagged as
* optional is necessary for successful authentication is when no other
* modules of that type have succeeded or failed. In this case, an optional
* module determines the overall PAM authentication for that module type.
* </li>
* </ul>
*/
public void callPlugins(PluginCaller<T> caller)
throws AuthenticationException
{
AuthenticationException firstRequiredPluginException=null;
for(GPlazmaPluginService<T> pluginElement: pluginElements) {
ConfigurationItemControl control = pluginElement.getControl();
NDC ndc = NDC.cloneNdc();
try {
NDC.push(pluginElement.getName());
try {
caller.call(pluginElement);
} catch(RuntimeException e) {
logger.error("Bug in plugin: ", e);
throw new AuthenticationException("bug in plugin " +
pluginElement.getName() + ": " + e.getMessage());
}
logger.debug("{} plugin completed", control.name());
if(control == SUFFICIENT) {
return;
}
} catch (AuthenticationException currentPluginException) {
logger.debug("{} plugin failed: {}", control.name(),
currentPluginException.getMessage());
switch (control) {
case SUFFICIENT:
case OPTIONAL:
break;
case REQUIRED:
if (firstRequiredPluginException == null) {
firstRequiredPluginException = currentPluginException;
}
break;
case REQUISITE:
if (firstRequiredPluginException != null) {
throw firstRequiredPluginException;
}
throw currentPluginException;
default:
//do nothing
}
} finally {
NDC.set(ndc);
}
}
if(firstRequiredPluginException != null) {
logger.info("all session plugins ran, at least one required failed, throwing exception : "+
firstRequiredPluginException);
throw firstRequiredPluginException;
}
}
}