/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt * or http://forgerock.org/license/CDDLv1.0.html. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at legal-notices/CDDLv1_0.txt. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Copyright 2008-2009 Sun Microsystems, Inc. * Portions Copyright 2012-2015 ForgeRock AS. */ package org.opends.server.authorization.dseecompat; import static org.opends.messages.AccessControlMessages.*; import static org.opends.server.authorization.dseecompat.Aci.*; import static org.opends.server.authorization.dseecompat.EnumEvalResult.*; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.forgerock.i18n.LocalizableMessage; /** * This class represents the body of an ACI. The body of the ACI is the * version, name, and permission-bind rule pairs. */ public class AciBody { /** * Regular expression group position for the version string. */ private static final int VERSION = 1; /** * Regular expression group position for the name string. */ private static final int NAME = 2; /** * Regular expression group position for the permission string. */ private static final int PERM = 1; /** * Regular expression group position for the rights string. */ private static final int RIGHTS = 2; /** * Regular expression group position for the bindrule string. */ private static final int BINDRULE = 3; /** * Index into the ACI string where the ACI body starts. */ private int startPos; /** * The name of the ACI, currently not used but parsed. */ private String name; /** * The version of the ACi, current not used but parsed and checked for 3.0. */ private String version; /** * This structure represents a permission-bind rule pairs. There can be * several of these. */ private List<PermBindRulePair> permBindRulePairs; /** * Regular expression used to match the access type group (allow, deny) and * the rights group "(read, write, ...)". The last pattern looks for a group * surrounded by parenthesis. The group must contain at least one * non-paren character. */ private static final String permissionRegex = WORD_GROUP + ZERO_OR_MORE_WHITESPACE + "\\(([^()]+)\\)"; /** * Regular expression that matches a bind rule group at a coarse level. It * matches any character one or more times, a single quotation and * an optional right parenthesis. */ private static final String bindRuleRegex = "(.+?\"[)]*)" + ACI_STATEMENT_SEPARATOR; /** * Regular expression used to match the actions of the ACI. The actions * are permissions and matching bind rules. */ private static final String actionRegex = ZERO_OR_MORE_WHITESPACE + permissionRegex + ZERO_OR_MORE_WHITESPACE + bindRuleRegex; /** * Regular expression used to match the version value (digit.digit). */ private static final String versionRegex = "(\\d\\.\\d)"; /** * Regular expression used to match the version token. Case insensitive. */ private static final String versionToken = "(?i)version(?-i)"; /** * Regular expression used to match the acl token. Case insensitive. */ private static final String aclToken = "(?i)acl(?-i)"; /** * Regular expression used to match the body of an ACI. This pattern is * a general verification check. */ public static final String bodyRegx = "\\(" + ZERO_OR_MORE_WHITESPACE + versionToken + ZERO_OR_MORE_WHITESPACE + versionRegex + ACI_STATEMENT_SEPARATOR + aclToken + ZERO_OR_MORE_WHITESPACE + "\"([^\"]*)\"" + ACI_STATEMENT_SEPARATOR + actionRegex + ZERO_OR_MORE_WHITESPACE + "\\)"; /** * Regular expression used to match the header of the ACI body. The * header is version and acl name. */ private static final String header = OPEN_PAREN + ZERO_OR_MORE_WHITESPACE + versionToken + ZERO_OR_MORE_WHITESPACE + versionRegex + ACI_STATEMENT_SEPARATOR + aclToken + ZERO_OR_MORE_WHITESPACE + "\"(.*?)\"" + ACI_STATEMENT_SEPARATOR; /** * Construct an ACI body from the specified version, name and * permission-bind rule pairs. * * @param verision The version of the ACI. * @param name The name of the ACI. * @param startPos The start position in the string of the ACI body. * @param permBindRulePairs The set of fully parsed permission-bind rule * pairs pertaining to this ACI. */ private AciBody(String verision, String name, int startPos, List<PermBindRulePair> permBindRulePairs) { this.version=verision; this.name=name; this.startPos=startPos; this.permBindRulePairs=permBindRulePairs; } /** * Decode an ACI string representing the ACI body. * * @param input String representation of the ACI body. * @return An AciBody class representing the decoded ACI body string. * @throws AciException If the provided string contains errors. */ public static AciBody decode(String input) throws AciException { String version=null, name=null; int startPos=0; List<PermBindRulePair> permBindRulePairs = new ArrayList<>(); Pattern bodyPattern = Pattern.compile(header); Matcher bodyMatcher = bodyPattern.matcher(input); if(bodyMatcher.find()) { startPos=bodyMatcher.start(); version = bodyMatcher.group(VERSION); if (!version.equalsIgnoreCase(supportedVersion)) { LocalizableMessage message = WARN_ACI_SYNTAX_INVAILD_VERSION.get(version); throw new AciException(message); } name = bodyMatcher.group(NAME); input = input.substring(bodyMatcher.end()); } Pattern bodyPattern1 = Pattern.compile("\\G" + actionRegex); Matcher bodyMatcher1 = bodyPattern1.matcher(input); /* * The may be many permission-bind rule pairs. */ int lastIndex = -1; while(bodyMatcher1.find()) { String perm=bodyMatcher1.group(PERM); String rights=bodyMatcher1.group(RIGHTS); String bRule=bodyMatcher1.group(BINDRULE); PermBindRulePair pair = PermBindRulePair.decode(perm, rights, bRule); permBindRulePairs.add(pair); lastIndex = bodyMatcher1.end(); } if (lastIndex >= 0 && input.charAt(lastIndex) != ')') { LocalizableMessage message = WARN_ACI_SYNTAX_GENERAL_PARSE_FAILED.get(input); throw new AciException(message); } return new AciBody(version, name, startPos, permBindRulePairs); } /** * Checks all of the permissions in this body for a specific access type. * Need to walk down each permission-bind rule pair and call it's * hasAccessType method. * * @param accessType The access type enumeration to search for. * @return True if the access type is found in a permission of * a permission bind rule pair. */ public boolean hasAccessType(EnumAccessType accessType) { List<PermBindRulePair>pairs=getPermBindRulePairs(); for(PermBindRulePair p : pairs) { if(p.hasAccessType(accessType)) { return true; } } return false; } /** * Search through each permission bind rule associated with this body and * try and match a single right of the specified rights. * * @param rights The rights that are used in the match. * @return True if a one or more right of the specified rights matches * a body's permission rights. */ public boolean hasRights(int rights) { List<PermBindRulePair>pairs=getPermBindRulePairs(); for(PermBindRulePair p : pairs) { if(p.hasRights(rights)) { return true; } } return false; } /** * Retrieve the permission-bind rule pairs of this ACI body. * * @return The permission-bind rule pairs. */ List<PermBindRulePair> getPermBindRulePairs() { return permBindRulePairs; } /** * Get the start position in the ACI string of the ACI body. * * @return Index into the ACI string of the ACI body. */ public int getMatcherStartPos() { return startPos; } /** * Performs an evaluation of the permission-bind rule pairs * using the evaluation context. The method walks down * each PermBindRulePair object and: * * 1. Skips a pair if the evaluation context rights don't * apply to that ACI. For example, an LDAP search would skip * an ACI pair that allows writes. * * 2. The pair's bind rule is evaluated using the evaluation context. * 3. The result of the evaluation is itself evaluated. See comments * below in the code. * * @param evalCtx The evaluation context to evaluate against. * @return An enumeration result of the evaluation. */ public EnumEvalResult evaluate(AciEvalContext evalCtx) { EnumEvalResult res = FALSE; List<PermBindRulePair>pairs=getPermBindRulePairs(); for(PermBindRulePair p : pairs) { if (evalCtx.isDenyEval() && p.hasAccessType(EnumAccessType.ALLOW)) { continue; } if(!p.hasRights(getEvalRights(evalCtx))) { continue; } res=p.getBindRule().evaluate(evalCtx); // The evaluation result could be FAIL. Stop processing and return //FAIL. Maybe an internal search failed. if(res != TRUE && res != FALSE) { res = FAIL; break; //If the access type is DENY and the pair evaluated to TRUE, //then stop processing and return TRUE. A deny pair succeeded. } else if (p.hasAccessType(EnumAccessType.DENY) && res == TRUE) { res = TRUE; break; //An allow access type evaluated TRUE, stop processing and return TRUE. } else if (p.hasAccessType(EnumAccessType.ALLOW) && res == TRUE) { res = TRUE; break; } } return res; } /** * Returns the name string. * @return The name string. */ public String getName() { return this.name; } /** * Mainly used because geteffectiverights adds flags to the rights that aren't * needed in the actual evaluation of the ACI. This routine returns only the * rights needed in the evaluation. The order does matter, ACI_SELF evaluation * needs to be before ACI_WRITE. * <p> * JNR: I find the implementation in this method dubious. * @see EnumRight#hasRights(int, int) * * @param evalCtx The evaluation context to determine the rights of. * @return The evaluation rights to used in the evaluation. */ private int getEvalRights(AciEvalContext evalCtx) { if(evalCtx.hasRights(ACI_WRITE) && evalCtx.hasRights(ACI_SELF)) { return ACI_SELF; } else if(evalCtx.hasRights(ACI_COMPARE)) { return ACI_COMPARE; } else if(evalCtx.hasRights(ACI_SEARCH)) { return ACI_SEARCH; } else if(evalCtx.hasRights(ACI_READ)) { return ACI_READ; } else if(evalCtx.hasRights(ACI_DELETE)) { return ACI_DELETE; } else if(evalCtx.hasRights(ACI_ADD)) { return ACI_ADD; } else if(evalCtx.hasRights(ACI_WRITE)) { return ACI_WRITE; } else if(evalCtx.hasRights(ACI_PROXY)) { return ACI_PROXY; } else if(evalCtx.hasRights(ACI_IMPORT)) { return ACI_IMPORT; } else if(evalCtx.hasRights(ACI_EXPORT)) { return ACI_EXPORT; } return ACI_NULL; } /** * Return version string of the ACI. * * @return The ACI version string. */ public String getVersion () { return version; } /** {@inheritDoc} */ @Override public String toString() { final StringBuilder sb = new StringBuilder(); toString(sb); return sb.toString(); } /** * Appends a string representation of this object to the provided buffer. * * @param buffer * The buffer into which a string representation of this object * should be appended. */ public final void toString(StringBuilder buffer) { buffer.append("(version ").append(this.version); buffer.append("; acl \"").append(this.name).append("\"; "); for (PermBindRulePair pair : this.permBindRulePairs) { buffer.append(pair); } } }