mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

coulbeck
26.34.2007 9e9d53db8853ebf62a6e579c2ec9915bcce00ad1
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).
2 files added
2 files modified
475 ■■■■ changed files
opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/PatternDN.java 130 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/Target.java 106 ●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/UserDN.java 52 ●●●● patch | view | raw | blame | history
opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/authorization/dseecompat/TargetTestCase.java 187 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/PatternDN.java
New file
@@ -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;
    }
  }
}
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);
    }
}
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;
    }
opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/authorization/dseecompat/TargetTestCase.java
New file
@@ -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);
  }
}