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}