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