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
010     * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011     * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012     * See the License for the specific language governing permissions
013     * and limitations under the License.
014     *
015     * When distributing Covered Code, include this CDDL HEADER in each
016     * file and include the License file at
017     * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
018     * add the following below this CDDL HEADER, with the fields enclosed
019     * by brackets "[]" replaced with your own identifying information:
020     *      Portions Copyright [yyyy] [name of copyright owner]
021     *
022     * CDDL HEADER END
023     *
024     *
025     *      Copyright 2006-2008 Sun Microsystems, Inc.
026     */
027    package org.opends.server.core;
028    import org.opends.messages.Message;
029    
030    
031    
032    import java.lang.reflect.Method;
033    import java.util.ArrayList;
034    import java.util.Iterator;
035    import java.util.List;
036    import java.util.concurrent.ConcurrentHashMap;
037    
038    import org.opends.server.admin.ClassPropertyDefinition;
039    import org.opends.server.admin.server.ConfigurationAddListener;
040    import org.opends.server.admin.server.ConfigurationChangeListener;
041    import org.opends.server.admin.server.ConfigurationDeleteListener;
042    import org.opends.server.admin.std.meta.PasswordValidatorCfgDefn;
043    import org.opends.server.admin.std.server.PasswordValidatorCfg;
044    import org.opends.server.admin.std.server.RootCfg;
045    import org.opends.server.admin.server.ServerManagementContext;
046    import org.opends.server.api.PasswordValidator;
047    import org.opends.server.config.ConfigException;
048    import org.opends.server.types.ConfigChangeResult;
049    import org.opends.server.types.DN;
050    import org.opends.server.types.InitializationException;
051    import org.opends.server.types.ResultCode;
052    
053    import static org.opends.messages.ConfigMessages.*;
054    import static org.opends.server.loggers.ErrorLogger.*;
055    
056    import static org.opends.server.util.StaticUtils.*;
057    
058    
059    
060    /**
061     * This class defines a utility that will be used to manage the set of
062     * password validators defined in the Directory Server.  It will initialize the
063     * validators when the server starts, and then will manage any additions,
064     * removals, or modifications to any password validators while the server is
065     * running.
066     */
067    public class PasswordValidatorConfigManager
068           implements ConfigurationChangeListener<PasswordValidatorCfg>,
069                      ConfigurationAddListener<PasswordValidatorCfg>,
070                      ConfigurationDeleteListener<PasswordValidatorCfg>
071    
072    {
073      // A mapping between the DNs of the config entries and the associated
074      // password validators.
075      private ConcurrentHashMap<DN,PasswordValidator> passwordValidators;
076    
077    
078    
079      /**
080       * Creates a new instance of this password validator config manager.
081       */
082      public PasswordValidatorConfigManager()
083      {
084        passwordValidators = new ConcurrentHashMap<DN,PasswordValidator>();
085      }
086    
087    
088    
089      /**
090       * Initializes all password validators currently defined in the Directory
091       * Server configuration.  This should only be called at Directory Server
092       * startup.
093       *
094       * @throws  ConfigException  If a configuration problem causes the password
095       *                           validator initialization process to fail.
096       *
097       * @throws  InitializationException  If a problem occurs while initializing
098       *                                   the password validators that is not
099       *                                   related to the server configuration.
100       */
101      public void initializePasswordValidators()
102             throws ConfigException, InitializationException
103      {
104        // Get the root configuration object.
105        ServerManagementContext managementContext =
106             ServerManagementContext.getInstance();
107        RootCfg rootConfiguration =
108             managementContext.getRootConfiguration();
109    
110    
111        // Register as an add and delete listener with the root configuration so we
112        // can be notified if any password validator entries are added or removed.
113        rootConfiguration.addPasswordValidatorAddListener(this);
114        rootConfiguration.addPasswordValidatorDeleteListener(this);
115    
116    
117        //Initialize the existing password validators.
118        for (String validatorName : rootConfiguration.listPasswordValidators())
119        {
120          PasswordValidatorCfg validatorConfiguration =
121               rootConfiguration.getPasswordValidator(validatorName);
122          validatorConfiguration.addChangeListener(this);
123    
124          if (validatorConfiguration.isEnabled())
125          {
126            String className = validatorConfiguration.getJavaClass();
127            try
128            {
129              PasswordValidator<? extends PasswordValidatorCfg>
130                   validator = loadValidator(className, validatorConfiguration,
131                                             true);
132              passwordValidators.put(validatorConfiguration.dn(), validator);
133              DirectoryServer.registerPasswordValidator(validatorConfiguration.dn(),
134                                                        validator);
135            }
136            catch (InitializationException ie)
137            {
138              logError(ie.getMessageObject());
139              continue;
140            }
141          }
142        }
143      }
144    
145    
146    
147      /**
148       * {@inheritDoc}
149       */
150      public boolean isConfigurationAddAcceptable(
151                          PasswordValidatorCfg configuration,
152                          List<Message> 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      /**
177       * {@inheritDoc}
178       */
179      public ConfigChangeResult applyConfigurationAdd(
180                                     PasswordValidatorCfg configuration)
181      {
182        ResultCode        resultCode          = ResultCode.SUCCESS;
183        boolean           adminActionRequired = false;
184        ArrayList<Message> messages            = new ArrayList<Message>();
185    
186        configuration.addChangeListener(this);
187    
188        if (! configuration.isEnabled())
189        {
190          return new ConfigChangeResult(resultCode, adminActionRequired, messages);
191        }
192    
193        PasswordValidator<? extends PasswordValidatorCfg>
194             passwordValidator = null;
195    
196        // Get the name of the class and make sure we can instantiate it as a
197        // password validator.
198        String className = configuration.getJavaClass();
199        try
200        {
201          passwordValidator = loadValidator(className, configuration, true);
202        }
203        catch (InitializationException ie)
204        {
205          if (resultCode == ResultCode.SUCCESS)
206          {
207            resultCode = DirectoryServer.getServerErrorResultCode();
208          }
209    
210          messages.add(ie.getMessageObject());
211        }
212    
213        if (resultCode == ResultCode.SUCCESS)
214        {
215          passwordValidators.put(configuration.dn(), passwordValidator);
216          DirectoryServer.registerPasswordValidator(configuration.dn(),
217                                                    passwordValidator);
218        }
219    
220        return new ConfigChangeResult(resultCode, adminActionRequired, messages);
221      }
222    
223    
224    
225      /**
226       * {@inheritDoc}
227       */
228      public boolean isConfigurationDeleteAcceptable(
229                          PasswordValidatorCfg configuration,
230                          List<Message> unacceptableReasons)
231      {
232        // FIXME -- We should try to perform some check to determine whether the
233        // password validator is in use.
234        return true;
235      }
236    
237    
238    
239      /**
240       * {@inheritDoc}
241       */
242      public ConfigChangeResult applyConfigurationDelete(
243                                     PasswordValidatorCfg configuration)
244      {
245        ResultCode        resultCode          = ResultCode.SUCCESS;
246        boolean           adminActionRequired = false;
247        ArrayList<Message> messages            = new ArrayList<Message>();
248    
249        DirectoryServer.deregisterPasswordValidator(configuration.dn());
250    
251        PasswordValidator passwordValidator =
252             passwordValidators.remove(configuration.dn());
253        if (passwordValidator != null)
254        {
255          passwordValidator.finalizePasswordValidator();
256        }
257    
258        return new ConfigChangeResult(resultCode, adminActionRequired, messages);
259      }
260    
261    
262    
263      /**
264       * {@inheritDoc}
265       */
266      public boolean isConfigurationChangeAcceptable(
267                          PasswordValidatorCfg configuration,
268                          List<Message> unacceptableReasons)
269      {
270        if (configuration.isEnabled())
271        {
272          // Get the name of the class and make sure we can instantiate it as a
273          // password validator.
274          String className = configuration.getJavaClass();
275          try
276          {
277            loadValidator(className, configuration, false);
278          }
279          catch (InitializationException ie)
280          {
281            unacceptableReasons.add(ie.getMessageObject());
282            return false;
283          }
284        }
285    
286        // If we've gotten here, then it's fine.
287        return true;
288      }
289    
290    
291    
292      /**
293       * {@inheritDoc}
294       */
295      public ConfigChangeResult applyConfigurationChange(
296                                     PasswordValidatorCfg configuration)
297      {
298        ResultCode        resultCode          = ResultCode.SUCCESS;
299        boolean           adminActionRequired = false;
300        ArrayList<Message> messages            = new ArrayList<Message>();
301    
302    
303        // Get the existing validator if it's already enabled.
304        PasswordValidator existingValidator =
305             passwordValidators.get(configuration.dn());
306    
307    
308        // If the new configuration has the validator disabled, then disable it if
309        // it is enabled, or do nothing if it's already disabled.
310        if (! configuration.isEnabled())
311        {
312          if (existingValidator != null)
313          {
314            DirectoryServer.deregisterPasswordValidator(configuration.dn());
315    
316            PasswordValidator passwordValidator =
317                 passwordValidators.remove(configuration.dn());
318            if (passwordValidator != null)
319            {
320              passwordValidator.finalizePasswordValidator();
321            }
322          }
323    
324          return new ConfigChangeResult(resultCode, adminActionRequired, messages);
325        }
326    
327    
328        // Get the class for the password validator.  If the validator is already
329        // enabled, then we shouldn't do anything with it although if the class has
330        // changed then we'll at least need to indicate that administrative action
331        // is required.  If the validator is disabled, then instantiate the class
332        // and initialize and register it as a password validator.
333        String className = configuration.getJavaClass();
334        if (existingValidator != null)
335        {
336          if (! className.equals(existingValidator.getClass().getName()))
337          {
338            adminActionRequired = true;
339          }
340    
341          return new ConfigChangeResult(resultCode, adminActionRequired, messages);
342        }
343    
344        PasswordValidator<? extends PasswordValidatorCfg>
345             passwordValidator = null;
346        try
347        {
348          passwordValidator = loadValidator(className, configuration, true);
349        }
350        catch (InitializationException ie)
351        {
352          if (resultCode == ResultCode.SUCCESS)
353          {
354            resultCode = DirectoryServer.getServerErrorResultCode();
355          }
356    
357          messages.add(ie.getMessageObject());
358        }
359    
360        if (resultCode == ResultCode.SUCCESS)
361        {
362          passwordValidators.put(configuration.dn(), passwordValidator);
363          DirectoryServer.registerPasswordValidator(configuration.dn(),
364                                                    passwordValidator);
365        }
366    
367        return new ConfigChangeResult(resultCode, adminActionRequired, messages);
368      }
369    
370    
371    
372      /**
373       * Loads the specified class, instantiates it as a password validator, and
374       * optionally initializes that instance.
375       *
376       * @param  className      The fully-qualified name of the password validator
377       *                        class to load, instantiate, and initialize.
378       * @param  configuration  The configuration to use to initialize the
379       *                        password validator.  It must not be {@code null}.
380       * @param  initialize     Indicates whether the password validator instance
381       *                        should be initialized.
382       *
383       * @return  The possibly initialized password validator.
384       *
385       * @throws  InitializationException  If a problem occurred while attempting to
386       *                                   initialize the password validator.
387       */
388      private PasswordValidator<? extends PasswordValidatorCfg>
389                   loadValidator(String className,
390                                 PasswordValidatorCfg configuration,
391                                 boolean initialize)
392              throws InitializationException
393      {
394        try
395        {
396          PasswordValidatorCfgDefn definition =
397               PasswordValidatorCfgDefn.getInstance();
398          ClassPropertyDefinition propertyDefinition =
399               definition.getJavaClassPropertyDefinition();
400          Class<? extends PasswordValidator> validatorClass =
401               propertyDefinition.loadClass(className, PasswordValidator.class);
402          PasswordValidator<? extends PasswordValidatorCfg> validator =
403               (PasswordValidator<? extends PasswordValidatorCfg>)
404               validatorClass.newInstance();
405    
406          if (initialize)
407          {
408            Method method = validator.getClass().getMethod(
409                "initializePasswordValidator", configuration.configurationClass());
410            method.invoke(validator, configuration);
411          }
412          else
413          {
414            Method method =
415                 validator.getClass().getMethod("isConfigurationAcceptable",
416                                                PasswordValidatorCfg.class,
417                                                List.class);
418    
419            List<Message> unacceptableReasons = new ArrayList<Message>();
420            Boolean acceptable = (Boolean) method.invoke(validator, configuration,
421                                                         unacceptableReasons);
422            if (! acceptable)
423            {
424              StringBuilder buffer = new StringBuilder();
425              if (! unacceptableReasons.isEmpty())
426              {
427                Iterator<Message> iterator = unacceptableReasons.iterator();
428                buffer.append(iterator.next());
429                while (iterator.hasNext())
430                {
431                  buffer.append(".  ");
432                  buffer.append(iterator.next());
433                }
434              }
435    
436              Message message = ERR_CONFIG_PWVALIDATOR_CONFIG_NOT_ACCEPTABLE.get(
437                  String.valueOf(configuration.dn()), buffer.toString());
438              throw new InitializationException(message);
439            }
440          }
441    
442          return validator;
443        }
444        catch (Exception e)
445        {
446          Message message = ERR_CONFIG_PWVALIDATOR_INITIALIZATION_FAILED.
447              get(className, String.valueOf(configuration.dn()),
448                  stackTraceToSingleLineString(e));
449          throw new InitializationException(message, e);
450        }
451      }
452    }
453