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 Sun Microsystems, Inc.
015 * Portions Copyright 2010-2016 ForgeRock AS.
016 */
017package org.opends.server.authorization.dseecompat;
018
019import static org.opends.messages.AccessControlMessages.*;
020import static org.opends.server.util.StaticUtils.*;
021
022import java.util.HashSet;
023import java.util.Set;
024import java.util.regex.Pattern;
025
026import org.forgerock.i18n.LocalizableMessage;
027import org.forgerock.opendj.ldap.ByteSequence;
028import org.forgerock.opendj.ldap.DN;
029
030/** The Aci class represents ACI strings. */
031public class Aci implements Comparable<Aci>
032{
033    /** Version that we support. */
034    public static final String supportedVersion="3.0";
035
036    /** The body of the ACI is the version, name and permission-bind rule pairs. */
037    private final AciBody body;
038    /** The ACI targets. */
039    private final AciTargets targets;
040    /** String representation of the ACI used. */
041    private final String aciString;
042    /** The DN of the entry containing this ACI. */
043    private final DN dn;
044
045    /** Regular expression matching a word group. */
046    public static final String WORD_GROUP="(\\w+)";
047
048    /** Regular expression matching a word group at the start of a pattern. */
049    static final String WORD_GROUP_START_PATTERN = "^" + WORD_GROUP;
050
051    /** Regular expression matching a white space. */
052    public static final String ZERO_OR_MORE_WHITESPACE="\\s*";
053
054    /** Regular expression matching a white space at the start of a pattern. */
055    public static final String ZERO_OR_MORE_WHITESPACE_START_PATTERN =
056                                             "^" + ZERO_OR_MORE_WHITESPACE ;
057
058    /** Regular expression matching a white space at the end of a pattern. */
059    private static final String ZERO_OR_MORE_WHITESPACE_END_PATTERN =
060                                             ZERO_OR_MORE_WHITESPACE  + "$";
061
062    /** Regular expression matching a ACL statement separator. */
063    public static final String ACI_STATEMENT_SEPARATOR =
064                ZERO_OR_MORE_WHITESPACE + ";" + ZERO_OR_MORE_WHITESPACE;
065
066    /** This regular expression is used to do a quick syntax check when an ACI is being decoded. */
067    private static final String aciRegex =
068           ZERO_OR_MORE_WHITESPACE_START_PATTERN + AciTargets.targetsRegex +
069           ZERO_OR_MORE_WHITESPACE + AciBody.bodyRegx +
070           ZERO_OR_MORE_WHITESPACE_END_PATTERN;
071
072    /**
073     * Regular expression that graciously matches an attribute type name. Must
074     * begin with an ASCII letter or digit, and contain only ASCII letters,
075     * digit characters, hyphens, semi-colons and underscores. It also allows
076     * the special shorthand characters "*" for all user attributes and "+" for
077     * all operational attributes.
078     */
079    static final String ATTR_NAME =
080              "((?i)[a-z\\d]{1}[[a-z]\\d-_.]*(?-i)|\\*{1}|\\+{1})";
081
082     /** Regular expression matching a LDAP URL. */
083     public  static final String LDAP_URL = ZERO_OR_MORE_WHITESPACE  +
084                                                 "(ldap:///[^\\|]+)";
085
086     /** String used to check for NULL ldap URL. */
087     public static final String NULL_LDAP_URL = "ldap:///";
088
089    /** Regular expression used to match token that joins expressions (||). */
090    static final String LOGICAL_OR = "\\|\\|";
091    /** Regular expression used to match an open parenthesis. */
092    static final String OPEN_PAREN = "\\(";
093    /** Regular expression used to match a closed parenthesis. */
094    static final String CLOSED_PAREN = "\\)";
095    /** Regular expression used to match a single equal sign. */
096    static final String EQUAL_SIGN = "={1}";
097
098    /** Regular expression the matches "*". */
099    public static final String ALL_USER_ATTRS_WILD_CARD =
100            ZERO_OR_MORE_WHITESPACE +
101                    "\\*" + ZERO_OR_MORE_WHITESPACE;
102
103    /** Regular expression the matches "+". */
104    public static final String ALL_OP_ATTRS_WILD_CARD =
105            ZERO_OR_MORE_WHITESPACE +
106                    "\\+" + ZERO_OR_MORE_WHITESPACE;
107
108    /** Regular expression used to do quick check of OID string. */
109    private static final String OID_NAME = "[\\d.\\*]*";
110
111    /** Regular expression that matches one or more OID_NAME's separated by the "||" token. */
112    private static final String oidListRegex  =  ZERO_OR_MORE_WHITESPACE +
113            OID_NAME + ZERO_OR_MORE_WHITESPACE + "(" +
114            LOGICAL_OR + ZERO_OR_MORE_WHITESPACE + OID_NAME +
115            ZERO_OR_MORE_WHITESPACE + ")*";
116
117    /** ACI_ADD is used to set the container rights for a LDAP add operation. */
118    public static final int ACI_ADD = 0x0020;
119
120    /** ACI_DELETE is used to set the container rights for a LDAP delete operation. */
121    static final int ACI_DELETE = 0x0010;
122    /** ACI_READ is used to set the container rights for a LDAP search operation. */
123    static final int ACI_READ = 0x0004;
124    /** ACI_WRITE is used to set the container rights for a LDAP modify operation. */
125    static final int ACI_WRITE = 0x0008;
126    /** ACI_COMPARE is used to set the container rights for a LDAP compare operation. */
127    static final int ACI_COMPARE = 0x0001;
128    /** ACI_SEARCH is used to set the container rights a LDAP search operation. */
129    static final int ACI_SEARCH = 0x0002;
130    /** ACI_SELF is used for the SELFWRITE right. */
131    public static final int ACI_SELF = 0x0040;
132    /**
133     * ACI_ALL is used to as a mask for all of the above. These
134     * six below are not masked by the ACI_ALL.
135     */
136    static final int ACI_ALL = 0x007F;
137    /** ACI_PROXY is used for the PROXY right. */
138    public static final int ACI_PROXY = 0x0080;
139    /** ACI_IMPORT is used to set the container rights for a LDAP modify dn operation. */
140    static final int ACI_IMPORT = 0x0100;
141    /** ACI_EXPORT is used to set the container rights for a LDAP modify dn operation. */
142    static final int ACI_EXPORT = 0x0200;
143    /** ACI_WRITE_ADD is used by the LDAP modify operation. */
144    static final int ACI_WRITE_ADD = 0x800;
145    /** ACI_WRITE_DELETE is used by the LDAP modify operation. */
146    public static final int ACI_WRITE_DELETE = 0x400;
147    /** ACI_SKIP_PROXY_CHECK is used to bypass the proxy access check. */
148    public static final int ACI_SKIP_PROXY_CHECK = 0x400000;
149
150    /**
151     * TARGATTRFILTER_ADD is used to specify that a
152     * targattrfilters ADD operation was seen in the ACI. For example,
153     * given an ACI with:
154     *
155     * (targattrfilters="add=mail:(mail=*@example.com)")
156     *
157     * The TARGATTRFILTERS_ADD flag would be set during ACI parsing in the
158     * TargAttrFilters class.
159     */
160    static final int TARGATTRFILTERS_ADD = 0x1000;
161
162    /**
163     * TARGATTRFILTER_DELETE is used to specify that a
164     * targattrfilters DELETE operation was seen in the ACI. For example,
165     * given an ACI with:
166     *
167     * (targattrfilters="del=mail:(mail=*@example.com)")
168     *
169     * The TARGATTRFILTERS_DELETE flag would be set during ACI parsing in the
170     * TargAttrFilters class.
171     */
172    static final int TARGATTRFILTERS_DELETE = 0x2000;
173
174    /** Used by the control evaluation access check. */
175    static final int ACI_CONTROL = 0x4000;
176
177    /** Used by the extended operation access check. */
178    public static final int ACI_EXT_OP = 0x8000;
179
180    /**
181     * ACI_ATTR_STAR_MATCHED is the flag set when the evaluation reason of a
182     * AciHandler.maysend ACI_READ access evaluation was the result of an
183     * ACI targetattr all attributes expression (targetattr="*") target match.
184     * For this flag to be set, there must be only one ACI matching.
185     *
186     * This flag and ACI_FOUND_ATTR_RULE are used in the
187     * AciHandler.filterEntry.accessAllowedAttrs method to skip access
188     * evaluation if the flag is ACI_ATTR_STAR_MATCHED (all attributes match)
189     * and the attribute type is not operational.
190     */
191    static final int ACI_USER_ATTR_STAR_MATCHED = 0x0008;
192
193    /**
194     * ACI_FOUND_USER_ATTR_RULE is the flag set when the evaluation reason of a
195     * AciHandler.maysend ACI_READ access evaluation was the result of an
196     * ACI targetattr specific user attribute expression
197     * (targetattr="some user attribute type") target match.
198     */
199    static final int ACI_FOUND_USER_ATTR_RULE = 0x0010;
200
201    /**
202     * ACI_OP_ATTR_PLUS_MATCHED is the flag set when the evaluation reason of a
203     * AciHandler.maysend ACI_READ access evaluation was the result of an
204     * ACI targetattr all operational attributes expression (targetattr="+")
205     * target match. For this flag to be set, there must be only one
206     * ACI matching.
207     *
208     * This flag and ACI_FOUND_OP_ATTR_RULE are used in the
209     * AciHandler.filterEntry.accessAllowedAttrs method to skip access
210     * evaluation if the flag is ACI_OP_ATTR_PLUS_MATCHED (all operational
211     * attributes match) and the attribute type is operational.
212     */
213    static final int ACI_OP_ATTR_PLUS_MATCHED = 0x0004;
214
215    /**
216     * ACI_FOUND_OP_ATTR_RULE is the flag set when the evaluation reason of a
217     * AciHandler.maysend ACI_READ access evaluation was the result of an
218     * ACI targetattr specific operational attribute expression
219     * (targetattr="some operational attribute type") target match.
220     */
221    static final int ACI_FOUND_OP_ATTR_RULE = 0x0020;
222
223    /** ACI_NULL is used to set the container rights to all zeros. Used by LDAP modify. */
224    static final int ACI_NULL = 0x0000;
225
226    /**
227     * Construct a new Aci from the provided arguments.
228     * @param input The string representation of the ACI.
229     * @param dn The DN of entry containing the ACI.
230     * @param body The body of the ACI.
231     * @param targets The targets of the ACI.
232     */
233    private  Aci(String input, DN dn, AciBody body, AciTargets targets) {
234        this.aciString  = input;
235        this.dn=dn;
236        this.body=body;
237        this.targets=targets;
238    }
239
240    /**
241     * Decode an ACI byte string.
242     * @param byteString The ByteString containing the ACI string.
243     * @param dn DN of the ACI entry.
244     * @return  Returns a decoded ACI representing the string argument.
245     * @throws AciException If the parsing of the ACI string fails.
246     */
247    public static Aci decode (ByteSequence byteString, DN dn)
248    throws AciException {
249        String input=byteString.toString();
250        //Perform a quick pattern check against the string to catch any
251        //obvious syntax errors.
252        if (!Pattern.matches(aciRegex, input)) {
253            throw new AciException(WARN_ACI_SYNTAX_GENERAL_PARSE_FAILED.get(input));
254        }
255        //Decode the body first.
256        AciBody body=AciBody.decode(input);
257        //Create a substring from the start of the string to start of
258        //the body. That should be the target.
259        String targetStr = input.substring(0, body.getMatcherStartPos());
260        //Decode that target string using the substring.
261        AciTargets targets=AciTargets.decode(targetStr, dn);
262        return new Aci(input, dn, body, targets);
263    }
264
265    /**
266     * Return the string representation of the ACI. This was the string that
267     * was used to create the Aci class.
268     * @return A string representation of the ACI.
269     */
270    @Override
271    public String toString() {
272        return aciString;
273    }
274
275    /**
276     * Returns the targets of the ACI.
277     * @return Any AciTargets of the ACI. There may be no targets
278     * so this might be null.
279     */
280    public AciTargets getTargets() {
281        return targets;
282    }
283
284    /**
285     * Return the DN of the entry containing the ACI.
286     * @return The DN of the entry containing the ACI.
287     */
288    public DN getDN() {
289        return dn;
290    }
291
292    /**
293     * Test if the given ACI is applicable using the target match information
294     * provided. The ACI target can have seven keywords at this time:
295     *
296     * These two base decision on the resource entry DN:
297     *
298     *       1. target - checked in isTargetApplicable.
299     *       2. targetscope - checked in isTargetApplicable.
300     *
301     * These three base decision on resource entry attributes:
302     *
303     *       3. targetfilter - checked in isTargetFilterApplicable.
304     *       4. targetattr - checked in isTargetAttrApplicable.
305     *       5. targattrfilters -  checked in isTargAttrFiltersApplicable.
306     *
307     * These two base decisions on a resource entry built by the ACI handler
308     * that only contains a DN:
309     *       6. targetcontrol - check in isTargetControlApplicable.
310     *       7. extop - check in isExtOpApplicable.
311     *
312     * Six and seven are specific to the check being done: targetcontrol when a
313     * control is being evaluated and extop when an extended operation is
314     * evaluated.  None of the attribute based keywords should be checked
315     * when a control or extended op is being evaluated, because one
316     * of those attribute keywords rule might incorrectly make an ACI
317     * applicable that shouldn't be. This can happen by erroneously basing
318     * their decision on the ACI handler generated stub resource entry. For
319     * example, a "(targetattr != userpassword)" rule would match the generated
320     * stub resource entry, even though a control or extended op might be
321     * denied.
322     *
323     * What is allowed is the target and targetscope keywords, since the DN is
324     * known, so they are checked along with the correct method for the access
325     * check (isTargetControlApplicable for control and
326     * isTExtOpApplicable for extended operations). See comments in code
327     * where these checks are done.
328     *
329     * @param aci The ACI to test.
330     * @param matchCtx The target matching context containing all the info
331     * needed to match ACI targets.
332     * @return  True if this ACI targets are applicable or match.
333     */
334    public static boolean isApplicable(Aci aci, AciTargetMatchContext matchCtx) {
335      if(matchCtx.hasRights(ACI_EXT_OP)) {
336        //Extended operation is being evaluated.
337         return AciTargets.isTargetApplicable(aci, matchCtx) &&
338                 AciTargets.isExtOpApplicable(aci, matchCtx);
339      } else if(matchCtx.hasRights(ACI_CONTROL)) {
340        //Control is being evaluated.
341         return AciTargets.isTargetApplicable(aci, matchCtx) &&
342                AciTargets.isTargetControlApplicable(aci, matchCtx);
343      } else {
344        //If an ACI has extOp or targetControl targets skip it because the
345        //matchCtx right does not contain either ACI_EXT_OP or ACI_CONTROL at
346        //this point.
347        return hasNoExtOpOrTargetControl(aci.getTargets())
348            && haveSimilarRights(aci, matchCtx)
349            && AciTargets.isTargetApplicable(aci, matchCtx)
350            && AciTargets.isTargetFilterApplicable(aci, matchCtx)
351            && AciTargets.isTargAttrFiltersApplicable(aci, matchCtx)
352            && AciTargets.isTargetAttrApplicable(aci, matchCtx);
353      }
354    }
355
356    private static boolean hasNoExtOpOrTargetControl(AciTargets aciTargets)
357    {
358      return aciTargets.getExtOp() == null
359          && aciTargets.getTargetControl() == null;
360    }
361
362    private static boolean haveSimilarRights(Aci aci,
363        AciTargetMatchContext matchCtx)
364    {
365      return aci.hasRights(matchCtx.getRights())
366            || (aci.hasRights(ACI_SEARCH| ACI_READ)
367                  && matchCtx.hasRights(ACI_SEARCH | ACI_READ));
368    }
369
370    /**
371     * Check if the body of the ACI matches the rights specified.
372     * @param rights Bit mask representing the rights to match.
373     * @return True if the body's rights match one of the rights specified.
374     */
375    public boolean hasRights(int rights) {
376        return body.hasRights(rights);
377    }
378
379    /**
380     * Re-direct has access type to the body's hasAccessType method.
381     * @param accessType The access type to match.
382     * @return  True if the body's hasAccessType determines a permission
383     * contains this access type (allow or deny are valid types).
384     */
385    public boolean hasAccessType(EnumAccessType accessType) {
386        return body.hasAccessType(accessType);
387    }
388
389    /**
390     * Evaluate this ACI using the evaluation context provided. Re-direct
391     * that calls the body's evaluate method.
392     * @param evalCtx The evaluation context to evaluate with.
393     * @return EnumEvalResult that contains the evaluation result of this
394     * aci evaluation.
395     */
396    private EnumEvalResult evaluate(AciEvalContext evalCtx) {
397        return body.evaluate(evalCtx);
398    }
399
400    /**
401     * Static class used to evaluate an ACI and evaluation context.
402     * @param evalCtx  The context to evaluate with.
403     * @param aci The ACI to evaluate.
404     * @return EnumEvalResult that contains the evaluation result of the aci
405     * evaluation.
406     */
407    public static EnumEvalResult evaluate(AciEvalContext evalCtx, Aci aci) {
408        return aci.evaluate(evalCtx);
409    }
410
411    /**
412     * Returns the name string of this ACI.
413     * @return The name string.
414     */
415    public String getName() {
416      return this.body.getName();
417    }
418
419  /**
420   *  Decode an OIDs expression string.
421   *
422   * @param expr A string representing the OID expression.
423   * @param msg  A message to be used if there is an exception.
424   *
425   * @return  Return a hash set of verified OID strings parsed from the OID
426   *          expression.
427   *
428   * @throws AciException If the specified expression string is invalid.
429   */
430    public static Set<String> decodeOID(String expr, LocalizableMessage msg)
431    throws AciException {
432      Set<String> OIDs = new HashSet<>();
433      //Quick check to see if the expression is valid.
434      if (Pattern.matches(oidListRegex, expr)) {
435        // Remove the spaces in the oid string and
436        // split the list.
437        Pattern separatorPattern =
438                Pattern.compile(LOGICAL_OR);
439        String oidString =
440                expr.replaceAll(ZERO_OR_MORE_WHITESPACE, "");
441        String[] oidArray=
442                separatorPattern.split(oidString);
443        //More careful analysis of each OID string.
444        for(String oid : oidArray) {
445          verifyOid(oid);
446          OIDs.add(oid);
447        }
448      } else {
449        throw new AciException(msg);
450      }
451      return OIDs;
452    }
453
454    /**
455     * Verify the specified OID string.
456     *
457     * @param oidStr The string representing an OID.
458     *
459     * @throws AciException If the specified string is invalid.
460     */
461    private static void verifyOid(String oidStr) throws AciException {
462      int pos=0, length=oidStr.length();
463      char c;
464      if("*".equals(oidStr))
465      {
466        return;
467      }
468      boolean lastWasPeriod = false;
469      while (pos < length && ((c = oidStr.charAt(pos++)) != ' ')) {
470        if (c == '.') {
471          if (lastWasPeriod) {
472            LocalizableMessage message = WARN_ACI_SYNTAX_DOUBLE_PERIOD_IN_NUMERIC_OID.get(
473                oidStr, pos-1);
474            throw new AciException(message);
475          }
476          lastWasPeriod = true;
477        }  else if (! isDigit(c)) {
478          LocalizableMessage message =
479              WARN_ACI_SYNTAX_ILLEGAL_CHAR_IN_NUMERIC_OID.get(oidStr, c, pos-1);
480          throw new AciException(message);
481        } else {
482          lastWasPeriod = false;
483        }
484      }
485    }
486
487    /**
488     * Compares this Aci with the provided Aci based on a natural order.
489     * This order will be first hierarchical (ancestors will come before
490     * descendants) and then alphabetical by attribute name(s) and
491     * value(s).
492     *
493     * @param  aci  The Aci against which to compare this Aci.
494     *
495     * @return  A negative integer if this Aci should come before the
496     *          provided Aci, a positive integer if this Aci should come
497     *          after the provided Aci, or zero if there is no difference
498     *          with regard to ordering.
499     */
500    @Override
501    public int compareTo(Aci aci)
502    {
503      return this.aciString.compareTo(aci.toString());
504    }
505 }