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 2011-2015 ForgeRock AS.
026 */
027package org.opends.server.core;
028
029import static org.opends.messages.ConfigMessages.*;
030import static org.opends.messages.CoreMessages.*;
031import static org.opends.server.schema.SchemaConstants.*;
032import static org.opends.server.util.ServerConstants.*;
033import static org.opends.server.util.StaticUtils.*;
034
035import java.text.SimpleDateFormat;
036import java.util.*;
037
038import org.forgerock.i18n.LocalizableMessage;
039import org.forgerock.i18n.slf4j.LocalizedLogger;
040import org.forgerock.opendj.config.server.ConfigChangeResult;
041import org.forgerock.opendj.config.server.ConfigException;
042import org.forgerock.opendj.ldap.ByteString;
043import org.forgerock.opendj.ldap.GeneralizedTime;
044import org.forgerock.opendj.ldap.ResultCode;
045import org.opends.server.admin.server.ConfigurationChangeListener;
046import org.opends.server.admin.std.meta.PasswordPolicyCfgDefn.StateUpdateFailurePolicy;
047import org.opends.server.admin.std.server.PasswordPolicyCfg;
048import org.opends.server.api.*;
049import org.opends.server.types.*;
050
051/**
052 * This class is the interface between the password policy configurable
053 * component and a password policy state object. When a password policy entry is
054 * added to the configuration, an instance of this class is created and
055 * registered to manage subsequent modification to that configuration entry,
056 * including validating any proposed modification and applying an accepted
057 * modification.
058 */
059public final class PasswordPolicyFactory implements
060    AuthenticationPolicyFactory<PasswordPolicyCfg>
061{
062  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
063
064
065
066  /**
067   * Password policy implementation.
068   */
069  private static final class PasswordPolicyImpl extends PasswordPolicy
070      implements ConfigurationChangeListener<PasswordPolicyCfg>
071  {
072    private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
073
074    /** Current configuration. */
075    private PasswordPolicyCfg configuration;
076
077    /** Indicates whether the attribute type uses the authPassword syntax. */
078    private boolean authPasswordSyntax;
079
080    /** The set of account status notification handlers for this password policy. */
081    private Map<DN, AccountStatusNotificationHandler<?>> notificationHandlers;
082
083    /**
084     * The set of password validators that will be used with this
085     * password policy.
086     */
087    private Map<DN, PasswordValidator<?>> passwordValidators;
088
089    /**
090     * The set of default password storage schemes for this password policy.
091     */
092    private List<PasswordStorageScheme<?>> defaultStorageSchemes;
093
094    /**
095     * The names of the deprecated password storage schemes for this password
096     * policy.
097     */
098    private Set<String> deprecatedStorageSchemes;
099
100    /** The password generator for use with this password policy. */
101    private PasswordGenerator<?> passwordGenerator;
102
103    /**
104     * The the time by which all users will be required to change their
105     * passwords.
106     */
107    private long requireChangeByTime;
108
109    private final ServerContext serverContext;
110
111    /** {@inheritDoc} */
112    @Override
113    public void finalizeAuthenticationPolicy()
114    {
115      configuration.removePasswordPolicyChangeListener(this);
116    }
117
118    /** {@inheritDoc} */
119    @Override
120    public ConfigChangeResult applyConfigurationChange(PasswordPolicyCfg configuration)
121    {
122      final ConfigChangeResult ccr = new ConfigChangeResult();
123      try
124      {
125        updateConfiguration(configuration, true);
126      }
127      catch (ConfigException ce)
128      {
129        ccr.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
130        ccr.addMessage(ERR_CONFIG_PWPOLICY_INVALID_POLICY_CONFIG.get(configuration.dn(), ce.getMessage()));
131      }
132      catch (InitializationException ie)
133      {
134        ccr.addMessage(ERR_CONFIG_PWPOLICY_INVALID_POLICY_CONFIG.get(
135            configuration.dn(), ie.getMessage()));
136        ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
137      }
138      catch (Exception e)
139      {
140        ccr.addMessage(ERR_CONFIG_PWPOLICY_INVALID_POLICY_CONFIG.get(
141            configuration.dn(), stackTraceToSingleLineString(e)));
142        ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
143      }
144      return ccr;
145    }
146
147    /** {@inheritDoc} */
148    @Override
149    public boolean isConfigurationChangeAcceptable(
150        PasswordPolicyCfg configuration, List<LocalizableMessage> unacceptableReasons)
151    {
152      try
153      {
154        updateConfiguration(configuration, false);
155      }
156      catch (ConfigException | InitializationException e)
157      {
158        LocalizableMessage message = ERR_CONFIG_PWPOLICY_INVALID_POLICY_CONFIG.get(
159            configuration.dn(), e.getMessage());
160        unacceptableReasons.add(message);
161        return false;
162      }
163      catch (Exception e)
164      {
165        LocalizableMessage message = ERR_CONFIG_PWPOLICY_INVALID_POLICY_CONFIG
166            .get(configuration.dn(), stackTraceToSingleLineString(e));
167        unacceptableReasons.add(message);
168        return false;
169      }
170
171      // If we've gotten here, then it is acceptable.
172      return true;
173    }
174
175
176
177    /**
178     * Creates a new password policy based on the configuration contained in the
179     * provided configuration entry. Any parameters not included in the provided
180     * configuration entry will be assigned server-wide default values.
181     * @param serverContext TODO
182     * @param configuration
183     *          The configuration with the information to use to initialize this
184     *          password policy.
185     *
186     * @throws ConfigException
187     *           If the provided entry does not contain a valid password policy
188     *           configuration.
189     * @throws InitializationException
190     *           If an error occurs while initializing the password policy that
191     *           is not related to the server configuration.
192     */
193    private PasswordPolicyImpl(ServerContext serverContext, PasswordPolicyCfg configuration)
194        throws ConfigException, InitializationException
195    {
196      this.serverContext = serverContext;
197      updateConfiguration(configuration, true);
198    }
199
200
201
202    private void updateConfiguration(PasswordPolicyCfg configuration,
203        boolean applyChanges) throws ConfigException,
204        InitializationException
205    {
206      final DN configEntryDN = configuration.dn();
207
208      // Get the password attribute. If specified, it must have either the
209      // user password or auth password syntax.
210      final AttributeType passwordAttribute = configuration
211          .getPasswordAttribute();
212      final String syntaxOID = passwordAttribute.getSyntax().getOID();
213      final boolean authPasswordSyntax;
214      if (syntaxOID.equals(SYNTAX_AUTH_PASSWORD_OID))
215      {
216        authPasswordSyntax = true;
217      }
218      else if (syntaxOID.equals(SYNTAX_USER_PASSWORD_OID))
219      {
220        authPasswordSyntax = false;
221      }
222      else
223      {
224        String syntax = passwordAttribute.getSyntax().getName();
225        if (syntax == null || syntax.length() == 0)
226        {
227          syntax = syntaxOID;
228        }
229
230        throw new ConfigException(ERR_PWPOLICY_INVALID_PASSWORD_ATTRIBUTE_SYNTAX.get(
231            configEntryDN, passwordAttribute.getNameOrOID(), syntax));
232      }
233
234      // Get the default storage schemes. They must all reference valid storage
235      // schemes that support the syntax for the specified password attribute.
236      List<PasswordStorageScheme<?>> defaultStorageSchemes = new LinkedList<>();
237      for (DN schemeDN : configuration.getDefaultPasswordStorageSchemeDNs())
238      {
239        PasswordStorageScheme<?> scheme = DirectoryServer
240            .getPasswordStorageScheme(schemeDN);
241
242        if (authPasswordSyntax && !scheme.supportsAuthPasswordSyntax())
243        {
244          throw new ConfigException(ERR_PWPOLICY_SCHEME_DOESNT_SUPPORT_AUTH.get(
245              schemeDN, passwordAttribute.getNameOrOID()));
246        }
247
248        defaultStorageSchemes.add(scheme);
249      }
250
251      // Get the names of the deprecated storage schemes.
252      Set<String> deprecatedStorageSchemes = new LinkedHashSet<>();
253      for (DN schemeDN : configuration.getDeprecatedPasswordStorageSchemeDNs())
254      {
255        PasswordStorageScheme<?> scheme = DirectoryServer
256            .getPasswordStorageScheme(schemeDN);
257        if (authPasswordSyntax)
258        {
259          if (scheme.supportsAuthPasswordSyntax())
260          {
261            deprecatedStorageSchemes.add(toLowerCase(scheme
262                .getAuthPasswordSchemeName()));
263          }
264          else
265          {
266            throw new ConfigException(ERR_PWPOLICY_DEPRECATED_SCHEME_NOT_AUTH.get(
267                configEntryDN, schemeDN));
268          }
269        }
270        else
271        {
272          deprecatedStorageSchemes.add(toLowerCase(scheme.getStorageSchemeName()));
273        }
274      }
275
276      // Get the password validators.
277      Map<DN, PasswordValidator<?>> passwordValidators = new HashMap<>();
278      for (DN validatorDN : configuration.getPasswordValidatorDNs())
279      {
280        passwordValidators.put(validatorDN,
281            DirectoryServer.getPasswordValidator(validatorDN));
282      }
283
284      // Get the status notification handlers.
285      Map<DN, AccountStatusNotificationHandler<?>> notificationHandlers = new HashMap<>();
286      for (DN handlerDN : configuration.getAccountStatusNotificationHandlerDNs())
287      {
288        AccountStatusNotificationHandler<?> handler = DirectoryServer
289            .getAccountStatusNotificationHandler(handlerDN);
290        notificationHandlers.put(handlerDN, handler);
291      }
292
293      // Get the password generator.
294      PasswordGenerator<?> passwordGenerator = null;
295      DN passGenDN = configuration.getPasswordGeneratorDN();
296      if (passGenDN != null)
297      {
298        passwordGenerator = DirectoryServer.getPasswordGenerator(passGenDN);
299      }
300
301      // If the expire without warning option is disabled, then there must be a
302      // warning interval.
303      if (!configuration.isExpirePasswordsWithoutWarning()
304          && configuration.getPasswordExpirationWarningInterval() <= 0)
305      {
306        LocalizableMessage message =
307          ERR_PWPOLICY_MUST_HAVE_WARNING_IF_NOT_EXPIRE_WITHOUT_WARNING.get(configEntryDN);
308        throw new ConfigException(message);
309      }
310
311      // Get the required change time.
312      String requireChangeBy = configuration.getRequireChangeByTime();
313      long requireChangeByTime = 0L;
314      try
315      {
316        if (requireChangeBy != null)
317        {
318          ByteString valueString = ByteString.valueOf(requireChangeBy);
319          requireChangeByTime = GeneralizedTime.valueOf(valueString.toString()).getTimeInMillis();
320        }
321      }
322      catch (Exception e)
323      {
324        logger.traceException(e);
325
326        LocalizableMessage message = ERR_PWPOLICY_CANNOT_DETERMINE_REQUIRE_CHANGE_BY_TIME
327            .get(configEntryDN, getExceptionMessage(e));
328        throw new InitializationException(message, e);
329      }
330
331      // Get the last login time format. If specified, it must be a valid format
332      // string.
333      String formatString = configuration.getLastLoginTimeFormat();
334      if (formatString != null)
335      {
336        try
337        {
338          new SimpleDateFormat(formatString);
339        }
340        catch (Exception e)
341        {
342          logger.traceException(e);
343
344          LocalizableMessage message = ERR_PWPOLICY_INVALID_LAST_LOGIN_TIME_FORMAT.get(configEntryDN, formatString);
345          throw new ConfigException(message);
346        }
347      }
348
349      // Get the previous last login time formats. If specified, they must all
350      // be valid format strings.
351      SortedSet<String> formatStrings = configuration
352          .getPreviousLastLoginTimeFormat();
353      if (formatStrings != null)
354      {
355        for (String s : formatStrings)
356        {
357          try
358          {
359            new SimpleDateFormat(s);
360          }
361          catch (Exception e)
362          {
363            logger.traceException(e);
364
365            LocalizableMessage message =
366              ERR_PWPOLICY_INVALID_PREVIOUS_LAST_LOGIN_TIME_FORMAT.get(configEntryDN, s);
367            throw new ConfigException(message);
368          }
369        }
370      }
371
372      // If both a maximum password age and a warning interval are provided,
373      // then
374      // ensure that the warning interval is less than the maximum age. Further,
375      // if a minimum age is specified, then the sum of the minimum age and the
376      // warning interval should be less than the maximum age.
377      if (configuration.getMaxPasswordAge() > 0)
378      {
379        long warnInterval = Math.max(0L,
380            configuration.getPasswordExpirationWarningInterval());
381        if (configuration.getMinPasswordAge() > 0)
382        {
383          if (warnInterval + configuration.getMinPasswordAge() >= configuration.getMaxPasswordAge())
384          {
385            LocalizableMessage message =
386              ERR_PWPOLICY_MIN_AGE_PLUS_WARNING_GREATER_THAN_MAX_AGE.get(configEntryDN);
387            throw new ConfigException(message);
388          }
389        }
390        else if (warnInterval >= configuration.getMaxPasswordAge())
391        {
392          LocalizableMessage message = ERR_PWPOLICY_WARNING_INTERVAL_LARGER_THAN_MAX_AGE.get(configEntryDN);
393          throw new ConfigException(message);
394        }
395      }
396
397      // If we've got this far then the configuration is good and we can commit
398      // the changes if required.
399      if (applyChanges)
400      {
401        this.configuration = configuration;
402        this.authPasswordSyntax = authPasswordSyntax;
403        this.defaultStorageSchemes = defaultStorageSchemes;
404        this.deprecatedStorageSchemes = deprecatedStorageSchemes;
405        this.notificationHandlers = notificationHandlers;
406        this.passwordGenerator = passwordGenerator;
407        this.passwordValidators = passwordValidators;
408        this.requireChangeByTime = requireChangeByTime;
409      }
410    }
411
412    /** {@inheritDoc} */
413    @Override
414    public boolean isAuthPasswordSyntax()
415    {
416      return authPasswordSyntax;
417    }
418
419    /** {@inheritDoc} */
420    @Override
421    public List<PasswordStorageScheme<?>> getDefaultPasswordStorageSchemes()
422    {
423      return defaultStorageSchemes;
424    }
425
426    /** {@inheritDoc} */
427    @Override
428    public Set<String> getDeprecatedPasswordStorageSchemes()
429    {
430      return deprecatedStorageSchemes;
431    }
432
433    /** {@inheritDoc} */
434    @Override
435    public DN getDN()
436    {
437      return configuration.dn();
438    }
439
440    /** {@inheritDoc} */
441    @Override
442    public boolean isDefaultPasswordStorageScheme(String name)
443    {
444      for (PasswordStorageScheme<?> s : defaultStorageSchemes)
445      {
446        if (authPasswordSyntax)
447        {
448          if (s.getAuthPasswordSchemeName().equalsIgnoreCase(name))
449          {
450            return true;
451          }
452        }
453        else
454        {
455          if (s.getStorageSchemeName().equalsIgnoreCase(name))
456          {
457            return true;
458          }
459        }
460      }
461
462      return false;
463    }
464
465    /** {@inheritDoc} */
466    @Override
467    public boolean isDeprecatedPasswordStorageScheme(String name)
468    {
469      return deprecatedStorageSchemes.contains(toLowerCase(name));
470    }
471
472    /** {@inheritDoc} */
473    @Override
474    public Collection<PasswordValidator<?>> getPasswordValidators()
475    {
476      return passwordValidators.values();
477    }
478
479    /** {@inheritDoc} */
480    @Override
481    public Collection<AccountStatusNotificationHandler<?>>
482      getAccountStatusNotificationHandlers()
483    {
484      return notificationHandlers.values();
485    }
486
487    /** {@inheritDoc} */
488    @Override
489    public PasswordGenerator<?> getPasswordGenerator()
490    {
491      return passwordGenerator;
492    }
493
494    /** {@inheritDoc} */
495    @Override
496    public long getRequireChangeByTime()
497    {
498      return requireChangeByTime;
499    }
500
501
502
503    /**
504     * Retrieves a string representation of this password policy.
505     *
506     * @return A string representation of this password policy.
507     */
508    @Override
509    public String toString()
510    {
511      StringBuilder buffer = new StringBuilder();
512      toString(buffer);
513      return buffer.toString();
514    }
515
516
517
518    /**
519     * Appends a string representation of this password policy to the provided
520     * buffer.
521     *
522     * @param buffer
523     *          The buffer to which the information should be appended.
524     */
525    public void toString(StringBuilder buffer)
526    {
527      buffer.append("Password Attribute:                    ");
528      buffer.append(configuration.getPasswordAttribute().getNameOrOID());
529      buffer.append(EOL);
530
531      buffer.append("Default Password Storage Schemes:      ");
532      if (defaultStorageSchemes == null || defaultStorageSchemes.isEmpty())
533      {
534        buffer.append("{none specified}");
535        buffer.append(EOL);
536      }
537      else
538      {
539        Iterator<PasswordStorageScheme<?>> iterator = defaultStorageSchemes
540            .iterator();
541        buffer.append(iterator.next().getStorageSchemeName());
542        buffer.append(EOL);
543
544        while (iterator.hasNext())
545        {
546          buffer.append("                                       ");
547          buffer.append(iterator.next().getStorageSchemeName());
548          buffer.append(EOL);
549        }
550      }
551
552      buffer.append("Deprecated Password Storage Schemes:   ");
553      if (deprecatedStorageSchemes == null || deprecatedStorageSchemes.isEmpty())
554      {
555        buffer.append("{none specified}");
556        buffer.append(EOL);
557      }
558      else
559      {
560        Iterator<String> iterator = deprecatedStorageSchemes.iterator();
561        buffer.append(iterator.next());
562        buffer.append(EOL);
563
564        while (iterator.hasNext())
565        {
566          buffer.append("                                       ");
567          buffer.append(iterator.next());
568          buffer.append(EOL);
569        }
570      }
571
572      buffer.append("Allow Multiple Password Values:        ");
573      buffer.append(configuration.isAllowMultiplePasswordValues());
574      buffer.append(EOL);
575
576      buffer.append("Allow Pre-Encoded Passwords:           ");
577      buffer.append(configuration.isAllowPreEncodedPasswords());
578      buffer.append(EOL);
579
580      buffer.append("Allow User Password Changes:           ");
581      buffer.append(configuration.isAllowUserPasswordChanges());
582      buffer.append(EOL);
583
584      buffer.append("Force Password Change on Add:          ");
585      buffer.append(configuration.isForceChangeOnAdd());
586      buffer.append(EOL);
587
588      buffer.append("Force Password Change on Admin Reset:  ");
589      buffer.append(configuration.isForceChangeOnReset());
590      buffer.append(EOL);
591
592      buffer.append("Require Current Password:              ");
593      buffer.append(configuration.isPasswordChangeRequiresCurrentPassword());
594      buffer.append(EOL);
595
596      buffer.append("Require Secure Authentication:         ");
597      buffer.append(configuration.isRequireSecureAuthentication());
598      buffer.append(EOL);
599
600      buffer.append("Require Secure Password Changes:       ");
601      buffer.append(configuration.isRequireSecurePasswordChanges());
602      buffer.append(EOL);
603
604      buffer.append("Lockout Failure Expiration Interval:   ");
605      buffer.append(configuration.getLockoutFailureExpirationInterval());
606      buffer.append(" seconds");
607      buffer.append(EOL);
608
609      buffer.append("Password Validators:                   ");
610      if (passwordValidators == null || passwordValidators.isEmpty())
611      {
612        buffer.append("{none specified}");
613        buffer.append(EOL);
614      }
615      else
616      {
617        Iterator<DN> iterator = passwordValidators.keySet().iterator();
618        iterator.next().toString(buffer);
619        buffer.append(EOL);
620
621        while (iterator.hasNext())
622        {
623          buffer.append("                                       ");
624          iterator.next().toString(buffer);
625          buffer.append(EOL);
626        }
627      }
628
629      buffer.append("Skip Validation for Administrators:    ");
630      buffer.append(configuration.isSkipValidationForAdministrators());
631      buffer.append(EOL);
632
633      buffer.append("Password Generator:                    ");
634      if (passwordGenerator == null)
635      {
636        buffer.append("{none specified}");
637      }
638      else
639      {
640        configuration.getPasswordGeneratorDN().toString(buffer);
641      }
642      buffer.append(EOL);
643
644      buffer.append("Account Status Notification Handlers:  ");
645      if (notificationHandlers == null || notificationHandlers.isEmpty())
646      {
647        buffer.append("{none specified}");
648        buffer.append(EOL);
649      }
650      else
651      {
652        Iterator<DN> iterator = notificationHandlers.keySet().iterator();
653        iterator.next().toString(buffer);
654        buffer.append(EOL);
655
656        while (iterator.hasNext())
657        {
658          buffer.append("                                       ");
659          iterator.next().toString(buffer);
660          buffer.append(EOL);
661        }
662      }
663
664      buffer.append("Minimum Password Age:                  ");
665      buffer.append(configuration.getMinPasswordAge());
666      buffer.append(" seconds");
667      buffer.append(EOL);
668
669      buffer.append("Maximum Password Age:                  ");
670      buffer.append(configuration.getMaxPasswordAge());
671      buffer.append(" seconds");
672      buffer.append(EOL);
673
674      buffer.append("Maximum Password Reset Age:            ");
675      buffer.append(configuration.getMaxPasswordResetAge());
676      buffer.append(" seconds");
677      buffer.append(EOL);
678
679      buffer.append("Expiration Warning Interval:           ");
680      buffer.append(configuration.getPasswordExpirationWarningInterval());
681      buffer.append(" seconds");
682      buffer.append(EOL);
683
684      buffer.append("Expire Passwords Without Warning:      ");
685      buffer.append(configuration.isExpirePasswordsWithoutWarning());
686      buffer.append(EOL);
687
688      buffer.append("Allow Expired Password Changes:        ");
689      buffer.append(configuration.isAllowExpiredPasswordChanges());
690      buffer.append(EOL);
691
692      buffer.append("Grace Login Count:                     ");
693      buffer.append(configuration.getGraceLoginCount());
694      buffer.append(EOL);
695
696      buffer.append("Lockout Failure Count:                 ");
697      buffer.append(configuration.getLockoutFailureCount());
698      buffer.append(EOL);
699
700      buffer.append("Lockout Duration:                      ");
701      buffer.append(configuration.getLockoutDuration());
702      buffer.append(" seconds");
703      buffer.append(EOL);
704
705      buffer.append("Lockout Count Expiration Interval:     ");
706      buffer.append(configuration.getLockoutFailureExpirationInterval());
707      buffer.append(" seconds");
708      buffer.append(EOL);
709
710      buffer.append("Required Password Change By Time:      ");
711      if (requireChangeByTime <= 0)
712      {
713        buffer.append("{none specified}");
714      }
715      else
716      {
717        SimpleDateFormat dateFormat = new SimpleDateFormat(
718            DATE_FORMAT_GENERALIZED_TIME);
719        dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
720        buffer.append(dateFormat.format(new Date(requireChangeByTime)));
721      }
722      buffer.append(EOL);
723
724      buffer.append("Last Login Time Attribute:             ");
725      if (configuration.getLastLoginTimeAttribute() == null)
726      {
727        buffer.append("{none specified}");
728      }
729      else
730      {
731        buffer.append(configuration.getLastLoginTimeAttribute().getNameOrOID());
732      }
733      buffer.append(EOL);
734
735      buffer.append("Last Login Time Format:                ");
736      if (configuration.getLastLoginTimeFormat() == null)
737      {
738        buffer.append("{none specified}");
739      }
740      else
741      {
742        buffer.append(configuration.getLastLoginTimeFormat());
743      }
744      buffer.append(EOL);
745
746      buffer.append("Previous Last Login Time Formats:      ");
747      if (configuration.getPreviousLastLoginTimeFormat().isEmpty())
748      {
749        buffer.append("{none specified}");
750        buffer.append(EOL);
751      }
752      else
753      {
754        Iterator<String> iterator = configuration
755            .getPreviousLastLoginTimeFormat().iterator();
756
757        buffer.append(iterator.next());
758        buffer.append(EOL);
759
760        while (iterator.hasNext())
761        {
762          buffer.append("                                       ");
763          buffer.append(iterator.next());
764          buffer.append(EOL);
765        }
766      }
767
768      buffer.append("Idle Lockout Interval:                 ");
769      buffer.append(configuration.getIdleLockoutInterval());
770      buffer.append(" seconds");
771      buffer.append(EOL);
772
773      buffer.append("History Count:                         ");
774      buffer.append(configuration.getPasswordHistoryCount());
775      buffer.append(EOL);
776
777      buffer.append("Update Failure Policy:                 ");
778      buffer.append(configuration.getStateUpdateFailurePolicy());
779      buffer.append(EOL);
780    }
781
782    /** {@inheritDoc} */
783    @Override
784    public boolean isAllowExpiredPasswordChanges()
785    {
786      return configuration.isAllowExpiredPasswordChanges();
787    }
788
789    /** {@inheritDoc} */
790    @Override
791    public boolean isAllowMultiplePasswordValues()
792    {
793      return configuration.isAllowMultiplePasswordValues();
794    }
795
796    /** {@inheritDoc} */
797    @Override
798    public boolean isAllowPreEncodedPasswords()
799    {
800      return configuration.isAllowPreEncodedPasswords();
801    }
802
803    /** {@inheritDoc} */
804    @Override
805    public boolean isAllowUserPasswordChanges()
806    {
807      return configuration.isAllowUserPasswordChanges();
808    }
809
810    /** {@inheritDoc} */
811    @Override
812    public boolean isExpirePasswordsWithoutWarning()
813    {
814      return configuration.isExpirePasswordsWithoutWarning();
815    }
816
817    /** {@inheritDoc} */
818    @Override
819    public boolean isForceChangeOnAdd()
820    {
821      return configuration.isForceChangeOnAdd();
822    }
823
824    /** {@inheritDoc} */
825    @Override
826    public boolean isForceChangeOnReset()
827    {
828      return configuration.isForceChangeOnReset();
829    }
830
831    /** {@inheritDoc} */
832    @Override
833    public int getGraceLoginCount()
834    {
835      return configuration.getGraceLoginCount();
836    }
837
838    /** {@inheritDoc} */
839    @Override
840    public long getIdleLockoutInterval()
841    {
842      return configuration.getIdleLockoutInterval();
843    }
844
845    /** {@inheritDoc} */
846    @Override
847    public AttributeType getLastLoginTimeAttribute()
848    {
849      return configuration.getLastLoginTimeAttribute();
850    }
851
852    /** {@inheritDoc} */
853    @Override
854    public String getLastLoginTimeFormat()
855    {
856      return configuration.getLastLoginTimeFormat();
857    }
858
859    /** {@inheritDoc} */
860    @Override
861    public long getLockoutDuration()
862    {
863      return configuration.getLockoutDuration();
864    }
865
866    /** {@inheritDoc} */
867    @Override
868    public int getLockoutFailureCount()
869    {
870      return configuration.getLockoutFailureCount();
871    }
872
873    /** {@inheritDoc} */
874    @Override
875    public long getLockoutFailureExpirationInterval()
876    {
877      return configuration.getLockoutFailureExpirationInterval();
878    }
879
880    /** {@inheritDoc} */
881    @Override
882    public long getMaxPasswordAge()
883    {
884      return configuration.getMaxPasswordAge();
885    }
886
887    /** {@inheritDoc} */
888    @Override
889    public long getMaxPasswordResetAge()
890    {
891      return configuration.getMaxPasswordResetAge();
892    }
893
894    /** {@inheritDoc} */
895    @Override
896    public long getMinPasswordAge()
897    {
898      return configuration.getMinPasswordAge();
899    }
900
901    /** {@inheritDoc} */
902    @Override
903    public AttributeType getPasswordAttribute()
904    {
905      return configuration.getPasswordAttribute();
906    }
907
908    /** {@inheritDoc} */
909    @Override
910    public boolean isPasswordChangeRequiresCurrentPassword()
911    {
912      return configuration.isPasswordChangeRequiresCurrentPassword();
913    }
914
915    /** {@inheritDoc} */
916    @Override
917    public long getPasswordExpirationWarningInterval()
918    {
919      return configuration.getPasswordExpirationWarningInterval();
920    }
921
922    /** {@inheritDoc} */
923    @Override
924    public int getPasswordHistoryCount()
925    {
926      return configuration.getPasswordHistoryCount();
927    }
928
929    /** {@inheritDoc} */
930    @Override
931    public long getPasswordHistoryDuration()
932    {
933      return configuration.getPasswordHistoryDuration();
934    }
935
936    /** {@inheritDoc} */
937    @Override
938    public SortedSet<String> getPreviousLastLoginTimeFormats()
939    {
940      return configuration.getPreviousLastLoginTimeFormat();
941    }
942
943    /** {@inheritDoc} */
944    @Override
945    public boolean isRequireSecureAuthentication()
946    {
947      return configuration.isRequireSecureAuthentication();
948    }
949
950    /** {@inheritDoc} */
951    @Override
952    public boolean isRequireSecurePasswordChanges()
953    {
954      return configuration.isRequireSecurePasswordChanges();
955    }
956
957    /** {@inheritDoc} */
958    @Override
959    public boolean isSkipValidationForAdministrators()
960    {
961      return configuration.isSkipValidationForAdministrators();
962    }
963
964    /** {@inheritDoc} */
965    @Override
966    public StateUpdateFailurePolicy getStateUpdateFailurePolicy()
967    {
968      return configuration.getStateUpdateFailurePolicy();
969    }
970
971  }
972
973  private ServerContext serverContext;
974
975  /**
976   * Default constructor instantiated from authentication policy config manager.
977   */
978  public PasswordPolicyFactory()
979  {
980    // Nothing to do .
981  }
982
983  /**
984   * Sets the server context.
985   *
986   * @param serverContext
987   *            The server context.
988   */
989  @Override
990  public void setServerContext(final ServerContext serverContext) {
991    this.serverContext = serverContext;
992  }
993
994  /** {@inheritDoc} */
995  @Override
996  public PasswordPolicy createAuthenticationPolicy(
997      final PasswordPolicyCfg configuration) throws ConfigException,
998      InitializationException
999  {
1000    PasswordPolicyImpl policy = new PasswordPolicyImpl(serverContext, configuration);
1001    configuration.addPasswordPolicyChangeListener(policy);
1002    return policy;
1003  }
1004
1005  /** {@inheritDoc} */
1006  @Override
1007  public boolean isConfigurationAcceptable(
1008      final PasswordPolicyCfg configuration,
1009      final List<LocalizableMessage> unacceptableReasons)
1010  {
1011    try
1012    {
1013      new PasswordPolicyImpl(null, configuration);
1014    }
1015    catch (final ConfigException | InitializationException ie)
1016    {
1017      logger.traceException(ie);
1018
1019      unacceptableReasons.add(ie.getMessageObject());
1020      return false;
1021    }
1022
1023    // If we made it here, then the configuration is acceptable.
1024    return true;
1025  }
1026
1027}