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-2009 Sun Microsystems, Inc. 025 * Portions Copyright 2012-2015 ForgeRock AS. 026 */ 027package org.opends.server.authorization.dseecompat; 028 029import static org.opends.messages.AccessControlMessages.*; 030import static org.opends.server.authorization.dseecompat.Aci.*; 031import static org.opends.server.authorization.dseecompat.EnumEvalResult.*; 032 033import java.util.ArrayList; 034import java.util.List; 035import java.util.regex.Matcher; 036import java.util.regex.Pattern; 037 038import org.forgerock.i18n.LocalizableMessage; 039 040/** 041 * This class represents the body of an ACI. The body of the ACI is the 042 * version, name, and permission-bind rule pairs. 043 */ 044public class AciBody { 045 046 /** 047 * Regular expression group position for the version string. 048 */ 049 private static final int VERSION = 1; 050 051 /** 052 * Regular expression group position for the name string. 053 */ 054 private static final int NAME = 2; 055 056 /** 057 * Regular expression group position for the permission string. 058 */ 059 private static final int PERM = 1; 060 061 /** 062 * Regular expression group position for the rights string. 063 */ 064 private static final int RIGHTS = 2; 065 066 /** 067 * Regular expression group position for the bindrule string. 068 */ 069 private static final int BINDRULE = 3; 070 071 /** 072 * Index into the ACI string where the ACI body starts. 073 */ 074 private int startPos; 075 076 /** 077 * The name of the ACI, currently not used but parsed. 078 */ 079 private String name; 080 081 /** 082 * The version of the ACi, current not used but parsed and checked for 3.0. 083 */ 084 private String version; 085 086 /** 087 * This structure represents a permission-bind rule pairs. There can be 088 * several of these. 089 */ 090 private List<PermBindRulePair> permBindRulePairs; 091 092 /** 093 * Regular expression used to match the access type group (allow, deny) and 094 * the rights group "(read, write, ...)". The last pattern looks for a group 095 * surrounded by parenthesis. The group must contain at least one 096 * non-paren character. 097 */ 098 private static final String permissionRegex = 099 WORD_GROUP + ZERO_OR_MORE_WHITESPACE + "\\(([^()]+)\\)"; 100 101 /** 102 * Regular expression that matches a bind rule group at a coarse level. It 103 * matches any character one or more times, a single quotation and 104 * an optional right parenthesis. 105 */ 106 private static final String bindRuleRegex = 107 "(.+?\"[)]*)" + ACI_STATEMENT_SEPARATOR; 108 109 /** 110 * Regular expression used to match the actions of the ACI. The actions 111 * are permissions and matching bind rules. 112 */ 113 private static final String actionRegex = 114 ZERO_OR_MORE_WHITESPACE + permissionRegex + 115 ZERO_OR_MORE_WHITESPACE + bindRuleRegex; 116 117 /** 118 * Regular expression used to match the version value (digit.digit). 119 */ 120 private static final String versionRegex = "(\\d\\.\\d)"; 121 122 /** 123 * Regular expression used to match the version token. Case insensitive. 124 */ 125 private static final String versionToken = "(?i)version(?-i)"; 126 127 /** 128 * Regular expression used to match the acl token. Case insensitive. 129 */ 130 private static final String aclToken = "(?i)acl(?-i)"; 131 132 /** 133 * Regular expression used to match the body of an ACI. This pattern is 134 * a general verification check. 135 */ 136 public static final String bodyRegx = 137 "\\(" + ZERO_OR_MORE_WHITESPACE + versionToken + 138 ZERO_OR_MORE_WHITESPACE + versionRegex + 139 ACI_STATEMENT_SEPARATOR + aclToken + ZERO_OR_MORE_WHITESPACE + 140 "\"([^\"]*)\"" + ACI_STATEMENT_SEPARATOR + actionRegex + 141 ZERO_OR_MORE_WHITESPACE + "\\)"; 142 143 /** 144 * Regular expression used to match the header of the ACI body. The 145 * header is version and acl name. 146 */ 147 private static final String header = 148 OPEN_PAREN + ZERO_OR_MORE_WHITESPACE + versionToken + 149 ZERO_OR_MORE_WHITESPACE + 150 versionRegex + ACI_STATEMENT_SEPARATOR + aclToken + 151 ZERO_OR_MORE_WHITESPACE + "\"(.*?)\"" + ACI_STATEMENT_SEPARATOR; 152 153 /** 154 * Construct an ACI body from the specified version, name and 155 * permission-bind rule pairs. 156 * 157 * @param verision The version of the ACI. 158 * @param name The name of the ACI. 159 * @param startPos The start position in the string of the ACI body. 160 * @param permBindRulePairs The set of fully parsed permission-bind rule 161 * pairs pertaining to this ACI. 162 */ 163 private AciBody(String verision, String name, int startPos, 164 List<PermBindRulePair> permBindRulePairs) { 165 this.version=verision; 166 this.name=name; 167 this.startPos=startPos; 168 this.permBindRulePairs=permBindRulePairs; 169 } 170 171 /** 172 * Decode an ACI string representing the ACI body. 173 * 174 * @param input String representation of the ACI body. 175 * @return An AciBody class representing the decoded ACI body string. 176 * @throws AciException If the provided string contains errors. 177 */ 178 public static AciBody decode(String input) 179 throws AciException { 180 String version=null, name=null; 181 int startPos=0; 182 List<PermBindRulePair> permBindRulePairs = new ArrayList<>(); 183 Pattern bodyPattern = Pattern.compile(header); 184 Matcher bodyMatcher = bodyPattern.matcher(input); 185 if(bodyMatcher.find()) { 186 startPos=bodyMatcher.start(); 187 version = bodyMatcher.group(VERSION); 188 if (!version.equalsIgnoreCase(supportedVersion)) { 189 LocalizableMessage message = WARN_ACI_SYNTAX_INVAILD_VERSION.get(version); 190 throw new AciException(message); 191 } 192 name = bodyMatcher.group(NAME); 193 input = input.substring(bodyMatcher.end()); 194 } 195 196 Pattern bodyPattern1 = Pattern.compile("\\G" + actionRegex); 197 Matcher bodyMatcher1 = bodyPattern1.matcher(input); 198 199 /* 200 * The may be many permission-bind rule pairs. 201 */ 202 int lastIndex = -1; 203 while(bodyMatcher1.find()) { 204 String perm=bodyMatcher1.group(PERM); 205 String rights=bodyMatcher1.group(RIGHTS); 206 String bRule=bodyMatcher1.group(BINDRULE); 207 PermBindRulePair pair = PermBindRulePair.decode(perm, rights, bRule); 208 permBindRulePairs.add(pair); 209 lastIndex = bodyMatcher1.end(); 210 } 211 212 if (lastIndex >= 0 && input.charAt(lastIndex) != ')') 213 { 214 LocalizableMessage message = WARN_ACI_SYNTAX_GENERAL_PARSE_FAILED.get(input); 215 throw new AciException(message); 216 } 217 218 return new AciBody(version, name, startPos, permBindRulePairs); 219 } 220 221 /** 222 * Checks all of the permissions in this body for a specific access type. 223 * Need to walk down each permission-bind rule pair and call it's 224 * hasAccessType method. 225 * 226 * @param accessType The access type enumeration to search for. 227 * @return True if the access type is found in a permission of 228 * a permission bind rule pair. 229 */ 230 public boolean hasAccessType(EnumAccessType accessType) { 231 List<PermBindRulePair>pairs=getPermBindRulePairs(); 232 for(PermBindRulePair p : pairs) { 233 if(p.hasAccessType(accessType)) { 234 return true; 235 } 236 } 237 return false; 238 } 239 240 /** 241 * Search through each permission bind rule associated with this body and 242 * try and match a single right of the specified rights. 243 * 244 * @param rights The rights that are used in the match. 245 * @return True if a one or more right of the specified rights matches 246 * a body's permission rights. 247 */ 248 public boolean hasRights(int rights) { 249 List<PermBindRulePair>pairs=getPermBindRulePairs(); 250 for(PermBindRulePair p : pairs) { 251 if(p.hasRights(rights)) { 252 return true; 253 } 254 } 255 return false; 256 } 257 258 /** 259 * Retrieve the permission-bind rule pairs of this ACI body. 260 * 261 * @return The permission-bind rule pairs. 262 */ 263 List<PermBindRulePair> getPermBindRulePairs() { 264 return permBindRulePairs; 265 } 266 267 /** 268 * Get the start position in the ACI string of the ACI body. 269 * 270 * @return Index into the ACI string of the ACI body. 271 */ 272 public int getMatcherStartPos() { 273 return startPos; 274 } 275 276 /** 277 * Performs an evaluation of the permission-bind rule pairs 278 * using the evaluation context. The method walks down 279 * each PermBindRulePair object and: 280 * 281 * 1. Skips a pair if the evaluation context rights don't 282 * apply to that ACI. For example, an LDAP search would skip 283 * an ACI pair that allows writes. 284 * 285 * 2. The pair's bind rule is evaluated using the evaluation context. 286 * 3. The result of the evaluation is itself evaluated. See comments 287 * below in the code. 288 * 289 * @param evalCtx The evaluation context to evaluate against. 290 * @return An enumeration result of the evaluation. 291 */ 292 public EnumEvalResult evaluate(AciEvalContext evalCtx) { 293 EnumEvalResult res = FALSE; 294 List<PermBindRulePair>pairs=getPermBindRulePairs(); 295 for(PermBindRulePair p : pairs) { 296 if (evalCtx.isDenyEval() && p.hasAccessType(EnumAccessType.ALLOW)) { 297 continue; 298 } 299 if(!p.hasRights(getEvalRights(evalCtx))) { 300 continue; 301 } 302 res=p.getBindRule().evaluate(evalCtx); 303 // The evaluation result could be FAIL. Stop processing and return 304 //FAIL. Maybe an internal search failed. 305 if(res != TRUE && res != FALSE) { 306 res = FAIL; 307 break; 308 //If the access type is DENY and the pair evaluated to TRUE, 309 //then stop processing and return TRUE. A deny pair succeeded. 310 } else if (p.hasAccessType(EnumAccessType.DENY) && res == TRUE) { 311 res = TRUE; 312 break; 313 //An allow access type evaluated TRUE, stop processing and return TRUE. 314 } else if (p.hasAccessType(EnumAccessType.ALLOW) && res == TRUE) { 315 res = TRUE; 316 break; 317 } 318 } 319 return res; 320 } 321 322 /** 323 * Returns the name string. 324 * @return The name string. 325 */ 326 public String getName() { 327 return this.name; 328 } 329 330 331 /** 332 * Mainly used because geteffectiverights adds flags to the rights that aren't 333 * needed in the actual evaluation of the ACI. This routine returns only the 334 * rights needed in the evaluation. The order does matter, ACI_SELF evaluation 335 * needs to be before ACI_WRITE. 336 * <p> 337 * JNR: I find the implementation in this method dubious. 338 * @see EnumRight#hasRights(int, int) 339 * 340 * @param evalCtx The evaluation context to determine the rights of. 341 * @return The evaluation rights to used in the evaluation. 342 */ 343 private int getEvalRights(AciEvalContext evalCtx) { 344 if(evalCtx.hasRights(ACI_WRITE) && evalCtx.hasRights(ACI_SELF)) { 345 return ACI_SELF; 346 } else if(evalCtx.hasRights(ACI_COMPARE)) { 347 return ACI_COMPARE; 348 } else if(evalCtx.hasRights(ACI_SEARCH)) { 349 return ACI_SEARCH; 350 } else if(evalCtx.hasRights(ACI_READ)) { 351 return ACI_READ; 352 } else if(evalCtx.hasRights(ACI_DELETE)) { 353 return ACI_DELETE; 354 } else if(evalCtx.hasRights(ACI_ADD)) { 355 return ACI_ADD; 356 } else if(evalCtx.hasRights(ACI_WRITE)) { 357 return ACI_WRITE; 358 } else if(evalCtx.hasRights(ACI_PROXY)) { 359 return ACI_PROXY; 360 } else if(evalCtx.hasRights(ACI_IMPORT)) { 361 return ACI_IMPORT; 362 } else if(evalCtx.hasRights(ACI_EXPORT)) { 363 return ACI_EXPORT; 364 } 365 return ACI_NULL; 366 } 367 368 /** 369 * Return version string of the ACI. 370 * 371 * @return The ACI version string. 372 */ 373 public String getVersion () { 374 return version; 375 } 376 377 /** {@inheritDoc} */ 378 @Override 379 public String toString() 380 { 381 final StringBuilder sb = new StringBuilder(); 382 toString(sb); 383 return sb.toString(); 384 } 385 386 /** 387 * Appends a string representation of this object to the provided buffer. 388 * 389 * @param buffer 390 * The buffer into which a string representation of this object 391 * should be appended. 392 */ 393 public final void toString(StringBuilder buffer) 394 { 395 buffer.append("(version ").append(this.version); 396 buffer.append("; acl \"").append(this.name).append("\"; "); 397 for (PermBindRulePair pair : this.permBindRulePairs) 398 { 399 buffer.append(pair); 400 } 401 } 402}