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