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