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