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