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