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