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