001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2008 Sun Microsystems, Inc.
015 * Portions Copyright 2014-2016 ForgeRock AS.
016 */
017package org.opends.server.authorization.dseecompat;
018
019import static org.opends.messages.AccessControlMessages.*;
020import static org.opends.server.util.CollectionUtils.*;
021
022import java.util.ArrayList;
023import java.util.Iterator;
024import java.util.List;
025import java.util.Set;
026import java.util.TreeMap;
027
028import org.forgerock.i18n.LocalizableMessage;
029import org.forgerock.i18n.slf4j.LocalizedLogger;
030import org.forgerock.opendj.ldap.AVA;
031import org.forgerock.opendj.ldap.ByteString;
032import org.forgerock.opendj.ldap.DecodeException;
033import org.forgerock.opendj.ldap.ResultCode;
034import org.forgerock.opendj.ldap.schema.AttributeType;
035import org.forgerock.opendj.ldap.schema.MatchingRule;
036import org.opends.server.core.DirectoryServer;
037import org.opends.server.types.Attribute;
038import org.opends.server.types.Attributes;
039import org.opends.server.types.DirectoryException;
040import org.forgerock.opendj.ldap.RDN;
041
042/**
043 * This class is used to match RDN patterns containing wildcards in either
044 * the attribute types or the attribute values.
045 * Substring matching on the attribute types is not supported.
046 */
047public class PatternRDN
048{
049
050  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
051
052  /** Indicate whether the RDN contains a wildcard in any of its attribute types. */
053  private boolean hasTypeWildcard;
054  /** The set of attribute type patterns. */
055  private String[] typePatterns;
056  /**
057   * The set of attribute value patterns.
058   * The value pattern is split into a list according to the positions of any
059   * wildcards.  For example, the value "A*B*C" is represented as a
060   * list of three elements A, B and C.  The value "A" is represented as
061   * a list of one element A.  The value "*A*" is represented as a list
062   * of three elements "", A and "".
063   */
064  private ArrayList<ArrayList<ByteString>> valuePatterns;
065  /** The number of attribute-value pairs in this RDN pattern. */
066  private int numValues;
067
068
069  /**
070   * Create a new RDN pattern composed of a single attribute-value pair.
071   * @param type The attribute type pattern.
072   * @param valuePattern The attribute value pattern.
073   * @param dnString The DN pattern containing the attribute-value pair.
074   * @throws DirectoryException If the attribute-value pair is not valid.
075   */
076  public PatternRDN(String type, ArrayList<ByteString> valuePattern, String dnString)
077       throws DirectoryException
078  {
079    // Only Whole-Type wildcards permitted.
080    if (type.contains("*"))
081    {
082      if (!type.equals("*"))
083      {
084        LocalizableMessage message =
085            WARN_PATTERN_DN_TYPE_CONTAINS_SUBSTRINGS.get(dnString);
086        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
087                                     message);
088      }
089      hasTypeWildcard = true;
090    }
091
092    numValues = 1;
093    typePatterns = new String[] { type };
094    valuePatterns = newArrayList(valuePattern);
095  }
096
097
098  /**
099   * Add another attribute-value pair to the pattern.
100   * @param type The attribute type pattern.
101   * @param valuePattern The attribute value pattern.
102   * @param dnString The DN pattern containing the attribute-value pair.
103   * @throws DirectoryException If the attribute-value pair is not valid.
104   * @return  <CODE>true</CODE> if the type-value pair was added to
105   *          this RDN, or <CODE>false</CODE> if it was not (e.g., it
106   *          was already present).
107   */
108  public boolean addValue(String type, ArrayList<ByteString> valuePattern,
109                          String dnString)
110       throws DirectoryException
111  {
112    // No type wildcards permitted in multi-valued patterns.
113    if (hasTypeWildcard || type.contains("*"))
114    {
115      LocalizableMessage message =
116          WARN_PATTERN_DN_TYPE_WILDCARD_IN_MULTIVALUED_RDN.get(dnString);
117      throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message);
118    }
119
120    numValues++;
121
122    String[] newTypes = new String[numValues];
123    System.arraycopy(typePatterns, 0, newTypes, 0,
124                     typePatterns.length);
125    newTypes[typePatterns.length] = type;
126    typePatterns = newTypes;
127
128    valuePatterns.add(valuePattern);
129
130    return true;
131  }
132
133
134  /**
135   * Retrieves the number of attribute-value pairs contained in this
136   * RDN pattern.
137   *
138   * @return  The number of attribute-value pairs contained in this
139   *          RDN pattern.
140   */
141  public int getNumValues()
142  {
143    return numValues;
144  }
145
146
147  /**
148   * Determine whether a given RDN matches the pattern.
149   * @param rdn The RDN to be matched.
150   * @return true if the RDN matches the pattern.
151   */
152  public boolean matchesRDN(RDN rdn)
153  {
154    if (getNumValues() == 1)
155    {
156      // Check for ",*," matching any RDN.
157      if (typePatterns[0].equals("*") && valuePatterns.get(0) == null)
158      {
159        return true;
160      }
161
162      if (rdn.size() != 1)
163      {
164        return false;
165      }
166
167      AVA firstAVA = rdn.getFirstAVA();
168      AttributeType thatType = firstAVA.getAttributeType();
169      if (!typePatterns[0].equals("*"))
170      {
171        AttributeType thisType = DirectoryServer.getAttributeType(typePatterns[0]);
172        if (thisType.isPlaceHolder() || !thisType.equals(thatType))
173        {
174          return false;
175        }
176      }
177
178      return matchValuePattern(valuePatterns.get(0), thatType, firstAVA.getAttributeValue());
179    }
180
181    if (hasTypeWildcard)
182    {
183      return false;
184    }
185
186    if (numValues != rdn.size())
187    {
188      return false;
189    }
190
191    // Sort the attribute-value pairs by attribute type.
192    TreeMap<String,ArrayList<ByteString>> patternMap = new TreeMap<>();
193    TreeMap<String, ByteString> rdnMap = new TreeMap<>();
194
195    for (AVA ava : rdn)
196    {
197      rdnMap.put(ava.getAttributeType().getNameOrOID(), ava.getAttributeValue());
198    }
199
200    for (int i = 0; i < numValues; i++)
201    {
202      AttributeType type = DirectoryServer.getAttributeType(typePatterns[i]);
203      if (type.isPlaceHolder())
204      {
205        return false;
206      }
207      patternMap.put(type.getNameOrOID(), valuePatterns.get(i));
208    }
209
210    Set<String> patternKeys = patternMap.keySet();
211    Set<String> rdnKeys = rdnMap.keySet();
212    Iterator<String> patternKeyIter = patternKeys.iterator();
213    for (String rdnKey : rdnKeys)
214    {
215      if (!rdnKey.equals(patternKeyIter.next()))
216      {
217        return false;
218      }
219
220      AttributeType rdnAttrType = DirectoryServer.getAttributeType(rdnKey);
221      if (!matchValuePattern(patternMap.get(rdnKey), rdnAttrType, rdnMap.get(rdnKey)))
222      {
223        return false;
224      }
225    }
226
227    return true;
228  }
229
230
231  /**
232   * Determine whether a value pattern matches a given attribute-value pair.
233   * @param pattern The value pattern where each element of the list is a
234   *                substring of the pattern appearing between wildcards.
235   * @param type The attribute type of the attribute-value pair.
236   * @param value The value of the attribute-value pair.
237   * @return true if the value pattern matches the attribute-value pair.
238   */
239  private boolean matchValuePattern(List<ByteString> pattern,
240                                    AttributeType type,
241                                    ByteString value)
242  {
243    if (pattern == null)
244    {
245      return true;
246    }
247
248    try
249    {
250      if (pattern.size() == 1)
251      {
252        // Handle this just like an equality filter.
253        MatchingRule rule = type.getEqualityMatchingRule();
254        ByteString thatNormValue = rule.normalizeAttributeValue(value);
255        return rule.getAssertion(pattern.get(0)).matches(thatNormValue).toBoolean();
256      }
257
258      // Handle this just like a substring filter.
259      ByteString subInitial = pattern.get(0);
260      if (subInitial.length() == 0)
261      {
262        subInitial = null;
263      }
264
265      ByteString subFinal = pattern.get(pattern.size() - 1);
266      if (subFinal.length() == 0)
267      {
268        subFinal = null;
269      }
270
271      List<ByteString> subAnyElements;
272      if (pattern.size() > 2)
273      {
274        subAnyElements = pattern.subList(1, pattern.size()-1);
275      }
276      else
277      {
278        subAnyElements = null;
279      }
280
281      Attribute attr = Attributes.create(type, value);
282      return attr.matchesSubstring(subInitial, subAnyElements, subFinal).toBoolean();
283    }
284    catch (DecodeException e)
285    {
286      logger.traceException(e);
287      return false;
288    }
289  }
290
291}