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 2010-2015 ForgeRock AS 026 */ 027package org.opends.server.authorization.dseecompat; 028 029import static org.opends.messages.AccessControlMessages.*; 030import static org.opends.server.util.StaticUtils.*; 031 032import java.util.HashSet; 033import java.util.Set; 034import java.util.regex.Pattern; 035 036import org.forgerock.i18n.LocalizableMessage; 037import org.forgerock.opendj.ldap.ByteSequence; 038import org.opends.server.types.DN; 039 040/** 041 * The Aci class represents ACI strings. 042 */ 043public class Aci implements Comparable<Aci> 044{ 045 046 /** 047 * The body of the ACI is the version, name and permission-bind rule 048 * pairs. 049 */ 050 private AciBody body; 051 052 /** 053 * The ACI targets. 054 */ 055 private AciTargets targets; 056 057 /** 058 * Version that we support. 059 */ 060 public static final String supportedVersion="3.0"; 061 062 /** 063 * String representation of the ACI used. 064 */ 065 private String aciString; 066 067 /** 068 * The DN of the entry containing this ACI. 069 */ 070 private final DN dn; 071 072 /** 073 * Regular expression matching a word group. 074 */ 075 public static final String WORD_GROUP="(\\w+)"; 076 077 /** 078 * Regular expression matching a word group at the start of a 079 * pattern. 080 */ 081 public static final String WORD_GROUP_START_PATTERN = "^" + WORD_GROUP; 082 083 /** 084 * Regular expression matching a white space. 085 */ 086 public static final String ZERO_OR_MORE_WHITESPACE="\\s*"; 087 088 /** 089 * Regular expression matching a white space at the start of a pattern. 090 */ 091 public static final String ZERO_OR_MORE_WHITESPACE_START_PATTERN = 092 "^" + ZERO_OR_MORE_WHITESPACE ; 093 094 /** 095 * Regular expression matching a white space at the end of a pattern. 096 */ 097 private static final String ZERO_OR_MORE_WHITESPACE_END_PATTERN = 098 ZERO_OR_MORE_WHITESPACE + "$"; 099 100 /** 101 * Regular expression matching a ACL statement separator. 102 */ 103 public static final String ACI_STATEMENT_SEPARATOR = 104 ZERO_OR_MORE_WHITESPACE + ";" + ZERO_OR_MORE_WHITESPACE; 105 106 /** 107 * This regular expression is used to do a quick syntax check 108 * when an ACI is being decoded. 109 */ 110 private static final String aciRegex = 111 ZERO_OR_MORE_WHITESPACE_START_PATTERN + AciTargets.targetsRegex + 112 ZERO_OR_MORE_WHITESPACE + AciBody.bodyRegx + 113 ZERO_OR_MORE_WHITESPACE_END_PATTERN; 114 115 116 /** 117 * Regular expression that graciously matches an attribute type name. Must 118 * begin with an ASCII letter or digit, and contain only ASCII letters, 119 * digit characters, hyphens, semi-colons and underscores. It also allows 120 * the special shorthand characters "*" for all user attributes and "+" for 121 * all operational attributes. 122 */ 123 public static final String ATTR_NAME = 124 "((?i)[a-z\\d]{1}[[a-z]\\d-_.]*(?-i)|\\*{1}|\\+{1})"; 125 126 /** 127 * Regular expression matching a LDAP URL. 128 */ 129 public static final String LDAP_URL = ZERO_OR_MORE_WHITESPACE + 130 "(ldap:///[^\\|]+)"; 131 132 /** 133 * String used to check for NULL ldap URL. 134 */ 135 public static final String NULL_LDAP_URL = "ldap:///"; 136 137 /** 138 * Regular expression used to match token that joins expressions (||). 139 */ 140 public static final String LOGICAL_OR = "\\|\\|"; 141 142 /** 143 * Regular expression used to match an open parenthesis. 144 */ 145 public static final String OPEN_PAREN = "\\("; 146 147 /** 148 * Regular expression used to match a closed parenthesis. 149 */ 150 public static final String CLOSED_PAREN = "\\)"; 151 152 /** 153 * Regular expression used to match a single equal sign. 154 */ 155 public static final String EQUAL_SIGN = "={1}"; 156 157 /** 158 * Regular expression the matches "*". 159 */ 160 public static final String ALL_USER_ATTRS_WILD_CARD = 161 ZERO_OR_MORE_WHITESPACE + 162 "\\*" + ZERO_OR_MORE_WHITESPACE; 163 164 /** 165 * Regular expression the matches "+". 166 */ 167 public static final String ALL_OP_ATTRS_WILD_CARD = 168 ZERO_OR_MORE_WHITESPACE + 169 "\\+" + ZERO_OR_MORE_WHITESPACE; 170 171 /** 172 * Regular expression used to do quick check of OID string. 173 */ 174 private static final String OID_NAME = "[\\d.\\*]*"; 175 176 /** 177 * Regular expression that matches one or more OID_NAME's separated by 178 * the "||" token. 179 */ 180 private static final String oidListRegex = ZERO_OR_MORE_WHITESPACE + 181 OID_NAME + ZERO_OR_MORE_WHITESPACE + "(" + 182 LOGICAL_OR + ZERO_OR_MORE_WHITESPACE + OID_NAME + 183 ZERO_OR_MORE_WHITESPACE + ")*"; 184 185 /** 186 * ACI_ADD is used to set the container rights for a LDAP add operation. 187 */ 188 public static final int ACI_ADD = 0x0020; 189 190 /** 191 * ACI_DELETE is used to set the container rights for a LDAP 192 * delete operation. 193 */ 194 public static final int ACI_DELETE = 0x0010; 195 196 /** 197 * ACI_READ is used to set the container rights for a LDAP 198 * search operation. 199 */ 200 public static final int ACI_READ = 0x0004; 201 202 /** 203 * ACI_WRITE is used to set the container rights for a LDAP 204 * modify operation. 205 */ 206 public static final int ACI_WRITE = 0x0008; 207 208 /** 209 * ACI_COMPARE is used to set the container rights for a LDAP 210 * compare operation. 211 */ 212 public static final int ACI_COMPARE = 0x0001; 213 214 /** 215 * ACI_SEARCH is used to set the container rights a LDAP search operation. 216 */ 217 public static final int ACI_SEARCH = 0x0002; 218 219 /** 220 * ACI_SELF is used for the SELFWRITE right. 221 */ 222 public static final int ACI_SELF = 0x0040; 223 224 /** 225 * ACI_ALL is used to as a mask for all of the above. These 226 * six below are not masked by the ACI_ALL. 227 */ 228 public static final int ACI_ALL = 0x007F; 229 230 /** 231 * ACI_PROXY is used for the PROXY right. 232 */ 233 public static final int ACI_PROXY = 0x0080; 234 235 /** 236 * ACI_IMPORT is used to set the container rights for a LDAP 237 * modify dn operation. 238 */ 239 public static final int ACI_IMPORT = 0x0100; 240 241 /** 242 * ACI_EXPORT is used to set the container rights for a LDAP 243 * modify dn operation. 244 */ 245 public static final int ACI_EXPORT = 0x0200; 246 247 /** 248 * ACI_WRITE_ADD is used by the LDAP modify operation. 249 */ 250 public static final int ACI_WRITE_ADD = 0x800; 251 252 /** 253 * ACI_WRITE_DELETE is used by the LDAP modify operation. 254 */ 255 public static final int ACI_WRITE_DELETE = 0x400; 256 257 /** 258 * ACI_SKIP_PROXY_CHECK is used to bypass the proxy access check. 259 */ 260 public static final int ACI_SKIP_PROXY_CHECK = 0x400000; 261 262 /** 263 * TARGATTRFILTER_ADD is used to specify that a 264 * targattrfilters ADD operation was seen in the ACI. For example, 265 * given an ACI with: 266 * 267 * (targattrfilters="add=mail:(mail=*@example.com)") 268 * 269 * The TARGATTRFILTERS_ADD flag would be set during ACI parsing in the 270 * TargAttrFilters class. 271 */ 272 public static final int TARGATTRFILTERS_ADD = 0x1000; 273 274 /** 275 * TARGATTRFILTER_DELETE is used to specify that a 276 * targattrfilters DELETE operation was seen in the ACI. For example, 277 * given an ACI with: 278 * 279 * (targattrfilters="del=mail:(mail=*@example.com)") 280 * 281 * The TARGATTRFILTERS_DELETE flag would be set during ACI parsing in the 282 * TargAttrFilters class. 283 */ 284 public static final int TARGATTRFILTERS_DELETE = 0x2000; 285 286 /** 287 * Used by the control evaluation access check. 288 */ 289 public static final int ACI_CONTROL = 0x4000; 290 291 /** 292 * Used by the extended operation access check. 293 */ 294 public static final int ACI_EXT_OP = 0x8000; 295 296 /** 297 * ACI_ATTR_STAR_MATCHED is the flag set when the evaluation reason of a 298 * AciHandler.maysend ACI_READ access evaluation was the result of an 299 * ACI targetattr all attributes expression (targetattr="*") target match. 300 * For this flag to be set, there must be only one ACI matching. 301 * 302 * This flag and ACI_FOUND_ATTR_RULE are used in the 303 * AciHandler.filterEntry.accessAllowedAttrs method to skip access 304 * evaluation if the flag is ACI_ATTR_STAR_MATCHED (all attributes match) 305 * and the attribute type is not operational. 306 */ 307 public static final int ACI_USER_ATTR_STAR_MATCHED = 0x0008; 308 309 /** 310 * ACI_FOUND_USER_ATTR_RULE is the flag set when the evaluation reason of a 311 * AciHandler.maysend ACI_READ access evaluation was the result of an 312 * ACI targetattr specific user attribute expression 313 * (targetattr="some user attribute type") target match. 314 */ 315 public static final int ACI_FOUND_USER_ATTR_RULE = 0x0010; 316 317 /** 318 * ACI_OP_ATTR_PLUS_MATCHED is the flag set when the evaluation reason of a 319 * AciHandler.maysend ACI_READ access evaluation was the result of an 320 * ACI targetattr all operational attributes expression (targetattr="+") 321 * target match. For this flag to be set, there must be only one 322 * ACI matching. 323 * 324 * This flag and ACI_FOUND_OP_ATTR_RULE are used in the 325 * AciHandler.filterEntry.accessAllowedAttrs method to skip access 326 * evaluation if the flag is ACI_OP_ATTR_PLUS_MATCHED (all operational 327 * attributes match) and the attribute type is operational. 328 */ 329 public static final int ACI_OP_ATTR_PLUS_MATCHED = 0x0004; 330 331 /** 332 * ACI_FOUND_OP_ATTR_RULE is the flag set when the evaluation reason of a 333 * AciHandler.maysend ACI_READ access evaluation was the result of an 334 * ACI targetattr specific operational attribute expression 335 * (targetattr="some operational attribute type") target match. 336 */ 337 public static final int ACI_FOUND_OP_ATTR_RULE = 0x0020; 338 339 /** 340 * ACI_NULL is used to set the container rights to all zeros. Used 341 * by LDAP modify. 342 */ 343 public static final int ACI_NULL = 0x0000; 344 345 /** 346 * Construct a new Aci from the provided arguments. 347 * @param input The string representation of the ACI. 348 * @param dn The DN of entry containing the ACI. 349 * @param body The body of the ACI. 350 * @param targets The targets of the ACI. 351 */ 352 private Aci(String input, DN dn, AciBody body, AciTargets targets) { 353 this.aciString = input; 354 this.dn=dn; 355 this.body=body; 356 this.targets=targets; 357 } 358 359 /** 360 * Decode an ACI byte string. 361 * @param byteString The ByteString containing the ACI string. 362 * @param dn DN of the ACI entry. 363 * @return Returns a decoded ACI representing the string argument. 364 * @throws AciException If the parsing of the ACI string fails. 365 */ 366 public static Aci decode (ByteSequence byteString, DN dn) 367 throws AciException { 368 String input=byteString.toString(); 369 //Perform a quick pattern check against the string to catch any 370 //obvious syntax errors. 371 if (!Pattern.matches(aciRegex, input)) { 372 LocalizableMessage message = WARN_ACI_SYNTAX_GENERAL_PARSE_FAILED.get(input); 373 throw new AciException(message); 374 } 375 //Decode the body first. 376 AciBody body=AciBody.decode(input); 377 //Create a substring from the start of the string to start of 378 //the body. That should be the target. 379 String targetStr = input.substring(0, body.getMatcherStartPos()); 380 //Decode that target string using the substring. 381 AciTargets targets=AciTargets.decode(targetStr, dn); 382 return new Aci(input, dn, body, targets); 383 } 384 385 /** 386 * Return the string representation of the ACI. This was the string that 387 * was used to create the Aci class. 388 * @return A string representation of the ACI. 389 */ 390 @Override 391 public String toString() { 392 return aciString; 393 } 394 395 /** 396 * Returns the targets of the ACI. 397 * @return Any AciTargets of the ACI. There may be no targets 398 * so this might be null. 399 */ 400 public AciTargets getTargets() { 401 return targets; 402 } 403 404 /** 405 * Return the DN of the entry containing the ACI. 406 * @return The DN of the entry containing the ACI. 407 */ 408 public DN getDN() { 409 return dn; 410 } 411 412 /** 413 * Test if the given ACI is applicable using the target match information 414 * provided. The ACI target can have seven keywords at this time: 415 * 416 * These two base decision on the resource entry DN: 417 * 418 * 1. target - checked in isTargetApplicable. 419 * 2. targetscope - checked in isTargetApplicable. 420 * 421 * These three base decision on resource entry attributes: 422 * 423 * 3. targetfilter - checked in isTargetFilterApplicable. 424 * 4. targetattr - checked in isTargetAttrApplicable. 425 * 5. targattrfilters - checked in isTargAttrFiltersApplicable. 426 * 427 * These two base decisions on a resource entry built by the ACI handler 428 * that only contains a DN: 429 * 6. targetcontrol - check in isTargetControlApplicable. 430 * 7. extop - check in isExtOpApplicable. 431 * 432 * Six and seven are specific to the check being done: targetcontrol when a 433 * control is being evaluated and extop when an extended operation is 434 * evaluated. None of the attribute based keywords should be checked 435 * when a control or extended op is being evaluated, because one 436 * of those attribute keywords rule might incorrectly make an ACI 437 * applicable that shouldn't be. This can happen by erroneously basing 438 * their decision on the ACI handler generated stub resource entry. For 439 * example, a "(targetattr != userpassword)" rule would match the generated 440 * stub resource entry, even though a control or extended op might be 441 * denied. 442 * 443 * What is allowed is the target and targetscope keywords, since the DN is 444 * known, so they are checked along with the correct method for the access 445 * check (isTargetControlApplicable for control and 446 * isTExtOpApplicable for extended operations). See comments in code 447 * where these checks are done. 448 * 449 * @param aci The ACI to test. 450 * @param matchCtx The target matching context containing all the info 451 * needed to match ACI targets. 452 * @return True if this ACI targets are applicable or match. 453 */ 454 public static boolean isApplicable(Aci aci, AciTargetMatchContext matchCtx) { 455 if(matchCtx.hasRights(ACI_EXT_OP)) { 456 //Extended operation is being evaluated. 457 return AciTargets.isTargetApplicable(aci, matchCtx) && 458 AciTargets.isExtOpApplicable(aci, matchCtx); 459 } else if(matchCtx.hasRights(ACI_CONTROL)) { 460 //Control is being evaluated. 461 return AciTargets.isTargetApplicable(aci, matchCtx) && 462 AciTargets.isTargetControlApplicable(aci, matchCtx); 463 } else { 464 //If an ACI has extOp or targetControl targets skip it because the 465 //matchCtx right does not contain either ACI_EXT_OP or ACI_CONTROL at 466 //this point. 467 return hasNoExtOpOrTargetControl(aci.getTargets()) 468 && haveSimilarRights(aci, matchCtx) 469 && AciTargets.isTargetApplicable(aci, matchCtx) 470 && AciTargets.isTargetFilterApplicable(aci, matchCtx) 471 && AciTargets.isTargAttrFiltersApplicable(aci, matchCtx) 472 && AciTargets.isTargetAttrApplicable(aci, matchCtx); 473 } 474 } 475 476 private static boolean hasNoExtOpOrTargetControl(AciTargets aciTargets) 477 { 478 return aciTargets.getExtOp() == null 479 && aciTargets.getTargetControl() == null; 480 } 481 482 private static boolean haveSimilarRights(Aci aci, 483 AciTargetMatchContext matchCtx) 484 { 485 return aci.hasRights(matchCtx.getRights()) 486 || (aci.hasRights(ACI_SEARCH| ACI_READ) 487 && matchCtx.hasRights(ACI_SEARCH | ACI_READ)); 488 } 489 490 /** 491 * Check if the body of the ACI matches the rights specified. 492 * @param rights Bit mask representing the rights to match. 493 * @return True if the body's rights match one of the rights specified. 494 */ 495 public boolean hasRights(int rights) { 496 return body.hasRights(rights); 497 } 498 499 /** 500 * Re-direct has access type to the body's hasAccessType method. 501 * @param accessType The access type to match. 502 * @return True if the body's hasAccessType determines a permission 503 * contains this access type (allow or deny are valid types). 504 */ 505 public boolean hasAccessType(EnumAccessType accessType) { 506 return body.hasAccessType(accessType); 507 } 508 509 /** 510 * Evaluate this ACI using the evaluation context provided. Re-direct 511 * that calls the body's evaluate method. 512 * @param evalCtx The evaluation context to evaluate with. 513 * @return EnumEvalResult that contains the evaluation result of this 514 * aci evaluation. 515 */ 516 private EnumEvalResult evaluate(AciEvalContext evalCtx) { 517 return body.evaluate(evalCtx); 518 } 519 520 /** 521 * Static class used to evaluate an ACI and evaluation context. 522 * @param evalCtx The context to evaluate with. 523 * @param aci The ACI to evaluate. 524 * @return EnumEvalResult that contains the evaluation result of the aci 525 * evaluation. 526 */ 527 public static EnumEvalResult evaluate(AciEvalContext evalCtx, Aci aci) { 528 return aci.evaluate(evalCtx); 529 } 530 531 /** 532 * Returns the name string of this ACI. 533 * @return The name string. 534 */ 535 public String getName() { 536 return this.body.getName(); 537 } 538 539 540 /** 541 * Decode an OIDs expression string. 542 * 543 * @param expr A string representing the OID expression. 544 * @param msg A message to be used if there is an exception. 545 * 546 * @return Return a hash set of verified OID strings parsed from the OID 547 * expression. 548 * 549 * @throws AciException If the specified expression string is invalid. 550 */ 551 public static Set<String> decodeOID(String expr, LocalizableMessage msg) 552 throws AciException { 553 Set<String> OIDs = new HashSet<>(); 554 //Quick check to see if the expression is valid. 555 if (Pattern.matches(oidListRegex, expr)) { 556 // Remove the spaces in the oid string and 557 // split the list. 558 Pattern separatorPattern = 559 Pattern.compile(LOGICAL_OR); 560 String oidString = 561 expr.replaceAll(ZERO_OR_MORE_WHITESPACE, ""); 562 String[] oidArray= 563 separatorPattern.split(oidString); 564 //More careful analysis of each OID string. 565 for(String oid : oidArray) { 566 verifyOid(oid); 567 OIDs.add(oid); 568 } 569 } else { 570 throw new AciException(msg); 571 } 572 return OIDs; 573 } 574 575 /** 576 * Verify the specified OID string. 577 * 578 * @param oidStr The string representing an OID. 579 * 580 * @throws AciException If the specified string is invalid. 581 */ 582 private static void verifyOid(String oidStr) throws AciException { 583 int pos=0, length=oidStr.length(); 584 char c; 585 if("*".equals(oidStr)) 586 { 587 return; 588 } 589 boolean lastWasPeriod = false; 590 while (pos < length && ((c = oidStr.charAt(pos++)) != ' ')) { 591 if (c == '.') { 592 if (lastWasPeriod) { 593 LocalizableMessage message = WARN_ACI_SYNTAX_DOUBLE_PERIOD_IN_NUMERIC_OID.get( 594 oidStr, pos-1); 595 throw new AciException(message); 596 } 597 lastWasPeriod = true; 598 } else if (! isDigit(c)) { 599 LocalizableMessage message = 600 WARN_ACI_SYNTAX_ILLEGAL_CHAR_IN_NUMERIC_OID.get(oidStr, c, pos-1); 601 throw new AciException(message); 602 } else { 603 lastWasPeriod = false; 604 } 605 } 606 } 607 608 /** 609 * Compares this Aci with the provided Aci based on a natural order. 610 * This order will be first hierarchical (ancestors will come before 611 * descendants) and then alphabetical by attribute name(s) and 612 * value(s). 613 * 614 * @param aci The Aci against which to compare this Aci. 615 * 616 * @return A negative integer if this Aci should come before the 617 * provided Aci, a positive integer if this Aci should come 618 * after the provided Aci, or zero if there is no difference 619 * with regard to ordering. 620 */ 621 @Override 622 public int compareTo(Aci aci) 623 { 624 return this.aciString.compareTo(aci.toString()); 625 } 626 }