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 }