/* | |
* Licensed to the Apache Software Foundation (ASF) under one | |
* or more contributor license agreements. See the NOTICE file | |
* distributed with this work for additional information | |
* regarding copyright ownership. The ASF licenses this file | |
* to you under the Apache License, Version 2.0 (the | |
* "License"); you may not use this file except in compliance | |
* with the License. You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, | |
* software distributed under the License is distributed on an | |
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
* KIND, either express or implied. See the License for the | |
* specific language governing permissions and limitations | |
* under the License. | |
* | |
*/ | |
/* | |
* AT&T - PROPRIETARY | |
* THIS FILE CONTAINS PROPRIETARY INFORMATION OF | |
* AT&T AND IS NOT TO BE DISCLOSED OR USED EXCEPT IN | |
* ACCORDANCE WITH APPLICABLE AGREEMENTS. | |
* | |
* Copyright (c) 2013 AT&T Knowledge Ventures | |
* Unpublished and Not for Publication | |
* All Rights Reserved | |
*/ | |
package org.apache.openaz.xacml.pdp.std.functions; | |
import java.math.BigInteger; | |
import java.util.ArrayList; | |
import java.util.Iterator; | |
import java.util.List; | |
import javax.xml.xpath.XPathExpression; | |
import org.apache.openaz.xacml.api.AttributeValue; | |
import org.apache.openaz.xacml.api.DataType; | |
import org.apache.openaz.xacml.api.Identifier; | |
import org.apache.openaz.xacml.api.RequestAttributes; | |
import org.apache.openaz.xacml.api.Status; | |
import org.apache.openaz.xacml.api.XACML; | |
import org.apache.openaz.xacml.pdp.eval.EvaluationContext; | |
import org.apache.openaz.xacml.pdp.policy.ExpressionResult; | |
import org.apache.openaz.xacml.pdp.policy.FunctionArgument; | |
import org.apache.openaz.xacml.std.StdAttributeValue; | |
import org.apache.openaz.xacml.std.StdStatus; | |
import org.apache.openaz.xacml.std.StdStatusCode; | |
import org.apache.openaz.xacml.std.datatypes.DataTypes; | |
import org.apache.openaz.xacml.std.datatypes.XPathExpressionWrapper; | |
import org.w3c.dom.NodeList; | |
/** | |
* FunctionDefinitionXPath extends {@link org.apache.openaz.xacml.pdp.std.functions.FunctionDefinitionHomogeneousSimple} to | |
* implement the XACML XPath predicates as functions taking one or two <code>XPathExpression</code> arguments and returning | |
* either an <code>Integer</code> or a <code>Boolean</code>. | |
* | |
* XACML version 1.0 and 2.0 used <code>String</code> data type as input. | |
* We do NOT support those functions because of ambiguity in the meaning of those Strings. | |
* The root of the XPath changed from the Request level in 2.0 to the Content level in 3.0. | |
* Also the 2.0 Requests contain only one Content, located in the resources category, while 3.0 allows Requests to contain Content in multiple categories. | |
* | |
* In the first implementation of XACML we had separate files for each XACML Function. | |
* This release combines multiple Functions in fewer files to minimize code duplication. | |
* This file supports the following XACML codes: | |
* xpath-node-count | |
* xpath-node-equals | |
* xpath-node-match | |
* | |
* | |
* @param <O> the java class for the data type of the function Output */ | |
public class FunctionDefinitionXPath<O> extends | |
FunctionDefinitionHomogeneousSimple<O, XPathExpressionWrapper> { | |
/** | |
* List of string normalization operations. | |
*/ | |
public enum OPERATION { | |
COUNT, | |
EQUAL, | |
MATCH | |
}; | |
// operation to be used in this instance of the Arightmetic class | |
private final OPERATION operation; | |
// result variables used by all functions | |
AttributeValue<String> result; | |
/** | |
* Constructor | |
* | |
* @param idIn | |
* @param dataTypeArgsIn | |
* @param op | |
*/ | |
public FunctionDefinitionXPath(Identifier idIn, DataType<O> dataTypeIn, OPERATION op) { | |
super(idIn, dataTypeIn, DataTypes.DT_XPATHEXPRESSION, ((op == OPERATION.COUNT) ? 1 : 2)); | |
// save the operation and data type to be used in this instance | |
operation = op; | |
} | |
@Override | |
public ExpressionResult evaluate(EvaluationContext evaluationContext, List<FunctionArgument> arguments) { | |
List<NodeList> nodeListList = new ArrayList<NodeList>(); | |
List<XPathExpressionWrapper> convertedArguments = new ArrayList<XPathExpressionWrapper>(); | |
Status status = this.validateArguments(arguments, convertedArguments); | |
/* | |
* If the function arguments are not correct, just return an error status immediately | |
*/ | |
if (!status.getStatusCode().equals(StdStatusCode.STATUS_CODE_OK)) { | |
return ExpressionResult.newError(getFunctionStatus(status)); | |
} | |
// check the evaluationContext and Request for null | |
if (evaluationContext == null) { | |
return ExpressionResult.newError(new StdStatus(StdStatusCode.STATUS_CODE_PROCESSING_ERROR, this | |
.getShortFunctionId() + " Got null EvaluationContext")); | |
} | |
if (evaluationContext.getRequest() == null) { | |
return ExpressionResult.newError(new StdStatus(StdStatusCode.STATUS_CODE_PROCESSING_ERROR, this | |
.getShortFunctionId() + " Got null Request in EvaluationContext")); | |
} | |
// each argument is an XPath that needs to be evaluated against the Content part of some Category | |
// (specified in the argument) | |
for (int i = 0; i < arguments.size(); i++) { | |
FunctionArgument functionArgument = arguments.get(i); | |
if (functionArgument.isBag()) { | |
return ExpressionResult.newError(new StdStatus(StdStatusCode.STATUS_CODE_PROCESSING_ERROR, | |
this.getShortFunctionId() | |
+ " Got bag at index " + i)); | |
} | |
AttributeValue<?> attributeValueFunctionArgument = functionArgument.getValue(); | |
if (attributeValueFunctionArgument == null) { | |
return ExpressionResult.newError(new StdStatus(StdStatusCode.STATUS_CODE_PROCESSING_ERROR, | |
this.getShortFunctionId() | |
+ " Got null value at index " + i)); | |
} | |
Identifier xpathCategory = attributeValueFunctionArgument.getXPathCategory(); | |
if (xpathCategory == null) { | |
return ExpressionResult.newError(new StdStatus(StdStatusCode.STATUS_CODE_SYNTAX_ERROR, this | |
.getShortFunctionId() + " Got null Category at index " + i)); | |
} | |
Iterator<RequestAttributes> it = evaluationContext.getRequest() | |
.getRequestAttributes(xpathCategory); | |
if (it == null) { | |
return ExpressionResult.newError(new StdStatus(StdStatusCode.STATUS_CODE_SYNTAX_ERROR, this | |
.getShortFunctionId() + " Got null Iterator at index " + i)); | |
} | |
NodeList nodeList = null; | |
while (it.hasNext()) { | |
if (nodeList != null) { | |
// the request has more than one Content entry for the same Category - error | |
return ExpressionResult | |
.newError(new StdStatus(StdStatusCode.STATUS_CODE_SYNTAX_ERROR, | |
this.getShortFunctionId() | |
+ " More than one Content section for id '" | |
+ xpathCategory + "'")); | |
} | |
RequestAttributes requestAttributes = it.next(); | |
// if there is no Content section then we return either 0 or FALSE | |
if (requestAttributes.getContentRoot() == null) { | |
if (operation == OPERATION.COUNT) { | |
return ExpressionResult | |
.newSingle(new StdAttributeValue<BigInteger>(XACML.ID_DATATYPE_INTEGER, | |
BigInteger.ZERO)); | |
} else { | |
return ER_FALSE; | |
} | |
} | |
try { | |
XPathExpression xPathExpression = convertedArguments.get(i).getXpathExpressionWrapped(); | |
nodeList = requestAttributes.getContentNodeListByXpathExpression(xPathExpression); | |
} catch (Exception e) { | |
return ExpressionResult.newError(new StdStatus(StdStatusCode.STATUS_CODE_SYNTAX_ERROR, | |
this.getShortFunctionId() | |
+ " XPath produces null result at '" | |
+ convertedArguments.get(i).getPath() | |
+ "' at index " + i)); | |
} | |
} | |
if (nodeList == null) { | |
return ExpressionResult | |
.newError(new StdStatus(StdStatusCode.STATUS_CODE_PROCESSING_ERROR, this | |
.getShortFunctionId() + " XPathExpression returned null at index " + i)); | |
} | |
// add this nodeList to the list of lists | |
nodeListList.add(nodeList); | |
} | |
/* | |
* Now perform the requested operation. | |
*/ | |
ExpressionResult expressionResult = null; | |
switch (operation) { | |
case COUNT: | |
Integer listLength = new Integer(nodeListList.get(0).getLength()); | |
expressionResult = ExpressionResult | |
.newSingle(new StdAttributeValue<BigInteger>(XACML.ID_DATATYPE_INTEGER, | |
new BigInteger(listLength.toString()))); | |
return expressionResult; | |
case EQUAL: | |
// true if any node in first list equals any node in second set. | |
// The spec says: "Two nodes are considered equal if they have the same identity." | |
// we use the isSameNode method in Node to determine that. | |
for (int index0 = 0; index0 < nodeListList.get(0).getLength(); index0++) { | |
for (int index1 = 0; index1 < nodeListList.get(1).getLength(); index1++) { | |
if (nodeListList.get(0).item(index0).isSameNode(nodeListList.get(1).item(index1))) { | |
return ER_TRUE; | |
} | |
} | |
} | |
// none from the first list found in the second | |
return ER_FALSE; | |
case MATCH: | |
// this is looking to see if any of the nodes in the second set are children of (or equal to) the | |
// nodes in the first set | |
// Call recursive check for that. | |
expressionResult = nodeListMatch(nodeListList.get(0), nodeListList.get(1)); | |
return expressionResult; | |
} | |
expressionResult = ExpressionResult.newSingle(result); | |
return expressionResult; | |
} | |
/** | |
* Recursive method checking to see if anything in list 2 equals anything in list 1 OR list 1's child | |
* nodes | |
* | |
* @param list1 | |
* @param list2 | |
* @return | |
*/ | |
private ExpressionResult nodeListMatch(NodeList list1, NodeList list2) { | |
// look for match with current contents of list 1 | |
for (int index1 = 0; index1 < list1.getLength(); index1++) { | |
for (int index2 = 0; index2 < list2.getLength(); index2++) { | |
if (list1.item(index1).isSameNode(list2.item(index2))) { | |
return ER_TRUE; | |
} | |
} | |
} | |
// look for match with children of list 1 | |
for (int index1 = 0; index1 < list1.getLength(); index1++) { | |
if (nodeListMatch(list1.item(index1).getChildNodes(), list2) == ER_TRUE) { | |
return ER_TRUE; | |
} | |
// this one had no children that matched, so check the next element in list1 | |
} | |
// no match anywhere | |
return ER_FALSE; | |
} | |
} |