/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying * information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Portions Copyright 2007 Sun Microsystems, Inc. */ package org.opends.server.authorization.dseecompat; import static org.opends.server.authorization.dseecompat.AciMessages.*; import static org.opends.server.messages.MessageHandler.getMessage; import org.opends.server.types.AttributeType; import org.opends.server.types.DN; import org.opends.server.types.SearchScope; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * This class represents target part of an ACI's syntax. This is the part * of an ACI before the ACI body and specifies the entry, attributes, or set * of entries and attributes which the ACI controls access. * * The four supported ACI target keywords currently * supported are: target, targetattr, targetscope and targetfilter. * Missing is support for targetattrfilters. */ public class AciTargets { /* * ACI syntax has a target keyword. */ private Target target = null ; /* * ACI syntax has a targetscope keyword. */ private SearchScope targetScope = SearchScope.WHOLE_SUBTREE; /* * ACI syntax has a targetattr keyword. */ private TargetAttr targetAttr = null ; /* * ACI syntax has a targetfilter keyword. */ private TargetFilter targetFilter=null; private TargAttrFilters targAttrFilters=null; /* * These are used in the regular expression parsing. */ private static final int targetElementCount = 3; private static final int targetKeywordPos = 1; private static final int targetOperatorPos = 2; private static final int targetExpressionPos = 3; /* * TODO Make the regular expression strings below easier to * understand. * * The same note earlier about making regex values easier to * understand applies to this class as well. */ private static final String targetRegex = "\\(\\s*(\\w+)\\s*(!?=)\\s*\"([^\"]+)\"\\s*\\)\\s*"; /** * Regular expression used in target matching. */ public static final String targetsRegex = "(" + targetRegex + ")*"; /* * Rights that are skipped for certain target evaluations. * The test is use the skipRights array is: * * Either the ACI has a targetattr's rule and the current * attribute type is null or the current attribute type has * a type specified and the targetattr's rule is null. * * The actual check against the skipRights array is: * * 1. Is the ACI's rights in this array? For example, * allow(all) or deny(add) * * AND * * 2. Is the rights from the LDAP operation in this array? For * example, an LDAP add would have rights of add and all. * * If both are true, than the target match test returns true * for this ACI. */ private static final int skipRights = (AciHandler.ACI_ADD | AciHandler.ACI_DELETE | AciHandler.ACI_PROXY); /** * Creates an ACI target from the specified arguments. All of these * may be null -- the ACI has no targets an will use defaults. * @param targetEntry The ACI target keyword if any. * @param targetAttr The ACI targetattr keyword if any. * @param targetFilter The ACI targetfilter keyword if any. * @param targetScope The ACI targetscope keyword if any. */ private AciTargets(Target targetEntry, TargetAttr targetAttr, TargetFilter targetFilter, SearchScope targetScope) { this.target=targetEntry; this.targetAttr=targetAttr; this.targetScope=targetScope; this.targetFilter=targetFilter; } /** * Return class representing the ACI target keyword. May be * null. The default is the use the DN of the entry containing * the ACI and check if the resource entry is a descendant of that. * @return The ACI target class. */ public Target getTarget() { return target; } /** * Return class representing the ACI targetattr keyword. May be null. * The default is to not match any attribute types in an entry. * @return The ACI targetattr class. */ public TargetAttr getTargetAttr() { return targetAttr; } /** * Return the ACI targetscope keyword. Default is WHOLE_SUBTREE. * @return The ACI targetscope information. */ public SearchScope getTargetScope() { return targetScope; } /** * Return class representing the ACI targetfilter keyword. May be null. * @return The targetscope information. */ public TargetFilter getTargetFilter() { return targetFilter; } /** * Decode an ACI's target part of the syntax from the string provided. * @param input String representing an ACI target part of syntax. * @param dn The DN of the entry containing the ACI. * @return An AciTargets class representing the decoded ACI target string. * @throws AciException If the provided string contains errors. */ public static AciTargets decode(String input, DN dn) throws AciException { Target target=null; TargetAttr targetAttr=null; TargetFilter targetFilter=null; TargAttrFilters targAttrFilters=null; SearchScope targetScope=SearchScope.WHOLE_SUBTREE; Pattern targetPattern = Pattern.compile(targetRegex); Matcher targetMatcher = targetPattern.matcher(input); while (targetMatcher.find()) { if (targetMatcher.groupCount() != targetElementCount) { int msgID = MSGID_ACI_SYNTAX_INVALID_TARGET_SYNTAX; String message = getMessage(msgID, input); throw new AciException(msgID, message); } String keyword = targetMatcher.group(targetKeywordPos); EnumTargetKeyword targetKeyword = EnumTargetKeyword.createKeyword(keyword); if (targetKeyword == null) { int msgID = MSGID_ACI_SYNTAX_INVALID_TARGET_KEYWORD; String message = getMessage(msgID, keyword ); throw new AciException(msgID, message); } String operator = targetMatcher.group(targetOperatorPos); EnumTargetOperator targetOperator = EnumTargetOperator.createOperator(operator); if (targetOperator == null) { int msgID = MSGID_ACI_SYNTAX_INVALID_TARGET_OPERATOR; String message = getMessage(msgID, operator); throw new AciException(msgID, message); } String expression = targetMatcher.group(targetExpressionPos); switch(targetKeyword) { case KEYWORD_TARGET: { if (target == null){ target = Target.decode(targetOperator, expression, dn); } else { int msgID = MSGID_ACI_SYNTAX_INVALID_TARGET_DUPLICATE_KEYWORDS; String message = getMessage(msgID, "target", input); throw new AciException(msgID, message); } break; } case KEYWORD_TARGETATTR: { if (targetAttr == null){ targetAttr = TargetAttr.decode(targetOperator, expression); } else { int msgID = MSGID_ACI_SYNTAX_INVALID_TARGET_DUPLICATE_KEYWORDS; String message = getMessage(msgID, "targetattr", input); throw new AciException(msgID, message); } break; } case KEYWORD_TARGETSCOPE: { // Check the operator for the targetscope is EQUALITY if (targetOperator == EnumTargetOperator.NOT_EQUALITY) { int msgID = MSGID_ACI_SYNTAX_INVALID_TARGETSCOPE_OPERATOR; String message = getMessage(msgID, operator); throw new AciException(msgID, message); } targetScope=createScope(expression); break; } case KEYWORD_TARGETFILTER: { if (targetFilter == null){ targetFilter = TargetFilter.decode(targetOperator, expression); } else { int msgID = MSGID_ACI_SYNTAX_INVALID_TARGET_DUPLICATE_KEYWORDS; String message = getMessage(msgID, "targetfilter", input); throw new AciException(msgID, message); } break; } case KEYWORD_TARGATTRFILTERS: { if (targAttrFilters == null){ targAttrFilters = TargAttrFilters.decode(targetOperator, expression); } else { int msgID = MSGID_ACI_SYNTAX_INVALID_TARGET_DUPLICATE_KEYWORDS; String message = getMessage(msgID, "targattrfilters", input); throw new AciException(msgID, message); } break; } } } return new AciTargets(target, targetAttr, targetFilter, targetScope); } /* * TODO Add support for the SearchScope.SUBORDINATE_SUBTREE scope. */ /** * Evaluates a provided scope string and returns an appropriate * SearchScope enumeration. * @param expression The expression string. * @return An search scope enumeration matching the string. * @throws AciException If the expression is an invalid targetscope * string. */ private static SearchScope createScope(String expression) throws AciException { if(expression.equalsIgnoreCase("base")) return SearchScope.BASE_OBJECT; else if(expression.equalsIgnoreCase("onelevel")) return SearchScope.SINGLE_LEVEL; else if(expression.equalsIgnoreCase("subtree")) return SearchScope.WHOLE_SUBTREE; else { int msgID = MSGID_ACI_SYNTAX_INVALID_TARGETSCOPE_EXPRESSION; String message = getMessage(msgID, expression); throw new AciException(msgID, message); } } /** * Checks an ACI's targetfilter information against an target match * context. * @param aci The ACI to try an match the targetfilter of. * @param matchCtx The target match context containing information needed * to perform a target match. * @return True if the targetfilter matched the target context. */ public static boolean isTargetFilterApplicable(Aci aci, AciTargetMatchContext matchCtx) { boolean ret=true; TargetFilter targetFilter=aci.getTargets().getTargetFilter(); if(targetFilter != null) ret=targetFilter.isApplicable(matchCtx); return ret; } /* * TODO Evaluate making this method more efficient. * The isTargetAttrApplicable method looks a lot less efficient than it * could be with regard to the logic that it employs and the repeated use * of method calls over local variables. */ /** * Checks an provided ACI's targetattr information against a target match * context. * @param aci The ACI to evaluate. * @param targetMatchCtx The target match context to check the ACI against. * @return True if the targetattr matched the target context. */ public static boolean isTargetAttrApplicable(Aci aci, AciTargetMatchContext targetMatchCtx) { boolean ret=true; AciTargets targets=aci.getTargets(); AttributeType a=targetMatchCtx.getCurrentAttributeType(); int rights=targetMatchCtx.getRights(); boolean isFirstAttr=targetMatchCtx.isFirstAttribute(); if((a != null) && (targets.getTargetAttr() != null)) { ret=TargetAttr.isApplicable(a, targets.getTargetAttr()); } else if((a != null) || (targets.getTargetAttr() != null)) { if((aci.hasRights(skipRights)) && (skipRightsHasRights(rights))) { ret=true; } else if ((targets.getTargetAttr() != null) && (a == null) && (aci.hasRights(AciHandler.ACI_WRITE))) { ret = true; } else { ret = false; } } if((isFirstAttr) && (aci.getTargets().getTargetAttr() == null)) targetMatchCtx.setEntryTestRule(true); return ret; } /** * Try and match a one or more of the specified rights in the skiprights * mask. * @param rights The rights to check for. * @return True if the one or more of the specified rights are in the * skiprights rights mask. */ public static boolean skipRightsHasRights(int rights) { return ((skipRights & rights) == rights); } /* * TODO Track DS 6.1 changes to ONELEVEL scope. * * The isTargetApplicable method appears to handle the ONELEVEL scope * incorrectly. The standard definition of onelevel only includes * the immediate children of a given entry -- it does not include that * entry itself. It is a bug for the server to behave in any other way. * Unfortunately, it does appear that the implementation you currently * have matches the implementation in DS6. Nevertheless, I don't think * that it is acceptable use this standard term in a nonstandard way and * therefore we must change it to the standards-compliant interpretation * which does not include the parent. * * TODO Investigate supporting alternative representations of the scope. * * Should we also consider supporting alternate representations of the * scope values (in particular, allow "one" in addition to "onelevel" * and "sub" in addition to "subtree") to match the very common * abbreviations in widespread use for those terms? */ /** * Checks an provided ACI's target information against an target match * context. * @param aci The ACI to match the target against. * @param matchCtx The target match context to check the ACI against. * @return True if the target matched the context. */ public static boolean isTargetApplicable(Aci aci, AciTargetMatchContext matchCtx) { boolean ret=true; DN entryDN=matchCtx.getResourceEntry().getDN(); DN targetDN=aci.getDN(); AciTargets targets=aci.getTargets(); /* * Scoping of the ACI uses either the DN of the entry * containing the ACI (aci.getDN above), or if the ACI item * contains a simple target DN and a equality operator that * target DN is used. */ if((targets.getTarget() != null) && (!targets.getTarget().isPattern())) { EnumTargetOperator op=targets.getTarget().getOperator(); if(op != EnumTargetOperator.NOT_EQUALITY) targetDN=targets.getTarget().getDN(); } switch(targets.getTargetScope()) { case BASE_OBJECT: if(!targetDN.equals(entryDN)) return false; break; case SINGLE_LEVEL: if((!targetDN.equals(entryDN)) && (!entryDN.getParent().equals(targetDN))) return false; break; case WHOLE_SUBTREE: if(!entryDN.isDescendantOf(targetDN)) return false; break; /* * TODO Add support for the SearchScope.SUBORDINATE_SUBTREE scope. * * The isTargetApplicable method doesn't account for the subordinate * subtree search scope. */ default: return false; } /* * The entry is in scope. For inequality checks, scope was tested * against the entry containing the ACI. If operator is inequality, * check that it doesn't match the target DN. */ if((targets.getTarget() != null) && (!targets.getTarget().isPattern())) { EnumTargetOperator op=targets.getTarget().getOperator(); if(op == EnumTargetOperator.NOT_EQUALITY) { DN tmpDN=targets.getTarget().getDN(); if(entryDN.isDescendantOf(tmpDN)) return false; } } /* * There is a pattern, need to match the substring filter * created when the ACI was decoded. If inequality flip the * result. */ if((targets.getTarget() != null) && (targets.getTarget().isPattern())) { ret=targets.getTarget().matchesPattern(entryDN); EnumTargetOperator op=targets.getTarget().getOperator(); if(ret && op == EnumTargetOperator.NOT_EQUALITY) ret=!ret; } return ret; } }