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