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 static org.opends.messages.AccessControlMessages.*;
032    import static org.opends.server.authorization.dseecompat.Aci.*;
033    import org.opends.server.types.*;
034    import java.util.regex.Pattern;
035    import java.util.regex.Matcher;
036    import java.util.*;
037    
038    /**
039     * The TargAttrFilters class represents a targattrfilters rule of an ACI.
040     */
041    public class TargAttrFilters {
042    
043        /*
044         * A valid targattrfilters rule may have two TargFilterlist parts -- the
045         * first one is required.
046         */
047        TargAttrFilterList firstFilterList=null;
048        TargAttrFilterList secondFilterList=null;
049    
050        /*
051         * Regular expression group position for the first operation value.
052         */
053        private static final int firstOpPos = 1;
054    
055        /*
056         * Regular expression group position for the rest of an partially parsed
057         * rule.
058         */
059        private static final int restOfExpressionPos=2;
060    
061        /*
062         * Regular expression used to match the operation group (either add or del).
063         */
064        private static final String ADD_OR_DEL_KEYWORD_GROUP = "(add|del)";
065    
066        /*
067         * Regular expression used to check for valid expression separator.
068         */
069    
070        private static final
071        String secondOpSeparator="\\)" +  ZERO_OR_MORE_WHITESPACE + ",";
072    
073        /**
074         * Regular expression used to match the second operation of the filter list.
075         * If the first was "add" this must be "del", if the first was "del" this
076         * must be "add".
077         */
078        public static final String secondOp =
079                "[,]{1}" + ZERO_OR_MORE_WHITESPACE + "del|add" +
080                ZERO_OR_MORE_WHITESPACE + EQUAL_SIGN + ZERO_OR_MORE_WHITESPACE;
081    
082        /*
083         * Regular expression used to match the first targFilterList, it must exist
084         * or an exception is thrown.
085         */
086        private static final String firstOp = "^" + ADD_OR_DEL_KEYWORD_GROUP +
087                ZERO_OR_MORE_WHITESPACE + EQUAL_SIGN + ZERO_OR_MORE_WHITESPACE;
088    
089        /*
090         * Regular expression used to group the remainder of a partially parsed
091         * rule.  Any character one or more times.
092         */
093        private static String restOfExpression = "(.+)";
094    
095        /*
096         * Regular expression used to match the first operation keyword and the
097         * rest of the expression.
098         */
099        private static String keywordFullPattern = firstOp + restOfExpression;
100    
101        /*
102         * The enumeration representing the operation.
103         */
104        EnumTargetOperator op;
105    
106        /*
107         * A mask used to denote if the rule has add, del or both operations in the
108         * composite TargFilterList parts.
109         */
110        private int operationMask;
111    
112        /**
113         * Represents an targatterfilters keyword rule.
114         * @param op The enumeration representing the operation type.
115         *
116         * @param firstFilterList  The first filter list class parsed from the rule.
117         * This one is required.
118         *
119         * @param secondFilterList The second filter list class parsed from the
120         * rule. This one is optional.
121         */
122        public TargAttrFilters(EnumTargetOperator op,
123                               TargAttrFilterList firstFilterList,
124                               TargAttrFilterList secondFilterList ) {
125            this.op=op;
126            this.firstFilterList=firstFilterList;
127            operationMask=firstFilterList.getMask();
128            if(secondFilterList != null) {
129                //Add the second filter list mask to the mask.
130                operationMask |= secondFilterList.getMask();
131                this.secondFilterList=secondFilterList;
132            }
133        }
134    
135        /**
136         * Decode an targattrfilter rule.
137         * @param type The enumeration representing the type of this rule. Defaults
138         * to equality for this target.
139         *
140         * @param expression The string expression to be decoded.
141         * @return  A TargAttrFilters class representing the decode expression.
142         * @throws AciException If the expression string contains errors and
143         * cannot be decoded.
144         */
145        public static TargAttrFilters decode(EnumTargetOperator type,
146                                            String expression) throws AciException {
147            Pattern fullPattern=Pattern.compile(keywordFullPattern);
148            Matcher matcher = fullPattern.matcher(expression);
149            //First match for overall correctness and to get the first operation.
150            if(!matcher.find()) {
151                Message message =
152                  WARN_ACI_SYNTAX_INVALID_TARGATTRFILTERS_EXPRESSION.
153                      get(expression);
154                throw new AciException(message);
155            }
156            String firstOp=matcher.group(firstOpPos);
157            String subExpression=matcher.group(restOfExpressionPos);
158            //This pattern is built dynamically and is used to see if the operations
159            //in the two filter list parts (if the second exists) are equal. See
160            //comment below.
161            String opPattern=
162                    "[,]{1}" + ZERO_OR_MORE_WHITESPACE  +
163                    firstOp + ZERO_OR_MORE_WHITESPACE + EQUAL_SIGN +
164                    ZERO_OR_MORE_WHITESPACE;
165            String[] temp=subExpression.split(opPattern);
166            /**
167             * Check that the initial list operation is not equal to the second.
168             * For example:  Matcher find
169             *
170             *  "add:cn:(cn=foo), add:cn:(cn=bar)"
171             *
172             * This is invalid.
173             */
174            if(temp.length > 1) {
175                Message message = WARN_ACI_SYNTAX_INVALID_TARGATTRFILTERS_OPS_MATCH.
176                    get(expression);
177                throw new AciException(message);
178            }
179            /**
180             * Check that there are not too many filter lists. There can only
181             * be either one or two.
182             */
183            String[] filterLists=
184                    subExpression.split(secondOp, -1);
185            if(filterLists.length > 2) {
186              Message message =
187                  WARN_ACI_SYNTAX_INVALID_TARGATTRFILTERS_MAX_FILTER_LISTS.
188                    get(expression);
189              throw new AciException(message);
190            } else if (filterLists.length == 1) {
191              //Check if the there is something like ") , deel=". A bad token
192              //that the regular expression didn't pick up.
193              String [] filterList2=subExpression.split(secondOpSeparator);
194              if(filterList2.length == 2) {
195                  Message message =
196                      WARN_ACI_SYNTAX_INVALID_TARGATTRFILTERS_EXPRESSION.
197                        get(expression);
198                  throw new AciException(message);
199              }
200              String sOp="del";
201              if(getMask(firstOp) == TARGATTRFILTERS_DELETE)
202                sOp="add";
203              String rg= sOp + "=";
204              //This check catches the case where there might not be a
205              //',' character between the first filter list and the second.
206              if(subExpression.indexOf(rg) != -1) {
207                Message message =
208                    WARN_ACI_SYNTAX_INVALID_TARGATTRFILTERS_EXPRESSION.
209                      get(expression);
210                throw new AciException(message);
211              }
212            }
213            filterLists[0]=filterLists[0].trim();
214            //First filter list must end in an ')' character.
215            if(!filterLists[0].endsWith(")")) {
216                Message message =
217                    WARN_ACI_SYNTAX_INVALID_TARGATTRFILTERS_EXPRESSION.
218                      get(expression);
219                throw new AciException(message);
220            }
221            TargAttrFilterList firstFilterList =
222                    TargAttrFilterList.decode(getMask(firstOp), filterLists[0]);
223            TargAttrFilterList secondFilterList=null;
224            //Handle the second filter list if there is one.
225              if(filterLists.length == 2) {
226                String filterList=filterLists[1].trim();
227                //Second filter list must start with a '='.
228                if(!filterList.startsWith("=")) {
229                  Message message =
230                      WARN_ACI_SYNTAX_INVALID_TARGATTRFILTERS_EXPRESSION.
231                        get(expression);
232                  throw new AciException(message);
233                }
234                String temp2= filterList.substring(1,filterList.length());
235                //Assume the first op is an "add" so this has to be a "del".
236                String secondOp="del";
237                //If the first op is a "del", the second has to be an "add".
238                if(getMask(firstOp) == TARGATTRFILTERS_DELETE)
239                    secondOp="add";
240                secondFilterList =
241                        TargAttrFilterList.decode(getMask(secondOp), temp2);
242            }
243            return new TargAttrFilters(type, firstFilterList, secondFilterList);
244        }
245    
246        /**
247         * Return the mask corrsponding to the specified string.
248         * @param op The op string.
249         * @return   The mask corresponding to the operation string.
250         */
251        private static  int getMask(String op) {
252            if(op.equals("add"))
253                return TARGATTRFILTERS_ADD;
254            else
255                return TARGATTRFILTERS_DELETE;
256        }
257    
258        /**
259         * Gets the TargFilterList  corresponding to the mask value.
260         * @param matchCtx The target match context containing the rights to
261         * match against.
262         * @return  A TargAttrFilterList matching both the rights of the target
263         * match context and the mask of the TargFilterAttrList. May return null.
264         */
265        public TargAttrFilterList
266        getTargAttrFilterList(AciTargetMatchContext matchCtx) {
267            TargAttrFilterList filterList=null;
268            int mask=ACI_NULL;
269            //Set up the wanted mask by evaluating both the target match
270            //context's rights and the mask.
271            if((matchCtx.hasRights(ACI_WRITE_ADD) || matchCtx.hasRights(ACI_ADD)) &&
272                    hasMask(TARGATTRFILTERS_ADD))
273                mask=TARGATTRFILTERS_ADD;
274            else if((matchCtx.hasRights(ACI_WRITE_DELETE) ||
275                     matchCtx.hasRights(ACI_DELETE)) &&
276                    hasMask(TARGATTRFILTERS_DELETE))
277                mask=TARGATTRFILTERS_DELETE;
278            //Check the first list first, it always has to be there. If it doesn't
279            //match then check the second if it exists.
280            if(firstFilterList.hasMask(mask))
281                filterList=firstFilterList;
282            else if((secondFilterList != null) &&
283                    secondFilterList.hasMask(mask))
284                filterList=secondFilterList;
285            return filterList;
286        }
287    
288        /**
289         * Check if this TargAttrFilters object is applicable to the target
290         * specified match context. This check is only used for the LDAP modify
291         * operation.
292         * @param matchCtx The target match context containing the information
293         * needed to match.
294         * @param aci  The ACI currently being evaluted for a target match.
295         * @return True if this TargAttrFitlers object is applicable to this
296         * target match context.
297         */
298        public boolean isApplicableMod(AciTargetMatchContext matchCtx,
299                                       Aci aci) {
300            //Get the targFitlerList corresponding to this context's rights.
301            TargAttrFilterList attrFilterList=getTargAttrFilterList(matchCtx);
302            //If the list is empty return true and go on to the targattr check
303            //in AciTargets.isApplicable().
304            if(attrFilterList == null)
305                return true;
306            LinkedHashMap<AttributeType, SearchFilter> filterList  =
307                    attrFilterList.getAttributeTypeFilterList();
308            boolean attrMatched=true;
309            AttributeType attrType=matchCtx.getCurrentAttributeType();
310            //If the filter list contains the current attribute type; check
311            //the attribute types value(s) against the corresponding filter.
312            // If the filter list does not contain the attribute type skip the
313            // attribute type.
314            if((attrType != null) && (filterList.containsKey(attrType))) {
315                AttributeValue value=matchCtx.getCurrentAttributeValue();
316                SearchFilter filter = filterList.get(attrType);
317                attrMatched=matchFilterAttributeValue(attrType, value, filter);
318                //This flag causes any targattr checks to be bypassed in AciTargets.
319                matchCtx.setTargAttrFiltersMatch(true);
320                //Doing a geteffectiverights eval, save the ACI and the name
321                //in the context.
322                if(matchCtx.isGetEffectiveRightsEval()) {
323                  matchCtx.setTargAttrFiltersAciName(aci.getName());
324                  matchCtx.addTargAttrFiltersMatchAci(aci);
325                }
326                if(op.equals(EnumTargetOperator.NOT_EQUALITY))
327                    attrMatched = !attrMatched;
328            }
329            return attrMatched;
330        }
331    
332        /**
333         * Check if this TargAttrFilters object is applicable to the specified
334         * target match context. This check is only used for either LDAP add or
335         * delete operations.
336         * @param matchCtx The target match context containing the information
337         * needed to match.
338         * @return True if this TargAttrFilters object is applicable to this
339         * target match context.
340         */
341        public boolean isApplicableAddDel(AciTargetMatchContext matchCtx) {
342            TargAttrFilterList attrFilterList=getTargAttrFilterList(matchCtx);
343            //List didn't match current operation return true.
344            if(attrFilterList == null)
345                return true;
346            LinkedHashMap<AttributeType, SearchFilter> filterList  =
347                    attrFilterList.getAttributeTypeFilterList();
348            boolean attrMatched=true;
349            //Get the resource entry.
350            Entry resEntry=matchCtx.getResourceEntry();
351            //Iterate through each attribute type in the filter list checking
352            //the resource entry to see if it has that attribute type. If not
353            //go to the next attribute type. If it is found, then check the entries
354            //attribute type values against the filter.
355          for(Map.Entry<AttributeType, SearchFilter> e : filterList.entrySet()) {
356                AttributeType attrType=e.getKey();
357                SearchFilter f=e.getValue();
358                //Found a match in the entry, iterate over each attribute
359                //type in the entry and check its values agaist the filter.
360                if(resEntry.hasAttribute(attrType)) {
361                    ListIterator<Attribute> attrIterator=
362                            resEntry.getAttribute(attrType).listIterator();
363                    for(;attrIterator.hasNext() && attrMatched;) {
364                        Attribute a=attrIterator.next();
365                        attrMatched=matchFilterAttributeValues(a, attrType, f);
366                    }
367                }
368                if(!attrMatched)
369                  break;
370            }
371            if(op.equals(EnumTargetOperator.NOT_EQUALITY))
372                attrMatched = !attrMatched;
373            return attrMatched;
374        }
375    
376        /**
377         * Iterate over each attribute type attribute and compare the values
378         * against the provided filter.
379         * @param a The attribute from the resource entry.
380         * @param attrType The attribute type currently working on.
381         * @param filter  The filter to evaluate the values against.
382         * @return  True if all of the values matched the filter.
383         */
384        private boolean matchFilterAttributeValues(Attribute a,
385                                                   AttributeType attrType,
386                                                   SearchFilter filter) {
387            boolean filterMatch=true;
388            Iterator<AttributeValue> valIterator = a.getValues().iterator();
389            //Iterate through each value and apply the filter against it.
390            for(; valIterator.hasNext() && filterMatch;) {
391                AttributeValue value=valIterator.next();
392                filterMatch=matchFilterAttributeValue(attrType, value, filter);
393            }
394            return filterMatch;
395        }
396    
397        /**
398         * Matches an specified attribute value against a specified filter. A dummy
399         * entry is created with only a single attribute containing the value  The
400         * filter is applied against that entry.
401         *
402         * @param attrType The attribute type currently being evaluated.
403         * @param value  The value to match the filter against.
404         * @param filter  The filter to match.
405         * @return  True if the value matches the filter.
406         */
407        private boolean matchFilterAttributeValue(AttributeType attrType,
408                                                  AttributeValue value,
409                                                  SearchFilter filter) {
410            boolean filterMatch;
411            LinkedHashSet<AttributeValue> values =
412                    new LinkedHashSet<AttributeValue>();
413            values.add(new AttributeValue(attrType, value.getValue()));
414            Attribute attr =
415                    new Attribute(attrType, attrType.toString(), values);
416            Entry e = new Entry(DN.nullDN(), null, null, null);
417            e.addAttribute(attr, new ArrayList<AttributeValue>());
418            try {
419                filterMatch=filter.matchesEntry(e);
420            } catch(DirectoryException ex) {
421                filterMatch=false;
422            }
423            return filterMatch;
424        }
425    
426        /**
427         * Return true if the TargAttrFilters mask contains the specified mask.
428         * @param mask  The mask to check for.
429         * @return  True if the mask matches.
430         */
431        public boolean hasMask(int mask) {
432            return (this.operationMask & mask) != 0;
433        }
434    
435    }