From 58feba98f2906308e48f2a34ba3d9c4889202281 Mon Sep 17 00:00:00 2001
From: coulbeck <coulbeck@localhost>
Date: Tue, 03 Apr 2007 18:01:32 +0000
Subject: [PATCH] Issue #1379: Revisit ACI DN wildcard matching.
---
opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/authorization/dseecompat/TargetTestCase.java | 256 ++++++
opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/PatternRDN.java | 330 +++++++++
opendj-sdk/opends/src/server/org/opends/server/messages/AciMessages.java | 42 +
opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/PatternDN.java | 1491 ++++++++++++++++++++++++++++++++++++++--
4 files changed, 2,001 insertions(+), 118 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
index 8cc5f2c..8d2475f 100644
--- 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
@@ -27,107 +27,102 @@
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 org.opends.server.types.*;
+import static org.opends.server.messages.SchemaMessages.*;
+import static org.opends.server.messages.AciMessages.*;
+import static org.opends.server.messages.MessageHandler.getMessage;
+import org.opends.server.protocols.asn1.ASN1OctetString;
+import static org.opends.server.util.StaticUtils.isDigit;
+import static org.opends.server.util.StaticUtils.isHexDigit;
+import static org.opends.server.util.StaticUtils.hexStringToByteArray;
+import org.opends.server.util.Validator;
+import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
+import static org.opends.server.loggers.debug.DebugLogger.debugCaught;
-import java.util.LinkedHashSet;
import java.util.ArrayList;
+import java.util.List;
/**
* This class is used to encapsulate DN pattern matching using wildcards.
+ * The following wildcard uses are supported.
*
- * The current implementation builds a fake entry containing the DN
- * in an attribute and then matches against a substring filter representing
- * the pattern.
+ * Value substring: Any number of wildcards may appear in RDN attribute
+ * values where they match zero or more characters, just like substring filters:
+ * uid=b*jensen*
*
- * TODO Evaluate making this more efficient.
+ * Whole-Type: A single wildcard may also be used to match any RDN attribute
+ * type, and the wildcard in this case may be omitted as a shorthand:
+ * *=bjensen
+ * bjensen
*
- * 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.
+ * Whole-RDN. A single wildcard may be used to match exactly one RDN component
+ * (which may be single or multi-valued):
+ * *,dc=example,dc=com
*
- * 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.
+ * Multiple-Whole-RDN: A double wildcard may be used to match one or more
+ * RDN components:
+ * uid=bjensen,**,dc=example,dc=com
+ *
*/
public class PatternDN
{
- private static final String PATTERN_DN_FAKE_TYPE_NAME = "patterndn";
- private AttributeType fakeType;
- private SearchFilter filter;
+ /**
+ * If the pattern did not include any Multiple-Whole-RDN wildcards, then
+ * this is the sequence of RDN patterns in the DN pattern. Otherwise it
+ * is null.
+ */
+ PatternRDN[] equality = null;
- 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.
+ * If the pattern included any Multiple-Whole-RDN wildcards, then these
+ * are the RDN pattern sequences that appear between those wildcards.
*/
- 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);
- }
+ PatternRDN[] subInitial = null;
+ List<PatternRDN[]> subAnyElements = null;
+ PatternRDN[] subFinal = null;
- 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);
- }
/**
- * Create a new DN pattern matcher to match a suffix.
- * @param pattern The suffix pattern string.
- * @throws org.opends.server.types.DirectoryException If the pattern string
- * is not valid.
- * @return A new DN pattern matcher.
+ * When there is no initial sequence, this is used to distinguish between
+ * the case where we have a suffix pattern (zero or more RDN components
+ * allowed before matching elements) and the case where it is not a
+ * suffix pattern but the pattern started with a Multiple-Whole-RDN wildcard
+ * (one or more RDN components allowed before matching elements).
*/
- public static PatternDN decodeSuffix(String pattern) throws DirectoryException
+ boolean isSuffix = false;
+
+
+ /**
+ * Create a DN pattern that does not include any Multiple-Whole-RDN wildcards.
+ * @param equality The sequence of RDN patterns making up the DN pattern.
+ */
+ private PatternDN(PatternRDN[] equality)
{
- 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);
+ this.equality = equality;
}
+
+ /**
+ * Create a DN pattern that includes Multiple-Whole-RDN wildcards.
+ * @param subInitial The sequence of RDN patterns appearing at the
+ * start of the DN, or null if there are none.
+ * @param subAnyElements The list of sequences of RDN patterns appearing
+ * in order anywhere in the DN.
+ * @param subFinal The sequence of RDN patterns appearing at the
+ * end of the DN, or null if there are none.
+ */
+ private PatternDN(PatternRDN[] subInitial,
+ List<PatternRDN[]> subAnyElements,
+ PatternRDN[] subFinal)
+ {
+ Validator.ensureNotNull(subAnyElements);
+ this.subInitial = subInitial;
+ this.subAnyElements = subAnyElements;
+ this.subFinal = subFinal;
+ }
+
+
/**
* Determine whether a given DN matches this pattern.
* @param dn The DN to be matched.
@@ -135,22 +130,1342 @@
*/
public boolean matchesDN(DN dn)
{
- String normalizedStr = dn.toNormalizedString();
+ if (equality != null)
+ {
+ // There are no Multiple-Whole-RDN wildcards in the pattern.
+ if (equality.length != dn.getNumComponents())
+ {
+ return false;
+ }
- 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>());
+ for (int i = 0; i < dn.getNumComponents(); i++)
+ {
+ if (!equality[i].matchesRDN(dn.getRDN(i)))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+ else
+ {
+ // There are Multiple-Whole-RDN wildcards in the pattern.
+ int valueLength = dn.getNumComponents();
+
+ int pos = 0;
+ if (subInitial != null)
+ {
+ int initialLength = subInitial.length;
+ if (initialLength > valueLength)
+ {
+ return false;
+ }
+
+ for (; pos < initialLength; pos++)
+ {
+ if (!subInitial[pos].matchesRDN(dn.getRDN(pos)))
+ {
+ return false;
+ }
+ }
+ pos++;
+ }
+ else
+ {
+ if (!isSuffix)
+ {
+ pos++;
+ }
+ }
+
+
+ if ((subAnyElements != null) && (! subAnyElements.isEmpty()))
+ {
+ for (PatternRDN[] element : subAnyElements)
+ {
+ int anyLength = element.length;
+
+ int end = valueLength - anyLength;
+ boolean match = false;
+ for (; pos < end; pos++)
+ {
+ if (element[0].matchesRDN(dn.getRDN(pos)))
+ {
+ boolean subMatch = true;
+ for (int i=1; i < anyLength; i++)
+ {
+ if (!element[i].matchesRDN(dn.getRDN(pos+i)))
+ {
+ subMatch = false;
+ break;
+ }
+ }
+
+ if (subMatch)
+ {
+ match = subMatch;
+ break;
+ }
+ }
+ }
+
+ if (match)
+ {
+ pos += anyLength + 1;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ }
+
+
+ if (subFinal != null)
+ {
+ int finalLength = subFinal.length;
+
+ if ((valueLength - finalLength) < pos)
+ {
+ return false;
+ }
+
+ pos = valueLength - finalLength;
+ for (int i=0; i < finalLength; i++,pos++)
+ {
+ if (!subFinal[i].matchesRDN(dn.getRDN(pos)))
+ {
+ return false;
+ }
+ }
+ }
+
+ return pos <= valueLength;
+ }
+ }
+
+
+ /**
+ * Create a new DN pattern matcher to match a suffix.
+ * @param pattern The suffix pattern string.
+ * @throws org.opends.server.types.DirectoryException If the pattern string
+ * is not valid.
+ * @return A new DN pattern matcher.
+ */
+ public static PatternDN decodeSuffix(String pattern)
+ throws DirectoryException
+ {
+ // Parse the user supplied pattern.
+ PatternDN patternDN = decode(pattern);
+
+ // Adjust the pattern so that it matches any DN ending with the pattern.
+ if (patternDN.equality != null)
+ {
+ // The pattern contained no Multiple-Whole-RDN wildcards,
+ // so we just convert the whole thing into a final fragment.
+ patternDN.subInitial = null;
+ patternDN.subFinal = patternDN.equality;
+ patternDN.subAnyElements = null;
+ patternDN.equality = null;
+ }
+ else if (patternDN.subInitial != null)
+ {
+ // The pattern had an initial fragment so we need to convert that into
+ // the head of the list of any elements.
+ patternDN.subAnyElements.add(0, patternDN.subInitial);
+ patternDN.subInitial = null;
+ }
+ patternDN.isSuffix = true;
+ return patternDN;
+ }
+
+
+ /**
+ * Create a new DN pattern matcher from a pattern string.
+ * @param dnString 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 dnString)
+ throws DirectoryException
+ {
+ ArrayList<PatternRDN> rdnComponents = new ArrayList<PatternRDN>();
+ ArrayList<Integer> doubleWildPos = new ArrayList<Integer>();
+
+ // A null or empty DN is acceptable.
+ if (dnString == null)
+ {
+ return new PatternDN(new PatternRDN[0]);
+ }
+
+ int length = dnString.length();
+ if (length == 0)
+ {
+ return new PatternDN(new PatternRDN[0]);
+ }
+
+
+ // Iterate through the DN string. The first thing to do is to get
+ // rid of any leading spaces.
+ int pos = 0;
+ char c = dnString.charAt(pos);
+ while (c == ' ')
+ {
+ pos++;
+ if (pos == length)
+ {
+ // This means that the DN was completely comprised of spaces
+ // and therefore should be considered the same as a null or
+ // empty DN.
+ return new PatternDN(new PatternRDN[0]);
+ }
+ else
+ {
+ c = dnString.charAt(pos);
+ }
+ }
+
+ // We know that it's not an empty DN, so we can do the real
+ // processing. Create a loop and iterate through all the RDN
+ // components.
+ rdnLoop:
+ while (true)
+ {
+ int attributePos = pos;
+ StringBuilder attributeName = new StringBuilder();
+ pos = parseAttributePattern(dnString, pos, attributeName);
+ String name = attributeName.toString();
+
+
+ // Make sure that we're not at the end of the DN string because
+ // that would be invalid.
+ if (pos >= length)
+ {
+ if (name.equals("*"))
+ {
+ rdnComponents.add(new PatternRDN(name, null, dnString));
+ break;
+ }
+ else if (name.equals("**"))
+ {
+ doubleWildPos.add(rdnComponents.size());
+ break;
+ }
+ else
+ {
+ pos = attributePos - 1;
+ name = "*";
+ c = '=';
+ }
+ }
+ else
+ {
+ // Skip over any spaces between the attribute name and its
+ // value.
+ c = dnString.charAt(pos);
+ while (c == ' ')
+ {
+ pos++;
+ if (pos >= length)
+ {
+ if (name.equals("*"))
+ {
+ rdnComponents.add(new PatternRDN(name, null, dnString));
+ break rdnLoop;
+ }
+ else if (name.equals("**"))
+ {
+ doubleWildPos.add(rdnComponents.size());
+ break rdnLoop;
+ }
+ else
+ {
+ pos = attributePos - 1;
+ name = "*";
+ c = '=';
+ }
+ }
+ else
+ {
+ c = dnString.charAt(pos);
+ }
+ }
+ }
+
+
+ if (c == '=')
+ {
+ pos++;
+ }
+ else if ((c == ',' || c == ';'))
+ {
+ if (name.equals("*"))
+ {
+ rdnComponents.add(new PatternRDN(name, null, dnString));
+ pos++;
+ continue;
+ }
+ else if (name.equals("**"))
+ {
+ doubleWildPos.add(rdnComponents.size());
+ pos++;
+ continue;
+ }
+ else
+ {
+ pos = attributePos;
+ name = "*";
+ }
+ }
+ else
+ {
+ int msgID = MSGID_ATTR_SYNTAX_DN_NO_EQUAL;
+ String message = getMessage(msgID, dnString,
+ attributeName.toString(), c);
+ throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
+ message, msgID);
+ }
+
+ // Skip over any spaces after the equal sign.
+ while ((pos < length) && (dnString.charAt(pos) == ' '))
+ {
+ pos++;
+ }
+
+
+ // If we are at the end of the DN string, then that must mean
+ // that the attribute value was empty. This will probably never
+ // happen in a real-world environment, but technically isn't
+ // illegal. If it does happen, then go ahead and create the
+ // RDN component and return the DN.
+ if (pos >= length)
+ {
+ ArrayList<ByteString> arrayList = new ArrayList<ByteString>(1);
+ arrayList.add(new ASN1OctetString());
+ rdnComponents.add(new PatternRDN(name, arrayList, dnString));
+ break;
+ }
+
+
+ // Parse the value for this RDN component.
+ ArrayList<ByteString> parsedValue = new ArrayList<ByteString>();
+ pos = parseValuePattern(dnString, pos, parsedValue);
+
+
+ // Create the new RDN with the provided information.
+ PatternRDN rdn = new PatternRDN(name, parsedValue, dnString);
+
+
+ // Skip over any spaces that might be after the attribute value.
+ while ((pos < length) && ((c = dnString.charAt(pos)) == ' '))
+ {
+ pos++;
+ }
+
+
+ // Most likely, we will be at either the end of the RDN
+ // component or the end of the DN. If so, then handle that
+ // appropriately.
+ if (pos >= length)
+ {
+ // We're at the end of the DN string and should have a valid
+ // DN so return it.
+ rdnComponents.add(rdn);
+ break;
+ }
+ else if ((c == ',') || (c == ';'))
+ {
+ // We're at the end of the RDN component, so add it to the
+ // list, skip over the comma/semicolon, and start on the next
+ // component.
+ rdnComponents.add(rdn);
+ pos++;
+ continue;
+ }
+ else if (c != '+')
+ {
+ // This should not happen. At any rate, it's an illegal
+ // character, so throw an exception.
+ int msgID = MSGID_ATTR_SYNTAX_DN_INVALID_CHAR;
+ String message = getMessage(msgID, dnString, c, pos);
+ throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
+ message, msgID);
+ }
+
+
+ // If we have gotten here, then this must be a multi-valued RDN.
+ // In that case, parse the remaining attribute/value pairs and
+ // add them to the RDN that we've already created.
+ while (true)
+ {
+ // Skip over the plus sign and any spaces that may follow it
+ // before the next attribute name.
+ pos++;
+ while ((pos < length) && (dnString.charAt(pos) == ' '))
+ {
+ pos++;
+ }
+
+
+ // Parse the attribute name from the DN string.
+ attributeName = new StringBuilder();
+ pos = parseAttributePattern(dnString, pos, attributeName);
+
+
+ // Make sure that we're not at the end of the DN string
+ // because that would be invalid.
+ if (pos >= length)
+ {
+ int msgID = MSGID_ATTR_SYNTAX_DN_END_WITH_ATTR_NAME;
+ String message = getMessage(msgID, dnString,
+ attributeName.toString());
+ throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
+ message, msgID);
+ }
+
+
+ name = attributeName.toString();
+
+ // Skip over any spaces between the attribute name and its
+ // value.
+ c = dnString.charAt(pos);
+ while (c == ' ')
+ {
+ pos++;
+ if (pos >= length)
+ {
+ // This means that we hit the end of the value before
+ // finding a '='. This is illegal because there is no
+ // attribute-value separator.
+ int msgID = MSGID_ATTR_SYNTAX_DN_END_WITH_ATTR_NAME;
+ String message = getMessage(msgID, dnString, name);
+ throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
+ message, msgID);
+ }
+ else
+ {
+ c = dnString.charAt(pos);
+ }
+ }
+
+
+ // The next character must be an equal sign. If it is not,
+ // then that's an error.
+ if (c == '=')
+ {
+ pos++;
+ }
+ else
+ {
+ int msgID = MSGID_ATTR_SYNTAX_DN_NO_EQUAL;
+ String message = getMessage(msgID, dnString, name, c);
+ throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
+ message, msgID);
+ }
+
+
+ // Skip over any spaces after the equal sign.
+ while ((pos < length) && ((c = dnString.charAt(pos)) == ' '))
+ {
+ pos++;
+ }
+
+
+ // If we are at the end of the DN string, then that must mean
+ // that the attribute value was empty. This will probably
+ // never happen in a real-world environment, but technically
+ // isn't illegal. If it does happen, then go ahead and create
+ // the RDN component and return the DN.
+ if (pos >= length)
+ {
+ ArrayList<ByteString> arrayList = new ArrayList<ByteString>(1);
+ arrayList.add(new ASN1OctetString());
+ rdn.addValue(name, arrayList, dnString);
+ rdnComponents.add(rdn);
+ break;
+ }
+
+
+ // Parse the value for this RDN component.
+ parsedValue = new ArrayList<ByteString>();
+ pos = parseValuePattern(dnString, pos, parsedValue);
+
+
+ // Create the new RDN with the provided information.
+ rdn.addValue(name, parsedValue, dnString);
+
+
+ // Skip over any spaces that might be after the attribute
+ // value.
+ while ((pos < length) && ((c = dnString.charAt(pos)) == ' '))
+ {
+ pos++;
+ }
+
+
+ // Most likely, we will be at either the end of the RDN
+ // component or the end of the DN. If so, then handle that
+ // appropriately.
+ if (pos >= length)
+ {
+ // We're at the end of the DN string and should have a valid
+ // DN so return it.
+ rdnComponents.add(rdn);
+ break;
+ }
+ else if ((c == ',') || (c == ';'))
+ {
+ // We're at the end of the RDN component, so add it to the
+ // list, skip over the comma/semicolon, and start on the
+ // next component.
+ rdnComponents.add(rdn);
+ pos++;
+ break;
+ }
+ else if (c != '+')
+ {
+ // This should not happen. At any rate, it's an illegal
+ // character, so throw an exception.
+ int msgID = MSGID_ATTR_SYNTAX_DN_INVALID_CHAR;
+ String message = getMessage(msgID, dnString, c, pos);
+ throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
+ message, msgID);
+ }
+ }
+ }
+
+ if (doubleWildPos.isEmpty())
+ {
+ PatternRDN[] patterns = new PatternRDN[rdnComponents.size()];
+ patterns = rdnComponents.toArray(patterns);
+ return new PatternDN(patterns);
+ }
+ else
+ {
+ PatternRDN[] subInitial = null;
+ PatternRDN[] subFinal = null;
+ List<PatternRDN[]> subAnyElements = new ArrayList<PatternRDN[]>();
+
+ int i = 0;
+ int numComponents = rdnComponents.size();
+
+ int to = doubleWildPos.get(i);
+ if (to != 0)
+ {
+ // Initial piece.
+ subInitial = new PatternRDN[to];
+ subInitial = rdnComponents.subList(0, to).toArray(subInitial);
+ i++;
+ }
+
+ int from;
+ for (; i < doubleWildPos.size() - 1; i++)
+ {
+ from = doubleWildPos.get(i);
+ to = doubleWildPos.get(i+1);
+ PatternRDN[] subAny = new PatternRDN[to-from];
+ subAny = rdnComponents.subList(from, to).toArray(subAny);
+ subAnyElements.add(subAny);
+ }
+
+ if (i < doubleWildPos.size())
+ {
+ from = doubleWildPos.get(i);
+ if (from != numComponents)
+ {
+ // Final piece.
+ subFinal = new PatternRDN[numComponents-from];
+ subFinal = rdnComponents.subList(from, numComponents).
+ toArray(subFinal);
+ }
+ }
+
+ return new PatternDN(subInitial, subAnyElements, subFinal);
+ }
+ }
+
+
+ /**
+ * Parses an attribute name pattern from the provided DN pattern string
+ * starting at the specified location.
+ *
+ * @param dnString The DN pattern string to be parsed.
+ * @param pos The position at which to start parsing
+ * the attribute name pattern.
+ * @param attributeName The buffer to which to append the parsed
+ * attribute name pattern.
+ *
+ * @return The position of the first character that is not part of
+ * the attribute name pattern.
+ *
+ * @throws DirectoryException If it was not possible to parse a
+ * valid attribute name pattern from the
+ * provided DN pattern string.
+ */
+ static int parseAttributePattern(String dnString, int pos,
+ StringBuilder attributeName)
+ throws DirectoryException
+ {
+ int length = dnString.length();
+
+
+ // Skip over any leading spaces.
+ if (pos < length)
+ {
+ while (dnString.charAt(pos) == ' ')
+ {
+ pos++;
+ if (pos == length)
+ {
+ // This means that the remainder of the DN was completely
+ // comprised of spaces. If we have gotten here, then we
+ // know that there is at least one RDN component, and
+ // therefore the last non-space character of the DN must
+ // have been a comma. This is not acceptable.
+ int msgID = MSGID_ATTR_SYNTAX_DN_END_WITH_COMMA;
+ String message = getMessage(msgID, dnString);
+ throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
+ message, msgID);
+ }
+ }
+ }
+
+ // Next, we should find the attribute name for this RDN component.
+ boolean checkForOID = false;
+ boolean endOfName = false;
+ while (pos < length)
+ {
+ // To make the switch more efficient, we'll include all ASCII
+ // characters in the range of allowed values and then reject the
+ // ones that aren't allowed.
+ char c = dnString.charAt(pos);
+ switch (c)
+ {
+ case ' ':
+ // This should denote the end of the attribute name.
+ endOfName = true;
+ break;
+
+
+ case '!':
+ case '"':
+ case '#':
+ case '$':
+ case '%':
+ case '&':
+ case '\'':
+ case '(':
+ case ')':
+ // None of these are allowed in an attribute name or any
+ // character immediately following it.
+ int msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR;
+ String message = getMessage(msgID, dnString, c, pos);
+ throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
+ message, msgID);
+
+
+ case '*':
+ // Wildcard character.
+ attributeName.append(c);
+ break;
+
+ case '+':
+ // None of these are allowed in an attribute name or any
+ // character immediately following it.
+ msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR;
+ message = getMessage(msgID, dnString, c, pos);
+ throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
+ message, msgID);
+
+
+ case ',':
+ // This should denote the end of the attribute name.
+ endOfName = true;
+ break;
+
+ case '-':
+ // This will be allowed as long as it isn't the first
+ // character in the attribute name.
+ if (attributeName.length() > 0)
+ {
+ attributeName.append(c);
+ }
+ else
+ {
+ msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_INITIAL_DASH;
+ message = getMessage(msgID, dnString, c);
+ throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
+ message, msgID);
+ }
+ break;
+
+
+ case '.':
+ // The period could be allowed if the attribute name is
+ // actually expressed as an OID. We'll accept it for now,
+ // but make sure to check it later.
+ attributeName.append(c);
+ checkForOID = true;
+ break;
+
+
+ case '/':
+ // This is not allowed in an attribute name or any character
+ // immediately following it.
+ msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR;
+ message = getMessage(msgID, dnString, c, pos);
+ throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
+ message, msgID);
+
+
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ // Digits are always allowed if they are not the first
+ // character. However, they may be allowed if they are the
+ // first character if the valid is an OID or if the
+ // attribute name exceptions option is enabled. Therefore,
+ // we'll accept it now and check it later.
+ attributeName.append(c);
+ break;
+
+
+ case ':':
+ // Not allowed in an attribute name or any
+ // character immediately following it.
+ msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR;
+ message = getMessage(msgID, dnString, c, pos);
+ throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
+ message, msgID);
+
+
+ case ';': // NOTE: attribute options are not allowed in a DN.
+ // This should denote the end of the attribute name.
+ endOfName = true;
+ break;
+
+ case '<':
+ // None of these are allowed in an attribute name or any
+ // character immediately following it.
+ msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR;
+ message = getMessage(msgID, dnString, c, pos);
+ throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
+ message, msgID);
+
+
+ case '=':
+ // This should denote the end of the attribute name.
+ endOfName = true;
+ break;
+
+
+ case '>':
+ case '?':
+ case '@':
+ // None of these are allowed in an attribute name or any
+ // character immediately following it.
+ msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR;
+ message = getMessage(msgID, dnString, c, pos);
+ throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
+ message, msgID);
+
+
+ case 'A':
+ case 'B':
+ case 'C':
+ case 'D':
+ case 'E':
+ case 'F':
+ case 'G':
+ case 'H':
+ case 'I':
+ case 'J':
+ case 'K':
+ case 'L':
+ case 'M':
+ case 'N':
+ case 'O':
+ case 'P':
+ case 'Q':
+ case 'R':
+ case 'S':
+ case 'T':
+ case 'U':
+ case 'V':
+ case 'W':
+ case 'X':
+ case 'Y':
+ case 'Z':
+ // These will always be allowed.
+ attributeName.append(c);
+ break;
+
+
+ case '[':
+ case '\\':
+ case ']':
+ case '^':
+ // None of these are allowed in an attribute name or any
+ // character immediately following it.
+ msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR;
+ message = getMessage(msgID, dnString, c, pos);
+ throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
+ message, msgID);
+
+
+ case '_':
+ attributeName.append(c);
+ break;
+
+
+ case '`':
+ // This is not allowed in an attribute name or any character
+ // immediately following it.
+ msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR;
+ message = getMessage(msgID, dnString, c, pos);
+ throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
+ message, msgID);
+
+
+ case 'a':
+ case 'b':
+ case 'c':
+ case 'd':
+ case 'e':
+ case 'f':
+ case 'g':
+ case 'h':
+ case 'i':
+ case 'j':
+ case 'k':
+ case 'l':
+ case 'm':
+ case 'n':
+ case 'o':
+ case 'p':
+ case 'q':
+ case 'r':
+ case 's':
+ case 't':
+ case 'u':
+ case 'v':
+ case 'w':
+ case 'x':
+ case 'y':
+ case 'z':
+ // These will always be allowed.
+ attributeName.append(c);
+ break;
+
+
+ default:
+ // This is not allowed in an attribute name or any character
+ // immediately following it.
+ msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR;
+ message = getMessage(msgID, dnString, c, pos);
+ throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
+ message, msgID);
+ }
+
+
+ if (endOfName)
+ {
+ break;
+ }
+
+ pos++;
+ }
+
+
+ // We should now have the full attribute name. However, we may
+ // still need to perform some validation, particularly if the
+ // name contains a period or starts with a digit. It must also
+ // have at least one character.
+ if (attributeName.length() == 0)
+ {
+ int msgID = MSGID_ATTR_SYNTAX_DN_ATTR_NO_NAME;
+ String message = getMessage(msgID, dnString);
+ throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
+ message, msgID);
+ }
+ else if (checkForOID)
+ {
+ boolean validOID = true;
+
+ int namePos = 0;
+ int nameLength = attributeName.length();
+ char ch = attributeName.charAt(0);
+ if ((ch == 'o') || (ch == 'O'))
+ {
+ if (nameLength <= 4)
+ {
+ validOID = false;
+ }
+ else
+ {
+ if ((((ch = attributeName.charAt(1)) == 'i') ||
+ (ch == 'I')) &&
+ (((ch = attributeName.charAt(2)) == 'd') ||
+ (ch == 'D')) &&
+ (attributeName.charAt(3) == '.'))
+ {
+ attributeName.delete(0, 4);
+ nameLength -= 4;
+ }
+ else
+ {
+ validOID = false;
+ }
+ }
+ }
+
+ while (validOID && (namePos < nameLength))
+ {
+ ch = attributeName.charAt(namePos++);
+ if (isDigit(ch))
+ {
+ while (validOID && (namePos < nameLength) &&
+ isDigit(attributeName.charAt(namePos)))
+ {
+ namePos++;
+ }
+
+ if ((namePos < nameLength) &&
+ (attributeName.charAt(namePos) != '.'))
+ {
+ validOID = false;
+ }
+ }
+ else if (ch == '.')
+ {
+ if ((namePos == 1) ||
+ (attributeName.charAt(namePos-2) == '.'))
+ {
+ validOID = false;
+ }
+ }
+ else
+ {
+ validOID = false;
+ }
+ }
+
+
+ if (validOID && (attributeName.charAt(nameLength-1) == '.'))
+ {
+ validOID = false;
+ }
+
+
+ if (! validOID)
+ {
+ int msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_PERIOD;
+ String message = getMessage(msgID, dnString,
+ attributeName.toString());
+ throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
+ message, msgID);
+ }
+ }
+
+
+ return pos;
+ }
+
+
+ /**
+ * Parses the attribute value pattern from the provided DN pattern
+ * string starting at the specified location. The value is split up
+ * according to the wildcard locations, and the fragments are inserted
+ * into the provided list.
+ *
+ * @param dnString The DN pattern string to be parsed.
+ * @param pos The position of the first character in
+ * the attribute value pattern to parse.
+ * @param attributeValues The list whose elements should be set to
+ * the parsed attribute value fragments when
+ * this method completes successfully.
+ *
+ * @return The position of the first character that is not part of
+ * the attribute value.
+ *
+ * @throws DirectoryException If it was not possible to parse a
+ * valid attribute value pattern from the
+ * provided DN string.
+ */
+ static private int parseValuePattern(String dnString, int pos,
+ ArrayList<ByteString> attributeValues)
+ throws DirectoryException
+ {
+ // All leading spaces have already been stripped so we can start
+ // reading the value. However, it may be empty so check for that.
+ int length = dnString.length();
+ if (pos >= length)
+ {
+ return pos;
+ }
+
+
+ // Look at the first character. If it is an octothorpe (#), then
+ // that means that the value should be a hex string.
+ char c = dnString.charAt(pos++);
+ if (c == '#')
+ {
+ // The first two characters must be hex characters.
+ StringBuilder hexString = new StringBuilder();
+ if ((pos+2) > length)
+ {
+ int msgID = MSGID_ATTR_SYNTAX_DN_HEX_VALUE_TOO_SHORT;
+ String message = getMessage(msgID, dnString);
+ throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
+ message, msgID);
+ }
+
+ for (int i=0; i < 2; i++)
+ {
+ c = dnString.charAt(pos++);
+ if (isHexDigit(c))
+ {
+ hexString.append(c);
+ }
+ else
+ {
+ int msgID = MSGID_ATTR_SYNTAX_DN_INVALID_HEX_DIGIT;
+ String message = getMessage(msgID, dnString, c);
+ throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
+ message, msgID);
+ }
+ }
+
+
+ // The rest of the value must be a multiple of two hex
+ // characters. The end of the value may be designated by the
+ // end of the DN, a comma or semicolon, or a space.
+ while (pos < length)
+ {
+ c = dnString.charAt(pos++);
+ if (isHexDigit(c))
+ {
+ hexString.append(c);
+
+ if (pos < length)
+ {
+ c = dnString.charAt(pos++);
+ if (isHexDigit(c))
+ {
+ hexString.append(c);
+ }
+ else
+ {
+ int msgID = MSGID_ATTR_SYNTAX_DN_INVALID_HEX_DIGIT;
+ String message = getMessage(msgID, dnString, c);
+ throw new DirectoryException(
+ ResultCode.INVALID_DN_SYNTAX, message,
+ msgID);
+ }
+ }
+ else
+ {
+ int msgID = MSGID_ATTR_SYNTAX_DN_HEX_VALUE_TOO_SHORT;
+ String message = getMessage(msgID, dnString);
+ throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
+ message, msgID);
+ }
+ }
+ else if ((c == ' ') || (c == ',') || (c == ';'))
+ {
+ // This denotes the end of the value.
+ pos--;
+ break;
+ }
+ else
+ {
+ int msgID = MSGID_ATTR_SYNTAX_DN_INVALID_HEX_DIGIT;
+ String message = getMessage(msgID, dnString, c);
+ throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
+ message, msgID);
+ }
+ }
+
+
+ // At this point, we should have a valid hex string. Convert it
+ // to a byte array and set that as the value of the provided
+ // octet string.
+ try
+ {
+ byte[] bytes = hexStringToByteArray(hexString.toString());
+ attributeValues.add(new ASN1OctetString(bytes));
+ return pos;
+ }
+ catch (Exception e)
+ {
+ if (debugEnabled())
+ {
+ debugCaught(DebugLogLevel.ERROR, e);
+ }
+
+ int msgID = MSGID_ATTR_SYNTAX_DN_ATTR_VALUE_DECODE_FAILURE;
+ String message = getMessage(msgID, dnString,
+ String.valueOf(e));
+ throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
+ message, msgID);
+ }
+ }
+
+
+ // If the first character is a quotation mark, then the value
+ // should continue until the corresponding closing quotation mark.
+ else if (c == '"')
+ {
+ // Keep reading until we find an unescaped closing quotation
+ // mark.
+ boolean escaped = false;
+ StringBuilder valueString = new StringBuilder();
+ while (true)
+ {
+ if (pos >= length)
+ {
+ // We hit the end of the DN before the closing quote.
+ // That's an error.
+ int msgID = MSGID_ATTR_SYNTAX_DN_UNMATCHED_QUOTE;
+ String message = getMessage(msgID, dnString);
+ throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
+ message, msgID);
+ }
+
+ c = dnString.charAt(pos++);
+ if (escaped)
+ {
+ // The previous character was an escape, so we'll take this
+ // one no matter what.
+ valueString.append(c);
+ escaped = false;
+ }
+ else if (c == '\\')
+ {
+ // The next character is escaped. Set a flag to denote
+ // this, but don't include the backslash.
+ escaped = true;
+ }
+ else if (c == '"')
+ {
+ // This is the end of the value.
+ break;
+ }
+ else
+ {
+ // This is just a regular character that should be in the
+ // value.
+ valueString.append(c);
+ }
+ }
+
+ attributeValues.add(new ASN1OctetString(valueString.toString()));
+ return pos;
+ }
+
+
+ // Otherwise, use general parsing to find the end of the value.
+ else
+ {
+ boolean escaped;
+ StringBuilder valueString = new StringBuilder();
+ StringBuilder hexChars = new StringBuilder();
+
+ if (c == '\\')
+ {
+ escaped = true;
+ }
+ else if (c == '*')
+ {
+ escaped = false;
+ attributeValues.add(new ASN1OctetString(valueString.toString()));
+ }
+ else
+ {
+ escaped = false;
+ valueString.append(c);
+ }
+
+
+ // Keep reading until we find an unescaped comma or plus sign or
+ // the end of the DN.
+ while (true)
+ {
+ if (pos >= length)
+ {
+ // This is the end of the DN and therefore the end of the
+ // value. If there are any hex characters, then we need to
+ // deal with them accordingly.
+ appendHexChars(dnString, valueString, hexChars);
+ break;
+ }
+
+ c = dnString.charAt(pos++);
+ if (escaped)
+ {
+ // The previous character was an escape, so we'll take this
+ // one. However, this could be a hex digit, and if that's
+ // the case then the escape would actually be in front of
+ // two hex digits that should be treated as a special
+ // character.
+ if (isHexDigit(c))
+ {
+ // It is a hexadecimal digit, so the next digit must be
+ // one too. However, this could be just one in a series
+ // of escaped hex pairs that is used in a string
+ // containing one or more multi-byte UTF-8 characters so
+ // we can't just treat this byte in isolation. Collect
+ // all the bytes together and make sure to take care of
+ // these hex bytes before appending anything else to the
+ // value.
+ if (pos >= length)
+ {
+ int msgID =
+ MSGID_ATTR_SYNTAX_DN_ESCAPED_HEX_VALUE_INVALID;
+ String message = getMessage(msgID, dnString);
+ throw new DirectoryException(
+ ResultCode.INVALID_DN_SYNTAX, message,
+ msgID);
+ }
+ else
+ {
+ char c2 = dnString.charAt(pos++);
+ if (isHexDigit(c2))
+ {
+ hexChars.append(c);
+ hexChars.append(c2);
+ }
+ else
+ {
+ int msgID =
+ MSGID_ATTR_SYNTAX_DN_ESCAPED_HEX_VALUE_INVALID;
+ String message = getMessage(msgID, dnString);
+ throw new DirectoryException(
+ ResultCode.INVALID_DN_SYNTAX, message,
+ msgID);
+ }
+ }
+ }
+ else
+ {
+ appendHexChars(dnString, valueString, hexChars);
+ valueString.append(c);
+ }
+
+ escaped = false;
+ }
+ else if (c == '\\')
+ {
+ escaped = true;
+ }
+ else if ((c == ',') || (c == ';'))
+ {
+ appendHexChars(dnString, valueString, hexChars);
+ pos--;
+ break;
+ }
+ else if (c == '+')
+ {
+ appendHexChars(dnString, valueString, hexChars);
+ pos--;
+ break;
+ }
+ else if (c == '*')
+ {
+ appendHexChars(dnString, valueString, hexChars);
+ if (valueString.length() == 0)
+ {
+ int msgID = MSGID_PATTERN_DN_CONSECUTIVE_WILDCARDS_IN_VALUE;
+ String message = getMessage(msgID, dnString);
+ throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
+ message, msgID);
+ }
+ attributeValues.add(new ASN1OctetString(valueString.toString()));
+ valueString = new StringBuilder();
+ hexChars = new StringBuilder();
+ }
+ else
+ {
+ appendHexChars(dnString, valueString, hexChars);
+ valueString.append(c);
+ }
+ }
+
+
+ // Strip off any unescaped spaces that may be at the end of the
+ // value.
+ if (pos > 2 && dnString.charAt(pos-1) == ' ' &&
+ dnString.charAt(pos-2) != '\\')
+ {
+ int lastPos = valueString.length() - 1;
+ while (lastPos > 0)
+ {
+ if (valueString.charAt(lastPos) == ' ')
+ {
+ valueString.delete(lastPos, lastPos+1);
+ lastPos--;
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+
+
+ attributeValues.add(new ASN1OctetString(valueString.toString()));
+ return pos;
+ }
+ }
+
+
+ /**
+ * Decodes a hexadecimal string from the provided
+ * <CODE>hexChars</CODE> buffer, converts it to a byte array, and
+ * then converts that to a UTF-8 string. The resulting UTF-8 string
+ * will be appended to the provided <CODE>valueString</CODE> buffer,
+ * and the <CODE>hexChars</CODE> buffer will be cleared.
+ *
+ * @param dnString The DN string that is being decoded.
+ * @param valueString The buffer containing the value to which the
+ * decoded string should be appended.
+ * @param hexChars The buffer containing the hexadecimal
+ * characters to decode to a UTF-8 string.
+ *
+ * @throws DirectoryException If any problem occurs during the
+ * decoding process.
+ */
+ private static void appendHexChars(String dnString,
+ StringBuilder valueString,
+ StringBuilder hexChars)
+ throws DirectoryException
+ {
try
{
- return filter.matchesEntry(e);
+ byte[] hexBytes = hexStringToByteArray(hexChars.toString());
+ valueString.append(new String(hexBytes, "UTF-8"));
+ hexChars.delete(0, hexChars.length());
}
- catch (DirectoryException ex)
+ catch (Exception e)
{
- return false;
+ if (debugEnabled())
+ {
+ debugCaught(DebugLogLevel.ERROR, e);
+ }
+
+ int msgID = MSGID_ATTR_SYNTAX_DN_ATTR_VALUE_DECODE_FAILURE;
+ String message = getMessage(msgID, dnString, String.valueOf(e));
+ throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
+ message, msgID);
}
}
}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/PatternRDN.java b/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/PatternRDN.java
new file mode 100644
index 0000000..d7805e1
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/PatternRDN.java
@@ -0,0 +1,330 @@
+/*
+ * 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.types.*;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.api.EqualityMatchingRule;
+import static org.opends.server.messages.AciMessages.
+ MSGID_PATTERN_DN_TYPE_CONTAINS_SUBSTRINGS;
+import static org.opends.server.messages.AciMessages.
+ MSGID_PATTERN_DN_TYPE_WILDCARD_IN_MULTIVALUED_RDN;
+import static org.opends.server.messages.MessageHandler.getMessage;
+
+import java.util.List;
+import java.util.LinkedHashSet;
+import java.util.ArrayList;
+import java.util.TreeMap;
+import java.util.Set;
+import java.util.Iterator;
+
+/**
+ * This class is used to match RDN patterns containing wildcards in either
+ * the attribute types or the attribute values.
+ * Substring matching on the attribute types is not supported.
+ */
+public class PatternRDN
+{
+ /**
+ * Indicate whether the RDN contains a wildcard in any of its attribute
+ * types.
+ */
+ private boolean hasTypeWildcard = false;
+
+
+ /**
+ * The set of attribute type patterns.
+ */
+ private String[] typePatterns;
+
+
+ /**
+ * The set of attribute value patterns.
+ * The value pattern is split into a list according to the positions of any
+ * wildcards. For example, the value "A*B*C" is represented as a
+ * list of three elements A, B and C. The value "A" is represented as
+ * a list of one element A. The value "*A*" is represented as a list
+ * of three elements "", A and "".
+ */
+ private ArrayList<ArrayList<ByteString>> valuePatterns;
+
+
+ /**
+ * The number of attribute-value pairs in this RDN pattern.
+ */
+ private int numValues;
+
+
+ /**
+ * Create a new RDN pattern composed of a single attribute-value pair.
+ * @param type The attribute type pattern.
+ * @param valuePattern The attribute value pattern.
+ * @param dnString The DN pattern containing the attribute-value pair.
+ * @throws DirectoryException If the attribute-value pair is not valid.
+ */
+ public PatternRDN(String type, ArrayList<ByteString> valuePattern,
+ String dnString)
+ throws DirectoryException
+ {
+ // Only Whole-Type wildcards permitted.
+ if (type.contains("*"))
+ {
+ if (!type.equals("*"))
+ {
+ int msgID = MSGID_PATTERN_DN_TYPE_CONTAINS_SUBSTRINGS;
+ String message = getMessage(msgID, dnString);
+ throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
+ message, msgID);
+ }
+ hasTypeWildcard = true;
+ }
+
+ numValues = 1;
+ typePatterns = new String[] { type };
+ valuePatterns = new ArrayList<ArrayList<ByteString>>(1);
+ valuePatterns.add(valuePattern);
+ }
+
+
+ /**
+ * Add another attribute-value pair to the pattern.
+ * @param type The attribute type pattern.
+ * @param valuePattern The attribute value pattern.
+ * @param dnString The DN pattern containing the attribute-value pair.
+ * @throws DirectoryException If the attribute-value pair is not valid.
+ * @return <CODE>true</CODE> if the type-value pair was added to
+ * this RDN, or <CODE>false</CODE> if it was not (e.g., it
+ * was already present).
+ */
+ public boolean addValue(String type, ArrayList<ByteString> valuePattern,
+ String dnString)
+ throws DirectoryException
+ {
+ // No type wildcards permitted in multi-valued patterns.
+ if (hasTypeWildcard || type.contains("*"))
+ {
+ int msgID = MSGID_PATTERN_DN_TYPE_WILDCARD_IN_MULTIVALUED_RDN;
+ String message = getMessage(msgID, dnString);
+ throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
+ message, msgID);
+ }
+
+ numValues++;
+
+ String[] newTypes = new String[numValues];
+ System.arraycopy(typePatterns, 0, newTypes, 0,
+ typePatterns.length);
+ newTypes[typePatterns.length] = type;
+ typePatterns = newTypes;
+
+ valuePatterns.add(valuePattern);
+
+ return true;
+ }
+
+
+ /**
+ * Retrieves the number of attribute-value pairs contained in this
+ * RDN pattern.
+ *
+ * @return The number of attribute-value pairs contained in this
+ * RDN pattern.
+ */
+ public int getNumValues()
+ {
+ return numValues;
+ }
+
+
+ /**
+ * Determine whether a given RDN matches the pattern.
+ * @param rdn The RDN to be matched.
+ * @return true if the RDN matches the pattern.
+ */
+ public boolean matchesRDN(RDN rdn)
+ {
+ if (getNumValues() == 1)
+ {
+ // Check for ",*," matching any RDN.
+ if (typePatterns[0].equals("*") && valuePatterns.get(0) == null)
+ {
+ return true;
+ }
+
+ if (rdn.getNumValues() != 1)
+ {
+ return false;
+ }
+
+ AttributeType thatType = rdn.getAttributeType(0);
+ if (!typePatterns[0].equals("*"))
+ {
+ AttributeType thisType =
+ DirectoryServer.getAttributeType(typePatterns[0].toLowerCase());
+ if (thisType == null || !thisType.equals(thatType))
+ {
+ return false;
+ }
+ }
+
+ return matchValuePattern(valuePatterns.get(0), thatType,
+ rdn.getAttributeValue(0));
+ }
+
+ if (hasTypeWildcard)
+ {
+ return false;
+ }
+
+ if (numValues != rdn.getNumValues())
+ {
+ return false;
+ }
+
+ // Sort the attribute-value pairs by attribute type.
+ TreeMap<String,ArrayList<ByteString>> patternMap =
+ new TreeMap<String, ArrayList<ByteString>>();
+ TreeMap<String,AttributeValue> rdnMap =
+ new TreeMap<String, AttributeValue>();
+
+ for (int i = 0; i < rdn.getNumValues(); i++)
+ {
+ rdnMap.put(rdn.getAttributeType(i).getNameOrOID(),
+ rdn.getAttributeValue(i));
+ }
+
+ for (int i = 0; i < numValues; i++)
+ {
+ String lowerName = typePatterns[i].toLowerCase();
+ AttributeType type = DirectoryServer.getAttributeType(lowerName);
+ if (type == null)
+ {
+ return false;
+ }
+ patternMap.put(type.getNameOrOID(), valuePatterns.get(i));
+ }
+
+ Set<String> patternKeys = patternMap.keySet();
+ Set<String> rdnKeys = rdnMap.keySet();
+ Iterator<String> patternKeyIter = patternKeys.iterator();
+ for (String rdnKey : rdnKeys)
+ {
+ if (!rdnKey.equals(patternKeyIter.next()))
+ {
+ return false;
+ }
+
+ if (!matchValuePattern(patternMap.get(rdnKey),
+ DirectoryServer.getAttributeType(rdnKey),
+ rdnMap.get(rdnKey)))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+
+ /**
+ * Determine whether a value pattern matches a given attribute-value pair.
+ * @param pattern The value pattern where each element of the list is a
+ * substring of the pattern appearing between wildcards.
+ * @param type The attribute type of the attribute-value pair.
+ * @param value The value of the attribute-value pair.
+ * @return true if the value pattern matches the attribute-value pair.
+ */
+ private boolean matchValuePattern(List<ByteString> pattern,
+ AttributeType type,
+ AttributeValue value)
+ {
+ if (pattern == null)
+ {
+ return true;
+ }
+
+ try
+ {
+ if (pattern.size() > 1)
+ {
+ // Handle this just like a substring filter.
+
+ ByteString subInitial = pattern.get(0);
+ if (subInitial.value().length == 0)
+ {
+ subInitial = null;
+ }
+
+ ByteString subFinal = pattern.get(pattern.size() - 1);
+ if (subFinal.value().length == 0)
+ {
+ subFinal = null;
+ }
+
+ List<ByteString> subAnyElements;
+ if (pattern.size() > 2)
+ {
+ subAnyElements = pattern.subList(1, pattern.size()-1);
+ }
+ else
+ {
+ subAnyElements = null;
+ }
+
+ LinkedHashSet<AttributeValue> values =
+ new LinkedHashSet<AttributeValue>(1);
+ values.add(value);
+ Attribute attr = new Attribute(type, type.getNameOrOID(), values);
+
+ switch (attr.matchesSubstring(subInitial, subAnyElements, subFinal))
+ {
+ case TRUE:
+ return true;
+
+ case FALSE:
+ case UNDEFINED:
+ default:
+ return false;
+ }
+ }
+ else
+ {
+ ByteString thisNormValue = type.normalize(pattern.get(0));
+ ByteString thatNormValue = value.getNormalizedValue();
+ EqualityMatchingRule mr = type.getEqualityMatchingRule();
+ return mr.areEqual(thisNormValue, thatNormValue);
+ }
+ }
+ catch (DirectoryException e)
+ {
+ return false;
+ }
+ }
+
+
+}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/messages/AciMessages.java b/opendj-sdk/opends/src/server/org/opends/server/messages/AciMessages.java
index d8abe6c..23c43e9 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/messages/AciMessages.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/messages/AciMessages.java
@@ -696,6 +696,34 @@
CATEGORY_MASK_ACCESS_CONTROL | 68;
/**
+ * The message ID for the message that will be used if a DN pattern failed
+ * parsing because it contained consecutive wildcards in an attribute value.
+ * This takes one argument, which is the invalid DN pattern string.
+ */
+ public static final int MSGID_PATTERN_DN_CONSECUTIVE_WILDCARDS_IN_VALUE =
+ CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 69;
+
+
+ /**
+ * The message ID for the message that will be used if a DN pattern failed
+ * parsing because it uses wildcards for substring matching on an attribute
+ * type. This takes one argument, which is the invalid DN pattern string.
+ */
+ public static final int MSGID_PATTERN_DN_TYPE_CONTAINS_SUBSTRINGS =
+ CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 70;
+
+
+ /**
+ * The message ID for the message that will be used if a DN pattern failed
+ * parsing because it contained a wildcard match on an attribute type
+ * in a multi-valued RDN. This takes one argument, which is the invalid
+ * DN pattern string.
+ */
+ public static final int MSGID_PATTERN_DN_TYPE_WILDCARD_IN_MULTIVALUED_RDN =
+ CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 71;
+
+
+ /**
* Associates a set of generic messages with the message IDs defined in
* this class.
*/
@@ -954,7 +982,7 @@
"The provided Access Control Instruction (ACI)" +
" target expression value \"%s\" is invalid. A valid target" +
" keyword expression value requires a LDAP URL in the" +
- " following format: ldap:///distinguished_name.");
+ " following format: ldap:///distinguished_name_pattern.");
registerMessage(MSGID_ACI_SYNTAX_TARGET_DN_NOT_DESCENDENTOF,
"The provided Access Control Instruction (ACI) " +
@@ -1097,5 +1125,17 @@
"An unexpected error occurred while processing the " +
" aci attributes in the configuration system.");
+ registerMessage(MSGID_PATTERN_DN_CONSECUTIVE_WILDCARDS_IN_VALUE,
+ "The pattern DN %s is not valid because it contains two " +
+ "consecutive wildcards in an attribute value.");
+
+ registerMessage(MSGID_PATTERN_DN_TYPE_CONTAINS_SUBSTRINGS,
+ "The pattern DN %s is not valid because it uses wildcards for " +
+ "substring matching on an attribute type. A single wildcard " +
+ "is allowed in place of an attribute type.");
+
+ registerMessage(MSGID_PATTERN_DN_TYPE_WILDCARD_IN_MULTIVALUED_RDN,
+ "The pattern DN %s is not valid because it contains a wildcard in " +
+ "an attribute type in a multi-valued RDN");
}
}
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
index 12d00fe..0956fed 100644
--- 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
@@ -31,7 +31,9 @@
import org.opends.server.TestCaseUtils;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.types.DN;
+import org.opends.server.types.DirectoryException;
import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import org.testng.annotations.BeforeClass;
@@ -46,6 +48,102 @@
@DataProvider
+ public Object[][] matchingPatterns()
+ {
+ return new Object[][] {
+ {
+ "uid=bj*,ou=people,dc=example,dc=com",
+ "uid=bjensen,ou=people,dc=example,dc=com"
+ },
+ {
+ "uid=*,ou=people,dc=example,dc=com",
+ "uid=bjensen,ou=people,dc=example,dc=com"
+ },
+ {
+ "uid=bjensen*,**",
+ "uid=bjensen,ou=people,dc=example,dc=com"
+ },
+ {
+ "*jensen,ou=People,dc=example,dc=com",
+ "uid=bjensen,ou=people,dc=example,dc=com"
+ },
+ {
+ "bjensen,ou=People,dc=example,dc=com",
+ "uid=bjensen,ou=people,dc=example,dc=com"
+ },
+ {
+ "**",
+ "uid=bjensen,ou=people,dc=example,dc=com"
+ },
+ {
+ "*",
+ "dc=com"
+ },
+ {
+ "uid=bj*+sn=*,ou=people,dc=example,dc=com",
+ "sn=jensen+uid=bjensen,ou=people,dc=example,dc=com"
+ },
+ {
+ "bjensen",
+ "uid=bjensen"
+ }
+ };
+ }
+
+
+ @DataProvider
+ public Object[][] nonMatchingPatterns()
+ {
+ return new Object[][] {
+ {
+ "uid=bj*,ou=people,dc=example,dc=com",
+ "uid=bjensen,ou=j,ou=people,dc=example,dc=com"
+ },
+ {
+ "uid=*,ou=people,dc=example,dc=com",
+ "cn=bjensen,ou=people,dc=example,dc=com"
+ },
+ {
+ "uid=bjensen*,**",
+ "uid=bjensen"
+ },
+ {
+ "**",
+ ""
+ },
+ {
+ "*",
+ "dc=example,dc=com"
+ },
+ {
+ "uid=bj*+cn=*,ou=people,dc=example,dc=com",
+ "sn=jensen+uid=bjensen,ou=people,dc=example,dc=com"
+ },
+ };
+ }
+
+
+ @DataProvider
+ public Object[][] invalidPatterns()
+ {
+ return new Object[][] {
+ {
+ "uid=bj**,ou=people,dc=example,dc=com"
+ },
+ {
+ "uid*=bjensen,ou=people,dc=example,dc=com",
+ },
+ {
+ "uid=bjensen*,***",
+ },
+ {
+ "uid=bjensen+*=jensen,ou=people,dc=example,dc=com"
+ },
+ };
+ }
+
+
+ @DataProvider
public Object[][] applicableTargets()
{
return new Object[][] {
@@ -67,7 +165,7 @@
},
{
"dc=example,dc=com",
- "(target=\"ldap:///uid=bjensen*\")" +
+ "(target=\"ldap:///uid=bjensen*,**\")" +
"(targetattr=\"*\")(targetScope=\"subtree\")" +
"(version 3.0; acl \"example\";" +
" allow (all) userdn=\"ldap:///self\";)",
@@ -75,7 +173,7 @@
},
{
"dc=example,dc=com",
- "(target=\"ldap:///uid=*,dc=example,dc=com\")" +
+ "(target=\"ldap:///uid=*,*,dc=example,dc=com\")" +
"(targetattr=\"*\")(targetScope=\"subtree\")" +
"(version 3.0; acl \"example\";" +
" allow (all) userdn=\"ldap:///self\";)",
@@ -105,33 +203,46 @@
" 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>
+ {
+ "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:///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:///*=*jensen,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:///*jensen,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",
+ },
{
"ou=aci branch,o=ACI Tests,dc=example,dc=com",
"(target=\"ldap:///ou=Peo*,ou=aci branch, o=ACI Tests," +
@@ -168,9 +279,26 @@
"uid=scarter,ou=People,ou=aci branch,o=ACI Tests," +
"dc=example,dc=com",
},
+ {
+ "dc=example,dc=com",
+ "(target=\"ldap:///**\")" +
+ "(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:///*\")" +
+ "(targetattr=\"*\")(targetScope=\"subtree\")" +
+ "(version 3.0; acl \"example\";" +
+ " allow (all) userdn=\"ldap:///self\";)",
+ "uid=bjensen,ou=people,dc=example,dc=com",
+ },
};
}
+
@DataProvider
public Object[][] nonApplicableTargets()
{
@@ -190,10 +318,80 @@
" 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:///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:///**\")" +
+ "(targetattr=\"*\")(targetScope=\"subtree\")" +
+ "(version 3.0; acl \"example\";" +
+ " allow (all) userdn=\"ldap:///self\";)",
+ "",
+ },
+ {
+ "dc=example,dc=com",
+ "(target=\"ldap:///*\")" +
+ "(targetattr=\"*\")(targetScope=\"subtree\")" +
+ "(version 3.0; acl \"example\";" +
+ " allow (all) userdn=\"ldap:///self\";)",
+ "",
+ },
};
}
+ @Test(dataProvider = "matchingPatterns")
+ public void matchingPatterns(String pattern, String entryDN)
+ throws Exception
+ {
+ PatternDN patternDN = PatternDN.decode(pattern);
+ boolean match = patternDN.matchesDN(DN.decode(entryDN));
+ assertTrue(match, pattern + " did not match " + entryDN);
+ }
+
+
+ @Test(dataProvider = "nonMatchingPatterns")
+ public void nonMatchingPatterns(String pattern, String entryDN)
+ throws Exception
+ {
+ PatternDN patternDN = PatternDN.decode(pattern);
+ boolean match = patternDN.matchesDN(DN.decode(entryDN));
+ assertTrue(!match, pattern + " should not have matched " + entryDN);
+ }
+
+
+ @Test(dataProvider = "invalidPatterns",
+ expectedExceptions = DirectoryException.class)
+ public void invalidPatterns(String pattern)
+ throws Exception
+ {
+ PatternDN.decode(pattern);
+ fail("Invalid DN pattern " + pattern + " did not throw an exception");
+ }
+
+
@Test(dataProvider = "applicableTargets")
public void applicableTargets(String aciDN, String aciString, String entryDN)
throws Exception
--
Gitblit v1.10.0