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 }