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-2010 Sun Microsystems, Inc.
015 * Portions Copyright 2013-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.EnumTargetOperator.*;
022
023import java.util.regex.Matcher;
024import java.util.regex.Pattern;
025
026import org.forgerock.i18n.LocalizableMessage;
027import org.forgerock.opendj.ldap.DN;
028import org.forgerock.opendj.ldap.SearchScope;
029import org.forgerock.opendj.ldap.schema.AttributeType;
030
031/**
032 * This class represents target part of an ACI's syntax. This is the part
033 * of an ACI before the ACI body and specifies the entry, attributes, or set
034 * of entries and attributes which the ACI controls access.
035 *
036 * The supported  ACI target keywords are: target, targetattr,
037 * targetscope, targetfilter, targattrfilters, targetcontrol and extop.
038 */
039public class AciTargets {
040    /** ACI syntax has a target keyword. */
041    private final Target target;
042    /** ACI syntax has a targetscope keyword. */
043    private final SearchScope targetScope;
044    /** ACI syntax has a targetattr keyword. */
045    private final TargetAttr targetAttr;
046    /** ACI syntax has a targetfilter keyword. */
047    private final TargetFilter targetFilter;
048    /** ACI syntax has a targattrtfilters keyword. */
049    private final TargAttrFilters targAttrFilters;
050    /** The ACI syntax has a targetcontrol keyword. */
051    private final TargetControl targetControl;
052    /** The ACI syntax has a extop keyword. */
053    private final ExtOp extOp;
054
055    /** The number of regular expression group positions in a valid ACI target expression. */
056    private static final int targetElementCount = 3;
057    /** Regular expression group position of a target keyword. */
058    private static final int targetKeywordPos       = 1;
059    /** Regular expression group position of a target operator enumeration. */
060    private static final int targetOperatorPos      = 2;
061    /** Regular expression group position of a target expression statement. */
062    private static final int targetExpressionPos    = 3;
063
064    /** Regular expression used to match a single target rule. */
065    private static final String targetRegex =
066           OPEN_PAREN +  ZERO_OR_MORE_WHITESPACE  +  WORD_GROUP +
067           ZERO_OR_MORE_WHITESPACE + "(!?=)" + ZERO_OR_MORE_WHITESPACE +
068           "\"([^\"]+)\"" + ZERO_OR_MORE_WHITESPACE + CLOSED_PAREN +
069           ZERO_OR_MORE_WHITESPACE;
070
071    /**
072     * Regular expression used to match one or more target rules. The pattern is
073     * part of a general ACI verification.
074     */
075    static final String targetsRegex = "(" + targetRegex + ")*";
076
077    /**
078     * Rights that are skipped for certain target evaluations.
079     * The test is use the skipRights array is:
080     *
081     * Either the ACI has a targetattr's rule and the current
082     * attribute type is null or the current attribute type has
083     * a type specified and the targetattr's rule is null.
084     *
085     * The actual check against the skipRights array is:
086     *
087     *  1. Is the ACI's rights in this array? For example,
088     *     allow(all) or deny(add)
089     *
090     *  AND
091     *
092     *  2. Is the rights from the LDAP operation in this array? For
093     *      example, an LDAP add would have rights of add and all.
094     *
095     *  If both are true, than the target match test returns true
096     *  for this ACI.
097     */
098    private static final int skipRights = ACI_ADD | ACI_DELETE | ACI_PROXY;
099
100    /**
101     * Creates an ACI target from the specified arguments. All of these
102     * may be null. If the ACI has no targets defaults will be used.
103     *
104     * @param targetEntry The ACI target keyword class.
105     * @param targetAttr The ACI targetattr keyword class.
106     * @param targetFilter The ACI targetfilter keyword class.
107     * @param targetScope The ACI targetscope keyword class.
108     * @param targAttrFilters The ACI targAttrFilters keyword class.
109     * @param targetControl The ACI targetControl keyword class.
110     * @param extOp The ACI extop keyword class.
111     */
112    private AciTargets(Target targetEntry, TargetAttr targetAttr,
113                       TargetFilter targetFilter,
114                       SearchScope targetScope,
115                       TargAttrFilters targAttrFilters,
116                       TargetControl targetControl,
117                       ExtOp extOp) {
118       this.target=targetEntry;
119       this.targetAttr=targetAttr;
120       this.targetScope=targetScope;
121       this.targetFilter=targetFilter;
122       this.targAttrFilters=targAttrFilters;
123       this.targetControl=targetControl;
124       this.extOp=extOp;
125    }
126
127    /**
128     * Return class representing the ACI target keyword. May be
129     * null. The default is the use the DN of the entry containing
130     * the ACI and check if the resource entry is a descendant of that.
131     * @return The ACI target class.
132     */
133    private Target getTarget() {
134        return target;
135    }
136
137    /**
138     * Return class representing the ACI targetattr keyword. May be null.
139     * The default is to not match any attribute types in an entry.
140     * @return The ACI targetattr class.
141     */
142    public TargetAttr getTargetAttr() {
143        return targetAttr;
144    }
145
146    /**
147     * Return the ACI targetscope keyword. Default is WHOLE_SUBTREE.
148     * @return The ACI targetscope information.
149     */
150    public SearchScope getTargetScope() {
151        return targetScope;
152    }
153
154    /**
155     * Return class representing the  ACI targetfilter keyword. May be null.
156     * @return The targetscope information.
157     */
158    public TargetFilter getTargetFilter() {
159        return targetFilter;
160    }
161
162    /**
163     * Return the class representing the ACI targattrfilters keyword. May be
164     * null.
165     * @return The targattrfilters information.
166     */
167    public TargAttrFilters getTargAttrFilters() {
168        return targAttrFilters;
169    }
170
171   /**
172    * Return the class representing the ACI targetcontrol keyword. May be
173    * null.
174    * @return The targetcontrol information.
175   */
176    public TargetControl getTargetControl() {
177      return targetControl;
178    }
179
180
181   /**
182    * Return the class representing the ACI extop keyword. May be
183    * null.
184    * @return The extop information.
185   */
186    public ExtOp getExtOp() {
187      return extOp;
188    }
189
190    /**
191     * Decode an ACI's target part of the syntax from the string provided.
192     * @param input String representing an ACI target part of syntax.
193     * @param dn The DN of the entry containing the ACI.
194     * @return An AciTargets class representing the decoded ACI target string.
195     * @throws AciException If the provided string contains errors.
196     */
197    public static AciTargets decode(String input, DN dn)
198    throws AciException {
199        Target target=null;
200        TargetAttr targetAttr=null;
201        TargetFilter targetFilter=null;
202        TargAttrFilters targAttrFilters=null;
203        TargetControl targetControl=null;
204        ExtOp extOp=null;
205        SearchScope targetScope=SearchScope.WHOLE_SUBTREE;
206        Pattern targetPattern = Pattern.compile(targetRegex);
207        Matcher targetMatcher = targetPattern.matcher(input);
208        while (targetMatcher.find())
209        {
210            if (targetMatcher.groupCount() != targetElementCount) {
211                LocalizableMessage message =
212                    WARN_ACI_SYNTAX_INVALID_TARGET_SYNTAX.get(input);
213                throw new AciException(message);
214            }
215            String keyword = targetMatcher.group(targetKeywordPos);
216            EnumTargetKeyword targetKeyword  =
217                EnumTargetKeyword.createKeyword(keyword);
218            if (targetKeyword == null) {
219                LocalizableMessage message =
220                    WARN_ACI_SYNTAX_INVALID_TARGET_KEYWORD.get(keyword);
221                throw new AciException(message);
222            }
223            String operator =
224                targetMatcher.group(targetOperatorPos);
225            EnumTargetOperator targetOperator =
226                EnumTargetOperator.createOperator(operator);
227            if (targetOperator == null) {
228                LocalizableMessage message =
229                    WARN_ACI_SYNTAX_INVALID_TARGETS_OPERATOR.get(operator);
230                throw new AciException(message);
231            }
232            String expression = targetMatcher.group(targetExpressionPos);
233            switch(targetKeyword)
234            {
235            case KEYWORD_TARGET:
236            {
237                if (target == null){
238                    target =  Target.decode(targetOperator, expression, dn);
239                }
240                else
241                {
242                  LocalizableMessage message =
243                          WARN_ACI_SYNTAX_INVALID_TARGET_DUPLICATE_KEYWORDS.
244                                  get("target", input);
245                  throw new AciException(message);
246                }
247                break;
248            }
249            case KEYWORD_TARGETCONTROL:
250            {
251              if (targetControl == null){
252                targetControl =
253                        TargetControl.decode(targetOperator, expression);
254              }
255              else
256              {
257                LocalizableMessage message =
258                        WARN_ACI_SYNTAX_INVALID_TARGET_DUPLICATE_KEYWORDS.
259                                get("targetcontrol", input);
260                throw new AciException(message);
261              }
262              break;
263            }
264            case KEYWORD_EXTOP:
265            {
266              if (extOp == null){
267                extOp =  ExtOp.decode(targetOperator, expression);
268              }
269              else
270              {
271                LocalizableMessage message =
272                        WARN_ACI_SYNTAX_INVALID_TARGET_DUPLICATE_KEYWORDS.
273                                get("extop", input);
274                throw new AciException(message);
275              }
276              break;
277            }
278            case KEYWORD_TARGETATTR:
279            {
280                if (targetAttr == null){
281                    targetAttr = TargetAttr.decode(targetOperator,
282                            expression);
283                }
284                else {
285                  LocalizableMessage message =
286                          WARN_ACI_SYNTAX_INVALID_TARGET_DUPLICATE_KEYWORDS.
287                                  get("targetattr", input);
288                  throw new AciException(message);
289                }
290                break;
291            }
292            case KEYWORD_TARGETSCOPE:
293            {
294                // Check the operator for the targetscope is EQUALITY
295                if (targetOperator == EnumTargetOperator.NOT_EQUALITY) {
296                    LocalizableMessage message =
297                            WARN_ACI_SYNTAX_INVALID_TARGET_NOT_OPERATOR.
298                              get(operator, targetKeyword.name());
299                    throw new AciException(message);
300                }
301                targetScope=createScope(expression);
302                break;
303            }
304            case KEYWORD_TARGETFILTER:
305            {
306                if (targetFilter == null){
307                    targetFilter = TargetFilter.decode(targetOperator,
308                            expression);
309                }
310                else {
311                  LocalizableMessage message =
312                          WARN_ACI_SYNTAX_INVALID_TARGET_DUPLICATE_KEYWORDS.
313                                  get("targetfilter", input);
314                  throw new AciException(message);
315                }
316                break;
317            }
318            case KEYWORD_TARGATTRFILTERS:
319            {
320                if (targAttrFilters == null){
321                    // Check the operator for the targattrfilters is EQUALITY
322                    if (targetOperator == EnumTargetOperator.NOT_EQUALITY) {
323                      LocalizableMessage message =
324                              WARN_ACI_SYNTAX_INVALID_TARGET_NOT_OPERATOR.
325                                      get(operator, targetKeyword.name());
326                      throw new AciException(message);
327                    }
328                    targAttrFilters = TargAttrFilters.decode(targetOperator,
329                            expression);
330                }
331                else {
332                  LocalizableMessage message =
333                          WARN_ACI_SYNTAX_INVALID_TARGET_DUPLICATE_KEYWORDS.
334                                  get("targattrfilters", input);
335                  throw new AciException(message);
336                }
337                break;
338            }
339            }
340        }
341        return new AciTargets(target, targetAttr, targetFilter,
342                              targetScope, targAttrFilters, targetControl,
343                              extOp);
344    }
345
346    /**
347     * Evaluates a provided scope string and returns an appropriate
348     * SearchScope enumeration.
349     * @param expression The expression string.
350     * @return An search scope enumeration matching the string.
351     * @throws AciException If the expression is an invalid targetscope
352     * string.
353     */
354    private static SearchScope createScope(String expression)
355    throws AciException {
356        if(expression.equalsIgnoreCase("base"))
357        {
358          return SearchScope.BASE_OBJECT;
359        }
360        else if(expression.equalsIgnoreCase("onelevel"))
361        {
362          return SearchScope.SINGLE_LEVEL;
363        }
364        else if(expression.equalsIgnoreCase("subtree"))
365        {
366          return SearchScope.WHOLE_SUBTREE;
367        }
368        else if(expression.equalsIgnoreCase("subordinate"))
369        {
370          return SearchScope.SUBORDINATES;
371        }
372        else {
373            LocalizableMessage message =
374                WARN_ACI_SYNTAX_INVALID_TARGETSCOPE_EXPRESSION.get(expression);
375            throw new AciException(message);
376        }
377    }
378
379    /**
380     * Checks an ACI's targetfilter rule information against a target match
381     * context.
382     * @param aci The ACI to try an match the targetfilter of.
383     * @param matchCtx The target match context containing information needed
384     * to perform a target match.
385     * @return True if the targetfilter rule matched the target context.
386     */
387    public static boolean isTargetFilterApplicable(Aci aci,
388                                              AciTargetMatchContext matchCtx) {
389        TargetFilter targetFilter=aci.getTargets().getTargetFilter();
390        return targetFilter == null || targetFilter.isApplicable(matchCtx);
391    }
392
393    /**
394     * Check an ACI's targetcontrol rule against a target match context.
395     *
396     * @param aci The ACI to match the targetcontrol against.
397     * @param matchCtx The target match context containing the information
398     *                 needed to perform the target match.
399     * @return  True if the targetcontrol rule matched the target context.
400     */
401    public static boolean isTargetControlApplicable(Aci aci,
402                                            AciTargetMatchContext matchCtx) {
403      TargetControl targetControl=aci.getTargets().getTargetControl();
404      return targetControl != null && targetControl.isApplicable(matchCtx);
405    }
406
407    /**
408     * Check an ACI's extop rule against a target match context.
409     *
410     * @param aci The ACI to match the extop rule against.
411     * @param matchCtx The target match context containing the information
412     *                 needed to perform the target match.
413     * @return  True if the extop rule matched the target context.
414     */
415    public static boolean isExtOpApplicable(Aci aci,
416                                              AciTargetMatchContext matchCtx) {
417      ExtOp extOp=aci.getTargets().getExtOp();
418      return extOp != null && extOp.isApplicable(matchCtx);
419    }
420
421
422    /**
423     * Check an ACI's targattrfilters rule against a target match context.
424     *
425     * @param aci The ACI to match the targattrfilters against.
426     * @param matchCtx  The target match context containing the information
427     * needed to perform the target match.
428     * @return True if the targattrfilters rule matched the target context.
429     */
430    public static boolean isTargAttrFiltersApplicable(Aci aci,
431                                               AciTargetMatchContext matchCtx) {
432        TargAttrFilters targAttrFilters=aci.getTargets().getTargAttrFilters();
433        if(targAttrFilters != null) {
434            if((matchCtx.hasRights(ACI_ADD) &&
435                targAttrFilters.hasMask(TARGATTRFILTERS_ADD)) ||
436              (matchCtx.hasRights(ACI_DELETE) &&
437               targAttrFilters.hasMask(TARGATTRFILTERS_DELETE)))
438            {
439              return targAttrFilters.isApplicableAddDel(matchCtx);
440            }
441            else if((matchCtx.hasRights(ACI_WRITE_ADD) &&
442                     targAttrFilters.hasMask(TARGATTRFILTERS_ADD)) ||
443                    (matchCtx.hasRights(ACI_WRITE_DELETE) &&
444                    targAttrFilters.hasMask(TARGATTRFILTERS_DELETE)))
445            {
446              return targAttrFilters.isApplicableMod(matchCtx, aci);
447            }
448        }
449        return true;
450    }
451
452    /*
453     * TODO Evaluate making this method more efficient.
454     * The isTargetAttrApplicable method looks a lot less efficient than it
455     * could be with regard to the logic that it employs and the repeated use
456     * of method calls over local variables.
457     */
458    /**
459     * Checks an provided ACI's targetattr rule against a target match
460     * context.
461     *
462     * @param aci The ACI to evaluate.
463     * @param targetMatchCtx The target match context to check the ACI against.
464     * @return True if the targetattr matched the target context.
465     */
466    public static boolean isTargetAttrApplicable(Aci aci,
467                                         AciTargetMatchContext targetMatchCtx) {
468        boolean ret=true;
469        if(!targetMatchCtx.getTargAttrFiltersMatch()) {
470            TargetAttr targetAttr = aci.getTargets().getTargetAttr();
471            AttributeType attrType = targetMatchCtx.getCurrentAttributeType();
472            boolean isFirstAttr=targetMatchCtx.isFirstAttribute();
473
474            if (attrType != null && targetAttr != null)  {
475              ret=TargetAttr.isApplicable(attrType,targetAttr);
476              setEvalAttributes(targetMatchCtx,targetAttr,ret);
477            } else if (attrType != null || targetAttr != null) {
478                if (aci.hasRights(skipRights)
479                        && skipRightsHasRights(targetMatchCtx.getRights())) {
480                    ret = true;
481                } else {
482                    ret = attrType == null
483                        && targetAttr != null
484                        && aci.hasRights(ACI_WRITE);
485                }
486            }
487            if (isFirstAttr && targetAttr == null
488                && aci.getTargets().getTargAttrFilters() == null)
489            {
490              targetMatchCtx.setEntryTestRule(true);
491            }
492        }
493        return ret;
494    }
495
496    /**
497     * Try and match a one or more of the specified rights in the skiprights
498     * mask.
499     * @param rights The rights to check for.
500     * @return  True if the one or more of the specified rights are in the
501     * skiprights rights mask.
502     */
503    private static boolean skipRightsHasRights(int rights) {
504      //geteffectiverights sets this flag, turn it off before evaluating.
505      int tmpRights=rights & ~ACI_SKIP_PROXY_CHECK;
506      return (skipRights & tmpRights) == tmpRights;
507    }
508
509
510    /**
511     * Wrapper class that passes an ACI, an ACI's targets and the specified
512     * target match context's resource entry DN to the main isTargetApplicable
513     * method.
514     * @param aci The ACI currently be matched.
515     * @param matchCtx The target match context to match against.
516     * @return True if the target matched the ACI.
517     */
518    public static boolean isTargetApplicable(Aci aci,
519                                             AciTargetMatchContext matchCtx) {
520        return isTargetApplicable(aci, aci.getTargets(),
521                                        matchCtx.getResourceEntry().getName());
522    }
523
524    /*
525     * TODO Investigate supporting alternative representations of the scope.
526     *
527     * Should we also consider supporting alternate representations of the
528     * scope values (in particular, allow "one" in addition to "onelevel"
529     * and "sub" in addition to "subtree") to match the very common
530     * abbreviations in widespread use for those terms?
531     */
532    /**
533     * Main target isApplicable method. This method performs the target keyword
534     * match functionality, which allows for directory entry "targeting" using
535     * the specified ACI, ACI targets class and DN.
536     *
537     * @param aci The ACI to match the target against.
538     * @param targets The targets to use in this evaluation.
539     * @param entryDN The DN to use in this evaluation.
540     * @return True if the ACI matched the target and DN.
541     */
542    public static boolean isTargetApplicable(Aci aci, AciTargets targets, DN entryDN) {
543        DN targetDN=aci.getDN();
544        /*
545         * Scoping of the ACI uses either the DN of the entry
546         * containing the ACI (aci.getDN above), or if the ACI item
547         * contains a simple target DN and a equality operator, that
548         * simple target DN is used as the target DN.
549         */
550        Target target = targets.getTarget();
551        if(target != null && !target.isPattern() && target.getOperator() != NOT_EQUALITY)
552        {
553          targetDN=target.getDN();
554        }
555        if (!isInScopeOf(entryDN, targetDN, targets.getTargetScope()))
556        {
557          return false;
558        }
559
560        if (target != null)
561        {
562          /*
563           * For inequality checks, scope was tested against the entry containing the ACI.
564           * If operator is inequality, check that it doesn't match the target DN.
565           */
566          if (!target.isPattern()
567              && target.getOperator() == NOT_EQUALITY
568              && entryDN.isSubordinateOrEqualTo(target.getDN()))
569          {
570            return false;
571          }
572          /*
573           * There is a pattern, need to match the substring filter
574           * created when the ACI was decoded. If inequality flip the result.
575           */
576          if(target.isPattern())  {
577            final boolean ret = target.matchesPattern(entryDN);
578            if (target.getOperator() == NOT_EQUALITY) {
579              return !ret;
580            }
581            return ret;
582          }
583        }
584        return true;
585    }
586
587    private static boolean isInScopeOf(DN entryDN, DN targetDN, SearchScope scope) {
588      switch(scope.asEnum()) {
589      case BASE_OBJECT:
590          return targetDN.equals(entryDN);
591      case SINGLE_LEVEL:
592          /*
593           * We use the standard definition of single level to mean the
594           * immediate children only -- not the target entry itself.
595           * Sun CR 6535035 has been raised on DSEE:
596           * Non-standard interpretation of onelevel in ACI targetScope.
597           */
598          return targetDN.equals(entryDN.parent());
599      case WHOLE_SUBTREE:
600          return entryDN.isSubordinateOrEqualTo(targetDN);
601      case SUBORDINATES:
602          return entryDN.size() > targetDN.size() && entryDN.isSubordinateOrEqualTo(targetDN);
603      default:
604          return false;
605      }
606    }
607
608    /**
609     * The method is used to try and determine if a targetAttr expression that
610     * is applicable has a '*' (or '+' operational attributes) token or if it
611     * was applicable because of a specific attribute type declared in the
612     * targetattrs expression (i.e., targetattrs=cn).
613     *
614     *
615     * @param ctx  The ctx to check against.
616     * @param targetAttr The targetattrs part of the ACI.
617     * @param ret  The is true if the ACI has already been evaluated to be
618     *             applicable.
619     */
620    private static
621    void setEvalAttributes(AciTargetMatchContext ctx, TargetAttr targetAttr,
622                           boolean ret) {
623        ctx.clearEvalAttributes(ACI_USER_ATTR_STAR_MATCHED);
624        ctx.clearEvalAttributes(ACI_OP_ATTR_PLUS_MATCHED);
625        /*
626         If an applicable targetattr's match rule has not
627         been seen (~ACI_FOUND_OP_ATTR_RULE or ~ACI_FOUND_USER_ATTR_RULE) and
628         the current attribute type is applicable because of a targetattr all
629         user (or operational) attributes rule match,
630         set a flag to indicate this situation (ACI_USER_ATTR_STAR_MATCHED or
631         ACI_OP_ATTR_PLUS_MATCHED). This check also catches the following case
632         where the match was by a specific attribute type (either user or
633         operational) and the other attribute type has an all attribute token.
634         For example, the expression is: (targetattrs="cn || +) and the current
635         attribute type is cn.
636        */
637        if(ret && targetAttr.isAllUserAttributes() &&
638                !ctx.hasEvalUserAttributes())
639        {
640          ctx.setEvalUserAttributes(ACI_USER_ATTR_STAR_MATCHED);
641        }
642        else
643        {
644          ctx.setEvalUserAttributes(ACI_FOUND_USER_ATTR_RULE);
645        }
646
647        if(ret && targetAttr.isAllOpAttributes() &&
648                !ctx.hasEvalOpAttributes())
649        {
650          ctx.setEvalOpAttributes(ACI_OP_ATTR_PLUS_MATCHED);
651        }
652        else
653        {
654          ctx.setEvalOpAttributes(ACI_FOUND_OP_ATTR_RULE);
655        }
656    }
657}