/*
* 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 legal-notices/CDDLv1_0.txt
* or http://forgerock.org/license/CDDLv1.0.html.
* 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 legal-notices/CDDLv1_0.txt.
* 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
*
*
* Copyright 2008 Sun Microsystems, Inc.
* Portions Copyright 2014-2015 ForgeRock AS
*/
package org.opends.server.authorization.dseecompat;
import static org.opends.messages.AccessControlMessages.*;
import static org.opends.server.util.CollectionUtils.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeMap;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.DecodeException;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.schema.MatchingRule;
import org.opends.server.core.DirectoryServer;
import org.opends.server.types.*;
/**
* 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
{
private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
/** Indicate whether the RDN contains a wildcard in any of its attribute types. */
private boolean hasTypeWildcard;
/** 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("*"))
{
LocalizableMessage message =
WARN_PATTERN_DN_TYPE_CONTAINS_SUBSTRINGS.get(dnString);
throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
message);
}
hasTypeWildcard = true;
}
numValues = 1;
typePatterns = new String[] { type };
valuePatterns = newArrayList(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("*"))
{
LocalizableMessage message =
WARN_PATTERN_DN_TYPE_WILDCARD_IN_MULTIVALUED_RDN.get(dnString);
throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message);
}
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.getAttributeTypeOrNull(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<>();
TreeMap<String, ByteString> rdnMap = new TreeMap<>();
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.getAttributeTypeOrNull(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;
}
AttributeType rdnAttrType = DirectoryServer.getAttributeTypeOrNull(rdnKey);
if (!matchValuePattern(patternMap.get(rdnKey), rdnAttrType, 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,
ByteString value)
{
if (pattern == null)
{
return true;
}
try
{
if (pattern.size() == 1)
{
// Handle this just like an equality filter.
MatchingRule rule = type.getEqualityMatchingRule();
ByteString thatNormValue = rule.normalizeAttributeValue(value);
return rule.getAssertion(pattern.get(0)).matches(thatNormValue).toBoolean();
}
// Handle this just like a substring filter.
ByteString subInitial = pattern.get(0);
if (subInitial.length() == 0)
{
subInitial = null;
}
ByteString subFinal = pattern.get(pattern.size() - 1);
if (subFinal.length() == 0)
{
subFinal = null;
}
List<ByteString> subAnyElements;
if (pattern.size() > 2)
{
subAnyElements = pattern.subList(1, pattern.size()-1);
}
else
{
subAnyElements = null;
}
Attribute attr = Attributes.create(type, value);
return attr.matchesSubstring(subInitial, subAnyElements, subFinal).toBoolean();
}
catch (DecodeException e)
{
logger.traceException(e);
return false;
}
}
}