From 8ad4c087580f646278f1394eb989967f68bf1f98 Mon Sep 17 00:00:00 2001
From: Ludovic Poitou <ludovic.poitou@forgerock.com>
Date: Thu, 30 Jan 2014 13:59:38 +0000
Subject: [PATCH] Port to opendj3-dev branch of the 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).
---
opendj3-server-dev/src/server/org/opends/server/core/SubentryPasswordPolicy.java | 139 ++++++++++++++++++++--
opendj3-server-dev/src/messages/messages/core.properties | 9 +
opendj3-server-dev/resource/schema/03-pwpolicyextension.ldif | 33 +++++
opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/core/SubentryPasswordPolicyTestCase.java | 138 ++++++++++++++++++++++
4 files changed, 301 insertions(+), 18 deletions(-)
diff --git a/opendj3-server-dev/resource/schema/03-pwpolicyextension.ldif b/opendj3-server-dev/resource/schema/03-pwpolicyextension.ldif
new file mode 100644
index 0000000..35250d4
--- /dev/null
+++ b/opendj3-server-dev/resource/schema/03-pwpolicyextension.ldif
@@ -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' )
diff --git a/opendj3-server-dev/src/messages/messages/core.properties b/opendj3-server-dev/src/messages/messages/core.properties
index b31222f..a03040b 100644
--- a/opendj3-server-dev/src/messages/messages/core.properties
+++ b/opendj3-server-dev/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
\ No newline at end of file
diff --git a/opendj3-server-dev/src/server/org/opends/server/core/SubentryPasswordPolicy.java b/opendj3-server-dev/src/server/org/opends/server/core/SubentryPasswordPolicy.java
index cf9195e..dc7d2aa 100644
--- a/opendj3-server-dev/src/server/org/opends/server/core/SubentryPasswordPolicy.java
+++ b/opendj3-server-dev/src/server/org/opends/server/core/SubentryPasswordPolicy.java
@@ -34,15 +34,20 @@
import static org.opends.server.schema.SchemaConstants.*;
import java.util.*;
+import java.util.concurrent.atomic.AtomicBoolean;
import org.forgerock.i18n.LocalizableMessage;
+import org.forgerock.i18n.LocalizableMessageBuilder;
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.forgerock.i18n.slf4j.LocalizedLogger;
+import org.opends.server.config.ConfigException;
import org.opends.server.types.*;
+import org.forgerock.opendj.ldap.ByteString;
@@ -77,36 +82,41 @@
private static final String PWD_ATTR_SAFEMODIFY = "pwdsafemodify";
private static final String PWD_ATTR_FAILURECOUNTINTERVAL =
"pwdfailurecountinterval";
+ private static final String PWD_ATTR_VALIDATOR = "ds-cfg-password-validator";
+ private static final String PWD_OC_VALIDATORPOLICY = "pwdvalidatorpolicy";
- // Password Policy Subentry DN.
+ /** Password Policy Subentry DN. */
private final DN passwordPolicySubentryDN;
- // The value of the "allow-user-password-changes" property.
+ /** The value of the "allow-user-password-changes" property. */
private final Boolean pAllowUserPasswordChanges;
- // The value of the "force-change-on-reset" property.
+ /** The value of the "force-change-on-reset" property. */
private final Boolean pForceChangeOnReset;
- // The value of the "grace-login-count" property.
+ /** The value of the "grace-login-count" property. */
private final Integer pGraceLoginCount;
- // The value of the "lockout-duration" property.
+ /** The value of the "lockout-duration" property. */
private final Long pLockoutDuration;
- // The value of the "lockout-failure-count" property.
+ /** The value of the "lockout-failure-count" property. */
private final Integer pLockoutFailureCount;
- // The value of the "lockout-failure-expiration-interval" property.
+ /** The value of the "lockout-failure-expiration-interval" property. */
private final Long pLockoutFailureExpirationInterval;
- // The value of the "max-password-age" property.
+ /** The value of the "max-password-age" property. */
private final Long pMaxPasswordAge;
- // The value of the "min-password-age" property.
+ /** The value of the "min-password-age" property. */
private final Long pMinPasswordAge;
- // The value of the "password-attribute" property.
+ /** The value of the "password-attribute" property. */
private final AttributeType pPasswordAttribute;
- // The value of the "password-change-requires-current-password" property.
+ /** The value of the "password-change-requires-current-password" property. */
private final Boolean pPasswordChangeRequiresCurrentPassword;
- // The value of the "password-expiration-warning-interval" property.
+ /** The value of the "password-expiration-warning-interval" property. */
private final Long pPasswordExpirationWarningInterval;
- // The value of the "password-history-count" property.
+ /** The value of the "password-history-count" property. */
private final Integer pPasswordHistoryCount;
- // Indicates if the password attribute uses auth password syntax.
+ /** 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 AtomicBoolean isAlreadyLogged = new AtomicBoolean();
// Returns the global default password policy which will be used for deriving
@@ -495,6 +505,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);
+ }
+ }
+ }
+ }
}
@@ -952,17 +992,84 @@
}
-
/**
* {@inheritDoc}
*/
@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, InitializationException
+ {
+ // do nothing
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override()
+ public boolean passwordIsAcceptable(ByteString newPassword,
+ Set<ByteString> currentPasswords,
+ Operation operation, Entry userEntry,
+ LocalizableMessageBuilder 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)) {
+ logger.error(ERR_PWPOLICY_REJECT_DUE_TO_UNKNOWN_VALIDATOR_LOG.get(
+ userEntry.getName().toNormalizedString(), pwPolicyName,
+ validatorName));
+ }
+ return false;
+ }
+ }
+
/**
* {@inheritDoc}
diff --git a/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/core/SubentryPasswordPolicyTestCase.java b/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/core/SubentryPasswordPolicyTestCase.java
index d59dc30..9f0435e 100644
--- a/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/core/SubentryPasswordPolicyTestCase.java
+++ b/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/core/SubentryPasswordPolicyTestCase.java
@@ -28,8 +28,10 @@
+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.
--
Gitblit v1.10.0