TargAttrFilters.java revision 917eb33ca3ffb73a34c0f733227d8f2215f9d978
/*
* 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 legal-notices/CDDLv1_0.txt
* or http://forgerock.org/license/CDDLv1.0.html.
* 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 legal-notices/CDDLv1_0.txt.
* 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 Sun Microsystems, Inc.
* Portions Copyright 2013-2015 ForgeRock AS
*/
package org.opends.server.authorization.dseecompat;
import static org.opends.messages.AccessControlMessages.*;
import static org.opends.server.authorization.dseecompat.Aci.*;
import java.util.ArrayList;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.opendj.ldap.ByteString;
import org.opends.server.types.*;
/**
* The TargAttrFilters class represents a targattrfilters rule of an ACI.
*/
public class TargAttrFilters {
/**
* A valid targattrfilters rule may have two TargFilterlist parts -- the
* first one is required.
*/
private TargAttrFilterList firstFilterList;
private TargAttrFilterList secondFilterList;
/**
* Regular expression group position for the first operation value.
*/
private static final int firstOpPos = 1;
/**
* Regular expression group position for the rest of an partially parsed
* rule.
*/
private static final int restOfExpressionPos=2;
/**
* Regular expression used to match the operation group (either add or del).
*/
private static final String ADD_OR_DEL_KEYWORD_GROUP = "(add|del)";
/**
* Regular expression used to check for valid expression separator.
*/
private static final
String secondOpSeparator="\\)" + ZERO_OR_MORE_WHITESPACE + ",";
/**
* Regular expression used to match the second operation of the filter list.
* If the first was "add" this must be "del", if the first was "del" this
* must be "add".
*/
public static final String secondOp =
"[,]{1}" + ZERO_OR_MORE_WHITESPACE + "del|add" +
ZERO_OR_MORE_WHITESPACE + EQUAL_SIGN + ZERO_OR_MORE_WHITESPACE;
/**
* Regular expression used to match the first targFilterList, it must exist
* or an exception is thrown.
*/
private static final String firstOp = "^" + ADD_OR_DEL_KEYWORD_GROUP +
ZERO_OR_MORE_WHITESPACE + EQUAL_SIGN + ZERO_OR_MORE_WHITESPACE;
/**
* Regular expression used to group the remainder of a partially parsed
* rule. Any character one or more times.
*/
private static String restOfExpression = "(.+)";
/**
* Regular expression used to match the first operation keyword and the
* rest of the expression.
*/
private static String keywordFullPattern = firstOp + restOfExpression;
/**
* The enumeration representing the operation.
*/
private EnumTargetOperator op;
/**
* A mask used to denote if the rule has add, del or both operations in the
* composite TargFilterList parts.
*/
private int operationMask;
/**
* Represents an targattrfilters keyword rule.
* @param op The enumeration representing the operation type.
*
* @param firstFilterList The first filter list class parsed from the rule.
* This one is required.
*
* @param secondFilterList The second filter list class parsed from the
* rule. This one is optional.
*/
public TargAttrFilters(EnumTargetOperator op,
TargAttrFilterList firstFilterList,
TargAttrFilterList secondFilterList ) {
this.op=op;
this.firstFilterList=firstFilterList;
operationMask=firstFilterList.getMask();
if(secondFilterList != null) {
//Add the second filter list mask to the mask.
operationMask |= secondFilterList.getMask();
this.secondFilterList=secondFilterList;
}
}
/**
* Decode an targattrfilter rule.
* @param type The enumeration representing the type of this rule. Defaults
* to equality for this target.
*
* @param expression The string expression to be decoded.
* @return A TargAttrFilters class representing the decode expression.
* @throws AciException If the expression string contains errors and
* cannot be decoded.
*/
public static TargAttrFilters decode(EnumTargetOperator type,
String expression) throws AciException {
Pattern fullPattern=Pattern.compile(keywordFullPattern);
Matcher matcher = fullPattern.matcher(expression);
//First match for overall correctness and to get the first operation.
if(!matcher.find()) {
LocalizableMessage message =
WARN_ACI_SYNTAX_INVALID_TARGATTRFILTERS_EXPRESSION.
get(expression);
throw new AciException(message);
}
String firstOp=matcher.group(firstOpPos);
String subExpression=matcher.group(restOfExpressionPos);
//This pattern is built dynamically and is used to see if the operations
//in the two filter list parts (if the second exists) are equal. See
//comment below.
String opPattern=
"[,]{1}" + ZERO_OR_MORE_WHITESPACE +
firstOp + ZERO_OR_MORE_WHITESPACE + EQUAL_SIGN +
ZERO_OR_MORE_WHITESPACE;
String[] temp=subExpression.split(opPattern);
/**
* Check that the initial list operation is not equal to the second.
* For example: Matcher find
*
* "add:cn:(cn=foo), add:cn:(cn=bar)"
*
* This is invalid.
*/
if(temp.length > 1) {
LocalizableMessage message = WARN_ACI_SYNTAX_INVALID_TARGATTRFILTERS_OPS_MATCH.
get(expression);
throw new AciException(message);
}
/**
* Check that there are not too many filter lists. There can only
* be either one or two.
*/
String[] filterLists = subExpression.split(secondOp, -1);
if(filterLists.length > 2) {
throw new AciException(WARN_ACI_SYNTAX_INVALID_TARGATTRFILTERS_MAX_FILTER_LISTS.get(expression));
} else if (filterLists.length == 1) {
//Check if the there is something like ") , deel=". A bad token
//that the regular expression didn't pick up.
String [] filterList2=subExpression.split(secondOpSeparator);
if(filterList2.length == 2) {
throw new AciException(WARN_ACI_SYNTAX_INVALID_TARGATTRFILTERS_EXPRESSION.get(expression));
}
String rg = getReverseOp(firstOp) + "=";
//This check catches the case where there might not be a
//',' character between the first filter list and the second.
if (subExpression.contains(rg)) {
throw new AciException(WARN_ACI_SYNTAX_INVALID_TARGATTRFILTERS_EXPRESSION.get(expression));
}
}
filterLists[0]=filterLists[0].trim();
//First filter list must end in an ')' character.
if(!filterLists[0].endsWith(")")) {
throw new AciException(WARN_ACI_SYNTAX_INVALID_TARGATTRFILTERS_EXPRESSION.get(expression));
}
TargAttrFilterList firstFilterList =
TargAttrFilterList.decode(getMask(firstOp), filterLists[0]);
TargAttrFilterList secondFilterList=null;
//Handle the second filter list if there is one.
if(filterLists.length == 2) {
String filterList=filterLists[1].trim();
//Second filter list must start with a '='.
if(!filterList.startsWith("=")) {
throw new AciException(WARN_ACI_SYNTAX_INVALID_TARGATTRFILTERS_EXPRESSION.get(expression));
}
String temp2= filterList.substring(1,filterList.length());
//Assume the first op is an "add" so this has to be a "del".
//If the first op is a "del", the second has to be an "add".
String secondOp = getReverseOp(firstOp);
secondFilterList =
TargAttrFilterList.decode(getMask(secondOp), temp2);
}
return new TargAttrFilters(type, firstFilterList, secondFilterList);
}
/**
* If the passed in op is an "add", then return "del"; Otherwise If the passed
* in op is an "del", then return "add".
*/
private static String getReverseOp(String op)
{
if (getMask(op) == TARGATTRFILTERS_DELETE)
return "add";
return "del";
}
/**
* Return the mask corresponding to the specified string.
*
* @param op The op string.
* @return The mask corresponding to the operation string.
*/
private static int getMask(String op) {
if(op.equals("add"))
return TARGATTRFILTERS_ADD;
return TARGATTRFILTERS_DELETE;
}
/**
* Gets the TargFilterList corresponding to the mask value.
* @param matchCtx The target match context containing the rights to
* match against.
* @return A TargAttrFilterList matching both the rights of the target
* match context and the mask of the TargFilterAttrList. May return null.
*/
public TargAttrFilterList
getTargAttrFilterList(AciTargetMatchContext matchCtx) {
int mask=ACI_NULL;
//Set up the wanted mask by evaluating both the target match
//context's rights and the mask.
if((matchCtx.hasRights(ACI_WRITE_ADD) || matchCtx.hasRights(ACI_ADD)) &&
hasMask(TARGATTRFILTERS_ADD))
mask=TARGATTRFILTERS_ADD;
else if((matchCtx.hasRights(ACI_WRITE_DELETE) ||
matchCtx.hasRights(ACI_DELETE)) &&
hasMask(TARGATTRFILTERS_DELETE))
mask=TARGATTRFILTERS_DELETE;
//Check the first list first, it always has to be there. If it doesn't
//match then check the second if it exists.
if(firstFilterList.hasMask(mask))
return firstFilterList;
else if((secondFilterList != null) &&
secondFilterList.hasMask(mask))
return secondFilterList;
return null;
}
/**
* Check if this TargAttrFilters object is applicable to the target
* specified match context. This check is only used for the LDAP modify
* operation.
* @param matchCtx The target match context containing the information
* needed to match.
* @param aci The ACI currently being evaluated for a target match.
* @return True if this TargAttrFitlers object is applicable to this
* target match context.
*/
public boolean isApplicableMod(AciTargetMatchContext matchCtx,
Aci aci) {
//Get the targFitlerList corresponding to this context's rights.
TargAttrFilterList attrFilterList=getTargAttrFilterList(matchCtx);
//If the list is empty return true and go on to the targattr check
//in AciTargets.isApplicable().
if(attrFilterList == null)
return true;
Map<AttributeType, SearchFilter> filterList =
attrFilterList.getAttributeTypeFilterList();
boolean attrMatched=true;
AttributeType attrType=matchCtx.getCurrentAttributeType();
//If the filter list contains the current attribute type; check
//the attribute types value(s) against the corresponding filter.
// If the filter list does not contain the attribute type skip the
// attribute type.
if((attrType != null) && (filterList.containsKey(attrType))) {
ByteString value = matchCtx.getCurrentAttributeValue();
SearchFilter filter = filterList.get(attrType);
attrMatched=matchFilterAttributeValue(attrType, value, filter);
//This flag causes any targattr checks to be bypassed in AciTargets.
matchCtx.setTargAttrFiltersMatch(true);
//Doing a geteffectiverights eval, save the ACI and the name
//in the context.
if(matchCtx.isGetEffectiveRightsEval()) {
matchCtx.setTargAttrFiltersAciName(aci.getName());
matchCtx.addTargAttrFiltersMatchAci(aci);
}
attrMatched = revertForInequalityOperator(op, attrMatched);
}
return attrMatched;
}
private boolean revertForInequalityOperator(EnumTargetOperator op,
boolean result)
{
if (EnumTargetOperator.NOT_EQUALITY.equals(op))
return !result;
return result;
}
/**
* Check if this TargAttrFilters object is applicable to the specified
* target match context. This check is only used for either LDAP add or
* delete operations.
* @param matchCtx The target match context containing the information
* needed to match.
* @return True if this TargAttrFilters object is applicable to this
* target match context.
*/
public boolean isApplicableAddDel(AciTargetMatchContext matchCtx) {
TargAttrFilterList attrFilterList=getTargAttrFilterList(matchCtx);
//List didn't match current operation return true.
if(attrFilterList == null)
return true;
Map<AttributeType, SearchFilter> filterList =
attrFilterList.getAttributeTypeFilterList();
Entry resEntry=matchCtx.getResourceEntry();
//Iterate through each attribute type in the filter list checking
//the resource entry to see if it has that attribute type. If not
//go to the next attribute type. If it is found, then check the entries
//attribute type values against the filter.
for(Map.Entry<AttributeType, SearchFilter> e : filterList.entrySet()) {
AttributeType attrType=e.getKey();
SearchFilter f=e.getValue();
if(!matchFilterAttributeType(resEntry, attrType, f)) {
return revertForInequalityOperator(op, false);
}
}
return revertForInequalityOperator(op, true);
}
private boolean matchFilterAttributeType(Entry entry,
AttributeType attrType, SearchFilter f)
{
if (entry.hasAttribute(attrType))
{
// Found a match in the entry, iterate over each attribute
// type in the entry and check its values against the filter.
for (Attribute a : entry.getAttribute(attrType))
{
if (!matchFilterAttributeValues(a, attrType, f))
{
return false;
}
}
}
return true;
}
/**
* Iterate over each attribute type attribute and compare the values
* against the provided filter.
* @param a The attribute from the resource entry.
* @param attrType The attribute type currently working on.
* @param filter The filter to evaluate the values against.
* @return True if all of the values matched the filter.
*/
private boolean matchFilterAttributeValues(Attribute a,
AttributeType attrType,
SearchFilter filter) {
//Iterate through each value and apply the filter against it.
for (ByteString value : a) {
if (!matchFilterAttributeValue(attrType, value, filter)) {
return false;
}
}
return true;
}
/**
* Matches an specified attribute value against a specified filter. A dummy
* entry is created with only a single attribute containing the value The
* filter is applied against that entry.
*
* @param attrType The attribute type currently being evaluated.
* @param value The value to match the filter against.
* @param filter The filter to match.
* @return True if the value matches the filter.
*/
private boolean matchFilterAttributeValue(AttributeType attrType,
ByteString value,
SearchFilter filter) {
Attribute attr = Attributes.create(attrType, value);
Entry e = new Entry(DN.rootDN(), null, null, null);
e.addAttribute(attr, new ArrayList<ByteString>());
try {
return filter.matchesEntry(e);
} catch(DirectoryException ex) {
return false;
}
}
/**
* Return true if the TargAttrFilters mask contains the specified mask.
* @param mask The mask to check for.
* @return True if the mask matches.
*/
public boolean hasMask(int mask) {
return (this.operationMask & mask) != 0;
}
}