mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Ludovic Poitou
30.50.2014 a00e82adec8a13d514f9aa29f56762591426bd5e
Fix for OPENDJ-1295, allow password policy subentries to refer to configured password validators.
To do so, one will need to add an auxiliary objectclass in the password policy subentry and the mandatory ds-cfg-password-validator attribute (value is the DN of the validator in cn=config DIT).
1 files added
3 files modified
349 ■■■■■ changed files
opends/resource/schema/03-pwpolicyextension.ldif 33 ●●●●● patch | view | raw | blame | history
opends/src/messages/messages/core.properties 9 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/SubentryPasswordPolicy.java 165 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/core/SubentryPasswordPolicyTestCase.java 142 ●●●●● patch | view | raw | blame | history
opends/resource/schema/03-pwpolicyextension.ldif
New file
@@ -0,0 +1,33 @@
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License, Version 1.0 only
# (the "License").  You may not use this file except in compliance
# with the License.
#
# You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
# or http://forgerock.org/license/CDDLv1.0.html.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at legal-notices/CDDLv1_0.txt.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information:
#      Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
#
#      Copyright 2014 ForgeRock AS.
#
#
# This file contains the attribute type and objectclass definitions for use
# with the Directory Server configuration.
dn: cn=schema
objectClass: top
objectClass: ldapSubentry
objectClass: subschema
objectClasses: ( 1.3.6.1.4.1.36733.2.1.2.18 NAME 'pwdValidatorPolicy' SUP top AUXILIARY
  MUST (ds-cfg-password-validator) X-ORIGIN 'OpenDJ Directory Server' )
opends/src/messages/messages/core.properties
@@ -1421,3 +1421,12 @@
MILD_ERR_MODIFY_REPLACE_INVALID_SYNTAX_NO_VALUE_744=When attempting to modify \
 entry %s to replace the set of values for attribute %s, one value was found to \
 be invalid according to the associated syntax:  %s
SEVERE_ERR_PWPOLICY_UNKNOWN_VALIDATOR_745=The password policy \
definition contained in configuration entry "%s" is invalid because the \
password validator "%s" specified in attribute "%s" cannot be found
SEVERE_ERR_PWPOLICY_REJECT_DUE_TO_UNKNOWN_VALIDATOR_REASON_746=The password could not \
 be validated because of misconfiguration. Please contact the administrator
SEVERE_ERR_PWPOLICY_REJECT_DUE_TO_UNKNOWN_VALIDATOR_LOG_747=The password for \
 user %s could not be validated because the password policy subentry %s is \
 referring to an unknown password validator (%s). Please make sure the password \
 policy subentry only refers to validators that exist on all replicas
opends/src/server/org/opends/server/core/SubentryPasswordPolicy.java
@@ -22,7 +22,7 @@
 *
 *
 *      Copyright 2010 Sun Microsystems, Inc.
 *      Portions copyright 2011-2013 ForgeRock AS.
 *      Portions copyright 2011-2014 ForgeRock AS.
 */
package org.opends.server.core;
@@ -31,17 +31,22 @@
import static org.opends.messages.ConfigMessages.*;
import static org.opends.messages.CoreMessages.*;
import static org.opends.server.loggers.ErrorLogger.logError;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.schema.SchemaConstants.*;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import org.opends.messages.Message;
import org.opends.messages.MessageBuilder;
import org.opends.server.admin.std.meta.PasswordPolicyCfgDefn.*;
import org.opends.server.admin.std.server.PasswordValidatorCfg;
import org.opends.server.api.AccountStatusNotificationHandler;
import org.opends.server.api.PasswordGenerator;
import org.opends.server.api.PasswordStorageScheme;
import org.opends.server.api.PasswordValidator;
import org.opends.server.config.ConfigException;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.*;
@@ -82,35 +87,41 @@
  private static final String PWD_ATTR_FAILURECOUNTINTERVAL =
      "pwdfailurecountinterval";
  // Password Policy Subentry DN.
  private final DN passwordPolicySubentryDN;
  // The value of the "allow-user-password-changes" property.
  private final Boolean pAllowUserPasswordChanges;
  // The value of the "force-change-on-reset" property.
  private final Boolean pForceChangeOnReset;
  // The value of the "grace-login-count" property.
  private final Integer pGraceLoginCount;
  // The value of the "lockout-duration" property.
  private final Long pLockoutDuration;
  // The value of the "lockout-failure-count" property.
  private final Integer pLockoutFailureCount;
  // The value of the "lockout-failure-expiration-interval" property.
  private final Long pLockoutFailureExpirationInterval;
  // The value of the "max-password-age" property.
  private final Long pMaxPasswordAge;
  // The value of the "min-password-age" property.
  private final Long pMinPasswordAge;
  // The value of the "password-attribute" property.
  private final AttributeType pPasswordAttribute;
  // The value of the "password-change-requires-current-password" property.
  private final Boolean pPasswordChangeRequiresCurrentPassword;
  // The value of the "password-expiration-warning-interval" property.
  private final Long pPasswordExpirationWarningInterval;
  // The value of the "password-history-count" property.
  private final Integer pPasswordHistoryCount;
  // Indicates if the password attribute uses auth password syntax.
  private final Boolean pAuthPasswordSyntax;
  private static final String PWD_ATTR_VALIDATOR = "ds-cfg-password-validator";
  private static final String PWD_OC_VALIDATORPOLICY = "pwdvalidatorpolicy";
  /** Password Policy Subentry DN. */
  private final DN passwordPolicySubentryDN;
  /** The value of the "allow-user-password-changes" property. */
  private final Boolean pAllowUserPasswordChanges;
  /** The value of the "force-change-on-reset" property. */
  private final Boolean pForceChangeOnReset;
  /** The value of the "grace-login-count" property. */
  private final Integer pGraceLoginCount;
  /** The value of the "lockout-duration" property. */
  private final Long pLockoutDuration;
  /** The value of the "lockout-failure-count" property. */
  private final Integer pLockoutFailureCount;
  /** The value of the "lockout-failure-expiration-interval" property. */
  private final Long pLockoutFailureExpirationInterval;
  /** The value of the "max-password-age" property. */
  private final Long pMaxPasswordAge;
  /** The value of the "min-password-age" property. */
  private final Long pMinPasswordAge;
  /** The value of the "password-attribute" property. */
  private final AttributeType pPasswordAttribute;
  /** The value of the "password-change-requires-current-password" property. */
  private final Boolean pPasswordChangeRequiresCurrentPassword;
  /** The value of the "password-expiration-warning-interval" property. */
  private final Long pPasswordExpirationWarningInterval;
  /** The value of the "password-history-count" property. */
  private final Integer pPasswordHistoryCount;
  /** Indicates if the password attribute uses auth password syntax. */
  private final Boolean pAuthPasswordSyntax;
  /** The set of password validators if any. */
  private final Set<DN> pValidatorNames = new HashSet<DN>();
  /** Used when logging errors due to invalid validator reference. */
  private final AtomicBoolean isAlreadyLogged = new AtomicBoolean();
  // Returns the global default password policy which will be used for deriving
@@ -500,6 +511,36 @@
    {
      this.pLockoutFailureExpirationInterval = null;
    }
    // Now check for the pwdValidatorPolicy OC and its attribute.
    // Determine if this is a password validator policy object class.
    ObjectClass pwdValidatorPolicyOC =
        DirectoryServer.getObjectClass(PWD_OC_VALIDATORPOLICY);
    if (pwdValidatorPolicyOC != null &&
        objectClasses.containsKey(pwdValidatorPolicyOC))
    {
      AttributeType pwdAttrType =
          DirectoryServer.getAttributeType(PWD_ATTR_VALIDATOR, true);
      List<Attribute> pwdAttrList = entry.getAttribute(pwdAttrType);
      if ((pwdAttrList != null) && (!pwdAttrList.isEmpty()))
      {
        for (Attribute attr : pwdAttrList)
        {
          for (AttributeValue val : attr)
          {
            DN validatorDN = DN.decode(val.getValue());
            if (DirectoryServer.getPasswordValidator(validatorDN) == null)
            {
              throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
                  ERR_PWPOLICY_UNKNOWN_VALIDATOR.get(
                      this.passwordPolicySubentryDN.toNormalizedString(),
                      validatorDN.toString(), PWD_ATTR_VALIDATOR));
            }
            pValidatorNames.add(validatorDN);
          }
        }
      }
    }
  }
@@ -964,10 +1005,76 @@
  @Override
  public Collection<PasswordValidator<?>> getPasswordValidators()
  {
    if (!pValidatorNames.isEmpty())
    {
      Collection<PasswordValidator<?>> values =
          new HashSet<PasswordValidator<?>>();
      for (DN validatorDN : pValidatorNames){
        PasswordValidator<?> validator = DirectoryServer
            .getPasswordValidator(validatorDN);
        if (validator == null) {
          PasswordValidator<?> errorValidator = new RejectPasswordValidator(
              validatorDN.toString(), passwordPolicySubentryDN.toString());
          values.clear();
          values.add(errorValidator);
          return values;
        }
         values.add(validator);
      }
      isAlreadyLogged.set(false);
      return values;
    }
    return getDefaultPasswordPolicy().getPasswordValidators();
  }
  /**
   * Implementation of a specific Password Validator that reject all
   * password due to mis-configured password policy subentry.
   * This is only used when a subentry is referencing a password
   * validator that is no longer configured.
   */
  private final class RejectPasswordValidator extends
      PasswordValidator<PasswordValidatorCfg>
  {
    private final String validatorName;
    private final String pwPolicyName;
    public RejectPasswordValidator(String name, String policyName)
    {
      super();
      validatorName = name;
      pwPolicyName = policyName;
    }
    /**
     * {@inheritDoc}
     */
    @Override()
    public void initializePasswordValidator(PasswordValidatorCfg configuration)
        throws ConfigException
    {
       // do nothing
    }
    /**
     * {@inheritDoc}
     */
    @Override()
    public boolean passwordIsAcceptable(ByteString newPassword,
                                        Set<ByteString> currentPasswords,
                                        Operation operation, Entry userEntry,
                                        MessageBuilder invalidReason)
    {
      invalidReason.append(ERR_PWPOLICY_REJECT_DUE_TO_UNKNOWN_VALIDATOR_REASON
          .get());
      // Only log an error once, on first error
      if (isAlreadyLogged.compareAndSet(false, true)) {
        logError(ERR_PWPOLICY_REJECT_DUE_TO_UNKNOWN_VALIDATOR_LOG.get(
          userEntry.getDN().toNormalizedString(), pwPolicyName,
          validatorName));
      }
      return false;
    }
  }
  /**
   * {@inheritDoc}
opends/tests/unit-tests-testng/src/server/org/opends/server/core/SubentryPasswordPolicyTestCase.java
@@ -22,14 +22,16 @@
 *
 *
 *      Copyright 2010 Sun Microsystems, Inc.
 *      Portions copyright 2011 ForgeRock AS.
 *      Portions copyright 2011-2014 ForgeRock AS.
 */
package org.opends.server.core;
import java.util.Collection;
import java.util.List;
import org.opends.server.api.PasswordValidator;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
@@ -191,8 +193,47 @@
         "pwdLockoutDuration: 2147483648",
         "pwdMaxFailure: 3",
         "pwdMustChange: TRUE",
         "pwdAttribute: userPassword"
         );
         "pwdAttribute: userPassword",
         "",
         "dn: cn=Temp Policy 6," + SUFFIX,
         "objectClass: top",
         "objectClass: pwdPolicy",
         "objectClass: pwdValidatorPolicy",
         "objectClass: subentry",
         "cn: Temp Policy 6",
         "subtreeSpecification: { base \"ou=people\" }",
         "pwdLockoutDuration: 300",
         "pwdMaxFailure: 3",
         "pwdMustChange: TRUE",
         "pwdAttribute: userPassword",
         "",
         "dn: cn=Temp Policy 7," + SUFFIX,
         "objectClass: top",
         "objectClass: pwdPolicy",
         "objectClass: pwdValidatorPolicy",
         "objectClass: subentry",
         "cn: Temp Policy 7",
         "subtreeSpecification: { base \"ou=people\" }",
         "pwdLockoutDuration: 300",
         "pwdMaxFailure: 3",
         "pwdMustChange: TRUE",
         "pwdAttribute: userPassword",
         "ds-cfg-password-validator: Not_A_DN",
         "",
         "dn: cn=Temp Policy 8," + SUFFIX,
         "objectClass: top",
         "objectClass: pwdPolicy",
         "objectClass: pwdValidatorPolicy",
         "objectClass: subentry",
         "cn: Temp Policy 8",
         "subtreeSpecification: { base \"ou=people\" }",
         "pwdLockoutDuration: 300",
         "pwdMaxFailure: 3",
         "pwdMustChange: TRUE",
         "pwdAttribute: userPassword",
         "ds-cfg-password-validator: cn=Unique Characters Inexistant,cn=Password Validators,cn=config"
    );
    Object[][] configEntries = new Object[entries.size()][1];
    for (int i=0; i < configEntries.length; i++)
@@ -297,6 +338,17 @@
    assertEquals(policy.isAllowUserPasswordChanges(), false);
    assertEquals(policy.isPasswordChangeRequiresCurrentPassword(), true);
    /* Check the password validator attributes for correct values.
     * The default unit-test config has a single Password validator which is
     * enabled for the default password policy.
     */
    Collection<PasswordValidator<?>> validators = policy.getPasswordValidators();
    assertEquals(validators.size(), 1);
    for (PasswordValidator<?> validator : validators)
    {
      assertTrue(validator.toString().startsWith("org.opends.server.extensions.TestPasswordValidator"));
    }
    // Make sure this policy applies to the test entry
    // its supposed to target and that its the same
    // policy object as previously tested.
@@ -318,6 +370,88 @@
  }
  /**
   * Ensures that password policy constructed from subentry,
   * containing a password validator reference,
   * is active and has a valid configuration.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testValidConfigurationWithValidator()
      throws Exception
  {
    PasswordPolicy defaultPolicy =
        DirectoryServer.getDefaultPasswordPolicy();
    assertNotNull(defaultPolicy);
    // The values are selected on a basis that they
    // should differ from default password policy.
    Entry policyEntry = TestCaseUtils.makeEntry(
        "dn: cn=Temp Validator Policy," + SUFFIX,
        "objectClass: top",
        "objectClass: pwdPolicy",
        "objectClass: pwdValidatorPolicy",
        "objectClass: subentry",
        "cn: Temp Policy",
        "subtreeSpecification: { base \"ou=people\" }",
        "pwdLockoutDuration: 300",
        "pwdMaxFailure: 3",
        "pwdMustChange: TRUE",
        "pwdAttribute: authPassword",
        "pwdMinAge: 600",
        "pwdMaxAge: 2147483647",
        "pwdInHistory: 5",
        "pwdExpireWarning: 864000",
        "pwdGraceAuthNLimit: 3",
        "pwdFailureCountInterval: 3600",
        "pwdAllowUserChange: FALSE",
        "pwdSafeModify: TRUE",
        "ds-cfg-password-validator: cn=Unique Characters,cn=Password Validators,cn=config",
        "ds-cfg-password-validator: cn=Length-Based Password Validator,cn=Password Validators,cn=config"
    );
    InternalClientConnection connection =
        InternalClientConnection.getRootConnection();
    AddOperation addOperation =
        connection.processAdd(policyEntry.getDN(),
            policyEntry.getObjectClasses(),
            policyEntry.getUserAttributes(),
            policyEntry.getOperationalAttributes());
    assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
    assertNotNull(DirectoryServer.getEntry(policyEntry.getDN()));
    PasswordPolicy policy = (PasswordPolicy) DirectoryServer.getAuthenticationPolicy(
        DN.decode("cn=Temp Validator Policy," + SUFFIX));
    assertNotNull(policy);
    // Check the password validator attributes for correct values.
    Collection<PasswordValidator<?>> validators = policy.getPasswordValidators();
    assertFalse(validators.isEmpty());
    assertEquals(validators.size(), 2);
    // Make sure this policy applies to the test entry
    // its supposed to target and that its the same
    // policy object as previously tested.
    Entry testEntry = DirectoryServer.getEntry(DN.decode(
        "uid=rogasawara," + BASE));
    assertNotNull(testEntry);
    AuthenticationPolicy statePolicy = AuthenticationPolicy.forUser(testEntry,
        false);
    assertNotNull(statePolicy);
    assertEquals(policy, statePolicy);
    // Make sure this policy is gone and default
    // policy is in effect instead.
    TestCaseUtils.deleteEntry(policyEntry.getDN());
    statePolicy = AuthenticationPolicy.forUser(testEntry, false);
    assertNotNull(statePolicy);
    assertEquals(defaultPolicy, statePolicy);
  }
  /**
   * Ensures that password policy pwdPolicySubentry
   * operational attribute reflects active password
   * policy for a given user entry.
@@ -397,4 +531,6 @@
            defaultPolicy.getDN(
            ).toString())));
  }
}