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}