opends/src/server/org/opends/server/api/AuthenticationPolicyState.java
@@ -29,9 +29,20 @@ import org.opends.server.types.ByteString; import org.opends.server.types.DirectoryException; import org.opends.server.types.Entry; import static org.opends.messages.CoreMessages.*; import static org.opends.server.config.ConfigConstants.OP_ATTR_ACCOUNT_DISABLED; import static org.opends.server.loggers.debug.DebugLogger.debugEnabled; import static org.opends.server.loggers.debug.DebugLogger.getTracer; import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString; import static org.opends.server.util.StaticUtils.toLowerCase; import java.util.List; import org.opends.messages.Message; import org.opends.server.core.DirectoryServer; import org.opends.server.loggers.debug.DebugTracer; import org.opends.server.schema.GeneralizedTimeSyntax; import org.opends.server.types.*; @@ -43,6 +54,13 @@ public abstract class AuthenticationPolicyState { /** * The tracer object for the debug logger. */ private static final DebugTracer TRACER = getTracer(); /** * Returns the authentication policy state for the user provided user. This * method is equivalent to the following: * @@ -68,10 +86,10 @@ * policy for the user. * @see AuthenticationPolicy#forUser(Entry, boolean) */ public final static AuthenticationPolicyState forUser(Entry userEntry, boolean useDefaultOnError) throws DirectoryException public final static AuthenticationPolicyState forUser(final Entry userEntry, final boolean useDefaultOnError) throws DirectoryException { AuthenticationPolicy policy = AuthenticationPolicy.forUser(userEntry, final AuthenticationPolicy policy = AuthenticationPolicy.forUser(userEntry, useDefaultOnError); return policy.createAuthenticationPolicyState(userEntry); } @@ -79,37 +97,184 @@ /** * Creates a new abstract authentication policy context. * A utility method which may be used by implementations in order to obtain * the value of the specified attribute from the provided entry as a boolean. * * @param entry * The entry whose attribute is to be parsed as a boolean. * @param attributeType * The attribute type whose value should be parsed as a boolean. * @return The attribute's value represented as a ConditionResult value, or * ConditionResult.UNDEFINED if the specified attribute does not exist * in the entry. * @throws DirectoryException * If the value cannot be decoded as a boolean. */ protected AuthenticationPolicyState() protected static final ConditionResult getBoolean(final Entry entry, final AttributeType attributeType) throws DirectoryException { // No implementation required. final List<Attribute> attrList = entry.getAttribute(attributeType); if (attrList != null) { for (final Attribute a : attrList) { if (a.isEmpty()) { continue; } final String valueString = toLowerCase(a.iterator().next().getValue() .toString()); if (valueString.equals("true") || valueString.equals("yes") || valueString.equals("on") || valueString.equals("1")) { if (debugEnabled()) { TRACER.debugInfo("Attribute %s resolves to true for user entry " + "%s", attributeType.getNameOrOID(), entry.getDN().toString()); } return ConditionResult.TRUE; } if (valueString.equals("false") || valueString.equals("no") || valueString.equals("off") || valueString.equals("0")) { if (debugEnabled()) { TRACER.debugInfo("Attribute %s resolves to false for user " + "entry %s", attributeType.getNameOrOID(), entry.getDN() .toString()); } return ConditionResult.FALSE; } if (debugEnabled()) { TRACER.debugError("Unable to resolve value %s for attribute %s " + "in user entry %s as a Boolean.", valueString, attributeType.getNameOrOID(), entry.getDN().toString()); } final Message message = ERR_PWPSTATE_CANNOT_DECODE_BOOLEAN .get(valueString, attributeType.getNameOrOID(), entry.getDN() .toString()); throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); } } if (debugEnabled()) { TRACER.debugInfo("Returning %s because attribute %s does not exist " + "in user entry %s", ConditionResult.UNDEFINED.toString(), attributeType.getNameOrOID(), entry.getDN().toString()); } return ConditionResult.UNDEFINED; } /** * Returns {@code true} if the provided password value matches any of the * user's passwords. * A utility method which may be used by implementations in order to obtain * the value of the specified attribute from the provided entry as a time in * generalized time format. * * @param password * The user-provided password to verify. * @return {@code true} if the provided password value matches any of the * user's passwords. * @param entry * The entry whose attribute is to be parsed as a boolean. * @param attributeType * The attribute type whose value should be parsed as a generalized * time value. * @return The requested time, or -1 if it could not be determined. * @throws DirectoryException * If verification unexpectedly failed. * If a problem occurs while attempting to decode the value as a * generalized time. */ public abstract boolean passwordMatches(ByteString password) throws DirectoryException; protected static final long getGeneralizedTime(final Entry entry, final AttributeType attributeType) throws DirectoryException { long timeValue = -1; final List<Attribute> attrList = entry.getAttribute(attributeType); if (attrList != null) { for (final Attribute a : attrList) { if (a.isEmpty()) { continue; } final AttributeValue v = a.iterator().next(); try { timeValue = GeneralizedTimeSyntax.decodeGeneralizedTimeValue(v .getNormalizedValue()); } catch (final Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); TRACER.debugWarning("Unable to decode value %s for attribute %s " + "in user entry %s: %s", v.getValue().toString(), attributeType.getNameOrOID(), entry.getDN().toString(), stackTraceToSingleLineString(e)); } final Message message = ERR_PWPSTATE_CANNOT_DECODE_GENERALIZED_TIME .get(v.getValue().toString(), attributeType.getNameOrOID(), entry .getDN().toString(), String.valueOf(e)); throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message, e); } break; } } if (timeValue == -1) { if (debugEnabled()) { TRACER.debugInfo("Returning -1 because attribute %s does not " + "exist in user entry %s", attributeType.getNameOrOID(), entry .getDN().toString()); } } // FIXME: else to be consistent... return timeValue; } /** * Returns the authentication policy associated with this state. * * @return The authentication policy associated with this state. * A boolean indicating whether or not the account associated with this * authentication state has been administratively disabled. */ public abstract AuthenticationPolicy getAuthenticationPolicy(); protected ConditionResult isDisabled = ConditionResult.UNDEFINED; /** * The user entry associated with this authentication policy state. */ protected final Entry userEntry; /** * Creates a new abstract authentication policy context. * * @param userEntry * The user's entry. */ protected AuthenticationPolicyState(final Entry userEntry) { this.userEntry = userEntry; } @@ -129,6 +294,76 @@ /** * Returns the authentication policy associated with this state. * * @return The authentication policy associated with this state. */ public abstract AuthenticationPolicy getAuthenticationPolicy(); /** * Returns {@code true} if this authentication policy state is associated with * a user whose account has been administratively disabled. * <p> * The default implementation is use the value of the "ds-pwp-account-disable" * attribute in the user's entry. * * @return {@code true} if this authentication policy state is associated with * a user whose account has been administratively disabled. */ public boolean isDisabled() { final AttributeType type = DirectoryServer.getAttributeType( OP_ATTR_ACCOUNT_DISABLED, true); try { isDisabled = getBoolean(userEntry, type); } catch (final Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } isDisabled = ConditionResult.TRUE; if (debugEnabled()) { TRACER.debugWarning("User %s is considered administratively " + "disabled because an error occurred while " + "attempting to make the determination: %s.", userEntry.getDN() .toString(), stackTraceToSingleLineString(e)); } return true; } if (isDisabled == ConditionResult.UNDEFINED) { isDisabled = ConditionResult.FALSE; if (debugEnabled()) { TRACER.debugInfo("User %s is not administratively disabled since " + "the attribute \"%s\" is not present in the entry.", userEntry .getDN().toString(), OP_ATTR_ACCOUNT_DISABLED); } return false; } if (debugEnabled()) { TRACER.debugInfo("User %s %s administratively disabled.", userEntry .getDN().toString(), ((isDisabled == ConditionResult.TRUE) ? " is" : " is not")); } return isDisabled == ConditionResult.TRUE; } /** * Returns {@code true} if this authentication policy state is associated with * a password policy and the method {@link #getAuthenticationPolicy} will * return a {@code PasswordPolicy}. @@ -140,4 +375,20 @@ { return getAuthenticationPolicy().isPasswordPolicy(); } /** * Returns {@code true} if the provided password value matches any of the * user's passwords. * * @param password * The user-provided password to verify. * @return {@code true} if the provided password value matches any of the * user's passwords. * @throws DirectoryException * If verification unexpectedly failed. */ public abstract boolean passwordMatches(ByteString password) throws DirectoryException; } opends/src/server/org/opends/server/controls/ProxiedAuthV1Control.java
@@ -32,7 +32,7 @@ import java.util.concurrent.locks.Lock; import java.io.IOException; import org.opends.server.api.AuthenticationPolicy; import org.opends.server.api.AuthenticationPolicyState; import org.opends.server.core.DirectoryServer; import org.opends.server.core.PasswordPolicyState; import org.opends.server.protocols.asn1.*; @@ -325,13 +325,20 @@ // FIXME -- We should provide some mechanism for enabling debug // processing. AuthenticationPolicy policy = AuthenticationPolicy.forUser(userEntry, false); if (policy.isPasswordPolicy()) AuthenticationPolicyState state = AuthenticationPolicyState.forUser( userEntry, false); if (state.isDisabled()) { PasswordPolicyState pwpState = (PasswordPolicyState) policy .createAuthenticationPolicyState(userEntry); if (pwpState.isDisabled() || pwpState.isAccountExpired() || Message message = ERR_PROXYAUTH1_UNUSABLE_ACCOUNT.get(String .valueOf(userEntry.getDN())); throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED, message); } if (state.isPasswordPolicy()) { PasswordPolicyState pwpState = (PasswordPolicyState) state; if (pwpState.isAccountExpired() || pwpState.lockedDueToFailures() || pwpState.lockedDueToIdleInterval() || pwpState.lockedDueToMaximumResetAge() || opends/src/server/org/opends/server/controls/ProxiedAuthV2Control.java
@@ -33,7 +33,7 @@ import java.util.concurrent.locks.Lock; import java.io.IOException; import org.opends.server.api.AuthenticationPolicy; import org.opends.server.api.AuthenticationPolicyState; import org.opends.server.api.IdentityMapper; import org.opends.server.core.DirectoryServer; import org.opends.server.core.PasswordPolicyState; @@ -333,13 +333,20 @@ private void checkAccountIsUsable(Entry userEntry) throws DirectoryException { AuthenticationPolicy policy = AuthenticationPolicy.forUser(userEntry, false); if (policy.isPasswordPolicy()) AuthenticationPolicyState state = AuthenticationPolicyState.forUser( userEntry, false); if (state.isDisabled()) { PasswordPolicyState pwpState = (PasswordPolicyState) policy .createAuthenticationPolicyState(userEntry); if (pwpState.isDisabled() || pwpState.isAccountExpired() || Message message = ERR_PROXYAUTH2_UNUSABLE_ACCOUNT.get(String .valueOf(userEntry.getDN())); throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED, message); } if (state.isPasswordPolicy()) { PasswordPolicyState pwpState = (PasswordPolicyState) state; if (pwpState.isAccountExpired() || pwpState.lockedDueToFailures() || pwpState.lockedDueToIdleInterval() || pwpState.lockedDueToMaximumResetAge() || opends/src/server/org/opends/server/core/PasswordPolicyState.java
@@ -77,9 +77,6 @@ // The user entry with which this state information is associated. private final Entry userEntry; // The string representation of the user's DN. private final String userDNString; @@ -169,7 +166,7 @@ */ PasswordPolicyState(PasswordPolicy policy, Entry userEntry, long currentTime) { this.userEntry = userEntry; super(userEntry); this.currentTime = currentTime; this.userDNString = userEntry.getDN().toString(); this.passwordPolicy = policy; @@ -225,74 +222,6 @@ /** * Retrieves the value of the specified attribute from the user's entry as a * time in generalized time format. * * @param attributeType The attribute type whose value should be parsed as a * generalized time value. * * @return The requested time, or -1 if it could not be determined. * * @throws DirectoryException If a problem occurs while attempting to * decode the value as a generalized time. */ private long getGeneralizedTime(AttributeType attributeType) throws DirectoryException { long timeValue = -1 ; List<Attribute> attrList = userEntry.getAttribute(attributeType); if (attrList != null) { for (Attribute a : attrList) { if (a.isEmpty()) continue; AttributeValue v = a.iterator().next(); try { timeValue = GeneralizedTimeSyntax.decodeGeneralizedTimeValue( v.getNormalizedValue()); } catch (Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); TRACER.debugWarning("Unable to decode value %s for attribute %s " + "in user entry %s: %s", v.getValue().toString(), attributeType.getNameOrOID(), userDNString, stackTraceToSingleLineString(e)); } Message message = ERR_PWPSTATE_CANNOT_DECODE_GENERALIZED_TIME. get(v.getValue().toString(), attributeType.getNameOrOID(), userDNString, String.valueOf(e)); throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message, e); } break ; } } if (timeValue == -1) { if (debugEnabled()) { TRACER.debugInfo("Returning -1 because attribute %s does not " + "exist in user entry %s", attributeType.getNameOrOID(), userDNString); } } // FIXME: else to be consistent... return timeValue; } /** * Retrieves the set of values of the specified attribute from the user's * entry in generalized time format. * @@ -359,84 +288,6 @@ /** * Retrieves the value of the specified attribute from the user's entry as a * Boolean. * * @param attributeType The attribute type whose value should be parsed as a * Boolean. * * @return The attribute's value represented as a ConditionResult value, or * ConditionResult.UNDEFINED if the specified attribute does not * exist in the entry. * * @throws DirectoryException If the value cannot be decoded as a Boolean. */ private ConditionResult getBoolean(AttributeType attributeType) throws DirectoryException { List<Attribute> attrList = userEntry.getAttribute(attributeType); if (attrList != null) { for (Attribute a : attrList) { if (a.isEmpty()) continue; String valueString = toLowerCase(a.iterator().next().getValue().toString()); if (valueString.equals("true") || valueString.equals("yes") || valueString.equals("on") || valueString.equals("1")) { if (debugEnabled()) { TRACER.debugInfo("Attribute %s resolves to true for user entry " + "%s", attributeType.getNameOrOID(), userDNString); } return ConditionResult.TRUE; } if (valueString.equals("false") || valueString.equals("no") || valueString.equals("off") || valueString.equals("0")) { if (debugEnabled()) { TRACER.debugInfo("Attribute %s resolves to false for user " + "entry %s", attributeType.getNameOrOID(), userDNString); } return ConditionResult.FALSE; } if(debugEnabled()) { TRACER.debugError("Unable to resolve value %s for attribute %s " + "in user entry %s as a Boolean.", valueString, attributeType.getNameOrOID(), userDNString); } Message message = ERR_PWPSTATE_CANNOT_DECODE_BOOLEAN.get( valueString, attributeType.getNameOrOID(), userDNString); throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); } } if (debugEnabled()) { TRACER.debugInfo("Returning %s because attribute %s does not exist " + "in user entry %s", ConditionResult.UNDEFINED.toString(), attributeType.getNameOrOID(), userDNString); } return ConditionResult.UNDEFINED; } /** * {@inheritDoc} */ public PasswordPolicy getAuthenticationPolicy() @@ -461,7 +312,7 @@ try { passwordChangedTime = getGeneralizedTime(type); passwordChangedTime = getGeneralizedTime(userEntry, type); } catch (DirectoryException e) { @@ -481,7 +332,7 @@ OP_ATTR_CREATE_TIMESTAMP_LC, true); try { passwordChangedTime = getGeneralizedTime(createTimeType); passwordChangedTime = getGeneralizedTime(userEntry, createTimeType); } catch (DirectoryException e) { @@ -626,7 +477,7 @@ DirectoryServer.getAttributeType(OP_ATTR_CREATE_TIMESTAMP_LC, true); try { passwordChangedTime = getGeneralizedTime(createTimeType); passwordChangedTime = getGeneralizedTime(userEntry, createTimeType); if (passwordChangedTime < 0) { passwordChangedTime = 0; @@ -640,81 +491,13 @@ /** * Indicates whether the user account has been administratively disabled. * * @return <CODE>true</CODE> if the user account has been administratively * disabled, or <CODE>false</CODE> otherwise. */ public boolean isDisabled() { if (isDisabled != ConditionResult.UNDEFINED) { if (debugEnabled()) { TRACER.debugInfo("Returning stored result of %b for user %s", (isDisabled == ConditionResult.TRUE), userDNString); } return isDisabled == ConditionResult.TRUE; } AttributeType type = DirectoryServer.getAttributeType(OP_ATTR_ACCOUNT_DISABLED, true); try { isDisabled = getBoolean(type); } catch (Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } isDisabled = ConditionResult.TRUE; if (debugEnabled()) { TRACER.debugWarning("User %s is considered administratively " + "disabled because an error occurred while attempting to make " + "the determination: %s.", userDNString, stackTraceToSingleLineString(e)); } return true; } if (isDisabled == ConditionResult.UNDEFINED) { isDisabled = ConditionResult.FALSE; if (debugEnabled()) { TRACER.debugInfo("User %s is not administratively disabled since " + "the attribute \"%s\" is not present in the entry.", userDNString, OP_ATTR_ACCOUNT_DISABLED); } return false; } if (debugEnabled()) { TRACER.debugInfo("User %s %s administratively disabled.", userDNString, ((isDisabled == ConditionResult.TRUE) ? " is" : " is not")); } return isDisabled == ConditionResult.TRUE; } /** * Updates the user entry to indicate whether user account has been * administratively disabled. * * @param isDisabled Indicates whether the user account has been * administratively disabled. * @param isDisabled * Indicates whether the user account has been administratively * disabled. */ public void setDisabled(boolean isDisabled) { @@ -775,7 +558,7 @@ try { accountExpirationTime = getGeneralizedTime(type); accountExpirationTime = getGeneralizedTime(userEntry, type); } catch (Exception e) { @@ -1216,7 +999,7 @@ try { failureLockedTime = getGeneralizedTime(type); failureLockedTime = getGeneralizedTime(userEntry, type); } catch (Exception e) { @@ -1811,7 +1594,7 @@ try { mustChangePassword = getBoolean(type); mustChangePassword = getBoolean(userEntry, type); } catch (Exception e) { @@ -2335,7 +2118,7 @@ try { requiredChangeTime = getGeneralizedTime(type); requiredChangeTime = getGeneralizedTime(userEntry, type); } catch (Exception e) { @@ -2449,7 +2232,7 @@ DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_WARNED_TIME, true); try { warnedTime = getGeneralizedTime(type); warnedTime = getGeneralizedTime(userEntry, type); } catch (Exception e) { opends/src/server/org/opends/server/core/SearchOperationBasis.java
@@ -37,7 +37,7 @@ import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import org.opends.server.api.AuthenticationPolicy; import org.opends.server.api.AuthenticationPolicyState; import org.opends.server.api.ClientConnection; import org.opends.server.api.plugin.PluginResult; import org.opends.server.controls.AccountUsableResponseControl; @@ -645,15 +645,19 @@ // create it now. if (isIncludeUsableControl()) { if (controls == null) { controls = new ArrayList<Control>(1); } try { // FIXME -- Need a way to enable PWP debugging. AuthenticationPolicy policy = AuthenticationPolicy .forUser(entry, false); if (policy.isPasswordPolicy()) AuthenticationPolicyState state = AuthenticationPolicyState.forUser( entry, false); if (state.isPasswordPolicy()) { PasswordPolicyState pwpState = (PasswordPolicyState) policy .createAuthenticationPolicyState(entry); PasswordPolicyState pwpState = (PasswordPolicyState) state; boolean isInactive = pwpState.isDisabled() || pwpState.isAccountExpired(); @@ -667,12 +671,6 @@ { int secondsBeforeUnlock = pwpState.getSecondsUntilUnlock(); int remainingGraceLogins = pwpState.getGraceLoginsRemaining(); if (controls == null) { controls = new ArrayList<Control>(1); } controls .add(new AccountUsableResponseControl(isInactive, isReset, isExpired, remainingGraceLogins, isLocked, @@ -680,16 +678,24 @@ } else { if (controls == null) { controls = new ArrayList<Control>(1); } int secondsBeforeExpiration = pwpState.getSecondsUntilExpiration(); controls.add(new AccountUsableResponseControl( secondsBeforeExpiration)); } } else { // Another type of authentication policy (e.g. PTA). if (state.isDisabled()) { controls.add(new AccountUsableResponseControl(false, false, false, -1, true, -1)); } else { controls.add(new AccountUsableResponseControl(-1)); } } } catch (Exception e) { opends/src/server/org/opends/server/extensions/LDAPPassThroughAuthenticationPolicyFactory.java
@@ -73,7 +73,6 @@ // TODO: handle password policy response controls? AD? // TODO: custom aliveness pings // TODO: manage account lockout // TODO: cache password /** @@ -1555,14 +1554,13 @@ private final class StateImpl extends AuthenticationPolicyState { private final Entry userEntry; private ByteString cachedPassword = null; private StateImpl(final Entry userEntry) { this.userEntry = userEntry; super(userEntry); } opends/src/server/org/opends/server/extensions/PlainSASLMechanismHandler.java
@@ -29,6 +29,7 @@ import static org.opends.messages.CoreMessages.*; import static org.opends.messages.ExtensionMessages.*; import static org.opends.server.loggers.debug.DebugLogger.debugEnabled; import static org.opends.server.loggers.debug.DebugLogger.getTracer; @@ -504,6 +505,17 @@ // the user's entry when the bind completes. AuthenticationPolicyState authState = AuthenticationPolicyState.forUser( userEntry, false); if (authState.isDisabled()) { // Check to see if the user is administratively disabled or locked. bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS); Message message = ERR_BIND_OPERATION_ACCOUNT_DISABLED.get(String .valueOf(userEntry.getDN())); bindOperation.setAuthFailureReason(message); return; } if (!authState.passwordMatches(ByteString.valueOf(password))) { bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS); opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendBindOperation.java
@@ -675,6 +675,14 @@ } else { // Check to see if the user is administratively disabled or locked. if (authPolicyState.isDisabled()) { throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, ERR_BIND_OPERATION_ACCOUNT_DISABLED.get(String.valueOf(userEntry .getDN()))); } // Invoke pre-operation plugins. if (!invokePreOpPlugins()) { opends/tests/unit-tests-testng/src/server/org/opends/server/api/AuthenticationPolicyTestCase.java
@@ -30,6 +30,7 @@ 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; @@ -38,6 +39,7 @@ import org.opends.server.protocols.internal.InternalClientConnection; import org.opends.server.types.*; import org.testng.annotations.BeforeClass; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -54,6 +56,8 @@ */ private final class MockPolicy extends AuthenticationPolicy { private final boolean isDisabled; private boolean isPolicyFinalized = false; private boolean isStateFinalized = false; @@ -93,9 +97,9 @@ * * @return The password which was tested. */ public String getMatchedPassword() public ByteString getMatchedPassword() { return matchedPassword.toString(); return matchedPassword; } @@ -105,10 +109,13 @@ * * @param matches * The result to always return from {@code passwordMatches}. * @param isDisabled * The result to return from {@code isDisabled}. */ public MockPolicy(boolean matches) public MockPolicy(boolean matches, boolean isDisabled) { this.matches = matches; this.isDisabled = isDisabled; } @@ -129,7 +136,7 @@ public AuthenticationPolicyState createAuthenticationPolicyState( Entry userEntry, long time) throws DirectoryException { return new AuthenticationPolicyState() return new AuthenticationPolicyState(userEntry) { /** @@ -147,6 +154,16 @@ /** * {@inheritDoc} */ public boolean isDisabled() { return MockPolicy.this.isDisabled; } /** * {@inheritDoc} */ public void finalizeStateAfterBind() throws DirectoryException { isStateFinalized = true; @@ -202,29 +219,22 @@ /** * Test simple authentication where password validation succeeds. * Returns test data for the simple/sasl tests. * * @throws Exception * If an unexpected exception occurred. * @return Test data for the simple/sasl tests. */ @Test public void testSimpleBindAllowed() throws Exception @DataProvider public Object[][] testBindData() { testSimpleBind(true); } /** * Test simple authentication where password validation fails. * * @throws Exception * If an unexpected exception occurred. */ @Test public void testSimpleBindRefused() throws Exception { testSimpleBind(false); // @formatter:off return new Object[][] { /* password matches, account is disabled */ { false, false }, { false, true }, { true, false }, { true, true }, }; // @formatter:on } @@ -232,34 +242,18 @@ /** * 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 public void testSASLPLAINBindAllowed() throws Exception @Test(dataProvider = "testBindData") public void testSimpleBind(boolean matches, boolean isDisabled) throws Exception { testSASLPLAINBind(true); } /** * Test simple authentication where password validation fails. * * @throws Exception * If an unexpected exception occurred. */ @Test public void testSASLPLAINBindRefused() throws Exception { testSASLPLAINBind(false); } private void testSimpleBind(boolean allow) throws Exception { MockPolicy policy = new MockPolicy(allow); MockPolicy policy = new MockPolicy(matches, isDisabled); DirectoryServer.registerAuthenticationPolicy(policyDN, policy); try { @@ -287,13 +281,24 @@ BindOperation bind = conn.processSimpleBind(userDNString, "password"); // Check authentication result. assertEquals(bind.getResultCode(), allow ? ResultCode.SUCCESS assertEquals(bind.getResultCode(), matches & !isDisabled ? ResultCode.SUCCESS : ResultCode.INVALID_CREDENTIALS); // Verify interaction with the policy/state. assertTrue(policy.isStateFinalized()); assertFalse(policy.isPolicyFinalized()); assertEquals(policy.getMatchedPassword(), "password"); 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 { @@ -304,9 +309,21 @@ private void testSASLPLAINBind(boolean allow) throws Exception /** * 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(allow); MockPolicy policy = new MockPolicy(matches, isDisabled); DirectoryServer.registerAuthenticationPolicy(policyDN, policy); try { @@ -342,13 +359,24 @@ credentials.toByteString()); // Check authentication result. assertEquals(bind.getResultCode(), allow ? ResultCode.SUCCESS assertEquals(bind.getResultCode(), matches & !isDisabled ? ResultCode.SUCCESS : ResultCode.INVALID_CREDENTIALS); // Verify interaction with the policy/state. assertTrue(policy.isStateFinalized()); assertFalse(policy.isPolicyFinalized()); assertEquals(policy.getMatchedPassword(), "password"); 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 { @@ -356,4 +384,5 @@ assertTrue(policy.isPolicyFinalized()); } } }