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