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.i18n.slf4j.LocalizedLogger;
038import org.forgerock.opendj.config.server.ConfigException;
039import org.forgerock.opendj.ldap.ResultCode;
040import org.forgerock.util.Utils;
041import org.opends.server.admin.ClassPropertyDefinition;
042import org.opends.server.admin.server.ConfigurationAddListener;
043import org.opends.server.admin.server.ConfigurationChangeListener;
044import org.opends.server.admin.server.ConfigurationDeleteListener;
045import org.opends.server.admin.server.ServerManagementContext;
046import org.opends.server.admin.std.meta.PasswordValidatorCfgDefn;
047import org.opends.server.admin.std.server.PasswordValidatorCfg;
048import org.opends.server.admin.std.server.RootCfg;
049import org.opends.server.api.PasswordValidator;
050import org.forgerock.opendj.config.server.ConfigChangeResult;
051import org.opends.server.types.DN;
052import org.opends.server.types.InitializationException;
053
054/**
055 * This class defines a utility that will be used to manage the set of
056 * password validators defined in the Directory Server.  It will initialize the
057 * validators when the server starts, and then will manage any additions,
058 * removals, or modifications to any password validators while the server is
059 * running.
060 */
061public class PasswordValidatorConfigManager
062       implements ConfigurationChangeListener<PasswordValidatorCfg>,
063                  ConfigurationAddListener<PasswordValidatorCfg>,
064                  ConfigurationDeleteListener<PasswordValidatorCfg>
065
066{
067
068  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
069
070  /**
071   * A mapping between the DNs of the config entries and the associated password
072   * validators.
073   */
074  private final ConcurrentHashMap<DN,PasswordValidator> passwordValidators;
075
076  private final ServerContext serverContext;
077
078  /**
079   * Creates a new instance of this password validator config manager.
080   *
081   * @param serverContext
082   *            The server context.
083   */
084  public PasswordValidatorConfigManager(ServerContext serverContext)
085  {
086    this.serverContext = serverContext;
087    passwordValidators = new ConcurrentHashMap<>();
088  }
089
090  /**
091   * Initializes all password validators currently defined in the Directory
092   * Server configuration.  This should only be called at Directory Server
093   * startup.
094   *
095   * @throws  ConfigException  If a configuration problem causes the password
096   *                           validator initialization process to fail.
097   *
098   * @throws  InitializationException  If a problem occurs while initializing
099   *                                   the password validators that is not
100   *                                   related to the server configuration.
101   */
102  public void initializePasswordValidators()
103         throws ConfigException, InitializationException
104  {
105    // Get the root configuration object.
106    ServerManagementContext managementContext =
107         ServerManagementContext.getInstance();
108    RootCfg rootConfiguration =
109         managementContext.getRootConfiguration();
110
111
112    // Register as an add and delete listener with the root configuration so we
113    // can be notified if any password validator entries are added or removed.
114    rootConfiguration.addPasswordValidatorAddListener(this);
115    rootConfiguration.addPasswordValidatorDeleteListener(this);
116
117
118    //Initialize the existing password validators.
119    for (String validatorName : rootConfiguration.listPasswordValidators())
120    {
121      PasswordValidatorCfg validatorConfiguration =
122           rootConfiguration.getPasswordValidator(validatorName);
123      validatorConfiguration.addChangeListener(this);
124
125      if (validatorConfiguration.isEnabled())
126      {
127        String className = validatorConfiguration.getJavaClass();
128        try
129        {
130          PasswordValidator<? extends PasswordValidatorCfg>
131               validator = loadValidator(className, validatorConfiguration,
132                                         true);
133          passwordValidators.put(validatorConfiguration.dn(), validator);
134          DirectoryServer.registerPasswordValidator(validatorConfiguration.dn(),
135                                                    validator);
136        }
137        catch (InitializationException ie)
138        {
139          logger.error(ie.getMessageObject());
140          continue;
141        }
142      }
143    }
144  }
145
146
147
148  /** {@inheritDoc} */
149  @Override
150  public boolean isConfigurationAddAcceptable(
151                      PasswordValidatorCfg configuration,
152                      List<LocalizableMessage> unacceptableReasons)
153  {
154    if (configuration.isEnabled())
155    {
156      // Get the name of the class and make sure we can instantiate it as a
157      // password validator.
158      String className = configuration.getJavaClass();
159      try
160      {
161        loadValidator(className, configuration, false);
162      }
163      catch (InitializationException ie)
164      {
165        unacceptableReasons.add(ie.getMessageObject());
166        return false;
167      }
168    }
169
170    // If we've gotten here, then it's fine.
171    return true;
172  }
173
174
175
176  /** {@inheritDoc} */
177  @Override
178  public ConfigChangeResult applyConfigurationAdd(
179                                 PasswordValidatorCfg configuration)
180  {
181    final ConfigChangeResult ccr = new ConfigChangeResult();
182
183    configuration.addChangeListener(this);
184
185    if (! configuration.isEnabled())
186    {
187      return ccr;
188    }
189
190    PasswordValidator<? extends PasswordValidatorCfg>
191         passwordValidator = null;
192
193    // Get the name of the class and make sure we can instantiate it as a
194    // password validator.
195    String className = configuration.getJavaClass();
196    try
197    {
198      passwordValidator = loadValidator(className, configuration, true);
199    }
200    catch (InitializationException ie)
201    {
202      ccr.setResultCodeIfSuccess(DirectoryServer.getServerErrorResultCode());
203      ccr.addMessage(ie.getMessageObject());
204    }
205
206    if (ccr.getResultCode() == ResultCode.SUCCESS)
207    {
208      passwordValidators.put(configuration.dn(), passwordValidator);
209      DirectoryServer.registerPasswordValidator(configuration.dn(), passwordValidator);
210    }
211
212    return ccr;
213  }
214
215
216
217  /** {@inheritDoc} */
218  @Override
219  public boolean isConfigurationDeleteAcceptable(
220                      PasswordValidatorCfg configuration,
221                      List<LocalizableMessage> unacceptableReasons)
222  {
223    // FIXME -- We should try to perform some check to determine whether the
224    // password validator is in use.
225    return true;
226  }
227
228
229
230  /** {@inheritDoc} */
231  @Override
232  public ConfigChangeResult applyConfigurationDelete(
233                                 PasswordValidatorCfg configuration)
234  {
235    final ConfigChangeResult ccr = new ConfigChangeResult();
236
237    DirectoryServer.deregisterPasswordValidator(configuration.dn());
238
239    PasswordValidator passwordValidator =
240         passwordValidators.remove(configuration.dn());
241    if (passwordValidator != null)
242    {
243      passwordValidator.finalizePasswordValidator();
244    }
245
246    return ccr;
247  }
248
249
250
251  /** {@inheritDoc} */
252  @Override
253  public boolean isConfigurationChangeAcceptable(
254                      PasswordValidatorCfg configuration,
255                      List<LocalizableMessage> unacceptableReasons)
256  {
257    if (configuration.isEnabled())
258    {
259      // Get the name of the class and make sure we can instantiate it as a
260      // password validator.
261      String className = configuration.getJavaClass();
262      try
263      {
264        loadValidator(className, configuration, false);
265      }
266      catch (InitializationException ie)
267      {
268        unacceptableReasons.add(ie.getMessageObject());
269        return false;
270      }
271    }
272
273    // If we've gotten here, then it's fine.
274    return true;
275  }
276
277
278
279  /** {@inheritDoc} */
280  @Override
281  public ConfigChangeResult applyConfigurationChange(
282                                 PasswordValidatorCfg configuration)
283  {
284    final ConfigChangeResult ccr = new ConfigChangeResult();
285
286
287    // Get the existing validator if it's already enabled.
288    PasswordValidator existingValidator =
289         passwordValidators.get(configuration.dn());
290
291
292    // If the new configuration has the validator disabled, then disable it if
293    // it is enabled, or do nothing if it's already disabled.
294    if (! configuration.isEnabled())
295    {
296      if (existingValidator != null)
297      {
298        DirectoryServer.deregisterPasswordValidator(configuration.dn());
299
300        PasswordValidator passwordValidator =
301             passwordValidators.remove(configuration.dn());
302        if (passwordValidator != null)
303        {
304          passwordValidator.finalizePasswordValidator();
305        }
306      }
307
308      return ccr;
309    }
310
311
312    // Get the class for the password validator.  If the validator is already
313    // enabled, then we shouldn't do anything with it although if the class has
314    // changed then we'll at least need to indicate that administrative action
315    // is required.  If the validator is disabled, then instantiate the class
316    // and initialize and register it as a password validator.
317    String className = configuration.getJavaClass();
318    if (existingValidator != null)
319    {
320      if (! className.equals(existingValidator.getClass().getName()))
321      {
322        ccr.setAdminActionRequired(true);
323      }
324
325      return ccr;
326    }
327
328    PasswordValidator<? extends PasswordValidatorCfg>
329         passwordValidator = null;
330    try
331    {
332      passwordValidator = loadValidator(className, configuration, true);
333    }
334    catch (InitializationException ie)
335    {
336      ccr.setResultCodeIfSuccess(DirectoryServer.getServerErrorResultCode());
337      ccr.addMessage(ie.getMessageObject());
338    }
339
340    if (ccr.getResultCode() == ResultCode.SUCCESS)
341    {
342      passwordValidators.put(configuration.dn(), passwordValidator);
343      DirectoryServer.registerPasswordValidator(configuration.dn(), passwordValidator);
344    }
345
346    return ccr;
347  }
348
349
350
351  /**
352   * Loads the specified class, instantiates it as a password validator, and
353   * optionally initializes that instance.
354   *
355   * @param  className      The fully-qualified name of the password validator
356   *                        class to load, instantiate, and initialize.
357   * @param  configuration  The configuration to use to initialize the
358   *                        password validator.  It must not be {@code null}.
359   * @param  initialize     Indicates whether the password validator instance
360   *                        should be initialized.
361   *
362   * @return  The possibly initialized password validator.
363   *
364   * @throws  InitializationException  If a problem occurred while attempting to
365   *                                   initialize the password validator.
366   */
367  private <T extends PasswordValidatorCfg> PasswordValidator<T>
368               loadValidator(String className,
369                             T configuration,
370                             boolean initialize)
371          throws InitializationException
372  {
373    try
374    {
375      PasswordValidatorCfgDefn definition =
376           PasswordValidatorCfgDefn.getInstance();
377      ClassPropertyDefinition propertyDefinition =
378           definition.getJavaClassPropertyDefinition();
379      Class<? extends PasswordValidator> validatorClass =
380           propertyDefinition.loadClass(className, PasswordValidator.class);
381      PasswordValidator<T> validator = validatorClass.newInstance();
382
383      if (initialize)
384      {
385        validator.initializePasswordValidator(configuration);
386      }
387      else
388      {
389        List<LocalizableMessage> unacceptableReasons = new ArrayList<>();
390        if (!validator.isConfigurationAcceptable(configuration, unacceptableReasons))
391        {
392          String reasons = Utils.joinAsString(".  ", unacceptableReasons);
393          throw new InitializationException(
394              ERR_CONFIG_PWVALIDATOR_CONFIG_NOT_ACCEPTABLE.get(configuration.dn(), reasons));
395        }
396      }
397
398      return validator;
399    }
400    catch (Exception e)
401    {
402      LocalizableMessage message = ERR_CONFIG_PWVALIDATOR_INITIALIZATION_FAILED.
403          get(className, configuration.dn(), stackTraceToSingleLineString(e));
404      throw new InitializationException(message, e);
405    }
406  }
407}
408