/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at
* trunk/opends/resource/legal-notices/OpenDS.LICENSE
* or https://OpenDS.dev.java.net/OpenDS.LICENSE.
* See the License 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
* trunk/opends/resource/legal-notices/OpenDS.LICENSE. 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 2006-2008 Sun Microsystems, Inc.
*/
package org.opends.server.core;
import org.opends.messages.Message;
import static org.opends.server.loggers.ErrorLogger.*;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.messages.ConfigMessages.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import org.opends.server.admin.ClassPropertyDefinition;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.admin.server.ServerManagementContext;
import org.opends.server.admin.std.meta.AccessControlHandlerCfgDefn;
import org.opends.server.admin.std.server.AccessControlHandlerCfg;
import org.opends.server.admin.std.server.RootCfg;
import org.opends.server.api.AccessControlHandler;
import org.opends.server.api.AlertGenerator;
import org.opends.server.config.ConfigException;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.DN;
import org.opends.server.types.InitializationException;
import org.opends.server.types.ResultCode;
/**
* This class manages the application-wide access-control configuration.
* <p>
* When access control is disabled a default "permissive" access control
* implementation is used, which permits all operations regardless of the
* identity of the user.
*/
public final class AccessControlConfigManager
implements AlertGenerator ,
ConfigurationChangeListener<AccessControlHandlerCfg>
{
/**
* The tracer object for the debug logger.
*/
private static final DebugTracer TRACER = getTracer();
// Fully qualified class name.
private static final String CLASS_NAME =
"org.opends.server.core.AccessControlConfigManager";
// The single application-wide instance.
private static AccessControlConfigManager instance = null;
// The active access control implementation.
private AtomicReference<AccessControlHandler> accessControlHandler;
// The current configuration.
private AccessControlHandlerCfg currentConfiguration;
/**
* Creates a new instance of this access control configuration
* manager.
*/
private AccessControlConfigManager()
{
this.accessControlHandler = new AtomicReference<AccessControlHandler>(
new DefaultAccessControlHandler());
this.currentConfiguration = null;
}
/**
* Get the single application-wide access control manager instance.
*
* @return The access control manager.
*/
public static AccessControlConfigManager getInstance()
{
if (instance == null)
{
instance = new AccessControlConfigManager();
}
return instance;
}
/**
* Determine if access control is enabled according to the current
* configuration.
*
* @return {@code true} if access control is enabled, {@code false}
* otherwise.
*/
public boolean isAccessControlEnabled()
{
return currentConfiguration.isEnabled();
}
/**
* Get the active access control handler.
* <p>
* When access control is disabled, this method returns a default access
* control implementation which permits all operations.
*
* @return The active access control handler (never {@code null}).
*/
public AccessControlHandler<?> getAccessControlHandler()
{
return accessControlHandler.get();
}
/**
* Initializes the access control sub-system. This should only be
* called at Directory Server startup. If an error occurs then an
* exception will be thrown and the Directory Server will fail to
* start (this prevents accidental exposure of user data due to
* misconfiguration).
*
* @throws ConfigException
* If an access control configuration error is detected.
* @throws InitializationException
* If a problem occurs while initializing the access control
* handler that is not related to the Directory Server
* configuration.
*/
public void initializeAccessControl()
throws ConfigException, InitializationException
{
// Get the root configuration object.
ServerManagementContext managementContext =
ServerManagementContext.getInstance();
RootCfg rootConfiguration =
managementContext.getRootConfiguration();
// Don't register as an add and delete listener with the root configuration
// as we can have only one object at a given time.
// //Initialize the current Access control.
AccessControlHandlerCfg accessControlConfiguration =
rootConfiguration.getAccessControlHandler();
// We have a valid usable entry, so register a change listener in
// order to handle configuration changes.
accessControlConfiguration.addChangeListener(this);
//This makes TestCaseUtils.reStartServer happy.
currentConfiguration=null;
// The configuration looks valid, so install it.
updateConfiguration(accessControlConfiguration);
}
/**
* Updates the access control configuration based on the contents of a
* valid configuration entry.
*
* @param newConfiguration The new configuration object.
*
* @throws ConfigException If the access control configuration is invalid.
*
* @throws InitializationException If the access control handler provider
* could not be instantiated.
*/
private void updateConfiguration(AccessControlHandlerCfg newConfiguration)
throws ConfigException, InitializationException
{
String newHandlerClass = null;
boolean enabledOld = false, enabledNew = newConfiguration.isEnabled();
if (currentConfiguration == null)
{
// Initialization phase.
if (enabledNew)
{
newHandlerClass = newConfiguration.getJavaClass();
}
else
{
newHandlerClass = DefaultAccessControlHandler.class.getName();
}
//Get a new handler, initialize it and make it the current handler.
accessControlHandler.getAndSet(getHandler(newHandlerClass,
newConfiguration, true, false));
} else {
enabledOld = currentConfiguration.isEnabled();
if(enabledNew) {
//Access control is either being enabled or a attribute in the
//configuration has changed such as class name or a global ACI.
newHandlerClass = newConfiguration.getJavaClass();
String oldHandlerClass = currentConfiguration.getJavaClass();
//Check if moving from not enabled to enabled state.
if(!enabledOld) {
AccessControlHandler oldHandler =
accessControlHandler.getAndSet(getHandler(newHandlerClass,
newConfiguration, true,
true));
oldHandler.finalizeAccessControlHandler();
} else {
//Check if the class name is being changed.
if(!newHandlerClass.equals(oldHandlerClass)) {
AccessControlHandler oldHandler =
accessControlHandler.getAndSet(getHandler(newHandlerClass,
newConfiguration, true, true));
oldHandler.finalizeAccessControlHandler();
} else {
//Some other attribute has changed, try to get a new non-initialized
//handler, but keep the old handler.
getHandler(newHandlerClass,newConfiguration, false, false);
}
}
} else if (enabledOld && (! enabledNew)) {
//Access control has been disabled, switch to the default handler and
//finalize the old handler.
newHandlerClass = DefaultAccessControlHandler.class.getName();
AccessControlHandler oldHandler =
accessControlHandler.getAndSet(getHandler(newHandlerClass,
newConfiguration, false, true));
oldHandler.finalizeAccessControlHandler();
}
}
// Switch in the local configuration.
currentConfiguration = newConfiguration;
}
/**
* Instantiates a new Access Control Handler using the specified class name,
* configuration.
*
* @param handlerClassName The name of the handler to instantiate.
* @param config The configuration to use when instantiating a new handler.
* @param initHandler <code>True</code> if the new handler should be
* initialized.
* @param logMessage <code>True</code> if an error message should be logged
* and an alert should be sent.
* @return The newly instantiated handler.
*
* @throws InitializationException If an error occurs instantiating the
* the new handler.
*/
AccessControlHandler<? extends AccessControlHandlerCfg>
getHandler(String handlerClassName, AccessControlHandlerCfg config,
boolean initHandler, boolean logMessage)
throws InitializationException {
AccessControlHandler<? extends AccessControlHandlerCfg> newHandler;
try {
if(handlerClassName.equals(DefaultAccessControlHandler.class.getName())) {
newHandler = new DefaultAccessControlHandler();
newHandler.initializeAccessControlHandler(null);
if(logMessage) {
Message message = WARN_CONFIG_AUTHZ_DISABLED.get();
logError(message);
if (currentConfiguration != null) {
DirectoryServer.sendAlertNotification(this,
ALERT_TYPE_ACCESS_CONTROL_DISABLED, message);
}
}
} else {
newHandler = loadHandler(handlerClassName, config, initHandler);
if(logMessage) {
Message message = NOTE_CONFIG_AUTHZ_ENABLED.get(handlerClassName);
logError(message);
if (currentConfiguration != null) {
DirectoryServer.sendAlertNotification(this,
ALERT_TYPE_ACCESS_CONTROL_ENABLED, message);
}
}
}
} catch (Exception e) {
if (debugEnabled()) {
TRACER.debugCaught(DebugLogLevel.ERROR, e);
}
Message message = ERR_CONFIG_AUTHZ_UNABLE_TO_INSTANTIATE_HANDLER.
get(handlerClassName, String.valueOf(config.dn().toString()),
stackTraceToSingleLineString(e));
throw new InitializationException(message, e);
}
return newHandler;
}
/**
* {@inheritDoc}
*/
public boolean isConfigurationChangeAcceptable(
AccessControlHandlerCfg configuration,
List<Message> unacceptableReasons)
{
try
{
// If the access control handler is disabled, we don't care about the
// configuration. If it is enabled, then all we care about is whether we
// can load the access control handler class.
if (configuration.isEnabled())
{
loadHandler(configuration.getJavaClass(), configuration, false);
}
}
catch (InitializationException e)
{
unacceptableReasons.add(e.getMessageObject());
return false;
}
return true;
}
/**
* {@inheritDoc}
*/
public ConfigChangeResult applyConfigurationChange(
AccessControlHandlerCfg configuration)
{
ResultCode resultCode = ResultCode.SUCCESS;
ArrayList<Message> messages = new ArrayList<Message>();
try
{
// Attempt to install the new configuration.
updateConfiguration(configuration);
}
catch (ConfigException e)
{
messages.add(e.getMessageObject());
resultCode = ResultCode.CONSTRAINT_VIOLATION;
}
catch (InitializationException e)
{
messages.add(e.getMessageObject());
resultCode = DirectoryServer.getServerErrorResultCode();
}
return new ConfigChangeResult(resultCode, false, messages);
}
/**
* {@inheritDoc}
*/
public DN getComponentEntryDN()
{
return currentConfiguration.dn();
}
/**
* {@inheritDoc}
*/
public String getClassName()
{
return CLASS_NAME;
}
/**
* {@inheritDoc}
*/
public LinkedHashMap<String,String> getAlerts()
{
LinkedHashMap<String,String> alerts = new LinkedHashMap<String,String>();
alerts.put(ALERT_TYPE_ACCESS_CONTROL_DISABLED,
ALERT_DESCRIPTION_ACCESS_CONTROL_DISABLED);
alerts.put(ALERT_TYPE_ACCESS_CONTROL_ENABLED,
ALERT_DESCRIPTION_ACCESS_CONTROL_ENABLED);
return alerts;
}
/**
* Loads the specified class, instantiates it as a AccessControlHandler, and
* optionally initializes that instance.
*
* @param className The fully-qualified name of the Access Control
* provider class to load, instantiate, and initialize.
* @param configuration The configuration to use to initialize the
* Access Control Handler. It must not be
* {@code null}.
* @param initialize Indicates whether the access control handler
* instance should be initialized.
*
* @return The possibly initialized Access Control Handler.
*
* @throws InitializationException If a problem occurred while attempting to
* initialize the Access Control Handler.
*/
private AccessControlHandler<? extends AccessControlHandlerCfg>
loadHandler(String className,
AccessControlHandlerCfg configuration,
boolean initialize)
throws InitializationException
{
try
{
AccessControlHandlerCfgDefn definition =
AccessControlHandlerCfgDefn.getInstance();
ClassPropertyDefinition propertyDefinition =
definition.getJavaClassPropertyDefinition();
Class<? extends AccessControlHandler> providerClass =
propertyDefinition.loadClass(className, AccessControlHandler.class);
AccessControlHandler<? extends AccessControlHandlerCfg> provider =
(AccessControlHandler<? extends AccessControlHandlerCfg>)
providerClass.newInstance();
if (configuration != null)
{
Method method = provider.getClass().getMethod(
"initializeAccessControlHandler",
configuration.configurationClass());
if(initialize) {
method.invoke(provider, configuration);
}
}
else
{
Method method =
provider.getClass().getMethod("isConfigurationAcceptable",
AccessControlHandlerCfg.class,
List.class);
List<Message> unacceptableReasons = new ArrayList<Message>();
Boolean acceptable = (Boolean) method.invoke(provider, configuration,
unacceptableReasons);
if (! acceptable)
{
StringBuilder buffer = new StringBuilder();
if (! unacceptableReasons.isEmpty())
{
Iterator<Message> iterator = unacceptableReasons.iterator();
buffer.append(iterator.next());
while (iterator.hasNext())
{
buffer.append(". ");
buffer.append(iterator.next());
}
}
Message message = ERR_CONFIG_AUTHZ_CONFIG_NOT_ACCEPTABLE.get(
// Bug: where in a section where configuration is null
// WAS: String.valueOf( configuration.dn())
// Now:
"null"
, buffer.toString());
throw new InitializationException(message);
}
}
return provider;
}
catch (Exception e)
{
Message message = ERR_CONFIG_AUTHZ_UNABLE_TO_INSTANTIATE_HANDLER.
get(className, String.valueOf(configuration.dn()),
stackTraceToSingleLineString(e));
throw new InitializationException(message, e);
}
}
}