/*
|
* 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
|
*
|
*
|
* Portions Copyright 2011-2015 ForgeRock AS.
|
* Portions Copyright 2014 ForgeRock AS
|
*/
|
package org.opends.server.api;
|
|
import static org.testng.Assert.assertEquals;
|
import static org.testng.Assert.assertFalse;
|
import static org.testng.Assert.assertNull;
|
import static org.testng.Assert.assertTrue;
|
|
import org.opends.server.TestCaseUtils;
|
import org.opends.server.core.BindOperation;
|
import org.opends.server.core.DirectoryServer;
|
import org.opends.server.protocols.internal.InternalClientConnection;
|
import org.opends.server.types.*;
|
import org.forgerock.opendj.ldap.ResultCode;
|
import org.forgerock.opendj.ldap.ByteString;
|
import org.forgerock.opendj.ldap.ByteStringBuilder;
|
import org.testng.annotations.BeforeClass;
|
import org.testng.annotations.DataProvider;
|
import org.testng.annotations.Test;
|
|
|
|
/**
|
* Test authentication policy interaction.
|
*/
|
public class AuthenticationPolicyTestCase extends APITestCase
|
{
|
|
/**
|
* A mock policy which records which methods have been called and their
|
* parameters.
|
*/
|
private final class MockPolicy extends AuthenticationPolicy
|
{
|
private final boolean isDisabled;
|
private final boolean matches;
|
private boolean isPolicyFinalized;
|
private boolean isStateFinalized;
|
private ByteString matchedPassword;
|
|
|
/**
|
* Returns {@code true} if {@code finalizeAuthenticationPolicy} was called.
|
*
|
* @return {@code true} if {@code finalizeAuthenticationPolicy} was called.
|
*/
|
public boolean isPolicyFinalized()
|
{
|
return isPolicyFinalized;
|
}
|
|
|
|
/**
|
* Returns {@code true} if {@code finalizeStateAfterBind} was called.
|
*
|
* @return {@code true} if {@code finalizeStateAfterBind} was called.
|
*/
|
public boolean isStateFinalized()
|
{
|
return isStateFinalized;
|
}
|
|
|
|
/**
|
* Returns the password which was tested.
|
*
|
* @return The password which was tested.
|
*/
|
public ByteString getMatchedPassword()
|
{
|
return matchedPassword;
|
}
|
|
|
|
/**
|
* Creates a new mock policy.
|
*
|
* @param matches
|
* The result to always return from {@code passwordMatches}.
|
* @param isDisabled
|
* The result to return from {@code isDisabled}.
|
*/
|
public MockPolicy(boolean matches, boolean isDisabled)
|
{
|
this.matches = matches;
|
this.isDisabled = isDisabled;
|
}
|
|
|
|
/** {@inheritDoc} */
|
public DN getDN()
|
{
|
return policyDN;
|
}
|
|
|
|
/** {@inheritDoc} */
|
public AuthenticationPolicyState createAuthenticationPolicyState(
|
Entry userEntry, long time) throws DirectoryException
|
{
|
return new AuthenticationPolicyState(userEntry)
|
{
|
|
/** {@inheritDoc} */
|
public boolean passwordMatches(ByteString password)
|
throws DirectoryException
|
{
|
matchedPassword = password;
|
return matches;
|
}
|
|
|
|
/** {@inheritDoc} */
|
public boolean isDisabled()
|
{
|
return MockPolicy.this.isDisabled;
|
}
|
|
|
|
/** {@inheritDoc} */
|
public void finalizeStateAfterBind() throws DirectoryException
|
{
|
isStateFinalized = true;
|
}
|
|
|
|
/** {@inheritDoc} */
|
public AuthenticationPolicy getAuthenticationPolicy()
|
{
|
return MockPolicy.this;
|
}
|
};
|
}
|
|
|
|
/** {@inheritDoc} */
|
public void finalizeAuthenticationPolicy()
|
{
|
isPolicyFinalized = true;
|
}
|
|
}
|
|
|
|
private final String policyDNString = "cn=test policy,o=test";
|
private final String userDNString = "cn=test user,o=test";
|
private DN policyDN;
|
|
|
|
/**
|
* Ensures that the Directory Server is running and creates a test backend
|
* containing a single test user.
|
*
|
* @throws Exception
|
* If an unexpected problem occurs.
|
*/
|
@BeforeClass
|
public void beforeClass() throws Exception
|
{
|
TestCaseUtils.startServer();
|
|
policyDN = DN.valueOf(policyDNString);
|
}
|
|
|
|
/**
|
* Returns test data for the simple/sasl tests.
|
*
|
* @return Test data for the simple/sasl tests.
|
*/
|
@DataProvider
|
public Object[][] testBindData()
|
{
|
// @formatter:off
|
return new Object[][] {
|
/* password matches, account is disabled */
|
{ false, false },
|
{ false, true },
|
{ true, false },
|
{ true, true },
|
};
|
// @formatter:on
|
}
|
|
|
|
/**
|
* Test simple authentication where password validation succeeds.
|
*
|
* @param matches
|
* The result to always return from {@code passwordMatches}.
|
* @param isDisabled
|
* The result to return from {@code isDisabled}.
|
* @throws Exception
|
* If an unexpected exception occurred.
|
*/
|
@Test(dataProvider = "testBindData")
|
public void testSimpleBind(boolean matches, boolean isDisabled)
|
throws Exception
|
{
|
MockPolicy policy = new MockPolicy(matches, isDisabled);
|
DirectoryServer.registerAuthenticationPolicy(policyDN, policy);
|
try
|
{
|
// Create an empty test backend 'o=test'
|
TestCaseUtils.initializeTestBackend(true);
|
|
/*
|
* The test user which who will be authenticated.
|
*/
|
TestCaseUtils.addEntries(
|
/* @formatter:off */
|
"dn: " + userDNString,
|
"objectClass: top",
|
"objectClass: person",
|
"ds-pwp-password-policy-dn: " + policyDNString,
|
"userPassword: password",
|
"sn: user",
|
"cn: test user"
|
/* @formatter:on */
|
);
|
|
// Perform the simple bind.
|
InternalClientConnection conn = InternalClientConnection
|
.getRootConnection();
|
BindOperation bind = conn.processSimpleBind(userDNString, "password");
|
|
// Check authentication result.
|
assertEquals(bind.getResultCode(),
|
matches & !isDisabled ? ResultCode.SUCCESS
|
: ResultCode.INVALID_CREDENTIALS);
|
|
// Verify interaction with the policy/state.
|
assertTrue(policy.isStateFinalized());
|
assertFalse(policy.isPolicyFinalized());
|
if (!isDisabled)
|
{
|
assertEquals(policy.getMatchedPassword().toString(), "password");
|
}
|
else
|
{
|
// If the account is disabled then the password should not have been
|
// checked. This is important because we want to avoid potentially
|
// expensive password fetches (e.g. PTA).
|
assertNull(policy.getMatchedPassword());
|
}
|
}
|
finally
|
{
|
DirectoryServer.deregisterAuthenticationPolicy(policyDN);
|
assertTrue(policy.isPolicyFinalized());
|
}
|
}
|
|
|
|
/**
|
* Test simple authentication where password validation succeeds.
|
*
|
* @param matches
|
* The result to always return from {@code passwordMatches}.
|
* @param isDisabled
|
* The result to return from {@code isDisabled}.
|
* @throws Exception
|
* If an unexpected exception occurred.
|
*/
|
@Test(dataProvider = "testBindData")
|
public void testSASLPLAINBind(boolean matches, boolean isDisabled)
|
throws Exception
|
{
|
MockPolicy policy = new MockPolicy(matches, isDisabled);
|
DirectoryServer.registerAuthenticationPolicy(policyDN, policy);
|
try
|
{
|
// Create an empty test backend 'o=test'
|
TestCaseUtils.initializeTestBackend(true);
|
|
/*
|
* The test user which who will be authenticated.
|
*/
|
TestCaseUtils.addEntries(
|
/* @formatter:off */
|
"dn: " + userDNString,
|
"objectClass: top",
|
"objectClass: person",
|
"ds-pwp-password-policy-dn: " + policyDNString,
|
"userPassword: password",
|
"sn: user",
|
"cn: test user"
|
/* @formatter:on */
|
);
|
|
// Perform the simple bind.
|
InternalClientConnection conn = InternalClientConnection
|
.getRootConnection();
|
|
ByteStringBuilder credentials = new ByteStringBuilder();
|
credentials.append((byte) 0);
|
credentials.append("dn:" + userDNString);
|
credentials.append((byte) 0);
|
credentials.append("password");
|
|
BindOperation bind = conn.processSASLBind(DN.rootDN(), "PLAIN",
|
credentials.toByteString());
|
|
// Check authentication result.
|
assertEquals(bind.getResultCode(),
|
matches & !isDisabled ? ResultCode.SUCCESS
|
: ResultCode.INVALID_CREDENTIALS);
|
|
// Verify interaction with the policy/state.
|
assertTrue(policy.isStateFinalized());
|
assertFalse(policy.isPolicyFinalized());
|
if (!isDisabled)
|
{
|
assertEquals(policy.getMatchedPassword().toString(), "password");
|
}
|
else
|
{
|
// If the account is disabled then the password should not have been
|
// checked. This is important because we want to avoid potentially
|
// expensive password fetches (e.g. PTA).
|
assertNull(policy.getMatchedPassword());
|
}
|
}
|
finally
|
{
|
DirectoryServer.deregisterAuthenticationPolicy(policyDN);
|
assertTrue(policy.isPolicyFinalized());
|
}
|
}
|
|
}
|