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 2010 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.*;
032
033import java.util.*;
034import java.util.concurrent.atomic.AtomicBoolean;
035
036import org.forgerock.i18n.LocalizableMessage;
037import org.forgerock.i18n.LocalizableMessageBuilder;
038import org.opends.server.admin.std.meta.PasswordPolicyCfgDefn.*;
039import org.opends.server.admin.std.server.PasswordValidatorCfg;
040import org.opends.server.api.AccountStatusNotificationHandler;
041import org.opends.server.api.PasswordGenerator;
042import org.opends.server.api.PasswordStorageScheme;
043import org.opends.server.api.PasswordValidator;
044import org.forgerock.i18n.slf4j.LocalizedLogger;
045import org.forgerock.opendj.config.server.ConfigException;
046import org.opends.server.types.*;
047import org.forgerock.opendj.ldap.ResultCode;
048import org.forgerock.opendj.ldap.ByteString;
049
050/**
051 * This class represents subentry password policy based on Password Policy for
052 * LDAP Directories Internet-Draft. In order to represent subentry password
053 * policies as OpenDJ password policies it performs a mapping of Draft defined
054 * attributes to OpenDJ implementation specific attributes. Any missing
055 * attributes are inherited from server default password policy. This class is
056 * also responsible for any Draft attributes validation ie making sure that
057 * provided values are acceptable and within the predefined range.
058 */
059public final class SubentryPasswordPolicy extends PasswordPolicy
060{
061  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
062
063  // Password Policy Subentry draft attributes.
064  private static final String PWD_OC_POLICY = "pwdpolicy";
065  private static final String PWD_ATTR_ATTRIBUTE = "pwdattribute";
066  private static final String PWD_ATTR_MINAGE = "pwdminage";
067  private static final String PWD_ATTR_MAXAGE = "pwdmaxage";
068  private static final String PWD_ATTR_INHISTORY = "pwdinhistory";
069  private static final String PWD_ATTR_CHECKQUALITY = "pwdcheckquality";
070  private static final String PWD_ATTR_MINLENGTH = "pwdminlength";
071  private static final String PWD_ATTR_EXPIREWARNING = "pwdexpirewarning";
072  private static final String PWD_ATTR_GRACEAUTHNLIMIT = "pwdgraceauthnlimit";
073  private static final String PWD_ATTR_LOCKOUT = "pwdlockout";
074  private static final String PWD_ATTR_LOCKOUTDURATION = "pwdlockoutduration";
075  private static final String PWD_ATTR_MAXFAILURE = "pwdmaxfailure";
076  private static final String PWD_ATTR_MUSTCHANGE = "pwdmustchange";
077  private static final String PWD_ATTR_ALLOWUSERCHANGE = "pwdallowuserchange";
078  private static final String PWD_ATTR_SAFEMODIFY = "pwdsafemodify";
079  private static final String PWD_ATTR_FAILURECOUNTINTERVAL =
080      "pwdfailurecountinterval";
081  private static final String PWD_ATTR_VALIDATOR = "ds-cfg-password-validator";
082  private static final String PWD_OC_VALIDATORPOLICY = "pwdvalidatorpolicy";
083
084  /** Password Policy Subentry DN. */
085  private final DN passwordPolicySubentryDN;
086  /** The value of the "allow-user-password-changes" property. */
087  private final Boolean pAllowUserPasswordChanges;
088  /** The value of the "force-change-on-reset" property. */
089  private final Boolean pForceChangeOnReset;
090  /** The value of the "grace-login-count" property. */
091  private final Integer pGraceLoginCount;
092  /** The value of the "lockout-duration" property. */
093  private final Long pLockoutDuration;
094  /** The value of the "lockout-failure-count" property. */
095  private final Integer pLockoutFailureCount;
096  /** The value of the "lockout-failure-expiration-interval" property. */
097  private final Long pLockoutFailureExpirationInterval;
098  /** The value of the "max-password-age" property. */
099  private final Long pMaxPasswordAge;
100  /** The value of the "min-password-age" property. */
101  private final Long pMinPasswordAge;
102  /** The value of the "password-attribute" property. */
103  private final AttributeType pPasswordAttribute;
104  /** The value of the "password-change-requires-current-password" property. */
105  private final Boolean pPasswordChangeRequiresCurrentPassword;
106  /** The value of the "password-expiration-warning-interval" property. */
107  private final Long pPasswordExpirationWarningInterval;
108  /** The value of the "password-history-count" property. */
109  private final Integer pPasswordHistoryCount;
110  /** Indicates if the password attribute uses auth password syntax. */
111  private final Boolean pAuthPasswordSyntax;
112  /** The set of password validators if any. */
113  private final Set<DN> pValidatorNames = new HashSet<>();
114  /** Used when logging errors due to invalid validator reference. */
115  private AtomicBoolean isAlreadyLogged = new AtomicBoolean();
116
117  /**
118   * Returns the global default password policy which will be used for deriving
119   * the default properties of sub-entries.
120   */
121  private PasswordPolicy getDefaultPasswordPolicy()
122  {
123    return DirectoryServer.getDefaultPasswordPolicy();
124  }
125
126  /**
127   * Creates subentry password policy object from the subentry, parsing and
128   * evaluating subentry password policy attributes.
129   *
130   * @param subentry
131   *          password policy subentry.
132   * @throws DirectoryException
133   *           If a problem occurs while creating subentry password policy
134   *           instance from given subentry.
135   */
136  public SubentryPasswordPolicy(SubEntry subentry) throws DirectoryException
137  {
138    // Determine if this is a password policy subentry.
139    ObjectClass pwdPolicyOC = DirectoryServer.getObjectClass(PWD_OC_POLICY);
140    Entry entry = subentry.getEntry();
141    Map<ObjectClass, String> objectClasses = entry.getObjectClasses();
142    if (pwdPolicyOC == null)
143    {
144      // This should not happen -- The server doesn't
145      // have a pwdPolicy objectclass defined.
146      if (logger.isTraceEnabled())
147      {
148        logger.trace("No %s objectclass is defined in the server schema.",
149                PWD_OC_POLICY);
150      }
151      for (String ocName : objectClasses.values())
152      {
153        if (PWD_OC_POLICY.equalsIgnoreCase(ocName))
154        {
155          break;
156        }
157      }
158      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
159          ERR_PWPOLICY_NO_PWDPOLICY_OC.get(subentry.getDN()));
160    }
161    else if (!objectClasses.containsKey(pwdPolicyOC))
162    {
163      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
164          ERR_PWPOLICY_NO_PWDPOLICY_OC.get(subentry.getDN()));
165    }
166
167    // Subentry DN for this password policy.
168    this.passwordPolicySubentryDN = subentry.getDN();
169
170    // Get known Password Policy draft attributes from the entry.
171    // If any given attribute is missing or empty set its value
172    // from default Password Policy configuration.
173    String value = getAttrValue(entry, PWD_ATTR_ATTRIBUTE);
174    if (value != null && value.length() > 0)
175    {
176      this.pPasswordAttribute = DirectoryServer.getAttributeType(value.toLowerCase());
177      if (this.pPasswordAttribute == null)
178      {
179        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
180            ERR_PWPOLICY_UNDEFINED_PASSWORD_ATTRIBUTE.get(this.passwordPolicySubentryDN, value));
181      }
182
183      // Check the syntax.
184      final String syntaxOID = pPasswordAttribute.getSyntax().getOID();
185      if (SYNTAX_AUTH_PASSWORD_OID.equals(syntaxOID))
186      {
187        pAuthPasswordSyntax = true;
188      }
189      else if (SYNTAX_USER_PASSWORD_OID.equals(syntaxOID))
190      {
191        pAuthPasswordSyntax = false;
192      }
193      else
194      {
195        String syntax = pPasswordAttribute.getSyntax().getName();
196        if (syntax == null || syntax.length() == 0)
197        {
198          syntax = syntaxOID;
199        }
200
201        LocalizableMessage message = ERR_PWPOLICY_INVALID_PASSWORD_ATTRIBUTE_SYNTAX.get(
202            passwordPolicySubentryDN, pPasswordAttribute.getNameOrOID(), syntax);
203        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
204      }
205    }
206    else
207    {
208      this.pPasswordAttribute = null;
209      this.pAuthPasswordSyntax = null;
210    }
211
212    this.pMinPasswordAge = asLong(entry, PWD_ATTR_MINAGE);
213    this.pMaxPasswordAge = asLong(entry, PWD_ATTR_MAXAGE);
214    this.pPasswordHistoryCount =
215        asInteger(entry, PWD_ATTR_INHISTORY, Integer.MAX_VALUE);
216
217    // This one is managed via the password validator
218    // so only check if its value is acceptable.
219    asInteger(entry, PWD_ATTR_CHECKQUALITY, 2);
220
221    // This one is managed via the password validator
222    // so only check if its value is acceptable.
223    asInteger(entry, PWD_ATTR_MINLENGTH, Integer.MAX_VALUE);
224
225    // This one depends on lockout failure count value
226    // so only check if its value is acceptable.
227    asBoolean(entry, PWD_ATTR_LOCKOUT);
228
229    this.pPasswordExpirationWarningInterval =
230        asLong(entry, PWD_ATTR_EXPIREWARNING);
231    this.pGraceLoginCount =
232        asInteger(entry, PWD_ATTR_GRACEAUTHNLIMIT, Integer.MAX_VALUE);
233    this.pLockoutDuration = asLong(entry, PWD_ATTR_LOCKOUTDURATION);
234    this.pLockoutFailureCount =
235        asInteger(entry, PWD_ATTR_MAXFAILURE, Integer.MAX_VALUE);
236    this.pForceChangeOnReset = asBoolean(entry, PWD_ATTR_MUSTCHANGE);
237    this.pAllowUserPasswordChanges = asBoolean(entry, PWD_ATTR_ALLOWUSERCHANGE);
238    this.pPasswordChangeRequiresCurrentPassword =
239        asBoolean(entry, PWD_ATTR_SAFEMODIFY);
240    this.pLockoutFailureExpirationInterval =
241        asLong(entry, PWD_ATTR_FAILURECOUNTINTERVAL);
242
243    // Now check for the pwdValidatorPolicy OC and its attribute.
244    // Determine if this is a password validator policy object class.
245    ObjectClass pwdValidatorPolicyOC =
246        DirectoryServer.getObjectClass(PWD_OC_VALIDATORPOLICY);
247    if (pwdValidatorPolicyOC != null &&
248        objectClasses.containsKey(pwdValidatorPolicyOC))
249    {
250      AttributeType pwdAttrType =
251          DirectoryServer.getAttributeTypeOrDefault(PWD_ATTR_VALIDATOR);
252      List<Attribute> pwdAttrList = entry.getAttribute(pwdAttrType);
253      if (pwdAttrList != null && !pwdAttrList.isEmpty())
254      {
255        for (Attribute attr : pwdAttrList)
256        {
257          for (ByteString val : attr)
258          {
259            DN validatorDN = DN.decode(val);
260            if (DirectoryServer.getPasswordValidator(validatorDN) == null)
261            {
262              throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
263                  ERR_PWPOLICY_UNKNOWN_VALIDATOR.get(this.passwordPolicySubentryDN, validatorDN, PWD_ATTR_VALIDATOR));
264            }
265            pValidatorNames.add(validatorDN);
266          }
267        }
268      }
269    }
270  }
271
272  private Boolean asBoolean(Entry entry, String attrName)
273      throws DirectoryException
274  {
275    final String value = getAttrValue(entry, attrName);
276    if (value != null && value.length() > 0)
277    {
278      if (value.equalsIgnoreCase(Boolean.TRUE.toString())
279          || value.equalsIgnoreCase(Boolean.FALSE.toString()))
280      {
281        return Boolean.valueOf(value);
282      }
283      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
284          ERR_CONFIG_ATTR_INVALID_BOOLEAN_VALUE.get(attrName, value));
285    }
286    return null;
287  }
288
289  private Integer asInteger(Entry entry, String attrName, int upperBound)
290      throws DirectoryException
291  {
292    final String value = getAttrValue(entry, attrName);
293    if (value != null && value.length() > 0)
294    {
295      try
296      {
297        final Integer result = Integer.valueOf(value);
298        checkIntegerAttr(attrName, result, 0, upperBound);
299        return result;
300      }
301      catch (NumberFormatException ne)
302      {
303        throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
304            ERR_CONFIG_ATTR_INVALID_INT_VALUE.get(attrName, value,
305                ne.getLocalizedMessage()));
306      }
307    }
308    return null;
309  }
310
311  private Long asLong(Entry entry, String attrName) throws DirectoryException
312  {
313    final String value = getAttrValue(entry, attrName);
314    if (value != null && value.length() > 0)
315    {
316      try
317      {
318        final Long result = Long.valueOf(value);
319        checkIntegerAttr(attrName, result, 0, Integer.MAX_VALUE);
320        return result;
321      }
322      catch (NumberFormatException ne)
323      {
324        throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
325            ERR_CONFIG_ATTR_INVALID_INT_VALUE.get(attrName, value,
326                ne.getLocalizedMessage()));
327      }
328    }
329    return null;
330  }
331
332
333
334  /**
335   * Helper method to validate integer values.
336   *
337   * @param attrName
338   *          integer attribute name.
339   * @param attrValue
340   *          integer value to validate.
341   * @param lowerBound
342   *          lowest acceptable value.
343   * @param upperBound
344   *          highest acceptable value.
345   * @throws DirectoryException
346   *           if the value is out of bounds.
347   */
348  private void checkIntegerAttr(String attrName, long attrValue,
349      long lowerBound, long upperBound) throws DirectoryException
350  {
351    if (attrValue < lowerBound)
352    {
353      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
354          ERR_CONFIG_ATTR_INT_BELOW_LOWER_BOUND.get(attrName, attrValue,
355              lowerBound));
356    }
357    if (attrValue > upperBound)
358    {
359      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
360          ERR_CONFIG_ATTR_INT_ABOVE_UPPER_BOUND.get(attrName, attrValue,
361              upperBound));
362    }
363  }
364
365
366
367  /**
368   * Helper method to retrieve an attribute value from given entry.
369   *
370   * @param entry
371   *          the entry to retrieve an attribute value from.
372   * @param pwdAttrName
373   *          attribute name to retrieve the value for.
374   * @return <CODE>String</CODE> or <CODE>null</CODE>.
375   */
376  private String getAttrValue(Entry entry, String pwdAttrName)
377  {
378    AttributeType pwdAttrType = DirectoryServer.getAttributeTypeOrDefault(pwdAttrName);
379    List<Attribute> pwdAttrList = entry.getAttribute(pwdAttrType);
380    if (pwdAttrList != null && !pwdAttrList.isEmpty())
381    {
382      for (Attribute attr : pwdAttrList)
383      {
384        for (ByteString value : attr)
385        {
386          return value.toString();
387        }
388      }
389    }
390    return null;
391  }
392
393  /** {@inheritDoc} */
394  @Override
395  public boolean isAllowExpiredPasswordChanges()
396  {
397    return getDefaultPasswordPolicy().isAllowExpiredPasswordChanges();
398  }
399
400  /** {@inheritDoc} */
401  @Override
402  public boolean isAllowMultiplePasswordValues()
403  {
404    return getDefaultPasswordPolicy().isAllowMultiplePasswordValues();
405  }
406
407  /** {@inheritDoc} */
408  @Override
409  public boolean isAllowPreEncodedPasswords()
410  {
411    return getDefaultPasswordPolicy().isAllowPreEncodedPasswords();
412  }
413
414  /** {@inheritDoc} */
415  @Override
416  public boolean isAllowUserPasswordChanges()
417  {
418    return pAllowUserPasswordChanges != null ? pAllowUserPasswordChanges
419        : getDefaultPasswordPolicy().isAllowUserPasswordChanges();
420  }
421
422  /** {@inheritDoc} */
423  @Override
424  public boolean isExpirePasswordsWithoutWarning()
425  {
426    return getDefaultPasswordPolicy().isExpirePasswordsWithoutWarning();
427  }
428
429  /** {@inheritDoc} */
430  @Override
431  public boolean isForceChangeOnAdd()
432  {
433    // Don't use pwdMustChange since the password provided when the entry was
434    // added may have been provided by the user. See OPENDJ-341.
435    return getDefaultPasswordPolicy().isForceChangeOnAdd();
436  }
437
438  /** {@inheritDoc} */
439  @Override
440  public boolean isForceChangeOnReset()
441  {
442    return pForceChangeOnReset != null ? pForceChangeOnReset
443        : getDefaultPasswordPolicy().isForceChangeOnReset();
444  }
445
446  /** {@inheritDoc} */
447  @Override
448  public int getGraceLoginCount()
449  {
450    return pGraceLoginCount != null ? pGraceLoginCount
451        : getDefaultPasswordPolicy().getGraceLoginCount();
452  }
453
454  /** {@inheritDoc} */
455  @Override
456  public long getIdleLockoutInterval()
457  {
458    return getDefaultPasswordPolicy().getIdleLockoutInterval();
459  }
460
461  /** {@inheritDoc} */
462  @Override
463  public AttributeType getLastLoginTimeAttribute()
464  {
465    return getDefaultPasswordPolicy().getLastLoginTimeAttribute();
466  }
467
468  /** {@inheritDoc} */
469  @Override
470  public String getLastLoginTimeFormat()
471  {
472    return getDefaultPasswordPolicy().getLastLoginTimeFormat();
473  }
474
475  /** {@inheritDoc} */
476  @Override
477  public long getLockoutDuration()
478  {
479    return pLockoutDuration != null ? pLockoutDuration
480        : getDefaultPasswordPolicy().getLockoutDuration();
481  }
482
483  /** {@inheritDoc} */
484  @Override
485  public int getLockoutFailureCount()
486  {
487    return pLockoutFailureCount != null ? pLockoutFailureCount
488        : getDefaultPasswordPolicy().getLockoutFailureCount();
489  }
490
491  /** {@inheritDoc} */
492  @Override
493  public long getLockoutFailureExpirationInterval()
494  {
495    return pLockoutFailureExpirationInterval != null ?
496        pLockoutFailureExpirationInterval
497        : getDefaultPasswordPolicy().getLockoutFailureExpirationInterval();
498  }
499
500  /** {@inheritDoc} */
501  @Override
502  public long getMaxPasswordAge()
503  {
504    return pMaxPasswordAge != null ? pMaxPasswordAge
505        : getDefaultPasswordPolicy().getMaxPasswordAge();
506  }
507
508  /** {@inheritDoc} */
509  @Override
510  public long getMaxPasswordResetAge()
511  {
512    return getDefaultPasswordPolicy().getMaxPasswordResetAge();
513  }
514
515  /** {@inheritDoc} */
516  @Override
517  public long getMinPasswordAge()
518  {
519    return pMinPasswordAge != null ? pMinPasswordAge
520        : getDefaultPasswordPolicy().getMinPasswordAge();
521  }
522
523  /** {@inheritDoc} */
524  @Override
525  public AttributeType getPasswordAttribute()
526  {
527    return pPasswordAttribute != null ? pPasswordAttribute
528        : getDefaultPasswordPolicy().getPasswordAttribute();
529  }
530
531  /** {@inheritDoc} */
532  @Override
533  public boolean isPasswordChangeRequiresCurrentPassword()
534  {
535    return pPasswordChangeRequiresCurrentPassword != null ?
536        pPasswordChangeRequiresCurrentPassword
537        : getDefaultPasswordPolicy().isPasswordChangeRequiresCurrentPassword();
538  }
539
540  /** {@inheritDoc} */
541  @Override
542  public long getPasswordExpirationWarningInterval()
543  {
544    return pPasswordExpirationWarningInterval != null ?
545        pPasswordExpirationWarningInterval
546        : getDefaultPasswordPolicy().getPasswordExpirationWarningInterval();
547  }
548
549  /** {@inheritDoc} */
550  @Override
551  public int getPasswordHistoryCount()
552  {
553    return pPasswordHistoryCount != null ? pPasswordHistoryCount
554        : getDefaultPasswordPolicy().getPasswordHistoryCount();
555  }
556
557  /** {@inheritDoc} */
558  @Override
559  public long getPasswordHistoryDuration()
560  {
561    return getDefaultPasswordPolicy().getPasswordHistoryDuration();
562  }
563
564  /** {@inheritDoc} */
565  @Override
566  public SortedSet<String> getPreviousLastLoginTimeFormats()
567  {
568    return getDefaultPasswordPolicy().getPreviousLastLoginTimeFormats();
569  }
570
571  /** {@inheritDoc} */
572  @Override
573  public long getRequireChangeByTime()
574  {
575    return getDefaultPasswordPolicy().getRequireChangeByTime();
576  }
577
578  /** {@inheritDoc} */
579  @Override
580  public boolean isRequireSecureAuthentication()
581  {
582    return getDefaultPasswordPolicy().isRequireSecureAuthentication();
583  }
584
585  /** {@inheritDoc} */
586  @Override
587  public boolean isRequireSecurePasswordChanges()
588  {
589    return getDefaultPasswordPolicy().isRequireSecurePasswordChanges();
590  }
591
592  /** {@inheritDoc} */
593  @Override
594  public boolean isSkipValidationForAdministrators()
595  {
596    return getDefaultPasswordPolicy().isSkipValidationForAdministrators();
597  }
598
599  /** {@inheritDoc} */
600  @Override
601  public StateUpdateFailurePolicy getStateUpdateFailurePolicy()
602  {
603    return getDefaultPasswordPolicy().getStateUpdateFailurePolicy();
604  }
605
606  /** {@inheritDoc} */
607  @Override
608  public boolean isAuthPasswordSyntax()
609  {
610    return pAuthPasswordSyntax != null ? pAuthPasswordSyntax
611        : getDefaultPasswordPolicy().isAuthPasswordSyntax();
612  }
613
614  /** {@inheritDoc} */
615  @Override
616  public List<PasswordStorageScheme<?>> getDefaultPasswordStorageSchemes()
617  {
618    return getDefaultPasswordPolicy().getDefaultPasswordStorageSchemes();
619  }
620
621  /** {@inheritDoc} */
622  @Override
623  public Set<String> getDeprecatedPasswordStorageSchemes()
624  {
625    return getDefaultPasswordPolicy().getDeprecatedPasswordStorageSchemes();
626  }
627
628  /** {@inheritDoc} */
629  @Override
630  public DN getDN()
631  {
632    return passwordPolicySubentryDN;
633  }
634
635  /** {@inheritDoc} */
636  @Override
637  public boolean isDefaultPasswordStorageScheme(String name)
638  {
639    return getDefaultPasswordPolicy().isDefaultPasswordStorageScheme(name);
640  }
641
642  /** {@inheritDoc} */
643  @Override
644  public boolean isDeprecatedPasswordStorageScheme(String name)
645  {
646    return getDefaultPasswordPolicy().isDeprecatedPasswordStorageScheme(name);
647  }
648
649  /** {@inheritDoc} */
650  @Override
651  public Collection<PasswordValidator<?>> getPasswordValidators()
652  {
653    if (!pValidatorNames.isEmpty())
654    {
655      Collection<PasswordValidator<?>> values = new HashSet<>();
656      for (DN validatorDN : pValidatorNames){
657        PasswordValidator<?> validator = DirectoryServer.getPasswordValidator(validatorDN);
658        if (validator == null) {
659          PasswordValidator<?> errorValidator = new RejectPasswordValidator(
660              validatorDN.toString(), passwordPolicySubentryDN.toString());
661          values.clear();
662          values.add(errorValidator);
663          return values;
664        }
665        values.add(validator);
666      }
667      isAlreadyLogged.set(false);
668      return values;
669    }
670    return getDefaultPasswordPolicy().getPasswordValidators();
671  }
672
673
674  /**
675   * Implementation of a specific Password Validator that reject all
676   * password due to mis-configured password policy subentry.
677   * This is only used when a subentry is referencing a password
678   * validator that is no longer configured.
679   */
680  private final class RejectPasswordValidator extends
681      PasswordValidator<PasswordValidatorCfg>
682  {
683    private final String validatorName;
684    private final String pwPolicyName;
685    public RejectPasswordValidator(String name, String policyName)
686    {
687      super();
688      validatorName = name;
689      pwPolicyName = policyName;
690    }
691
692    /** {@inheritDoc} */
693    @Override
694    public void initializePasswordValidator(PasswordValidatorCfg configuration)
695        throws ConfigException, InitializationException
696    {
697      // do nothing
698    }
699
700    /** {@inheritDoc} */
701    @Override
702    public boolean passwordIsAcceptable(ByteString newPassword,
703                                        Set<ByteString> currentPasswords,
704                                        Operation operation, Entry userEntry,
705                                        LocalizableMessageBuilder invalidReason)
706    {
707      invalidReason.append(ERR_PWPOLICY_REJECT_DUE_TO_UNKNOWN_VALIDATOR_REASON
708          .get());
709
710      // Only log an error once, on first error
711      if (isAlreadyLogged.compareAndSet(false, true)) {
712        logger.error(ERR_PWPOLICY_REJECT_DUE_TO_UNKNOWN_VALIDATOR_LOG,
713            userEntry.getName(), pwPolicyName, validatorName);
714      }
715      return false;
716    }
717  }
718
719  /** {@inheritDoc} */
720  @Override
721  public Collection<AccountStatusNotificationHandler<?>>
722    getAccountStatusNotificationHandlers()
723  {
724    return getDefaultPasswordPolicy().getAccountStatusNotificationHandlers();
725  }
726
727  /** {@inheritDoc} */
728  @Override
729  public PasswordGenerator<?> getPasswordGenerator()
730  {
731    return getDefaultPasswordPolicy().getPasswordGenerator();
732  }
733
734}