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}