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