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