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