From 9e9d53db8853ebf62a6e579c2ec9915bcce00ad1 Mon Sep 17 00:00:00 2001
From: coulbeck <coulbeck@localhost>
Date: Mon, 26 Mar 2007 19:34:02 +0000
Subject: [PATCH] These refactoring changes move the ACI DN pattern matching into a separate class called PatternDN.  This will make it easier to rewrite the pattern matching to actually fix the related TODOs.  I also have added unit tests for the DN wildcard examples in the DSEE documentation (three of which fail and are commented out for the time being).

---
 opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/Target.java                                 |  106 ++----------
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/authorization/dseecompat/TargetTestCase.java |  187 +++++++++++++++++++++++
 opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/UserDN.java                                 |   52 -----
 opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/PatternDN.java                              |  130 ++++++++++++++++
 4 files changed, 344 insertions(+), 131 deletions(-)

diff --git a/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/PatternDN.java b/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/PatternDN.java
new file mode 100644
index 0000000..7428a38
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/PatternDN.java
@@ -0,0 +1,130 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  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 2007 Sun Microsystems, Inc.
+ */
+
+package org.opends.server.authorization.dseecompat;
+
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.types.AttributeType;
+import org.opends.server.types.SearchFilter;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.DN;
+import org.opends.server.types.AttributeValue;
+import org.opends.server.types.Attribute;
+import org.opends.server.types.Entry;
+
+import java.util.LinkedHashSet;
+import java.util.ArrayList;
+
+/**
+ * This class is used to encapsulate DN pattern matching using wildcards.
+ *
+ * The current implementation builds a fake entry containing the DN
+ * in an attribute and then matches against a substring filter representing
+ * the pattern.
+ *
+ * TODO Evaluate making this more efficient.
+ *
+ * Creating a dummy entry and attempting to do substring
+ * matching on a DN is a pretty expensive and error-prone approach.
+ * Using a regular expression would likely be much more efficient and
+ * should be simpler.
+
+ * TODO Evaluate re-writing pattern (substring) determination code.
+ * The current code is similar to current DS6 implementation.
+ *
+ * I'm confused by the part of the constructor that generates a search
+ * filter. First, there is no substring matching rule defined for the
+ * DN syntax in the official standard, so technically trying to perform
+ * substring matching against DNs is illegal.  Although we do try to use
+ * the caseIgnoreSubstringsMatch rule, it is extremely unreliable for DNs
+ * because it's just not possible to do substring matching correctly in all
+ * cases for them.
+ */
+public class PatternDN
+{
+  private static final String PATTERN_DN_FAKE_TYPE_NAME = "patterndn";
+  private AttributeType fakeType;
+  private SearchFilter filter;
+
+  private PatternDN(AttributeType fakeType, SearchFilter filter)
+  {
+    this.fakeType = fakeType;
+    this.filter = filter;
+  }
+
+  /**
+   * Create a new DN pattern matcher from a pattern string.
+   * @param pattern The DN pattern string.
+   * @throws org.opends.server.types.DirectoryException If the pattern string
+   * is not valid.
+   * @return A new DN pattern matcher.
+   */
+  public static PatternDN decode(String pattern) throws DirectoryException
+  {
+    AttributeType fakeType =
+         DirectoryServer.getAttributeType(PATTERN_DN_FAKE_TYPE_NAME);
+    if (fakeType == null)
+    {
+       fakeType =
+            DirectoryServer.getDefaultAttributeType(PATTERN_DN_FAKE_TYPE_NAME);
+    }
+
+    SearchFilter filter;
+    DN patternDN = DN.decode(pattern);
+    String filterStr = PATTERN_DN_FAKE_TYPE_NAME + "=" +
+         patternDN.toNormalizedString();
+    filter=SearchFilter.createFilterFromString(filterStr);
+
+    return new PatternDN(fakeType, filter);
+  }
+
+  /**
+   * Determine whether a given DN matches this pattern.
+   * @param dn The DN to be matched.
+   * @return true if the DN matches the pattern.
+   */
+  public boolean matchesDN(DN dn)
+  {
+    String normalizedStr = dn.toNormalizedString();
+
+    LinkedHashSet<AttributeValue> vals =
+            new LinkedHashSet<AttributeValue>();
+    vals.add(new AttributeValue(fakeType, normalizedStr));
+    Attribute attr = new Attribute(fakeType, PATTERN_DN_FAKE_TYPE_NAME, vals);
+    Entry e = new Entry(DN.nullDN(), null, null, null);
+    e.addAttribute(attr,new ArrayList<AttributeValue>());
+
+    try
+    {
+      return filter.matchesEntry(e);
+    }
+    catch (DirectoryException ex)
+    {
+      return false;
+    }
+  }
+}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/Target.java b/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/Target.java
index 6efe719..509aaaf 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/Target.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/Target.java
@@ -30,57 +30,38 @@
 import static org.opends.server.messages.AciMessages.*;
 import static org.opends.server.authorization.dseecompat.Aci.*;
 import static org.opends.server.messages.MessageHandler.getMessage;
-import java.util.ArrayList;
-import java.util.LinkedHashSet;
 import java.util.regex.Pattern;
-import org.opends.server.core.DirectoryServer;
-import org.opends.server.types.Attribute;
-import org.opends.server.types.AttributeType;
-import org.opends.server.types.AttributeValue;
 import org.opends.server.types.DN;
 import org.opends.server.types.DirectoryException;
-import org.opends.server.types.Entry;
 import org.opends.server.types.LDAPURL;
-import org.opends.server.types.SearchFilter;
 
 /**
  * A class representing an ACI target keyword.
  */
 public class Target
 {
-    /*
+    /**
      * Enumeration representing the target operator.
      */
     private EnumTargetOperator operator = EnumTargetOperator.EQUALITY;
 
-    /*
-     * The target URL.
-     */
-    private LDAPURL targetURL = null;
-
-    /*
-     * The DN of the above URL.
-     */
-    private DN urlDN=null;
-
-    /*
-     * True of a wild-card pattern was seen.
+    /**
+     * True if the URL contained a DN wild-card pattern.
      */
     private boolean isPattern=false;
 
-    /*
-     * Filter used to apply to an dummy entry when a wild-card pattern is
-     * used.
+    /**
+     * The target DN from the URL or null if it was a wild-card pattern.
      */
-    private SearchFilter filter=null;
+    private DN urlDN=null;
+
+    /**
+     * The pattern matcher for a wild-card pattern or null if the URL
+     * contained an ordinary DN.
+     */
+    private PatternDN patternDN =null;
 
     /*
-     * Attribute type that is used to create the pattern attribute.
-     *  See matchesPattern method.
-     */
-    private AttributeType targetType;
-
-      /*
      * TODO Save aciDN parameter and use it in matchesPattern re-write.
      *
      * Should the aciDN argument provided to the constructor be stored so that
@@ -88,20 +69,6 @@
      * considered a potential match if it is at or below the entry containing
      * the ACI.
      *
-     * TODO Evaluate re-writing pattern (substring) determination code. The
-     * current code is similar to current DS6 implementation.
-     *
-     * I'm confused by the part of the constructor that generates a search
-     * filter. First, there is no substring matching rule defined for the
-     * DN syntax in the official standard, so technically trying to perform
-     * substring matching against DNs is illegal.  Although we do try to use
-     * the caseIgnoreSubstringsMatch rule, it is extremely unreliable for DNs
-     * because it's just not possible to do substring matching correctly in all
-     * cases for them.  Also, the logic in place there will only generate a
-     * filter if the DN contains a wildcard, and if it starts with a wildcard
-     * (which is handled by the targetDN.startsWith("*") clause), then you'll
-     * end up with something like "(target=**dc=example,dc=com)", which isn't
-     *  legal.
      */
     /**
      * This constructor parses the target string.
@@ -119,19 +86,12 @@
               String message = getMessage(msgID, target);
               throw new AciException(msgID, message);
           }
-          targetURL =  LDAPURL.decode(target, false);
-          urlDN=targetURL.getBaseDN();
-          String targetDN=urlDN.toNormalizedString();
-          if((targetDN.startsWith("*")) ||
-             (targetDN.indexOf("*") != -1)) {
+          LDAPURL targetURL =  LDAPURL.decode(target, false);
+          if(targetURL.getRawBaseDN().indexOf("*") != -1) {
               this.isPattern=true;
-              String pattern="target=*"+targetDN;
-              filter=SearchFilter.createFilterFromString(pattern);
-              targetType = DirectoryServer.getAttributeType("target");
-              if (targetType == null)
-                  targetType =
-                          DirectoryServer.getDefaultAttributeType("target");
+              patternDN = PatternDN.decode(targetURL.getRawBaseDN());
           } else {
+              urlDN=targetURL.getBaseDN();
               if(!urlDN.isDescendantOf(aciDN)) {
                   int msgID = MSGID_ACI_SYNTAX_TARGET_DN_NOT_DESCENDENTOF;
                   String message = getMessage(msgID,
@@ -172,7 +132,7 @@
 
     /**
      * Returns the URL DN of the expression.
-     * @return A DN of the URL.
+     * @return A DN of the URL or null if the URL contained a DN pattern.
      */
     public DN getDN() {
         return urlDN;
@@ -180,44 +140,18 @@
 
     /**
      * Returns boolean if a pattern was seen during parsing.
-     * @return  True if the DN is a wild-card.
+     * @return  True if the URL contained a DN pattern.
      */
     public boolean isPattern() {
         return isPattern;
     }
 
-    /*
-     * TODO Evaluate re-writing this method. Evaluate if we want to dis-allow
-     * wild-card matching in the suffix part of the dn.
-     *
-     * The matchesPattern() method really needs to be rewritten.  It's using a
-     * very inefficient and very error-prone method to make the determination.
-     * If you're really going to attempt pattern matching on a DN, then I'd
-     * suggest trying a regular expression against the normalized DN rather
-     * than a filter.
-     */
     /**
-     * This method tries to match a pattern against a DN. It builds an entry
-     * with a target attribute containing the pattern and then matches against
-     * it.
+     * This method tries to match a pattern against a DN.
      * @param dn  The DN to try an match.
      * @return True if the pattern matches.
      */
     public boolean matchesPattern(DN dn) {
-        boolean ret;
-        String targetDN=dn.toNormalizedString();
-        LinkedHashSet<AttributeValue> values =
-            new LinkedHashSet<AttributeValue>();
-        values.add(new AttributeValue(targetType, targetDN));
-        Attribute attr = new Attribute(targetType, "target", values);
-        Entry e = new Entry(DN.nullDN(), null, null, null);
-        e.addAttribute(attr,new ArrayList<AttributeValue>());
-        try {
-            ret=filter.matchesEntry(e);
-        } catch (DirectoryException ex) {
-            //TODO information message?
-            return false;
-        }
-        return  ret;
+        return patternDN.matchesDN(dn);
     }
 }
diff --git a/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/UserDN.java b/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/UserDN.java
index c39a1d6..5b1028a 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/UserDN.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/UserDN.java
@@ -32,7 +32,6 @@
 
 import java.util.*;
 import org.opends.server.types.*;
-import org.opends.server.core.DirectoryServer;
 
 /**
  * This class represents the userdn keyword in a bind rule.
@@ -54,11 +53,6 @@
      */
     private EnumBindRuleType type=null;
 
-    /*
-     * Used to evaluate a userdn that has a pattern  (wild-card).
-     */
-    private AttributeType userDNAttrType;
-
     /**
      * Constructor that creates the userdn class. It also sets up an attribute
      * type ("userdn") needed  for wild-card matching.
@@ -69,9 +63,6 @@
     private UserDN(EnumBindRuleType type, List<UserDNTypeURL> urlList) {
        this.type=type;
        this.urlList=urlList;
-       userDNAttrType = DirectoryServer.getAttributeType("userdn");
-       if (userDNAttrType == null)
-          userDNAttrType = DirectoryServer.getDefaultAttributeType("userdn");
     }
 
     /**
@@ -267,51 +258,22 @@
         return matched;
     }
 
-    /*
-     * TODO Evaluate making this more efficient.
-     *
-     * The evalDNPattern() method looks like it suffers from the
-     * same problem as the matchesPattern() method in the Target
-     * class.  Creating a dummy entry and attempting to do substring
-     * matching on a DN is a pretty expensive and error-prone approach.
-     * Using a regular expression would likely be much more efficient and
-     *  should be simpler.
-     */
     /**
-     * This method evaluates a DN pattern userdn expression. It creates a
-     * dummy entry and a substring filter and applies the filter to the
-     * entry.
+     * This method evaluates a DN pattern userdn expression.
      * @param evalCtx  The evaluation context to use.
      * @param url The LDAP URL containing the pattern.
      * @return An enumeration evaluation result.
      */
     private EnumEvalResult evalDNPattern(AciEvalContext evalCtx, LDAPURL url) {
-        boolean rc;
-        EnumEvalResult ret=EnumEvalResult.TRUE;
-        String urlDN;
-        SearchFilter filter;
+        PatternDN pattern;
         try {
-            urlDN=url.getBaseDN().toNormalizedString();
-            String pattern="userdn="+urlDN;
-            filter=SearchFilter.createFilterFromString(pattern);
+          pattern = PatternDN.decode(url.getRawBaseDN());
         } catch (DirectoryException ex) {
-            return EnumEvalResult.FALSE;
+          return EnumEvalResult.FALSE;
         }
-        LinkedHashSet<AttributeValue> vals =
-                new LinkedHashSet<AttributeValue>();
-        String userDNStr=evalCtx.getClientDN().toNormalizedString();
-        vals.add(new AttributeValue(userDNAttrType, userDNStr));
-        Attribute attr = new Attribute(userDNAttrType, "userdn", vals);
-        Entry e = new Entry(DN.nullDN(), null, null, null);
-        e.addAttribute(attr,new ArrayList<AttributeValue>());
-        try {
-            rc=filter.matchesEntry(e);
-        } catch (DirectoryException ex) {
-            return EnumEvalResult.FALSE;
-        }
-        if(!rc)
-            ret=EnumEvalResult.FALSE;
-        return ret;
+
+        return pattern.matchesDN(evalCtx.getClientDN()) ?
+             EnumEvalResult.TRUE : EnumEvalResult.FALSE;
     }
 
 
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/authorization/dseecompat/TargetTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/authorization/dseecompat/TargetTestCase.java
new file mode 100644
index 0000000..c932d94
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/authorization/dseecompat/TargetTestCase.java
@@ -0,0 +1,187 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  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 2007 Sun Microsystems, Inc.
+ */
+
+package org.opends.server.authorization.dseecompat;
+
+import org.opends.server.DirectoryServerTestCase;
+import org.opends.server.TestCaseUtils;
+import org.opends.server.protocols.asn1.ASN1OctetString;
+import org.opends.server.types.DN;
+import static org.testng.Assert.assertTrue;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+import org.testng.annotations.BeforeClass;
+
+public class TargetTestCase extends DirectoryServerTestCase
+{
+  @BeforeClass
+  public void startServer() throws Exception
+  {
+    TestCaseUtils.startServer();
+  }
+
+
+  @DataProvider
+  public Object[][] applicableTargets()
+  {
+    return new Object[][] {
+         {
+              "dc=example,dc=com",
+              "(target=\"ldap:///uid=bj*,ou=people,dc=example,dc=com\")" +
+                   "(targetattr=\"*\")(targetScope=\"subtree\")" +
+                   "(version 3.0; acl \"example\";" +
+                   " allow (all) userdn=\"ldap:///self\";)",
+              "uid=bjensen,ou=people,dc=example,dc=com",
+         },
+         {
+              "dc=example,dc=com",
+              "(target=\"ldap:///uid=*,ou=people,dc=example,dc=com\")" +
+                   "(targetattr=\"*\")(targetScope=\"subtree\")" +
+                   "(version 3.0; acl \"example\";" +
+                   " allow (all) userdn=\"ldap:///self\";)",
+              "uid=bjensen,ou=people,dc=example,dc=com",
+         },
+         {
+              "dc=example,dc=com",
+              "(target=\"ldap:///uid=bjensen*\")" +
+                   "(targetattr=\"*\")(targetScope=\"subtree\")" +
+                   "(version 3.0; acl \"example\";" +
+                   " allow (all) userdn=\"ldap:///self\";)",
+              "uid=bjensen,ou=people,dc=example,dc=com",
+         },
+         {
+              "dc=example,dc=com",
+              "(target=\"ldap:///uid=*,dc=example,dc=com\")" +
+                   "(targetattr=\"*\")(targetScope=\"subtree\")" +
+                   "(version 3.0; acl \"example\";" +
+                   " allow (all) userdn=\"ldap:///self\";)",
+              "uid=bjensen,ou=people,dc=example,dc=com",
+         },
+         {
+              "dc=example,dc=com",
+              "(target=\"ldap:///uid=*,ou=*,dc=example,dc=com\")" +
+                   "(targetattr=\"*\")(targetScope=\"subtree\")" +
+                   "(version 3.0; acl \"example\";" +
+                   " allow (all) userdn=\"ldap:///self\";)",
+              "uid=bjensen,ou=people,dc=example,dc=com",
+         },
+         {
+              "dc=example,dc=com",
+              "(target=\"ldap:///uid=BJ*,ou=people,dc=example,dc=com\")" +
+                   "(targetattr=\"*\")(targetScope=\"subtree\")" +
+                   "(version 3.0; acl \"example\";" +
+                   " allow (all) userdn=\"ldap:///self\";)",
+              "uid=bjensen,ou=people,dc=example,dc=com",
+         },
+         {
+              "dc=example,dc=com",
+              "(target!=\"ldap:///cn=*,ou=people,dc=example,dc=com\")" +
+                   "(targetattr=\"*\")(targetScope=\"subtree\")" +
+                   "(version 3.0; acl \"example\";" +
+                   " allow (all) userdn=\"ldap:///self\";)",
+              "uid=bjensen,ou=people,dc=example,dc=com",
+         },
+         // These tests fail as we attempt to normalize the pattern as a DN.
+         // <FAIL>
+//         {
+//              "dc=example,dc=com",
+//              "(target=\"ldap:///*,ou=people,dc=example,dc=com\")" +
+//                   "(targetattr=\"*\")(targetScope=\"subtree\")" +
+//                   "(version 3.0; acl \"example\";" +
+//                   " allow (all) userdn=\"ldap:///self\";)",
+//              "uid=bjensen,ou=people,dc=example,dc=com",
+//         },
+//         {
+//              "dc=example,dc=com",
+//              "(target=\"ldap:///uid=bjensen,*,dc=com\")" +
+//                   "(targetattr=\"*\")(targetScope=\"subtree\")" +
+//                   "(version 3.0; acl \"example\";" +
+//                   " allow (all) userdn=\"ldap:///self\";)",
+//              "uid=bjensen,ou=people,dc=example,dc=com",
+//         },
+//         {
+//              "dc=example,dc=com",
+//              "(target=\"ldap:///*Anderson,ou=People,dc=example,dc=com\")" +
+//                   "(targetattr=\"*\")(targetScope=\"subtree\")" +
+//                   "(version 3.0; acl \"example\";" +
+//                   " allow (all) userdn=\"ldap:///self\";)",
+//              "uid=bjensen,ou=people,dc=example,dc=com",
+//         },
+         // </FAIL>
+    };
+  }
+
+
+  @DataProvider
+  public Object[][] nonApplicableTargets()
+  {
+    return new Object[][] {
+         {
+              "ou=staff,dc=example,dc=com",
+              "(target=\"ldap:///uid=bj*,ou=people,dc=example,dc=com\")" +
+                   "(targetattr=\"*\")(targetScope=\"subtree\")" +
+                   "(version 3.0; acl \"example\";" +
+                   " allow (all) userdn=\"ldap:///self\";)",
+              "uid=bjensen,ou=people,dc=example,dc=com",
+         },
+         {
+              "uid=bjensen,ou=people,dc=example,dc=com",
+              "(targetattr=\"*\")(targetScope=\"onelevel\")" +
+                   "(version 3.0; acl \"example\";" +
+                   " allow (all) userdn=\"ldap:///self\";)",
+              "uid=bjensen,ou=people,dc=example,dc=com",
+         },
+    };
+  }
+
+
+  @Test(dataProvider = "applicableTargets")
+  public void applicableTargets(String aciDN, String aciString, String entryDN)
+       throws Exception
+  {
+    Aci aci = Aci.decode(new ASN1OctetString(aciString), DN.decode(aciDN));
+    boolean match = AciTargets.isTargetApplicable(aci,
+                                                  aci.getTargets(),
+                                                  DN.decode(entryDN));
+    assertTrue(match, aciString + " in entry " + aciDN +
+         " did not apply to " + entryDN);
+  }
+
+
+  @Test(dataProvider = "nonApplicableTargets")
+  public void nonApplicableTargets(String aciDN, String aciString,
+                                   String entryDN)
+       throws Exception
+  {
+    Aci aci = Aci.decode(new ASN1OctetString(aciString), DN.decode(aciDN));
+    boolean match = AciTargets.isTargetApplicable(aci,
+                                                  aci.getTargets(),
+                                                  DN.decode(entryDN));
+    assertTrue(!match, aciString + " in entry " + aciDN +
+         " incorrectly applied to " + entryDN);
+  }
+}

--
Gitblit v1.10.0