001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2006-2008 Sun Microsystems, Inc.
015 * Portions Copyright 2014-2016 ForgeRock AS.
016 */
017package org.opends.server.core;
018
019import static org.opends.messages.ConfigMessages.*;
020import static org.opends.server.util.ServerConstants.*;
021import static org.opends.server.util.StaticUtils.*;
022
023import java.util.ArrayList;
024import java.util.LinkedHashMap;
025import java.util.List;
026import java.util.concurrent.atomic.AtomicReference;
027
028import org.forgerock.i18n.LocalizableMessage;
029import org.forgerock.i18n.slf4j.LocalizedLogger;
030import org.forgerock.opendj.config.server.ConfigException;
031import org.forgerock.opendj.ldap.ResultCode;
032import org.forgerock.util.Utils;
033import org.opends.server.admin.ClassPropertyDefinition;
034import org.opends.server.admin.server.ConfigurationChangeListener;
035import org.opends.server.admin.server.ServerManagementContext;
036import org.opends.server.admin.std.meta.AccessControlHandlerCfgDefn;
037import org.opends.server.admin.std.server.AccessControlHandlerCfg;
038import org.opends.server.admin.std.server.RootCfg;
039import org.opends.server.api.AccessControlHandler;
040import org.opends.server.api.AlertGenerator;
041import org.forgerock.opendj.config.server.ConfigChangeResult;
042import org.forgerock.opendj.ldap.DN;
043import org.opends.server.types.InitializationException;
044
045/**
046 * This class manages the application-wide access-control configuration.
047 * <p>
048 * When access control is disabled a default "permissive" access control
049 * implementation is used, which permits all operations regardless of the
050 * identity of the user.
051 */
052public final class AccessControlConfigManager
053       implements AlertGenerator ,
054                  ConfigurationChangeListener<AccessControlHandlerCfg>
055{
056  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
057
058  private static final String CLASS_NAME =
059    "org.opends.server.core.AccessControlConfigManager";
060
061  /** The single application-wide instance. */
062  private static AccessControlConfigManager instance;
063
064  /** The active access control implementation. */
065  private AtomicReference<AccessControlHandler> accessControlHandler;
066
067  /** The current configuration. */
068  private AccessControlHandlerCfg currentConfiguration;
069
070  private ServerContext serverContext;
071
072  /**
073   * Creates a new instance of this access control configuration
074   * manager.
075   */
076  private AccessControlConfigManager()
077  {
078    this.accessControlHandler = new AtomicReference<AccessControlHandler>(
079        new DefaultAccessControlHandler());
080    this.currentConfiguration = null;
081  }
082
083
084
085  /**
086   * Get the single application-wide access control manager instance.
087   *
088   * @return The access control manager.
089   */
090  public static AccessControlConfigManager getInstance()
091  {
092    if (instance == null)
093    {
094      instance = new AccessControlConfigManager();
095    }
096
097    return instance;
098  }
099
100
101
102  /**
103   * Determine if access control is enabled according to the current
104   * configuration.
105   *
106   * @return  {@code true} if access control is enabled, {@code false}
107   *          otherwise.
108   */
109  public boolean isAccessControlEnabled()
110  {
111    return currentConfiguration.isEnabled();
112  }
113
114
115
116  /**
117   * Get the active access control handler.
118   * <p>
119   * When access control is disabled, this method returns a default access
120   * control implementation which permits all operations.
121   *
122   * @return   The active access control handler (never {@code null}).
123   */
124  public AccessControlHandler<?> getAccessControlHandler()
125  {
126    return accessControlHandler.get();
127  }
128
129
130
131  /**
132   * Initializes the access control sub-system. This should only be called at
133   * Directory Server startup. If an error occurs then an exception will be
134   * thrown and the Directory Server will fail to start (this prevents
135   * accidental exposure of user data due to misconfiguration).
136   *
137   * @param serverContext
138   *          The server context.
139   * @throws ConfigException
140   *           If an access control configuration error is detected.
141   * @throws InitializationException
142   *           If a problem occurs while initializing the access control handler
143   *           that is not related to the Directory Server configuration.
144   */
145  public void initializeAccessControl(ServerContext serverContext)
146         throws ConfigException, InitializationException
147  {
148    this.serverContext = serverContext;
149    // Get the root configuration object.
150    ServerManagementContext managementContext =
151         ServerManagementContext.getInstance();
152    RootCfg rootConfiguration =
153         managementContext.getRootConfiguration();
154
155    // Don't register as an add and delete listener with the root configuration
156    // as we can have only one object at a given time.
157
158    // //Initialize the current Access control.
159    AccessControlHandlerCfg accessControlConfiguration =
160           rootConfiguration.getAccessControlHandler();
161
162    // We have a valid usable entry, so register a change listener in
163    // order to handle configuration changes.
164    accessControlConfiguration.addChangeListener(this);
165
166    //This makes TestCaseUtils.reStartServer happy.
167    currentConfiguration=null;
168
169    // The configuration looks valid, so install it.
170    updateConfiguration(accessControlConfiguration);
171  }
172
173
174
175  /**
176   * Updates the access control configuration based on the contents of a
177   * valid configuration entry.
178   *
179   * @param  newConfiguration  The new configuration object.
180   *
181   * @throws  ConfigException If the access control configuration is invalid.
182   *
183   * @throws  InitializationException  If the access control handler provider
184   *                                   could not be instantiated.
185   */
186
187  private void updateConfiguration(AccessControlHandlerCfg newConfiguration)
188          throws ConfigException, InitializationException
189  {
190    String newHandlerClass = null;
191    boolean enabledOld = false, enabledNew = newConfiguration.isEnabled();
192
193    if (currentConfiguration == null)
194    {
195      // Initialization phase.
196      if (enabledNew)
197      {
198        newHandlerClass = newConfiguration.getJavaClass();
199      }
200      else
201      {
202        newHandlerClass = DefaultAccessControlHandler.class.getName();
203      }
204      //Get a new handler, initialize it and make it the current handler.
205      accessControlHandler.getAndSet(getHandler(newHandlerClass,
206              newConfiguration, true, false));
207    } else {
208      enabledOld = currentConfiguration.isEnabled();
209      if(enabledNew) {
210        //Access control is either being enabled or a attribute in the
211        //configuration has changed such as class name or a global ACI.
212        newHandlerClass = newConfiguration.getJavaClass();
213        String oldHandlerClass = currentConfiguration.getJavaClass();
214        //Check if moving from not enabled to enabled state.
215        if(!enabledOld) {
216           AccessControlHandler oldHandler =
217                   accessControlHandler.getAndSet(getHandler(newHandlerClass,
218                                                  newConfiguration, true,
219                                                  true));
220           oldHandler.finalizeAccessControlHandler();
221        } else {
222          //Check if the class name is being changed.
223          if(!newHandlerClass.equals(oldHandlerClass)) {
224           AccessControlHandler oldHandler =
225            accessControlHandler.getAndSet(getHandler(newHandlerClass,
226                    newConfiguration, true, true));
227            oldHandler.finalizeAccessControlHandler();
228          } else {
229            //Some other attribute has changed, try to get a new non-initialized
230            //handler, but keep the old handler.
231            getHandler(newHandlerClass,newConfiguration, false, false);
232          }
233        }
234      } else if (enabledOld && !enabledNew) {
235        //Access control has been disabled, switch to the default handler and
236        //finalize the old handler.
237        newHandlerClass = DefaultAccessControlHandler.class.getName();
238        AccessControlHandler oldHandler =
239                accessControlHandler.getAndSet(getHandler(newHandlerClass,
240                        newConfiguration, false, true));
241        oldHandler.finalizeAccessControlHandler();
242      }
243    }
244    // Switch in the local configuration.
245    currentConfiguration = newConfiguration;
246  }
247
248  /**
249   * Instantiates a new Access Control Handler using the specified class name,
250   * configuration.
251   *
252   * @param handlerClassName The name of the handler to instantiate.
253   * @param config The configuration to use when instantiating a new handler.
254   * @param initHandler <code>True</code> if the new handler should be
255   *                    initialized.
256   * @param logMessage <code>True</code> if an error message should be logged
257   *                                     and an alert should be sent.
258   * @return The newly instantiated handler.
259   *
260   * @throws InitializationException  If an error occurs instantiating the
261   *                                  the new handler.
262   */
263  AccessControlHandler<? extends AccessControlHandlerCfg>
264  getHandler(String handlerClassName, AccessControlHandlerCfg config,
265             boolean initHandler, boolean logMessage)
266          throws InitializationException {
267    AccessControlHandler<? extends AccessControlHandlerCfg> newHandler;
268    try {
269      if(handlerClassName.equals(DefaultAccessControlHandler.class.getName())) {
270        newHandler = new DefaultAccessControlHandler();
271        newHandler.initializeAccessControlHandler(null);
272        if(logMessage) {
273          LocalizableMessage message = WARN_CONFIG_AUTHZ_DISABLED.get();
274          logger.warn(message);
275          if (currentConfiguration != null) {
276            DirectoryServer.sendAlertNotification(this,
277                    ALERT_TYPE_ACCESS_CONTROL_DISABLED, message);
278          }
279        }
280      } else {
281        newHandler = loadHandler(handlerClassName, config, initHandler);
282        if(logMessage) {
283          LocalizableMessage message = NOTE_CONFIG_AUTHZ_ENABLED.get(handlerClassName);
284          logger.info(message);
285          if (currentConfiguration != null) {
286            DirectoryServer.sendAlertNotification(this,
287                    ALERT_TYPE_ACCESS_CONTROL_ENABLED, message);
288          }
289        }
290      }
291    } catch (Exception e) {
292      logger.traceException(e);
293      LocalizableMessage message = ERR_CONFIG_AUTHZ_UNABLE_TO_INSTANTIATE_HANDLER.
294              get(handlerClassName, config.dn(), stackTraceToSingleLineString(e));
295      throw new InitializationException(message, e);
296    }
297    return newHandler;
298  }
299
300
301  /** {@inheritDoc} */
302  @Override
303  public boolean isConfigurationChangeAcceptable(
304                      AccessControlHandlerCfg configuration,
305                      List<LocalizableMessage> unacceptableReasons)
306  {
307    try
308    {
309      // If the access control handler is disabled, we don't care about the
310      // configuration.  If it is enabled, then all we care about is whether we
311      // can load the access control handler class.
312      if (configuration.isEnabled())
313      {
314        loadHandler(configuration.getJavaClass(), configuration, false);
315      }
316    }
317    catch (InitializationException e)
318    {
319      unacceptableReasons.add(e.getMessageObject());
320      return false;
321    }
322
323    return true;
324  }
325
326
327
328  /** {@inheritDoc} */
329  @Override
330  public ConfigChangeResult applyConfigurationChange(
331                                 AccessControlHandlerCfg configuration)
332  {
333    final ConfigChangeResult ccr = new ConfigChangeResult();
334
335    try
336    {
337      // Attempt to install the new configuration.
338      updateConfiguration(configuration);
339    }
340    catch (ConfigException e)
341    {
342      ccr.addMessage(e.getMessageObject());
343      ccr.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
344    }
345    catch (InitializationException e)
346    {
347      ccr.addMessage(e.getMessageObject());
348      ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
349    }
350
351    return ccr;
352  }
353
354
355
356  /** {@inheritDoc} */
357  @Override
358  public DN getComponentEntryDN()
359  {
360    return currentConfiguration.dn();
361  }
362
363
364
365  /** {@inheritDoc} */
366  @Override
367  public String getClassName()
368  {
369    return CLASS_NAME;
370  }
371
372
373
374  /** {@inheritDoc} */
375  @Override
376  public LinkedHashMap<String,String> getAlerts()
377  {
378    LinkedHashMap<String,String> alerts = new LinkedHashMap<>();
379
380    alerts.put(ALERT_TYPE_ACCESS_CONTROL_DISABLED,
381               ALERT_DESCRIPTION_ACCESS_CONTROL_DISABLED);
382    alerts.put(ALERT_TYPE_ACCESS_CONTROL_ENABLED,
383               ALERT_DESCRIPTION_ACCESS_CONTROL_ENABLED);
384
385    return alerts;
386  }
387
388
389
390  /**
391   * Loads the specified class, instantiates it as a AccessControlHandler, and
392   * optionally initializes that instance.
393   *
394   * @param  className      The fully-qualified name of the Access Control
395   *                        provider class to load, instantiate, and initialize.
396   * @param  configuration  The configuration to use to initialize the
397   *                        Access Control Handler.  It must not be
398   *                        {@code null}.
399   * @param  initialize     Indicates whether the access control handler
400   *                        instance should be initialized.
401   *
402   * @return  The possibly initialized Access Control Handler.
403   *
404   * @throws  InitializationException  If a problem occurred while attempting to
405   *                                   initialize the Access Control Handler.
406   */
407  private <T extends AccessControlHandlerCfg> AccessControlHandler<T>
408               loadHandler(String className,
409                           T configuration,
410                           boolean initialize)
411          throws InitializationException
412  {
413    try
414    {
415      AccessControlHandlerCfgDefn definition =
416           AccessControlHandlerCfgDefn.getInstance();
417      ClassPropertyDefinition propertyDefinition =
418           definition.getJavaClassPropertyDefinition();
419      Class<? extends AccessControlHandler> providerClass =
420           propertyDefinition.loadClass(className, AccessControlHandler.class);
421      AccessControlHandler<T> provider = providerClass.newInstance();
422
423      if (configuration != null)
424      {
425        if(initialize) {
426          provider.initializeAccessControlHandler(configuration);
427        }
428      }
429      else
430      {
431        List<LocalizableMessage> unacceptableReasons = new ArrayList<>();
432        if (!provider.isConfigurationAcceptable(configuration, unacceptableReasons))
433        {
434          String reasons = Utils.joinAsString(".  ", unacceptableReasons);
435          // Bug: we are in a section where configuration is null
436          throw new InitializationException(ERR_CONFIG_AUTHZ_CONFIG_NOT_ACCEPTABLE.get(
437                  null /* WAS: configuration.dn() */, reasons));
438        }
439      }
440
441      return provider;
442    }
443    catch (Exception e)
444    {
445      LocalizableMessage message = ERR_CONFIG_AUTHZ_UNABLE_TO_INSTANTIATE_HANDLER.
446          get(className, configuration.dn(), stackTraceToSingleLineString(e));
447      throw new InitializationException(message, e);
448    }
449  }
450}
451