/* * The contents of this file are subject to the terms of the Common Development and * Distribution License (the License). You may not use this file except in compliance with the * License. * * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the * specific language governing permission and limitations under the License. * * When distributing Covered Software, include this CDDL Header Notice in each file and include * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL * Header, with the fields enclosed by brackets [] replaced by your own identifying * information: "Portions Copyright [year] [name of copyright owner]". * * Copyright 2006-2008 Sun Microsystems, Inc. * Portions Copyright 2011-2016 ForgeRock AS. */ package org.opends.server.api; import static org.forgerock.opendj.ldap.ModificationType.*; import static org.forgerock.opendj.ldap.requests.Requests.*; import static org.opends.server.TestCaseUtils.*; import static org.testng.Assert.*; import java.util.Set; import org.forgerock.opendj.ldap.ByteString; import org.forgerock.opendj.ldap.Connection; import org.forgerock.opendj.ldap.ConstraintViolationException; import org.forgerock.opendj.ldap.LDAPConnectionFactory; import org.opends.server.TestCaseUtils; import org.opends.server.extensions.TestPasswordValidator; import org.opends.server.types.NullOutputStream; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import com.forgerock.opendj.ldap.tools.LDAPPasswordModify; /** A set of generic test cases for password validators. */ public class PasswordValidatorTestCase extends APITestCase { /** * Ensures that the Directory Server is running. * * @throws Exception If an unexpected problem occurs. */ @BeforeClass public void startServer() throws Exception { restartServer(); } /** Drops static references to allow garbage collection. */ @AfterClass public void shutdown() { TestPasswordValidator.clearInstanceAfterTests(); } /** * Gets simple test coverage for the default * PasswordValidator.finalizePasswordValidator method. */ @Test public void testFinalizePasswordValidator() { TestPasswordValidator.getInstance().finalizePasswordValidator(); } /** * Performs a test to ensure that the password validation will be successful * under the base conditions for the password modify extended operation. * * @throws Exception If an unexpected problem occurs. */ @Test public void testSuccessfulValidationPasswordModifyExtOp() throws Exception { TestPasswordValidator.setNextReturnValue(true); TestPasswordValidator.setNextInvalidReason(null); TestCaseUtils.initializeTestBackend(true); TestCaseUtils.addEntry( "dn: uid=test.user,o=test", "objectClass: top", "objectClass: person", "objectClass: organizationalPerson", "objectClass: inetOrgPerson", "uid: test.user", "givenName: Test", "sn: User", "cn: Test User", "ds-privilege-name: bypass-acl", "userPassword: password"); String[] args = { "-h", "127.0.0.1", "-p", String.valueOf(TestCaseUtils.getServerLdapPort()), "-D", "uid=test.user,o=test", "-w", "password", "-c", "password", "-n", "newPassword" }; assertEquals(runLDAPPasswordModify(args), 0); assertEquals(TestPasswordValidator.getLastNewPassword(), ByteString.valueOfUtf8("newPassword")); assertFalse(TestPasswordValidator.getLastCurrentPasswords().isEmpty()); } /** * Performs a test to ensure that the password validation will fail if the * test validator is configured to make it fail for the password modify * extended operation. * * @throws Exception If an unexpected problem occurs. */ @Test public void testFailedValidationPasswordModifyExtOp() throws Exception { TestPasswordValidator.setNextReturnValue(true); TestPasswordValidator.setNextInvalidReason(null); TestCaseUtils.initializeTestBackend(true); TestCaseUtils.addEntry( "dn: uid=test.user,o=test", "objectClass: top", "objectClass: person", "objectClass: organizationalPerson", "objectClass: inetOrgPerson", "uid: test.user", "givenName: Test", "sn: User", "cn: Test User", "ds-privilege-name: bypass-acl", "userPassword: password"); TestPasswordValidator.setNextReturnValue(false); String[] args = { "-h", "127.0.0.1", "-p", String.valueOf(TestCaseUtils.getServerLdapPort()), "-D", "uid=test.user,o=test", "-w", "password", "-c", "password", "-n", "newPassword" }; assertNotEquals(runLDAPPasswordModify(args), 0); assertEquals(TestPasswordValidator.getLastNewPassword(), ByteString.valueOfUtf8("newPassword")); assertFalse(TestPasswordValidator.getLastCurrentPasswords().isEmpty()); TestPasswordValidator.setNextReturnValue(true); } private int runLDAPPasswordModify(String[] args) { return LDAPPasswordModify.run(NullOutputStream.nullPrintStream(), NullOutputStream.nullPrintStream(), args); } /** * Performs a test to make sure that the clear-text password will not be * provided if the user has a non-reversible scheme and does not provide the * current password for a password modify extended operation. * * @throws Exception If an unexpected problem occurs. */ @Test public void testCurrentPasswordNotAvailablePasswordModifyExtOp() throws Exception { TestPasswordValidator.setNextReturnValue(true); TestPasswordValidator.setNextInvalidReason(null); TestCaseUtils.initializeTestBackend(true); TestCaseUtils.addEntry( "dn: uid=test.user,o=test", "objectClass: top", "objectClass: person", "objectClass: organizationalPerson", "objectClass: inetOrgPerson", "uid: test.user", "givenName: Test", "sn: User", "cn: Test User", "ds-privilege-name: bypass-acl", "userPassword: password"); String[] args = { "-h", "127.0.0.1", "-p", String.valueOf(TestCaseUtils.getServerLdapPort()), "-D", "uid=test.user,o=test", "-w", "password", "-n", "newPassword" }; assertEquals(runLDAPPasswordModify(args), 0); Set currentPasswords = TestPasswordValidator.getLastCurrentPasswords(); assertTrue(currentPasswords.isEmpty(), "currentPasswords=" + currentPasswords); } /** * Performs a test to make sure that the clear-text password will be provided * if the user has a non-reversible scheme but provides the current password * for a password modify extended operation. * * @throws Exception If an unexpected problem occurs. */ @Test public void testCurrentPasswordAvailablePasswordModifyExtOp() throws Exception { TestPasswordValidator.setNextReturnValue(true); TestPasswordValidator.setNextInvalidReason(null); TestCaseUtils.initializeTestBackend(true); TestCaseUtils.addEntry( "dn: uid=test.user,o=test", "objectClass: top", "objectClass: person", "objectClass: organizationalPerson", "objectClass: inetOrgPerson", "uid: test.user", "givenName: Test", "sn: User", "cn: Test User", "ds-privilege-name: bypass-acl", "userPassword: password"); String[] args = { "-h", "127.0.0.1", "-p", String.valueOf(TestCaseUtils.getServerLdapPort()), "-D", "uid=test.user,o=test", "-w", "password", "-c", "password", "-n", "newPassword" }; assertEquals(runLDAPPasswordModify(args), 0); Set currentPasswords = TestPasswordValidator.getLastCurrentPasswords(); assertFalse(currentPasswords.isEmpty()); assertEquals(currentPasswords.size(), 1); assertEquals(currentPasswords.iterator().next(), ByteString.valueOfUtf8("password")); } /** * Performs a test to make sure that the clear-text password will be provided * if the user has a reversible scheme and does not provide the current * password for a password modify extended operation. * * @throws Exception If an unexpected problem occurs. */ @Test public void testStoredPasswordAvailablePasswordModifyExtOp() throws Exception { TestPasswordValidator.setNextReturnValue(true); TestPasswordValidator.setNextInvalidReason(null); TestCaseUtils.initializeTestBackend(true); TestCaseUtils.addEntry( "dn: uid=test.user,o=test", "objectClass: top", "objectClass: person", "objectClass: organizationalPerson", "objectClass: inetOrgPerson", "uid: test.user", "givenName: Test", "sn: User", "cn: Test User", "ds-privilege-name: bypass-acl", "userPassword: password", "ds-pwp-password-policy-dn: cn=Clear UserPassword Policy," + "cn=Password Policies,cn=config"); String[] args = { "-h", "127.0.0.1", "-p", String.valueOf(TestCaseUtils.getServerLdapPort()), "-D", "uid=test.user,o=test", "-w", "password", "-n", "newPassword" }; assertEquals(runLDAPPasswordModify(args), 0); Set currentPasswords = TestPasswordValidator.getLastCurrentPasswords(); assertFalse(currentPasswords.isEmpty()); assertEquals(currentPasswords.size(), 1); assertEquals(currentPasswords.iterator().next(), ByteString.valueOfUtf8("password")); } /** * Performs a test to make sure that the clear-text password will be provided * if the user has a reversible scheme and also provides the current password * for a password modify extended operation. * * @throws Exception If an unexpected problem occurs. */ @Test public void testStoredAndCurrentPasswordAvailablePasswordModifyExtOp() throws Exception { TestPasswordValidator.setNextReturnValue(true); TestPasswordValidator.setNextInvalidReason(null); TestCaseUtils.initializeTestBackend(true); TestCaseUtils.addEntry( "dn: uid=test.user,o=test", "objectClass: top", "objectClass: person", "objectClass: organizationalPerson", "objectClass: inetOrgPerson", "uid: test.user", "givenName: Test", "sn: User", "cn: Test User", "ds-privilege-name: bypass-acl", "userPassword: password", "ds-pwp-password-policy-dn: cn=Clear UserPassword Policy," + "cn=Password Policies,cn=config"); String[] args = { "-h", "127.0.0.1", "-p", String.valueOf(TestCaseUtils.getServerLdapPort()), "-D", "uid=test.user,o=test", "-w", "password", "-c", "password", "-n", "newPassword" }; assertEquals(runLDAPPasswordModify(args), 0); Set currentPasswords = TestPasswordValidator.getLastCurrentPasswords(); assertFalse(currentPasswords.isEmpty()); assertEquals(currentPasswords.size(), 1); assertEquals(currentPasswords.iterator().next(), ByteString.valueOfUtf8("password")); } /** * Performs a test to ensure that the password validation will be successful * under the base conditions for the modify operation. * * @throws Exception If an unexpected problem occurs. */ @Test public void testSuccessfulValidationModify() throws Exception { TestPasswordValidator.setNextReturnValue(true); TestPasswordValidator.setNextInvalidReason(null); TestCaseUtils.initializeTestBackend(true); TestCaseUtils.addEntry( "dn: uid=test.user,o=test", "objectClass: top", "objectClass: person", "objectClass: organizationalPerson", "objectClass: inetOrgPerson", "uid: test.user", "givenName: Test", "sn: User", "cn: Test User", "ds-privilege-name: bypass-acl", "userPassword: password"); try (LDAPConnectionFactory factory = new LDAPConnectionFactory("localhost", getServerLdapPort()); Connection conn = factory.getConnection()) { conn.bind("uid=test.user,o=test", "password".toCharArray()); conn.modify(newModifyRequest("uid=test.user,o=test") .addModification(REPLACE, "userPassword", "newPassword")); assertEquals(TestPasswordValidator.getLastNewPassword(), ByteString.valueOfUtf8("newPassword")); assertTrue(TestPasswordValidator.getLastCurrentPasswords().isEmpty()); } } /** * Performs a test to ensure that the password validation will fail if the * test validator is configured to make it fail for the modify operation. * * @throws Exception If an unexpected problem occurs. */ @Test public void testFailedValidationModify() throws Exception { TestPasswordValidator.setNextReturnValue(true); TestPasswordValidator.setNextInvalidReason(null); TestCaseUtils.initializeTestBackend(true); TestCaseUtils.addEntry( "dn: uid=test.user,o=test", "objectClass: top", "objectClass: person", "objectClass: organizationalPerson", "objectClass: inetOrgPerson", "uid: test.user", "givenName: Test", "sn: User", "cn: Test User", "ds-privilege-name: bypass-acl", "userPassword: password"); try (LDAPConnectionFactory factory = new LDAPConnectionFactory("localhost", getServerLdapPort()); Connection conn = factory.getConnection()) { conn.bind("uid=test.user,o=test", "password".toCharArray()); TestPasswordValidator.setNextReturnValue(false); try { conn.modify( newModifyRequest("uid=test.user,o=test") .addModification(REPLACE, "userPassword", "newPassword")); fail("Expected ConstraintViolationException"); } catch (ConstraintViolationException expected) {} } finally { TestPasswordValidator.setNextReturnValue(true); } assertEquals(TestPasswordValidator.getLastNewPassword(), ByteString.valueOfUtf8("newPassword")); assertTrue(TestPasswordValidator.getLastCurrentPasswords().isEmpty()); } /** * Performs a test to make sure that the clear-text password will be provided * if the user has a non-reversible scheme but provides the current password * for a modify operation. * * @throws Exception If an unexpected problem occurs. */ @Test public void testCurrentPasswordAvailableModify() throws Exception { TestPasswordValidator.setNextReturnValue(true); TestPasswordValidator.setNextInvalidReason(null); TestCaseUtils.initializeTestBackend(true); TestCaseUtils.addEntry( "dn: uid=test.user,o=test", "objectClass: top", "objectClass: person", "objectClass: organizationalPerson", "objectClass: inetOrgPerson", "uid: test.user", "givenName: Test", "sn: User", "cn: Test User", "ds-privilege-name: bypass-acl", "userPassword: password"); try (LDAPConnectionFactory factory = new LDAPConnectionFactory("localhost", getServerLdapPort()); Connection conn = factory.getConnection()) { conn.bind("uid=test.user,o=test", "password".toCharArray()); conn.modify( newModifyRequest("uid=test.user,o=test") .addModification(DELETE, "userPassword", "password") .addModification(ADD, "userPassword", "newPassword")); } Set currentPasswords = TestPasswordValidator.getLastCurrentPasswords(); assertFalse(currentPasswords.isEmpty()); assertEquals(currentPasswords.size(), 1); assertEquals(currentPasswords.iterator().next(), ByteString.valueOfUtf8("password")); } /** * Performs a test to make sure that the clear-text password will be provided * if the user has a reversible scheme and does not provide the current * password for a modify operation. * * @throws Exception If an unexpected problem occurs. */ @Test public void testStoredPasswordAvailableModify() throws Exception { TestPasswordValidator.setNextReturnValue(true); TestPasswordValidator.setNextInvalidReason(null); TestCaseUtils.initializeTestBackend(true); TestCaseUtils.addEntry( "dn: uid=test.user,o=test", "objectClass: top", "objectClass: person", "objectClass: organizationalPerson", "objectClass: inetOrgPerson", "uid: test.user", "givenName: Test", "sn: User", "cn: Test User", "userPassword: password", "ds-privilege-name: bypass-acl", "ds-pwp-password-policy-dn: cn=Clear UserPassword Policy," + "cn=Password Policies,cn=config"); try (LDAPConnectionFactory factory = new LDAPConnectionFactory("localhost", getServerLdapPort()); Connection conn = factory.getConnection()) { conn.bind("uid=test.user,o=test", "password".toCharArray()); conn.modify( newModifyRequest("uid=test.user,o=test") .addModification(REPLACE, "userPassword", "newPassword")); } Set currentPasswords = TestPasswordValidator.getLastCurrentPasswords(); assertFalse(currentPasswords.isEmpty()); assertEquals(currentPasswords.size(), 1); assertEquals(currentPasswords.iterator().next(), ByteString.valueOfUtf8("password")); } /** * Performs a test to make sure that the clear-text password will be provided * if the user has a reversible scheme and also provides the current password * for a modify operation. * * @throws Exception If an unexpected problem occurs. */ @Test public void testStoredAndCurrentPasswordAvailableModify() throws Exception { TestPasswordValidator.setNextReturnValue(true); TestPasswordValidator.setNextInvalidReason(null); TestCaseUtils.initializeTestBackend(true); TestCaseUtils.addEntry( "dn: uid=test.user,o=test", "objectClass: top", "objectClass: person", "objectClass: organizationalPerson", "objectClass: inetOrgPerson", "uid: test.user", "givenName: Test", "sn: User", "cn: Test User", "userPassword: password", "ds-privilege-name: bypass-acl", "ds-pwp-password-policy-dn: cn=Clear UserPassword Policy," + "cn=Password Policies,cn=config"); try (LDAPConnectionFactory factory = new LDAPConnectionFactory("localhost", getServerLdapPort()); Connection conn = factory.getConnection()) { conn.bind("uid=test.user,o=test", "password".toCharArray()); conn.modify( newModifyRequest("uid=test.user,o=test") .addModification(DELETE, "userPassword", "password") .addModification(ADD, "userPassword", "newPassword")); } Set currentPasswords = TestPasswordValidator.getLastCurrentPasswords(); assertFalse(currentPasswords.isEmpty()); assertEquals(currentPasswords.size(), 1); assertEquals(currentPasswords.iterator().next(), ByteString.valueOfUtf8("password")); } }