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