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