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