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