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