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-2010 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.*; 021import static org.opends.server.authorization.dseecompat.EnumTargetOperator.*; 022 023import java.util.regex.Matcher; 024import java.util.regex.Pattern; 025 026import org.forgerock.i18n.LocalizableMessage; 027import org.forgerock.opendj.ldap.DN; 028import org.forgerock.opendj.ldap.SearchScope; 029import org.forgerock.opendj.ldap.schema.AttributeType; 030 031/** 032 * This class represents target part of an ACI's syntax. This is the part 033 * of an ACI before the ACI body and specifies the entry, attributes, or set 034 * of entries and attributes which the ACI controls access. 035 * 036 * The supported ACI target keywords are: target, targetattr, 037 * targetscope, targetfilter, targattrfilters, targetcontrol and extop. 038 */ 039public class AciTargets { 040 /** ACI syntax has a target keyword. */ 041 private final Target target; 042 /** ACI syntax has a targetscope keyword. */ 043 private final SearchScope targetScope; 044 /** ACI syntax has a targetattr keyword. */ 045 private final TargetAttr targetAttr; 046 /** ACI syntax has a targetfilter keyword. */ 047 private final TargetFilter targetFilter; 048 /** ACI syntax has a targattrtfilters keyword. */ 049 private final TargAttrFilters targAttrFilters; 050 /** The ACI syntax has a targetcontrol keyword. */ 051 private final TargetControl targetControl; 052 /** The ACI syntax has a extop keyword. */ 053 private final ExtOp extOp; 054 055 /** The number of regular expression group positions in a valid ACI target expression. */ 056 private static final int targetElementCount = 3; 057 /** Regular expression group position of a target keyword. */ 058 private static final int targetKeywordPos = 1; 059 /** Regular expression group position of a target operator enumeration. */ 060 private static final int targetOperatorPos = 2; 061 /** Regular expression group position of a target expression statement. */ 062 private static final int targetExpressionPos = 3; 063 064 /** Regular expression used to match a single target rule. */ 065 private static final String targetRegex = 066 OPEN_PAREN + ZERO_OR_MORE_WHITESPACE + WORD_GROUP + 067 ZERO_OR_MORE_WHITESPACE + "(!?=)" + ZERO_OR_MORE_WHITESPACE + 068 "\"([^\"]+)\"" + ZERO_OR_MORE_WHITESPACE + CLOSED_PAREN + 069 ZERO_OR_MORE_WHITESPACE; 070 071 /** 072 * Regular expression used to match one or more target rules. The pattern is 073 * part of a general ACI verification. 074 */ 075 static final String targetsRegex = "(" + targetRegex + ")*"; 076 077 /** 078 * Rights that are skipped for certain target evaluations. 079 * The test is use the skipRights array is: 080 * 081 * Either the ACI has a targetattr's rule and the current 082 * attribute type is null or the current attribute type has 083 * a type specified and the targetattr's rule is null. 084 * 085 * The actual check against the skipRights array is: 086 * 087 * 1. Is the ACI's rights in this array? For example, 088 * allow(all) or deny(add) 089 * 090 * AND 091 * 092 * 2. Is the rights from the LDAP operation in this array? For 093 * example, an LDAP add would have rights of add and all. 094 * 095 * If both are true, than the target match test returns true 096 * for this ACI. 097 */ 098 private static final int skipRights = ACI_ADD | ACI_DELETE | ACI_PROXY; 099 100 /** 101 * Creates an ACI target from the specified arguments. All of these 102 * may be null. If the ACI has no targets defaults will be used. 103 * 104 * @param targetEntry The ACI target keyword class. 105 * @param targetAttr The ACI targetattr keyword class. 106 * @param targetFilter The ACI targetfilter keyword class. 107 * @param targetScope The ACI targetscope keyword class. 108 * @param targAttrFilters The ACI targAttrFilters keyword class. 109 * @param targetControl The ACI targetControl keyword class. 110 * @param extOp The ACI extop keyword class. 111 */ 112 private AciTargets(Target targetEntry, TargetAttr targetAttr, 113 TargetFilter targetFilter, 114 SearchScope targetScope, 115 TargAttrFilters targAttrFilters, 116 TargetControl targetControl, 117 ExtOp extOp) { 118 this.target=targetEntry; 119 this.targetAttr=targetAttr; 120 this.targetScope=targetScope; 121 this.targetFilter=targetFilter; 122 this.targAttrFilters=targAttrFilters; 123 this.targetControl=targetControl; 124 this.extOp=extOp; 125 } 126 127 /** 128 * Return class representing the ACI target keyword. May be 129 * null. The default is the use the DN of the entry containing 130 * the ACI and check if the resource entry is a descendant of that. 131 * @return The ACI target class. 132 */ 133 private Target getTarget() { 134 return target; 135 } 136 137 /** 138 * Return class representing the ACI targetattr keyword. May be null. 139 * The default is to not match any attribute types in an entry. 140 * @return The ACI targetattr class. 141 */ 142 public TargetAttr getTargetAttr() { 143 return targetAttr; 144 } 145 146 /** 147 * Return the ACI targetscope keyword. Default is WHOLE_SUBTREE. 148 * @return The ACI targetscope information. 149 */ 150 public SearchScope getTargetScope() { 151 return targetScope; 152 } 153 154 /** 155 * Return class representing the ACI targetfilter keyword. May be null. 156 * @return The targetscope information. 157 */ 158 public TargetFilter getTargetFilter() { 159 return targetFilter; 160 } 161 162 /** 163 * Return the class representing the ACI targattrfilters keyword. May be 164 * null. 165 * @return The targattrfilters information. 166 */ 167 public TargAttrFilters getTargAttrFilters() { 168 return targAttrFilters; 169 } 170 171 /** 172 * Return the class representing the ACI targetcontrol keyword. May be 173 * null. 174 * @return The targetcontrol information. 175 */ 176 public TargetControl getTargetControl() { 177 return targetControl; 178 } 179 180 181 /** 182 * Return the class representing the ACI extop keyword. May be 183 * null. 184 * @return The extop information. 185 */ 186 public ExtOp getExtOp() { 187 return extOp; 188 } 189 190 /** 191 * Decode an ACI's target part of the syntax from the string provided. 192 * @param input String representing an ACI target part of syntax. 193 * @param dn The DN of the entry containing the ACI. 194 * @return An AciTargets class representing the decoded ACI target string. 195 * @throws AciException If the provided string contains errors. 196 */ 197 public static AciTargets decode(String input, DN dn) 198 throws AciException { 199 Target target=null; 200 TargetAttr targetAttr=null; 201 TargetFilter targetFilter=null; 202 TargAttrFilters targAttrFilters=null; 203 TargetControl targetControl=null; 204 ExtOp extOp=null; 205 SearchScope targetScope=SearchScope.WHOLE_SUBTREE; 206 Pattern targetPattern = Pattern.compile(targetRegex); 207 Matcher targetMatcher = targetPattern.matcher(input); 208 while (targetMatcher.find()) 209 { 210 if (targetMatcher.groupCount() != targetElementCount) { 211 LocalizableMessage message = 212 WARN_ACI_SYNTAX_INVALID_TARGET_SYNTAX.get(input); 213 throw new AciException(message); 214 } 215 String keyword = targetMatcher.group(targetKeywordPos); 216 EnumTargetKeyword targetKeyword = 217 EnumTargetKeyword.createKeyword(keyword); 218 if (targetKeyword == null) { 219 LocalizableMessage message = 220 WARN_ACI_SYNTAX_INVALID_TARGET_KEYWORD.get(keyword); 221 throw new AciException(message); 222 } 223 String operator = 224 targetMatcher.group(targetOperatorPos); 225 EnumTargetOperator targetOperator = 226 EnumTargetOperator.createOperator(operator); 227 if (targetOperator == null) { 228 LocalizableMessage message = 229 WARN_ACI_SYNTAX_INVALID_TARGETS_OPERATOR.get(operator); 230 throw new AciException(message); 231 } 232 String expression = targetMatcher.group(targetExpressionPos); 233 switch(targetKeyword) 234 { 235 case KEYWORD_TARGET: 236 { 237 if (target == null){ 238 target = Target.decode(targetOperator, expression, dn); 239 } 240 else 241 { 242 LocalizableMessage message = 243 WARN_ACI_SYNTAX_INVALID_TARGET_DUPLICATE_KEYWORDS. 244 get("target", input); 245 throw new AciException(message); 246 } 247 break; 248 } 249 case KEYWORD_TARGETCONTROL: 250 { 251 if (targetControl == null){ 252 targetControl = 253 TargetControl.decode(targetOperator, expression); 254 } 255 else 256 { 257 LocalizableMessage message = 258 WARN_ACI_SYNTAX_INVALID_TARGET_DUPLICATE_KEYWORDS. 259 get("targetcontrol", input); 260 throw new AciException(message); 261 } 262 break; 263 } 264 case KEYWORD_EXTOP: 265 { 266 if (extOp == null){ 267 extOp = ExtOp.decode(targetOperator, expression); 268 } 269 else 270 { 271 LocalizableMessage message = 272 WARN_ACI_SYNTAX_INVALID_TARGET_DUPLICATE_KEYWORDS. 273 get("extop", input); 274 throw new AciException(message); 275 } 276 break; 277 } 278 case KEYWORD_TARGETATTR: 279 { 280 if (targetAttr == null){ 281 targetAttr = TargetAttr.decode(targetOperator, 282 expression); 283 } 284 else { 285 LocalizableMessage message = 286 WARN_ACI_SYNTAX_INVALID_TARGET_DUPLICATE_KEYWORDS. 287 get("targetattr", input); 288 throw new AciException(message); 289 } 290 break; 291 } 292 case KEYWORD_TARGETSCOPE: 293 { 294 // Check the operator for the targetscope is EQUALITY 295 if (targetOperator == EnumTargetOperator.NOT_EQUALITY) { 296 LocalizableMessage message = 297 WARN_ACI_SYNTAX_INVALID_TARGET_NOT_OPERATOR. 298 get(operator, targetKeyword.name()); 299 throw new AciException(message); 300 } 301 targetScope=createScope(expression); 302 break; 303 } 304 case KEYWORD_TARGETFILTER: 305 { 306 if (targetFilter == null){ 307 targetFilter = TargetFilter.decode(targetOperator, 308 expression); 309 } 310 else { 311 LocalizableMessage message = 312 WARN_ACI_SYNTAX_INVALID_TARGET_DUPLICATE_KEYWORDS. 313 get("targetfilter", input); 314 throw new AciException(message); 315 } 316 break; 317 } 318 case KEYWORD_TARGATTRFILTERS: 319 { 320 if (targAttrFilters == null){ 321 // Check the operator for the targattrfilters is EQUALITY 322 if (targetOperator == EnumTargetOperator.NOT_EQUALITY) { 323 LocalizableMessage message = 324 WARN_ACI_SYNTAX_INVALID_TARGET_NOT_OPERATOR. 325 get(operator, targetKeyword.name()); 326 throw new AciException(message); 327 } 328 targAttrFilters = TargAttrFilters.decode(targetOperator, 329 expression); 330 } 331 else { 332 LocalizableMessage message = 333 WARN_ACI_SYNTAX_INVALID_TARGET_DUPLICATE_KEYWORDS. 334 get("targattrfilters", input); 335 throw new AciException(message); 336 } 337 break; 338 } 339 } 340 } 341 return new AciTargets(target, targetAttr, targetFilter, 342 targetScope, targAttrFilters, targetControl, 343 extOp); 344 } 345 346 /** 347 * Evaluates a provided scope string and returns an appropriate 348 * SearchScope enumeration. 349 * @param expression The expression string. 350 * @return An search scope enumeration matching the string. 351 * @throws AciException If the expression is an invalid targetscope 352 * string. 353 */ 354 private static SearchScope createScope(String expression) 355 throws AciException { 356 if(expression.equalsIgnoreCase("base")) 357 { 358 return SearchScope.BASE_OBJECT; 359 } 360 else if(expression.equalsIgnoreCase("onelevel")) 361 { 362 return SearchScope.SINGLE_LEVEL; 363 } 364 else if(expression.equalsIgnoreCase("subtree")) 365 { 366 return SearchScope.WHOLE_SUBTREE; 367 } 368 else if(expression.equalsIgnoreCase("subordinate")) 369 { 370 return SearchScope.SUBORDINATES; 371 } 372 else { 373 LocalizableMessage message = 374 WARN_ACI_SYNTAX_INVALID_TARGETSCOPE_EXPRESSION.get(expression); 375 throw new AciException(message); 376 } 377 } 378 379 /** 380 * Checks an ACI's targetfilter rule information against a target match 381 * context. 382 * @param aci The ACI to try an match the targetfilter of. 383 * @param matchCtx The target match context containing information needed 384 * to perform a target match. 385 * @return True if the targetfilter rule matched the target context. 386 */ 387 public static boolean isTargetFilterApplicable(Aci aci, 388 AciTargetMatchContext matchCtx) { 389 TargetFilter targetFilter=aci.getTargets().getTargetFilter(); 390 return targetFilter == null || targetFilter.isApplicable(matchCtx); 391 } 392 393 /** 394 * Check an ACI's targetcontrol rule against a target match context. 395 * 396 * @param aci The ACI to match the targetcontrol against. 397 * @param matchCtx The target match context containing the information 398 * needed to perform the target match. 399 * @return True if the targetcontrol rule matched the target context. 400 */ 401 public static boolean isTargetControlApplicable(Aci aci, 402 AciTargetMatchContext matchCtx) { 403 TargetControl targetControl=aci.getTargets().getTargetControl(); 404 return targetControl != null && targetControl.isApplicable(matchCtx); 405 } 406 407 /** 408 * Check an ACI's extop rule against a target match context. 409 * 410 * @param aci The ACI to match the extop rule against. 411 * @param matchCtx The target match context containing the information 412 * needed to perform the target match. 413 * @return True if the extop rule matched the target context. 414 */ 415 public static boolean isExtOpApplicable(Aci aci, 416 AciTargetMatchContext matchCtx) { 417 ExtOp extOp=aci.getTargets().getExtOp(); 418 return extOp != null && extOp.isApplicable(matchCtx); 419 } 420 421 422 /** 423 * Check an ACI's targattrfilters rule against a target match context. 424 * 425 * @param aci The ACI to match the targattrfilters against. 426 * @param matchCtx The target match context containing the information 427 * needed to perform the target match. 428 * @return True if the targattrfilters rule matched the target context. 429 */ 430 public static boolean isTargAttrFiltersApplicable(Aci aci, 431 AciTargetMatchContext matchCtx) { 432 TargAttrFilters targAttrFilters=aci.getTargets().getTargAttrFilters(); 433 if(targAttrFilters != null) { 434 if((matchCtx.hasRights(ACI_ADD) && 435 targAttrFilters.hasMask(TARGATTRFILTERS_ADD)) || 436 (matchCtx.hasRights(ACI_DELETE) && 437 targAttrFilters.hasMask(TARGATTRFILTERS_DELETE))) 438 { 439 return targAttrFilters.isApplicableAddDel(matchCtx); 440 } 441 else if((matchCtx.hasRights(ACI_WRITE_ADD) && 442 targAttrFilters.hasMask(TARGATTRFILTERS_ADD)) || 443 (matchCtx.hasRights(ACI_WRITE_DELETE) && 444 targAttrFilters.hasMask(TARGATTRFILTERS_DELETE))) 445 { 446 return targAttrFilters.isApplicableMod(matchCtx, aci); 447 } 448 } 449 return true; 450 } 451 452 /* 453 * TODO Evaluate making this method more efficient. 454 * The isTargetAttrApplicable method looks a lot less efficient than it 455 * could be with regard to the logic that it employs and the repeated use 456 * of method calls over local variables. 457 */ 458 /** 459 * Checks an provided ACI's targetattr rule against a target match 460 * context. 461 * 462 * @param aci The ACI to evaluate. 463 * @param targetMatchCtx The target match context to check the ACI against. 464 * @return True if the targetattr matched the target context. 465 */ 466 public static boolean isTargetAttrApplicable(Aci aci, 467 AciTargetMatchContext targetMatchCtx) { 468 boolean ret=true; 469 if(!targetMatchCtx.getTargAttrFiltersMatch()) { 470 TargetAttr targetAttr = aci.getTargets().getTargetAttr(); 471 AttributeType attrType = targetMatchCtx.getCurrentAttributeType(); 472 boolean isFirstAttr=targetMatchCtx.isFirstAttribute(); 473 474 if (attrType != null && targetAttr != null) { 475 ret=TargetAttr.isApplicable(attrType,targetAttr); 476 setEvalAttributes(targetMatchCtx,targetAttr,ret); 477 } else if (attrType != null || targetAttr != null) { 478 if (aci.hasRights(skipRights) 479 && skipRightsHasRights(targetMatchCtx.getRights())) { 480 ret = true; 481 } else { 482 ret = attrType == null 483 && targetAttr != null 484 && aci.hasRights(ACI_WRITE); 485 } 486 } 487 if (isFirstAttr && targetAttr == null 488 && aci.getTargets().getTargAttrFilters() == null) 489 { 490 targetMatchCtx.setEntryTestRule(true); 491 } 492 } 493 return ret; 494 } 495 496 /** 497 * Try and match a one or more of the specified rights in the skiprights 498 * mask. 499 * @param rights The rights to check for. 500 * @return True if the one or more of the specified rights are in the 501 * skiprights rights mask. 502 */ 503 private static boolean skipRightsHasRights(int rights) { 504 //geteffectiverights sets this flag, turn it off before evaluating. 505 int tmpRights=rights & ~ACI_SKIP_PROXY_CHECK; 506 return (skipRights & tmpRights) == tmpRights; 507 } 508 509 510 /** 511 * Wrapper class that passes an ACI, an ACI's targets and the specified 512 * target match context's resource entry DN to the main isTargetApplicable 513 * method. 514 * @param aci The ACI currently be matched. 515 * @param matchCtx The target match context to match against. 516 * @return True if the target matched the ACI. 517 */ 518 public static boolean isTargetApplicable(Aci aci, 519 AciTargetMatchContext matchCtx) { 520 return isTargetApplicable(aci, aci.getTargets(), 521 matchCtx.getResourceEntry().getName()); 522 } 523 524 /* 525 * TODO Investigate supporting alternative representations of the scope. 526 * 527 * Should we also consider supporting alternate representations of the 528 * scope values (in particular, allow "one" in addition to "onelevel" 529 * and "sub" in addition to "subtree") to match the very common 530 * abbreviations in widespread use for those terms? 531 */ 532 /** 533 * Main target isApplicable method. This method performs the target keyword 534 * match functionality, which allows for directory entry "targeting" using 535 * the specified ACI, ACI targets class and DN. 536 * 537 * @param aci The ACI to match the target against. 538 * @param targets The targets to use in this evaluation. 539 * @param entryDN The DN to use in this evaluation. 540 * @return True if the ACI matched the target and DN. 541 */ 542 public static boolean isTargetApplicable(Aci aci, AciTargets targets, DN entryDN) { 543 DN targetDN=aci.getDN(); 544 /* 545 * Scoping of the ACI uses either the DN of the entry 546 * containing the ACI (aci.getDN above), or if the ACI item 547 * contains a simple target DN and a equality operator, that 548 * simple target DN is used as the target DN. 549 */ 550 Target target = targets.getTarget(); 551 if(target != null && !target.isPattern() && target.getOperator() != NOT_EQUALITY) 552 { 553 targetDN=target.getDN(); 554 } 555 if (!isInScopeOf(entryDN, targetDN, targets.getTargetScope())) 556 { 557 return false; 558 } 559 560 if (target != null) 561 { 562 /* 563 * For inequality checks, scope was tested against the entry containing the ACI. 564 * If operator is inequality, check that it doesn't match the target DN. 565 */ 566 if (!target.isPattern() 567 && target.getOperator() == NOT_EQUALITY 568 && entryDN.isSubordinateOrEqualTo(target.getDN())) 569 { 570 return false; 571 } 572 /* 573 * There is a pattern, need to match the substring filter 574 * created when the ACI was decoded. If inequality flip the result. 575 */ 576 if(target.isPattern()) { 577 final boolean ret = target.matchesPattern(entryDN); 578 if (target.getOperator() == NOT_EQUALITY) { 579 return !ret; 580 } 581 return ret; 582 } 583 } 584 return true; 585 } 586 587 private static boolean isInScopeOf(DN entryDN, DN targetDN, SearchScope scope) { 588 switch(scope.asEnum()) { 589 case BASE_OBJECT: 590 return targetDN.equals(entryDN); 591 case SINGLE_LEVEL: 592 /* 593 * We use the standard definition of single level to mean the 594 * immediate children only -- not the target entry itself. 595 * Sun CR 6535035 has been raised on DSEE: 596 * Non-standard interpretation of onelevel in ACI targetScope. 597 */ 598 return targetDN.equals(entryDN.parent()); 599 case WHOLE_SUBTREE: 600 return entryDN.isSubordinateOrEqualTo(targetDN); 601 case SUBORDINATES: 602 return entryDN.size() > targetDN.size() && entryDN.isSubordinateOrEqualTo(targetDN); 603 default: 604 return false; 605 } 606 } 607 608 /** 609 * The method is used to try and determine if a targetAttr expression that 610 * is applicable has a '*' (or '+' operational attributes) token or if it 611 * was applicable because of a specific attribute type declared in the 612 * targetattrs expression (i.e., targetattrs=cn). 613 * 614 * 615 * @param ctx The ctx to check against. 616 * @param targetAttr The targetattrs part of the ACI. 617 * @param ret The is true if the ACI has already been evaluated to be 618 * applicable. 619 */ 620 private static 621 void setEvalAttributes(AciTargetMatchContext ctx, TargetAttr targetAttr, 622 boolean ret) { 623 ctx.clearEvalAttributes(ACI_USER_ATTR_STAR_MATCHED); 624 ctx.clearEvalAttributes(ACI_OP_ATTR_PLUS_MATCHED); 625 /* 626 If an applicable targetattr's match rule has not 627 been seen (~ACI_FOUND_OP_ATTR_RULE or ~ACI_FOUND_USER_ATTR_RULE) and 628 the current attribute type is applicable because of a targetattr all 629 user (or operational) attributes rule match, 630 set a flag to indicate this situation (ACI_USER_ATTR_STAR_MATCHED or 631 ACI_OP_ATTR_PLUS_MATCHED). This check also catches the following case 632 where the match was by a specific attribute type (either user or 633 operational) and the other attribute type has an all attribute token. 634 For example, the expression is: (targetattrs="cn || +) and the current 635 attribute type is cn. 636 */ 637 if(ret && targetAttr.isAllUserAttributes() && 638 !ctx.hasEvalUserAttributes()) 639 { 640 ctx.setEvalUserAttributes(ACI_USER_ATTR_STAR_MATCHED); 641 } 642 else 643 { 644 ctx.setEvalUserAttributes(ACI_FOUND_USER_ATTR_RULE); 645 } 646 647 if(ret && targetAttr.isAllOpAttributes() && 648 !ctx.hasEvalOpAttributes()) 649 { 650 ctx.setEvalOpAttributes(ACI_OP_ATTR_PLUS_MATCHED); 651 } 652 else 653 { 654 ctx.setEvalOpAttributes(ACI_FOUND_OP_ATTR_RULE); 655 } 656 } 657}