From 5d68b3db0962c8d3b5d9545d54453f18b918dd79 Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Mon, 12 Sep 2011 13:39:42 +0000
Subject: [PATCH] Issue OPENDJ-262: Implement pass through authentication (PTA)
---
opendj-sdk/opends/src/server/org/opends/server/extensions/LDAPPassThroughAuthenticationPolicyFactory.java | 47 ++++--
opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/LDAPPassThroughAuthenticationPolicyTestCase.java | 317 ++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 341 insertions(+), 23 deletions(-)
diff --git a/opendj-sdk/opends/src/server/org/opends/server/extensions/LDAPPassThroughAuthenticationPolicyFactory.java b/opendj-sdk/opends/src/server/org/opends/server/extensions/LDAPPassThroughAuthenticationPolicyFactory.java
index c212f79..d02d685 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/extensions/LDAPPassThroughAuthenticationPolicyFactory.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/extensions/LDAPPassThroughAuthenticationPolicyFactory.java
@@ -36,10 +36,7 @@
import java.io.Closeable;
import java.io.IOException;
import java.net.*;
-import java.util.LinkedHashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Set;
+import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;
@@ -65,7 +62,6 @@
import org.opends.server.tools.LDAPReader;
import org.opends.server.tools.LDAPWriter;
import org.opends.server.types.*;
-import org.opends.server.util.StaticUtils;
@@ -1382,11 +1378,10 @@
* references a non-user entry.
*/
throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
- ERR_LDAP_PTA_MAPPING_ATTRIBUTE_NOT_FOUND.get(
- String.valueOf(userEntry.getDN()),
- String.valueOf(configuration.dn()),
- StaticUtils.collectionToString(
- configuration.getMappedAttribute(), ", ")));
+ ERR_LDAP_PTA_MAPPING_ATTRIBUTE_NOT_FOUND.get(String
+ .valueOf(userEntry.getDN()), String.valueOf(configuration
+ .dn()), mappedAttributesAsString(configuration
+ .getMappedAttribute())));
}
break;
@@ -1422,11 +1417,10 @@
* references a non-user entry.
*/
throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
- ERR_LDAP_PTA_MAPPING_ATTRIBUTE_NOT_FOUND.get(
- String.valueOf(userEntry.getDN()),
- String.valueOf(configuration.dn()),
- StaticUtils.collectionToString(
- configuration.getMappedAttribute(), ", ")));
+ ERR_LDAP_PTA_MAPPING_ATTRIBUTE_NOT_FOUND.get(String
+ .valueOf(userEntry.getDN()), String.valueOf(configuration
+ .dn()), mappedAttributesAsString(configuration
+ .getMappedAttribute())));
}
final SearchFilter filter;
@@ -1906,4 +1900,27 @@
return true;
}
+
+
+ private static String mappedAttributesAsString(
+ Collection<AttributeType> attributes)
+ {
+ switch (attributes.size())
+ {
+ case 0:
+ return "";
+ case 1:
+ return attributes.iterator().next().getNameOrOID();
+ default:
+ StringBuilder builder = new StringBuilder();
+ Iterator<AttributeType> i = attributes.iterator();
+ builder.append(i.next().getNameOrOID());
+ while (i.hasNext())
+ {
+ builder.append(", ");
+ builder.append(i.next().getNameOrOID());
+ }
+ return builder.toString();
+ }
+ }
}
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/LDAPPassThroughAuthenticationPolicyTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/LDAPPassThroughAuthenticationPolicyTestCase.java
index c29b25c..6766d55 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/LDAPPassThroughAuthenticationPolicyTestCase.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/LDAPPassThroughAuthenticationPolicyTestCase.java
@@ -71,14 +71,6 @@
ExtensionsTestCase
{
- // TODO: multiple search base DNs
- // TODO: multiple mapping attributes/values
- // TODO: searches returning no results or too many
- // TODO: missing mapping attributes
- // TODO: connection pooling
- // TODO: load balancing
- // TODO: fail-over
-
static class CloseEvent extends Event<Void>
{
private final GetConnectionEvent getConnectionEvent;
@@ -2542,6 +2534,315 @@
+ /**
+ * Tests that mapped PTA performs searches across multiple base DNs if
+ * configured.
+ *
+ * @throws Exception
+ * If an unexpected exception occurred.
+ */
+ @Test(enabled = true)
+ public void testMultipleSearchBaseDNs() throws Exception
+ {
+ // Mock configuration.
+ final LDAPPassThroughAuthenticationPolicyCfg cfg = mockCfg()
+ .withPrimaryServer(phost1)
+ .withMappingPolicy(MappingPolicy.MAPPED_SEARCH)
+ .withMappedAttribute("uid").withBaseDN("o=first")
+ .withBaseDN("o=second").withBaseDN("o=third");
+
+ // Create the provider and its list of expected events.
+ final GetLDAPConnectionFactoryEvent fe = new GetLDAPConnectionFactoryEvent(
+ phost1, cfg);
+ final MockProvider provider = new MockProvider().expectEvent(fe);
+
+ // Add search events.
+ GetConnectionEvent ceSearch = new GetConnectionEvent(fe);
+ provider
+ .expectEvent(ceSearch)
+ .expectEvent(
+ new SimpleBindEvent(ceSearch, searchBindDNString, "searchPassword",
+ ResultCode.SUCCESS))
+ .expectEvent(
+ new SearchEvent(ceSearch, "o=first", SearchScope.WHOLE_SUBTREE,
+ "(uid=aduser)", ResultCode.CLIENT_SIDE_NO_RESULTS_RETURNED))
+ .expectEvent(
+ new SearchEvent(ceSearch, "o=second", SearchScope.WHOLE_SUBTREE,
+ "(uid=aduser)", ResultCode.CLIENT_SIDE_NO_RESULTS_RETURNED))
+ .expectEvent(
+ new SearchEvent(ceSearch, "o=third", SearchScope.WHOLE_SUBTREE,
+ "(uid=aduser)", adDNString));
+ // Connection should be cached until the policy is finalized.
+
+ // Add bind events.
+ final GetConnectionEvent ceBind = new GetConnectionEvent(fe);
+ provider.expectEvent(ceBind).expectEvent(
+ new SimpleBindEvent(ceBind, adDNString, userPassword,
+ ResultCode.SUCCESS));
+
+ // Connection should be cached until the policy is finalized.
+
+ // Obtain policy and state.
+ final LDAPPassThroughAuthenticationPolicyFactory factory = new LDAPPassThroughAuthenticationPolicyFactory(
+ provider);
+ assertTrue(factory.isConfigurationAcceptable(cfg, null));
+ final AuthenticationPolicy policy = factory.createAuthenticationPolicy(cfg);
+ final AuthenticationPolicyState state = policy
+ .createAuthenticationPolicyState(userEntry);
+ assertEquals(state.getAuthenticationPolicy(), policy);
+
+ // Perform authentication.
+ assertTrue(state.passwordMatches(ByteString.valueOf(userPassword)));
+
+ // There should be no more pending events.
+ provider.assertAllExpectedEventsReceived();
+ state.finalizeStateAfterBind();
+
+ // Cached connections should be closed when the policy is finalized.
+ provider.expectEvent(new CloseEvent(ceSearch));
+ provider.expectEvent(new CloseEvent(ceBind));
+
+ // Tear down and check final state.
+ policy.finalizeAuthenticationPolicy();
+ provider.assertAllExpectedEventsReceived();
+ }
+
+
+
+ /**
+ * Tests that mapped PTA uses an appropriate filter when multiple match
+ * attributes are defined.
+ *
+ * @throws Exception
+ * If an unexpected exception occurred.
+ */
+ @Test(enabled = true)
+ public void testMultipleMappingAttributes() throws Exception
+ {
+ // Mock configuration.
+ final LDAPPassThroughAuthenticationPolicyCfg cfg = mockCfg()
+ .withPrimaryServer(phost1)
+ .withMappingPolicy(MappingPolicy.MAPPED_SEARCH)
+ .withMappedAttribute("uid1").withMappedAttribute("uid2")
+ .withMappedAttribute("uid3").withBaseDN("o=ad");
+
+ // Create the provider and its list of expected events.
+ final GetLDAPConnectionFactoryEvent fe = new GetLDAPConnectionFactoryEvent(
+ phost1, cfg);
+ final MockProvider provider = new MockProvider().expectEvent(fe);
+
+ // Add search events.
+ GetConnectionEvent ceSearch = new GetConnectionEvent(fe);
+ provider
+ .expectEvent(ceSearch)
+ .expectEvent(
+ new SimpleBindEvent(ceSearch, searchBindDNString, "searchPassword",
+ ResultCode.SUCCESS))
+ .expectEvent(
+ new SearchEvent(ceSearch, "o=ad", SearchScope.WHOLE_SUBTREE,
+ "(|(uid1=one)(uid2=two)(uid3=three))", adDNString));
+ // Connection should be cached until the policy is finalized.
+
+ // Add bind events.
+ final GetConnectionEvent ceBind = new GetConnectionEvent(fe);
+ provider.expectEvent(ceBind).expectEvent(
+ new SimpleBindEvent(ceBind, adDNString, userPassword,
+ ResultCode.SUCCESS));
+
+ // Connection should be cached until the policy is finalized.
+
+ // Obtain policy and state.
+ Entry testUser = TestCaseUtils.makeEntry(
+ /* @formatter:off */
+ "dn: " + opendjDNString,
+ "objectClass: top",
+ "objectClass: person",
+ "sn: user",
+ "cn: test user",
+ "aduser: " + adDNString,
+ "uid1: one",
+ "uid2: two",
+ "uid3: three"
+ /* @formatter:on */
+ );
+
+ final LDAPPassThroughAuthenticationPolicyFactory factory = new LDAPPassThroughAuthenticationPolicyFactory(
+ provider);
+ assertTrue(factory.isConfigurationAcceptable(cfg, null));
+ final AuthenticationPolicy policy = factory.createAuthenticationPolicy(cfg);
+ final AuthenticationPolicyState state = policy
+ .createAuthenticationPolicyState(testUser);
+ assertEquals(state.getAuthenticationPolicy(), policy);
+
+ // Perform authentication.
+ assertTrue(state.passwordMatches(ByteString.valueOf(userPassword)));
+
+ // There should be no more pending events.
+ provider.assertAllExpectedEventsReceived();
+ state.finalizeStateAfterBind();
+
+ // Cached connections should be closed when the policy is finalized.
+ provider.expectEvent(new CloseEvent(ceSearch));
+ provider.expectEvent(new CloseEvent(ceBind));
+
+ // Tear down and check final state.
+ policy.finalizeAuthenticationPolicy();
+ provider.assertAllExpectedEventsReceived();
+ }
+
+
+
+ /**
+ * Tests that mapped PTA uses an appropriate filter when multiple match
+ * attribute values are found.
+ *
+ * @throws Exception
+ * If an unexpected exception occurred.
+ */
+ @Test(enabled = true)
+ public void testMultipleMappingAttributeValues() throws Exception
+ {
+ // Mock configuration.
+ final LDAPPassThroughAuthenticationPolicyCfg cfg = mockCfg()
+ .withPrimaryServer(phost1)
+ .withMappingPolicy(MappingPolicy.MAPPED_SEARCH)
+ .withMappedAttribute("uid1").withBaseDN("o=ad");
+
+ // Create the provider and its list of expected events.
+ final GetLDAPConnectionFactoryEvent fe = new GetLDAPConnectionFactoryEvent(
+ phost1, cfg);
+ final MockProvider provider = new MockProvider().expectEvent(fe);
+
+ // Add search events.
+ GetConnectionEvent ceSearch = new GetConnectionEvent(fe);
+ provider
+ .expectEvent(ceSearch)
+ .expectEvent(
+ new SimpleBindEvent(ceSearch, searchBindDNString, "searchPassword",
+ ResultCode.SUCCESS))
+ .expectEvent(
+ new SearchEvent(ceSearch, "o=ad", SearchScope.WHOLE_SUBTREE,
+ "(|(uid1=one)(uid1=two)(uid1=three))", adDNString));
+ // Connection should be cached until the policy is finalized.
+
+ // Add bind events.
+ final GetConnectionEvent ceBind = new GetConnectionEvent(fe);
+ provider.expectEvent(ceBind).expectEvent(
+ new SimpleBindEvent(ceBind, adDNString, userPassword,
+ ResultCode.SUCCESS));
+
+ // Connection should be cached until the policy is finalized.
+
+ // Obtain policy and state.
+ Entry testUser = TestCaseUtils.makeEntry(
+ /* @formatter:off */
+ "dn: " + opendjDNString,
+ "objectClass: top",
+ "objectClass: person",
+ "sn: user",
+ "cn: test user",
+ "aduser: " + adDNString,
+ "uid1: one",
+ "uid1: two",
+ "uid1: three"
+ /* @formatter:on */
+ );
+
+ final LDAPPassThroughAuthenticationPolicyFactory factory = new LDAPPassThroughAuthenticationPolicyFactory(
+ provider);
+ assertTrue(factory.isConfigurationAcceptable(cfg, null));
+ final AuthenticationPolicy policy = factory.createAuthenticationPolicy(cfg);
+ final AuthenticationPolicyState state = policy
+ .createAuthenticationPolicyState(testUser);
+ assertEquals(state.getAuthenticationPolicy(), policy);
+
+ // Perform authentication.
+ assertTrue(state.passwordMatches(ByteString.valueOf(userPassword)));
+
+ // There should be no more pending events.
+ provider.assertAllExpectedEventsReceived();
+ state.finalizeStateAfterBind();
+
+ // Cached connections should be closed when the policy is finalized.
+ provider.expectEvent(new CloseEvent(ceSearch));
+ provider.expectEvent(new CloseEvent(ceBind));
+
+ // Tear down and check final state.
+ policy.finalizeAuthenticationPolicy();
+ provider.assertAllExpectedEventsReceived();
+ }
+
+
+
+ /**
+ * Tests that mapped PTA fails when no match attribute values are found.
+ *
+ * @throws Exception
+ * If an unexpected exception occurred.
+ */
+ @Test(enabled = true)
+ public void testMissingMappingAttributes() throws Exception
+ {
+ // Mock configuration.
+ final LDAPPassThroughAuthenticationPolicyCfg cfg = mockCfg()
+ .withPrimaryServer(phost1)
+ .withMappingPolicy(MappingPolicy.MAPPED_SEARCH)
+ .withMappedAttribute("uid1").withBaseDN("o=ad");
+
+ // Create the provider and its list of expected events.
+ final GetLDAPConnectionFactoryEvent fe = new GetLDAPConnectionFactoryEvent(
+ phost1, cfg);
+ final MockProvider provider = new MockProvider().expectEvent(fe);
+
+ // Obtain policy and state.
+ Entry testUser = TestCaseUtils.makeEntry(
+ /* @formatter:off */
+ "dn: " + opendjDNString,
+ "objectClass: top",
+ "objectClass: person",
+ "sn: user",
+ "cn: test user",
+ "aduser: " + adDNString
+ /* @formatter:on */
+ );
+
+ final LDAPPassThroughAuthenticationPolicyFactory factory = new LDAPPassThroughAuthenticationPolicyFactory(
+ provider);
+ assertTrue(factory.isConfigurationAcceptable(cfg, null));
+ final AuthenticationPolicy policy = factory.createAuthenticationPolicy(cfg);
+ final AuthenticationPolicyState state = policy
+ .createAuthenticationPolicyState(testUser);
+ assertEquals(state.getAuthenticationPolicy(), policy);
+
+ // Perform authentication.
+ try
+ {
+ state.passwordMatches(ByteString.valueOf(userPassword));
+ fail("password match unexpectedly succeeded");
+ }
+ catch (final DirectoryException e)
+ {
+ // No mapping attributes so this should always fail with
+ // INVALID_CREDENTIALS.
+ assertEquals(e.getResultCode(), ResultCode.INVALID_CREDENTIALS,
+ e.getMessage());
+ }
+
+ // There should be no more pending events.
+ provider.assertAllExpectedEventsReceived();
+ state.finalizeStateAfterBind();
+
+ // Tear down and check final state.
+ policy.finalizeAuthenticationPolicy();
+ provider.assertAllExpectedEventsReceived();
+ }
+
+
+
+ // TODO: connection pooling
+ // TODO: load balancing
+ // TODO: fail-over
+
MockPolicyCfg mockCfg()
{
return new MockPolicyCfg();
--
Gitblit v1.10.0