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