/*
* 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
*
*
* Copyright 2008-2010 Sun Microsystems, Inc.
*/
package org.opends.server.authorization.dseecompat;
import org.opends.messages.Message;
import static org.opends.messages.AccessControlMessages.*;
import static org.opends.server.authorization.dseecompat.Aci.*;
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 supported ACI target keywords are: target, targetattr,
* targetscope, targetfilter, targattrfilters, targetcontrol and extop.
*/
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;
/*
* ACI syntax has a targattrtfilters keyword.
*/
private TargAttrFilters targAttrFilters=null;
/**
* The ACI syntax has a targetcontrol keyword.
*/
private TargetControl targetControl=null;
/**
* The ACI syntax has a extop keyword.
*/
private ExtOp extOp=null;
/*
* The number of regular expression group positions in a valid ACI target
* expression.
*/
private static final int targetElementCount = 3;
/*
* Regular expression group position of a target keyword.
*/
private static final int targetKeywordPos = 1;
/*
* Regular expression group position of a target operator enumeration.
*/
private static final int targetOperatorPos = 2;
/*
* Regular expression group position of a target expression statement.
*/
private static final int targetExpressionPos = 3;
/*
* Regular expression used to match a single target rule.
*/
private static final String targetRegex =
OPEN_PAREN + ZERO_OR_MORE_WHITESPACE + WORD_GROUP +
ZERO_OR_MORE_WHITESPACE + "(!?=)" + ZERO_OR_MORE_WHITESPACE +
"\"([^\"]+)\"" + ZERO_OR_MORE_WHITESPACE + CLOSED_PAREN +
ZERO_OR_MORE_WHITESPACE;
/**
* Regular expression used to match one or more target rules. The patern is
* part of a general ACI verification.
*/
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 = (ACI_ADD | ACI_DELETE | ACI_PROXY);
/**
* Creates an ACI target from the specified arguments. All of these
* may be null. If the ACI has no targets defaults will be used.
*
* @param targetEntry The ACI target keyword class.
* @param targetAttr The ACI targetattr keyword class.
* @param targetFilter The ACI targetfilter keyword class.
* @param targetScope The ACI targetscope keyword class.
* @param targAttrFilters The ACI targAttrFilters keyword class.
* @param targetControl The ACI targetControl keyword class.
* @param extOp The ACI extop keyword class.
*/
private AciTargets(Target targetEntry, TargetAttr targetAttr,
TargetFilter targetFilter,
SearchScope targetScope,
TargAttrFilters targAttrFilters,
TargetControl targetControl,
ExtOp extOp) {
this.target=targetEntry;
this.targetAttr=targetAttr;
this.targetScope=targetScope;
this.targetFilter=targetFilter;
this.targAttrFilters=targAttrFilters;
this.targetControl=targetControl;
this.extOp=extOp;
}
/**
* 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.
*/
private 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;
}
/**
* Return the class representing the ACI targattrfilters keyword. May be
* null.
* @return The targattrfilters information.
*/
public TargAttrFilters getTargAttrFilters() {
return targAttrFilters;
}
/**
* Return the class representing the ACI targetcontrol keyword. May be
* null.
* @return The targetcontrol information.
*/
public TargetControl getTargetControl() {
return targetControl;
}
/**
* Return the class representing the ACI extop keyword. May be
* null.
* @return The extop information.
*/
public ExtOp getExtOp() {
return extOp;
}
/**
* 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;
TargetControl targetControl=null;
ExtOp extOp=null;
SearchScope targetScope=SearchScope.WHOLE_SUBTREE;
Pattern targetPattern = Pattern.compile(targetRegex);
Matcher targetMatcher = targetPattern.matcher(input);
while (targetMatcher.find())
{
if (targetMatcher.groupCount() != targetElementCount) {
Message message =
WARN_ACI_SYNTAX_INVALID_TARGET_SYNTAX.get(input);
throw new AciException(message);
}
String keyword = targetMatcher.group(targetKeywordPos);
EnumTargetKeyword targetKeyword =
EnumTargetKeyword.createKeyword(keyword);
if (targetKeyword == null) {
Message message =
WARN_ACI_SYNTAX_INVALID_TARGET_KEYWORD.get(keyword);
throw new AciException(message);
}
String operator =
targetMatcher.group(targetOperatorPos);
EnumTargetOperator targetOperator =
EnumTargetOperator.createOperator(operator);
if (targetOperator == null) {
Message message =
WARN_ACI_SYNTAX_INVALID_TARGETS_OPERATOR.get(operator);
throw new AciException(message);
}
String expression = targetMatcher.group(targetExpressionPos);
switch(targetKeyword)
{
case KEYWORD_TARGET:
{
if (target == null){
target = Target.decode(targetOperator, expression, dn);
}
else
{
Message message =
WARN_ACI_SYNTAX_INVALID_TARGET_DUPLICATE_KEYWORDS.
get("target", input);
throw new AciException(message);
}
break;
}
case KEYWORD_TARGETCONTROL:
{
if (targetControl == null){
targetControl =
TargetControl.decode(targetOperator, expression);
}
else
{
Message message =
WARN_ACI_SYNTAX_INVALID_TARGET_DUPLICATE_KEYWORDS.
get("targetcontrol", input);
throw new AciException(message);
}
break;
}
case KEYWORD_EXTOP:
{
if (extOp == null){
extOp = ExtOp.decode(targetOperator, expression);
}
else
{
Message message =
WARN_ACI_SYNTAX_INVALID_TARGET_DUPLICATE_KEYWORDS.
get("extop", input);
throw new AciException(message);
}
break;
}
case KEYWORD_TARGETATTR:
{
if (targetAttr == null){
targetAttr = TargetAttr.decode(targetOperator,
expression);
}
else {
Message message =
WARN_ACI_SYNTAX_INVALID_TARGET_DUPLICATE_KEYWORDS.
get("targetattr", input);
throw new AciException(message);
}
break;
}
case KEYWORD_TARGETSCOPE:
{
// Check the operator for the targetscope is EQUALITY
if (targetOperator == EnumTargetOperator.NOT_EQUALITY) {
Message message =
WARN_ACI_SYNTAX_INVALID_TARGET_NOT_OPERATOR.
get(operator, targetKeyword.name());
throw new AciException(message);
}
targetScope=createScope(expression);
break;
}
case KEYWORD_TARGETFILTER:
{
if (targetFilter == null){
targetFilter = TargetFilter.decode(targetOperator,
expression);
}
else {
Message message =
WARN_ACI_SYNTAX_INVALID_TARGET_DUPLICATE_KEYWORDS.
get("targetfilter", input);
throw new AciException(message);
}
break;
}
case KEYWORD_TARGATTRFILTERS:
{
if (targAttrFilters == null){
// Check the operator for the targattrfilters is EQUALITY
if (targetOperator == EnumTargetOperator.NOT_EQUALITY) {
Message message =
WARN_ACI_SYNTAX_INVALID_TARGET_NOT_OPERATOR.
get(operator, targetKeyword.name());
throw new AciException(message);
}
targAttrFilters = TargAttrFilters.decode(targetOperator,
expression);
}
else {
Message message =
WARN_ACI_SYNTAX_INVALID_TARGET_DUPLICATE_KEYWORDS.
get("targattrfilters", input);
throw new AciException(message);
}
break;
}
}
}
return new AciTargets(target, targetAttr, targetFilter,
targetScope, targAttrFilters, targetControl,
extOp);
}
/**
* 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 if(expression.equalsIgnoreCase("subordinate"))
return SearchScope.SUBORDINATE_SUBTREE;
else {
Message message =
WARN_ACI_SYNTAX_INVALID_TARGETSCOPE_EXPRESSION.get(expression);
throw new AciException(message);
}
}
/**
* Checks an ACI's targetfilter rule information against a 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 rule 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;
}
/**
* Check an ACI's targetcontrol rule against a target match context.
*
* @param aci The ACI to match the targetcontrol against.
* @param matchCtx The target match context containing the information
* needed to perform the target match.
* @return True if the targetcontrol rule matched the target context.
*/
public static boolean isTargetControlApplicable(Aci aci,
AciTargetMatchContext matchCtx) {
boolean ret=false;
TargetControl targetControl=aci.getTargets().getTargetControl();
if(targetControl != null)
ret=targetControl.isApplicable(matchCtx);
return ret;
}
/**
* Check an ACI's extop rule against a target match context.
*
* @param aci The ACI to match the extop rule against.
* @param matchCtx The target match context containing the information
* needed to perform the target match.
* @return True if the extop rule matched the target context.
*/
public static boolean isExtOpApplicable(Aci aci,
AciTargetMatchContext matchCtx) {
boolean ret=false;
ExtOp extOp=aci.getTargets().getExtOp();
if(extOp != null)
ret=extOp.isApplicable(matchCtx);
return ret;
}
/**
* Check an ACI's targattrfilters rule against a target match context.
*
* @param aci The ACI to match the targattrfilters against.
* @param matchCtx The target match context containing the information
* needed to perform the target match.
* @return True if the targattrfilters rule matched the target context.
*/
public static boolean isTargAttrFiltersApplicable(Aci aci,
AciTargetMatchContext matchCtx) {
boolean ret=true;
TargAttrFilters targAttrFilters=aci.getTargets().getTargAttrFilters();
if(targAttrFilters != null) {
if((matchCtx.hasRights(ACI_ADD) &&
targAttrFilters.hasMask(TARGATTRFILTERS_ADD)) ||
(matchCtx.hasRights(ACI_DELETE) &&
targAttrFilters.hasMask(TARGATTRFILTERS_DELETE)))
ret=targAttrFilters.isApplicableAddDel(matchCtx);
else if((matchCtx.hasRights(ACI_WRITE_ADD) &&
targAttrFilters.hasMask(TARGATTRFILTERS_ADD)) ||
(matchCtx.hasRights(ACI_WRITE_DELETE) &&
targAttrFilters.hasMask(TARGATTRFILTERS_DELETE)))
ret=targAttrFilters.isApplicableMod(matchCtx, aci);
}
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 rule 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;
if(!targetMatchCtx.getTargAttrFiltersMatch()) {
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());
setEvalAttributes(targetMatchCtx,targets,ret);
} else if((a != null) || (targets.getTargetAttr() != null)) {
if((aci.hasRights(skipRights)) &&
(skipRightsHasRights(rights)))
ret=true;
else if ((targets.getTargetAttr() != null) &&
(a == null) && (aci.hasRights(ACI_WRITE)))
ret = true;
else
ret = false;
}
if((isFirstAttr) && (aci.getTargets().getTargetAttr() == null)
&& aci.getTargets().getTargAttrFilters() == 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) {
//geteffectiverights sets this flag, turn it off before evaluating.
int tmpRights=rights & ~ACI_SKIP_PROXY_CHECK;
return ((skipRights & tmpRights) == tmpRights);
}
/**
* Wrapper class that passes an ACI, an ACI's targets and the specified
* target match context's resource entry DN to the main isTargetApplicable
* method.
* @param aci The ACI currently be matched.
* @param matchCtx The target match context to match against.
* @return True if the target matched the ACI.
*/
public static boolean isTargetApplicable(Aci aci,
AciTargetMatchContext matchCtx) {
return isTargetApplicable(aci, aci.getTargets(),
matchCtx.getResourceEntry().getDN());
}
/*
* 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?
*/
/**
* Main target isApplicable method. This method performs the target keyword
* match functionality, which allows for directory entry "targeting" using
* the specifed ACI, ACI targets class and DN.
* @param aci The ACI to match the target against.
* @param targets The targets to use in this evaluation.
* @param entryDN The DN to use in this evaluation.
* @return True if the ACI matched the target and DN.
*/
public static boolean isTargetApplicable(Aci aci,
AciTargets targets, DN entryDN) {
boolean ret=true;
DN targetDN=aci.getDN();
/*
* 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
* simple target DN is used as the target DN.
*/
if((targets.getTarget() != null) &&
(!targets.getTarget().isPattern())) {
EnumTargetOperator op=targets.getTarget().getOperator();
if(op != EnumTargetOperator.NOT_EQUALITY)
targetDN=targets.getTarget().getDN();
}
//Check if the scope is correct.
switch(targets.getTargetScope()) {
case BASE_OBJECT:
if(!targetDN.equals(entryDN))
return false;
break;
case SINGLE_LEVEL:
/**
* We use the standard definition of single level to mean the
* immediate children only -- not the target entry itself.
* Sun CR 6535035 has been raised on DSEE:
* Non-standard interpretation of onelevel in ACI targetScope.
*/
if(!targetDN.equals(entryDN.getParent()))
return false;
break;
case WHOLE_SUBTREE:
if(!entryDN.isDescendantOf(targetDN))
return false;
break;
case SUBORDINATE_SUBTREE:
if ((entryDN.getNumComponents() <= targetDN.getNumComponents()) ||
!entryDN.isDescendantOf(targetDN)) {
return false;
}
break;
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(op == EnumTargetOperator.NOT_EQUALITY)
ret=!ret;
}
return ret;
}
/**
* The method is used to try and determine if a targetAttr expression that
* is applicable has a '*' (or '+' operational attributes) token or if it
* was applicable because of a specific attribute type declared in the
* targetattrs expression (i.e., targetattrs=cn).
*
*
* @param ctx The ctx to check against.
* @param targets The targets part of the ACI.
* @param ret The is true if the ACI has already been evaluated to be
* applicable.
*/
private static
void setEvalAttributes(AciTargetMatchContext ctx, AciTargets targets,
boolean ret) {
ctx.clearEvalAttributes(ACI_USER_ATTR_STAR_MATCHED);
ctx.clearEvalAttributes(ACI_OP_ATTR_PLUS_MATCHED);
/*
If an applicable targetattr's match rule has not
been seen (~ACI_FOUND_OP_ATTR_RULE or ~ACI_FOUND_USER_ATTR_RULE) and
the current attribute type is applicable because of a targetattr all
user (or operational) attributes rule match,
set a flag to indicate this situation (ACI_USER_ATTR_STAR_MATCHED or
ACI_OP_ATTR_PLUS_MATCHED). This check also catches the following case
where the match was by a specific attribute type (either user or
operational) and the other attribute type has an all attribute token.
For example, the expression is: (targetattrs="cn || +) and the current
attribute type is cn.
*/
if(ret && targets.getTargetAttr().isAllUserAttributes() &&
!ctx.hasEvalUserAttributes())
ctx.setEvalUserAttributes(ACI_USER_ATTR_STAR_MATCHED);
else
ctx.setEvalUserAttributes(ACI_FOUND_USER_ATTR_RULE);
if(ret && targets.getTargetAttr().isAllOpAttributes() &&
!ctx.hasEvalOpAttributes())
ctx.setEvalOpAttributes(ACI_OP_ATTR_PLUS_MATCHED);
else
ctx.setEvalOpAttributes(ACI_FOUND_OP_ATTR_RULE);
}
}