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.StaticUtils.*;
031
032import java.util.ArrayList;
033import java.util.List;
034import java.util.concurrent.ConcurrentHashMap;
035
036import org.forgerock.i18n.LocalizableMessage;
037import org.forgerock.opendj.config.server.ConfigException;
038import org.forgerock.util.Utils;
039import org.opends.server.admin.ClassPropertyDefinition;
040import org.opends.server.admin.server.ConfigurationAddListener;
041import org.opends.server.admin.server.ConfigurationChangeListener;
042import org.opends.server.admin.server.ConfigurationDeleteListener;
043import org.opends.server.admin.server.ServerManagementContext;
044import org.opends.server.admin.std.meta.PasswordStorageSchemeCfgDefn;
045import org.opends.server.admin.std.server.PasswordStorageSchemeCfg;
046import org.opends.server.admin.std.server.RootCfg;
047import org.opends.server.api.PasswordStorageScheme;
048import org.forgerock.opendj.config.server.ConfigChangeResult;
049import org.opends.server.types.DN;
050import org.opends.server.types.InitializationException;
051
052/**
053 * This class defines a utility that will be used to manage the set of password
054 * storage schemes defined in the Directory Server.  It will initialize the
055 * storage schemes when the server starts, and then will manage any additions,
056 * removals, or modifications to any schemes while the server is running.
057 */
058public class PasswordStorageSchemeConfigManager
059       implements
060          ConfigurationChangeListener <PasswordStorageSchemeCfg>,
061          ConfigurationAddListener    <PasswordStorageSchemeCfg>,
062          ConfigurationDeleteListener <PasswordStorageSchemeCfg>
063{
064  /**
065   * A mapping between the DNs of the config entries and the associated password
066   * storage schemes.
067   */
068  private final ConcurrentHashMap<DN,PasswordStorageScheme> storageSchemes;
069
070  private final ServerContext serverContext;
071
072  /**
073   * Creates a new instance of this password storage scheme config manager.
074   *
075   * @param serverContext
076   *            The server context.
077   */
078  public PasswordStorageSchemeConfigManager(ServerContext serverContext)
079  {
080    this.serverContext = serverContext;
081    storageSchemes = new ConcurrentHashMap<>();
082  }
083
084
085
086  /**
087   * Initializes all password storage schemes currently defined in the Directory
088   * Server configuration.  This should only be called at Directory Server
089   * startup.
090   *
091   * @throws  ConfigException  If a configuration problem causes the password
092   *                           storage scheme initialization process to fail.
093   *
094   * @throws  InitializationException  If a problem occurs while initializing
095   *                                   the password storage scheme that is not
096   *                                   related to the server configuration.
097   */
098  public void initializePasswordStorageSchemes()
099         throws ConfigException, InitializationException
100  {
101    // Get the root configuration object.
102    ServerManagementContext managementContext =
103      ServerManagementContext.getInstance();
104    RootCfg rootConfiguration =
105      managementContext.getRootConfiguration();
106
107    // Register as an add and delete listener with the root configuration so we
108    // can be notified if any entry cache entry is added or removed.
109    rootConfiguration.addPasswordStorageSchemeAddListener (this);
110    rootConfiguration.addPasswordStorageSchemeDeleteListener (this);
111
112    // Initialize existing password storage schemes.
113    for (String schemeName: rootConfiguration.listPasswordStorageSchemes())
114    {
115      // Get the password storage scheme's configuration.
116      PasswordStorageSchemeCfg config =
117        rootConfiguration.getPasswordStorageScheme (schemeName);
118
119      // Register as a change listener for this password storage scheme
120      // entry so that we will be notified of any changes that may be
121      // made to it.
122      config.addChangeListener (this);
123
124      // Ignore this password storage scheme if it is disabled.
125      if (config.isEnabled())
126      {
127        // Load the password storage scheme implementation class.
128        String className = config.getJavaClass();
129        loadAndInstallPasswordStorageScheme (className, config);
130      }
131    }
132  }
133
134
135
136  /** {@inheritDoc} */
137  @Override
138  public boolean isConfigurationChangeAcceptable(
139      PasswordStorageSchemeCfg configuration,
140      List<LocalizableMessage> unacceptableReasons
141      )
142  {
143    // returned status -- all is fine by default
144    boolean status = true;
145
146    if (configuration.isEnabled())
147    {
148      // Get the name of the class and make sure we can instantiate it as
149      // a password storage scheme.
150      String className = configuration.getJavaClass();
151      try
152      {
153        // Load the class but don't initialize it.
154        loadPasswordStorageScheme (className, configuration, false);
155      }
156      catch (InitializationException ie)
157      {
158        unacceptableReasons.add(ie.getMessageObject());
159        status = false;
160      }
161    }
162
163    return status;
164  }
165
166
167
168  /** {@inheritDoc} */
169  @Override
170  public ConfigChangeResult applyConfigurationChange(
171      PasswordStorageSchemeCfg configuration
172      )
173  {
174    final ConfigChangeResult changeResult = new ConfigChangeResult();
175
176    // Get the configuration entry DN and the associated
177    // password storage scheme class.
178    DN configEntryDN = configuration.dn();
179    PasswordStorageScheme storageScheme = storageSchemes.get(configEntryDN);
180
181    // If the new configuration has the password storage scheme disabled,
182    // then remove it from the mapping list and clean it.
183    if (! configuration.isEnabled())
184    {
185      if (storageScheme != null)
186      {
187        uninstallPasswordStorageScheme (configEntryDN);
188      }
189
190      return changeResult;
191    }
192
193    // At this point, new configuration is enabled...
194    // If the current password storage scheme is already enabled then we
195    // don't do anything unless the class has changed in which case we
196    // should indicate that administrative action is required.
197    String newClassName = configuration.getJavaClass();
198    if (storageScheme != null)
199    {
200      String curClassName = storageScheme.getClass().getName();
201      boolean classIsNew = !newClassName.equals(curClassName);
202      if (classIsNew)
203      {
204        changeResult.setAdminActionRequired (true);
205      }
206      return changeResult;
207    }
208
209    // New entry cache is enabled and there were no previous one.
210    // Instantiate the new class and initialize it.
211    try
212    {
213      loadAndInstallPasswordStorageScheme (newClassName, configuration);
214    }
215    catch (InitializationException ie)
216    {
217      changeResult.addMessage (ie.getMessageObject());
218      changeResult.setResultCode (DirectoryServer.getServerErrorResultCode());
219      return changeResult;
220    }
221
222    return changeResult;
223  }
224
225
226
227  /** {@inheritDoc} */
228  @Override
229  public boolean isConfigurationAddAcceptable(
230      PasswordStorageSchemeCfg configuration,
231      List<LocalizableMessage> unacceptableReasons
232      )
233  {
234    // returned status -- all is fine by default
235    boolean status = true;
236
237    // Make sure that no entry already exists with the specified DN.
238    DN configEntryDN = configuration.dn();
239    if (storageSchemes.containsKey(configEntryDN))
240    {
241      unacceptableReasons.add (ERR_CONFIG_PWSCHEME_EXISTS.get(configEntryDN));
242      status = false;
243    }
244    // If configuration is enabled then check that password storage scheme
245    // class can be instantiated.
246    else if (configuration.isEnabled())
247    {
248      // Get the name of the class and make sure we can instantiate it as
249      // an entry cache.
250      String className = configuration.getJavaClass();
251      try
252      {
253        // Load the class but don't initialize it.
254        loadPasswordStorageScheme (className, configuration, false);
255      }
256      catch (InitializationException ie)
257      {
258        unacceptableReasons.add (ie.getMessageObject());
259        status = false;
260      }
261    }
262
263    return status;
264  }
265
266
267
268  /** {@inheritDoc} */
269  @Override
270  public ConfigChangeResult applyConfigurationAdd(
271      PasswordStorageSchemeCfg configuration
272      )
273  {
274    final ConfigChangeResult changeResult = new ConfigChangeResult();
275
276    // Register a change listener with it so we can be notified of changes
277    // to it over time.
278    configuration.addChangeListener(this);
279
280    if (configuration.isEnabled())
281    {
282      // Instantiate the class as password storage scheme
283      // and initialize it.
284      String className = configuration.getJavaClass();
285      try
286      {
287        loadAndInstallPasswordStorageScheme (className, configuration);
288      }
289      catch (InitializationException ie)
290      {
291        changeResult.addMessage (ie.getMessageObject());
292        changeResult.setResultCode (DirectoryServer.getServerErrorResultCode());
293        return changeResult;
294      }
295    }
296
297    return changeResult;
298  }
299
300
301
302  /** {@inheritDoc} */
303  @Override
304  public boolean isConfigurationDeleteAcceptable(
305      PasswordStorageSchemeCfg configuration,
306      List<LocalizableMessage> unacceptableReasons
307      )
308  {
309    // A delete should always be acceptable, so just return true.
310    return true;
311  }
312
313
314
315  /** {@inheritDoc} */
316  @Override
317  public ConfigChangeResult applyConfigurationDelete(
318      PasswordStorageSchemeCfg configuration
319      )
320  {
321    final ConfigChangeResult changeResult = new ConfigChangeResult();
322
323    uninstallPasswordStorageScheme (configuration.dn());
324
325    return changeResult;
326  }
327
328
329
330  /**
331   * Loads the specified class, instantiates it as a password storage scheme,
332   * and optionally initializes that instance. Any initialized password
333   * storage scheme is registered in the server.
334   *
335   * @param  className      The fully-qualified name of the password storage
336   *                        scheme class to load, instantiate, and initialize.
337   * @param  configuration  The configuration to use to initialize the
338   *                        password storage scheme, or {@code null} if the
339   *                        password storage scheme should not be initialized.
340   *
341   * @throws  InitializationException  If a problem occurred while attempting
342   *                                   to initialize the class.
343   */
344  private void loadAndInstallPasswordStorageScheme(
345       String className,
346       PasswordStorageSchemeCfg configuration
347       )
348       throws InitializationException
349  {
350    // Load the password storage scheme class...
351    PasswordStorageScheme
352        <? extends PasswordStorageSchemeCfg> schemeClass;
353    schemeClass = loadPasswordStorageScheme (className, configuration, true);
354
355    // ... and install the password storage scheme in the server.
356    DN configEntryDN = configuration.dn();
357    storageSchemes.put (configEntryDN, schemeClass);
358    DirectoryServer.registerPasswordStorageScheme (configEntryDN, schemeClass);
359  }
360
361
362  /**
363   * Loads the specified class, instantiates it as a password storage scheme,
364   * and optionally initializes that instance.
365   *
366   * @param  className      The fully-qualified name of the class
367   *                        to load, instantiate, and initialize.
368   * @param  configuration  The configuration to use to initialize the
369   *                        class.  It must not be {@code null}.
370   * @param  initialize     Indicates whether the password storage scheme
371   *                        instance should be initialized.
372   *
373   * @return  The possibly initialized password storage scheme.
374   *
375   * @throws  InitializationException  If a problem occurred while attempting
376   *                                   to initialize the class.
377   */
378  private <T extends PasswordStorageSchemeCfg> PasswordStorageScheme<T>
379    loadPasswordStorageScheme(
380       String className,
381       T configuration,
382       boolean initialize)
383       throws InitializationException
384  {
385    try
386    {
387      ClassPropertyDefinition propertyDefinition;
388      Class<? extends PasswordStorageScheme> schemeClass;
389
390      PasswordStorageSchemeCfgDefn definition = PasswordStorageSchemeCfgDefn.getInstance();
391      propertyDefinition = definition.getJavaClassPropertyDefinition();
392      schemeClass = propertyDefinition.loadClass(className, PasswordStorageScheme.class);
393      PasswordStorageScheme<T> passwordStorageScheme = schemeClass.newInstance();
394
395      if (initialize)
396      {
397        passwordStorageScheme.initializePasswordStorageScheme(configuration);
398      }
399      else
400      {
401        List<LocalizableMessage> unacceptableReasons = new ArrayList<>();
402        if (!passwordStorageScheme.isConfigurationAcceptable(configuration, unacceptableReasons))
403        {
404          String reasons = Utils.joinAsString(".  ", unacceptableReasons);
405          throw new InitializationException(
406              ERR_CONFIG_PWSCHEME_CONFIG_NOT_ACCEPTABLE.get(configuration.dn(), reasons));
407        }
408      }
409
410      return passwordStorageScheme;
411    }
412    catch (Exception e)
413    {
414      LocalizableMessage message = ERR_CONFIG_PWSCHEME_INITIALIZATION_FAILED.get(className,
415          configuration.dn(), stackTraceToSingleLineString(e));
416      throw new InitializationException(message, e);
417    }
418  }
419
420
421  /**
422   * Remove a password storage that has been installed in the server.
423   *
424   * @param configEntryDN  the DN of the configuration enry associated to
425   *                       the password storage scheme to remove
426   */
427  private void uninstallPasswordStorageScheme(
428      DN configEntryDN
429      )
430  {
431    PasswordStorageScheme scheme =
432        storageSchemes.remove (configEntryDN);
433    if (scheme != null)
434    {
435      DirectoryServer.deregisterPasswordStorageScheme(configEntryDN);
436      scheme.finalizePasswordStorageScheme();
437    }
438  }
439}
440