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