opendj3-server-dev/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' ) 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 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} 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,7 +193,46 @@ "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]; @@ -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.