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.Arrays;
023import java.util.Iterator;
024import java.util.List;
025import java.util.TreeMap;
026
027import org.forgerock.i18n.LocalizableMessage;
028import org.forgerock.i18n.slf4j.LocalizedLogger;
029import org.forgerock.opendj.ldap.AVA;
030import org.forgerock.opendj.ldap.ByteString;
031import org.forgerock.opendj.ldap.DecodeException;
032import org.forgerock.opendj.ldap.RDN;
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;
040
041/**
042 * This class is used to match RDN patterns containing wildcards in either
043 * the attribute types or the attribute values.
044 * Substring matching on the attribute types is not supported.
045 */
046public class PatternRDN
047{
048
049  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
050
051  /** Indicate whether the RDN contains a wildcard in any of its attribute types. */
052  private final boolean hasTypeWildcard;
053  /** The set of attribute type patterns. */
054  private String[] typePatterns;
055  /**
056   * The set of attribute value patterns.
057   * The value pattern is split into a list according to the positions of any
058   * wildcards.  For example, the value "A*B*C" is represented as a
059   * list of three elements A, B and C.  The value "A" is represented as
060   * a list of one element A.  The value "*A*" is represented as a list
061   * of three elements "", A and "".
062   */
063  private final List<List<ByteString>> valuePatterns;
064
065  /**
066   * Create a new RDN pattern composed of a single attribute-value pair.
067   * @param type The attribute type pattern.
068   * @param valuePattern The attribute value pattern.
069   * @param dnString The DN pattern containing the attribute-value pair.
070   * @throws DirectoryException If the attribute-value pair is not valid.
071   */
072  public PatternRDN(String type, List<ByteString> valuePattern, String dnString)
073       throws DirectoryException
074  {
075    // Only Whole-Type wildcards permitted.
076    if (type.contains("*"))
077    {
078      if (!type.equals("*"))
079      {
080        LocalizableMessage message =
081            WARN_PATTERN_DN_TYPE_CONTAINS_SUBSTRINGS.get(dnString);
082        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
083                                     message);
084      }
085      hasTypeWildcard = true;
086    }
087    else
088    {
089      hasTypeWildcard = false;
090    }
091
092    typePatterns = new String[] { type };
093    valuePatterns = newArrayList(valuePattern);
094  }
095
096
097  /**
098   * Add another attribute-value pair to the pattern.
099   * @param type The attribute type pattern.
100   * @param valuePattern The attribute value pattern.
101   * @param dnString The DN pattern containing the attribute-value pair.
102   * @throws DirectoryException If the attribute-value pair is not valid.
103   * @return  <CODE>true</CODE> if the type-value pair was added to
104   *          this RDN, or <CODE>false</CODE> if it was not (e.g., it
105   *          was already present).
106   */
107  public boolean addValue(String type, List<ByteString> valuePattern, String dnString) throws DirectoryException
108  {
109    // No type wildcards permitted in multi-valued patterns.
110    if (hasTypeWildcard || type.contains("*"))
111    {
112      LocalizableMessage message =
113          WARN_PATTERN_DN_TYPE_WILDCARD_IN_MULTIVALUED_RDN.get(dnString);
114      throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message);
115    }
116
117    int oldLength = typePatterns.length;
118    typePatterns = Arrays.copyOf(typePatterns, oldLength + 1);
119    typePatterns[oldLength] = type;
120
121    valuePatterns.add(valuePattern);
122
123    return true;
124  }
125
126
127  /**
128   * Retrieves the number of attribute-value pairs contained in this
129   * RDN pattern.
130   *
131   * @return  The number of attribute-value pairs contained in this
132   *          RDN pattern.
133   */
134  public int getNumValues()
135  {
136    return typePatterns.length;
137  }
138
139
140  /**
141   * Determine whether a given RDN matches the pattern.
142   * @param rdn The RDN to be matched.
143   * @return true if the RDN matches the pattern.
144   */
145  public boolean matchesRDN(RDN rdn)
146  {
147    if (getNumValues() == 1)
148    {
149      // Check for ",*," matching any RDN.
150      if (typePatterns[0].equals("*") && valuePatterns.get(0) == null)
151      {
152        return true;
153      }
154
155      if (rdn.size() != 1)
156      {
157        return false;
158      }
159
160      AVA ava = rdn.getFirstAVA();
161      if (!typePatterns[0].equals("*"))
162      {
163        AttributeType thisType = DirectoryServer.getSchema().getAttributeType(typePatterns[0]);
164        if (thisType.isPlaceHolder() || !thisType.equals(ava.getAttributeType()))
165        {
166          return false;
167        }
168      }
169
170      return matchValuePattern(valuePatterns.get(0), ava);
171    }
172
173    if (hasTypeWildcard || typePatterns.length != rdn.size())
174    {
175      return false;
176    }
177
178    // Sort the attribute-value pairs by attribute type.
179    TreeMap<String, List<ByteString>> patternMap = new TreeMap<>();
180    for (int i = 0; i < typePatterns.length; i++)
181    {
182      AttributeType type = DirectoryServer.getSchema().getAttributeType(typePatterns[i]);
183      if (type.isPlaceHolder())
184      {
185        return false;
186      }
187      patternMap.put(type.getNameOrOID(), valuePatterns.get(i));
188    }
189
190    Iterator<String> patternKeyIter = patternMap.keySet().iterator();
191    for (AVA ava : rdn)
192    {
193      String rdnKey = ava.getAttributeType().getNameOrOID();
194      if (!rdnKey.equals(patternKeyIter.next())
195          || !matchValuePattern(patternMap.get(rdnKey), ava))
196      {
197        return false;
198      }
199    }
200
201    return true;
202  }
203
204  /**
205   * Determine whether a value pattern matches a given attribute-value pair.
206   * @param pattern The value pattern where each element of the list is a
207   *                substring of the pattern appearing between wildcards.
208   * @param type The attribute type of the attribute-value pair.
209   * @param value The value of the attribute-value pair.
210   * @return true if the value pattern matches the attribute-value pair.
211   */
212  private boolean matchValuePattern(List<ByteString> pattern, AVA ava)
213  {
214    if (pattern == null)
215    {
216      return true;
217    }
218
219    final AttributeType type = ava.getAttributeType();
220    ByteString value = ava.getAttributeValue();
221    try
222    {
223      if (pattern.size() == 1)
224      {
225        // Handle this just like an equality filter.
226        MatchingRule rule = type.getEqualityMatchingRule();
227        ByteString thatNormValue = rule.normalizeAttributeValue(value);
228        return rule.getAssertion(pattern.get(0)).matches(thatNormValue).toBoolean();
229      }
230
231      // Handle this just like a substring filter.
232      ByteString subInitial = pattern.get(0);
233      if (subInitial.length() == 0)
234      {
235        subInitial = null;
236      }
237
238      ByteString subFinal = pattern.get(pattern.size() - 1);
239      if (subFinal.length() == 0)
240      {
241        subFinal = null;
242      }
243
244      List<ByteString> subAnyElements;
245      if (pattern.size() > 2)
246      {
247        subAnyElements = pattern.subList(1, pattern.size()-1);
248      }
249      else
250      {
251        subAnyElements = null;
252      }
253
254      Attribute attr = Attributes.create(type, value);
255      return attr.matchesSubstring(subInitial, subAnyElements, subFinal).toBoolean();
256    }
257    catch (DecodeException e)
258    {
259      logger.traceException(e);
260      return false;
261    }
262  }
263
264  @Override
265  public String toString()
266  {
267    StringBuilder sb = new StringBuilder(getClass().getSimpleName()).append("(");
268    for (int i = 0; i < typePatterns.length; i++)
269    {
270      sb.append(typePatterns[i]).append("=");
271      List<ByteString> patterns = valuePatterns.get(i);
272      if (patterns.size() == 1)
273      {
274        sb.append(patterns.get(0));
275      }
276      else
277      {
278        sb.append(patterns);
279      }
280    }
281    sb.append(")");
282    return sb.toString();
283  }
284}