/*
* @(#)TargetMatch.java
*
* Copyright 2003-2005 Sun Microsystems, Inc. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistribution of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistribution in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* Neither the name of Sun Microsystems, Inc. or the names of contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* This software is provided "AS IS," without a warranty of any kind. ALL
* EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
* ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
* OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
* AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
* AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
* INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
* OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
*
* You acknowledge that this software is not designed or intended for use in
* the design, construction, operation or maintenance of any nuclear facility.
*/
package com.sun.xacml;
import com.sun.xacml.EvaluationCtx;
import com.sun.xacml.attr.AttributeDesignator;
import com.sun.xacml.attr.AttributeFactory;
import com.sun.xacml.attr.AttributeSelector;
import com.sun.xacml.attr.AttributeValue;
import com.sun.xacml.attr.BagAttribute;
import com.sun.xacml.attr.BooleanAttribute;
import com.sun.xacml.cond.Evaluatable;
import com.sun.xacml.cond.EvaluationResult;
import com.sun.xacml.cond.Expression;
import com.sun.xacml.cond.Function;
import com.sun.xacml.cond.FunctionFactory;
import com.sun.xacml.cond.FunctionTypeException;
import com.sun.xacml.ctx.Status;
import com.sun.xacml.debug.RuntimeInfo;
import com.sun.xacml.debug.RuntimeInfo.ELEMENT_TYPE;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* Represents the Match XML type in XACML. This is the part of the Target that
* actually evaluates whether the specified attribute values in the Target match
* the corresponding attribute values in the request context.
*
* @since 1.0
* @author Seth Proctor
* @author Ludwig Seitz
*/
public class TargetMatch implements MatchElement
{
/**
* The function used for matching.
*/
private Function function;
/**
* The <code>AttributeDesignator</code> or
* <code>AttributeSelector</code> to be used to select
* attributes from the request context
*/
private Evaluatable eval;
/**
* The value to compare against.
*/
private AttributeValue attrValue;
/**
* The xacml version for encoding.
*/
private int xacmlVersion;
/**
* The match category. Used only for backwards compatibility.
*/
private URI category;
private RuntimeInfo src;
/**
* Constructor that creates a default version<code>TargetMatch</code>
* from components.
*
* @param function the <code>Function</code> that represents the MatchId
* @param eval the <code>AttributeDesignator</code> or
* <code>AttributeSelector</code> to be used to select
* attributes from the request context
* @param attrValue the <code>AttributeValue</code> to compare against
*
* @throws IllegalArgumentException if the input type isn't a valid value
*/
public TargetMatch(Function function, Evaluatable eval,
AttributeValue attrValue)
throws IllegalArgumentException {
this(function, eval, attrValue, Constants.XACML_DEFAULT_VERSION,
null);
}
/**
* Constructor that creates a <code>TargetMatch</code> from components.
*
* @param function the <code>Function</code> that represents the MatchId
* @param eval the <code>AttributeDesignator</code> or
* <code>AttributeSelector</code> to be used to select
* attributes from the request context
* @param attrValue the <code>AttributeValue</code> to compare against
* @param xacmlVersion The XACML version number.
* @param category The category of this match (e.g. "Subject") for
* XACML versions 2.0 and earlier. Is null for
* later versions.
*
* @throws IllegalArgumentException if the input type isn't a valid value
*/
public TargetMatch(Function function, Evaluatable eval,
AttributeValue attrValue, int xacmlVersion,
URI category)
throws IllegalArgumentException {
this.function = function;
this.eval = eval;
this.attrValue = attrValue;
this.xacmlVersion = xacmlVersion;
this.category = category;
}
/**
* Creates a <code>TargetMatch</code> by parsing a node, using the
* input prefix to determine which category of match this is.
*
* @param root The node to parse for the <code>TargetMatch</code>
* @param metaData The policy's meta-data
* @param masterCategory The category of the TargetMatchGroup, for
* checking consistency.
*
* @return a new <code>TargetMatch</code> constructed by parsing
*
* @throws ParsingException if there was an error during parsing
*/
public static TargetMatch getInstance(Node root,
PolicyMetaData metaData, URI masterCategory)
throws ParsingException {
RuntimeInfo src = RuntimeInfo.getRuntimeInfo(root, ELEMENT_TYPE.TARGET_MATCH);
// first make sure the node passed is indeed a TargetMatch
if (root.getNodeType() != Node.ELEMENT_NODE) {
throw new ParsingException("Can't create a TargetMatch from"
+ " a node that is not an element-node"
+ (src != null ? src.getLocationMsgForError() : ""));
}
if (!root.getLocalName().endsWith("Match")) {
throw new ParsingException("Can't create a TargetMatch from a "
+ root.getLocalName() + " element"
+ (src != null ? src.getLocationMsgForError() : ""));
}
Function function;
Evaluatable eval = null;
AttributeValue attrValue = null;
AttributeFactory attrFactory = AttributeFactory.getInstance();
// get the function type, making sure that it's really a correct
// Target function
String funcName = null;
if (root.getAttributes().getNamedItem("MatchId") != null) {
funcName = root.getAttributes().getNamedItem("MatchId")
.getNodeValue();
} else {
throw new ParsingException("Required XML attribute "
+ "MatchId not found while parsing a TargetMatch"
+ (src != null ? src.getLocationMsgForError() : ""));
}
FunctionFactory factory = FunctionFactory.getTargetInstance();
try {
URI funcId = new URI(funcName);
function = factory.createFunction(funcId);
} catch (URISyntaxException use) {
throw new ParsingException("Error parsing TargetMatch"
+ (src != null ? src.getLocationMsgForError() : ""), use);
} catch (UnknownIdentifierException uie) {
throw new ParsingException("Unknown MatchId"
+ (src != null ? src.getLocationMsgForError() : ""), uie);
} catch (FunctionTypeException fte) {
// try to create an abstract function
try {
URI funcId = new URI(funcName);
function = factory.createAbstractFunction(funcId, root);
} catch (Exception e) {
// any exception here is an error
throw new ParsingException("invalid abstract function"
+ (src != null ? src.getLocationMsgForError() : ""), e);
}
}
// next, get the designator or selector being used, and the attribute
// value paired with it
NodeList nodes = root.getChildNodes();
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
String name = node.getLocalName();
// endsWith() is compatibility code for XACML 2.0
if (name.endsWith("AttributeDesignator")) {
if (name.equals("AttributeDesignator")) {
if (metaData.getXACMLVersion()
< Constants.XACML_VERSION_3_0) {
throw new ParsingException(
"Can't create a < XACML 3.0 "
+ "AttributeDesignator out of a "
+ name + " element"
+ (src != null ? src.getLocationMsgForError() : ""));
}
} else { // XACML 2.0 compatibility
if (metaData.getXACMLVersion()
> Constants.XACML_VERSION_2_0) {
throw new ParsingException(
"Can't create a > XACML 2.0 "
+ "AttributeDesignator out of a "
+ name + " element"
+ (src != null ? src.getLocationMsgForError() : ""));
}
String categoryStr
= name.replaceAll("AttributeDesignator", "");
URI category = null;
if (categoryStr.equals("Subject")) {
category = Constants.SUBJECT_CAT;
} else if (categoryStr.equals("Resource")) {
category = Constants.RESOURCE_CAT;
} else if (categoryStr.equals("Action")) {
category = Constants.ACTION_CAT;
} else if (categoryStr.equals("Environment")){
category = Constants.ENVIRONMENT_CAT;
} else {
throw new ParsingException("Can't create a "
+ "< XACML 3.0 AttributeDesignator out of"
+ " a " + categoryStr + " element"
+ (src != null ? src.getLocationMsgForError() : ""));
}
if (!category.equals(masterCategory)) {
throw new ParsingException(categoryStr + "Match"
+ " can't be located in enclosing "
+ masterCategory.toString() + " element"
+ (src != null ? src.getLocationMsgForError() : ""));
}
}
eval = AttributeDesignator.getInstance(node, metaData);
} else if (name.equals("AttributeSelector")) {
eval = AttributeSelector.getInstance(node, metaData);
} else if (name.equals("AttributeValue")) {
try {
attrValue = attrFactory.createValue(node);
} catch (UnknownIdentifierException uie) {
throw new ParsingException(
"Unknown Attribute Type"
+ (src != null ? src.getLocationMsgForError() : ""), uie);
}
} else {
throw new ParsingException("Encountered: '"
+ name + "' node while parsing a TargetMatch"
+ (src != null ? src.getLocationMsgForError() : ""));
}
}
}
// finally, check that the inputs are valid for this function
List<Evaluatable> inputs = new ArrayList<Evaluatable>();
inputs.add(attrValue);
inputs.add(eval);
function.checkInputsNoBag(inputs, src);
TargetMatch targetMatch = new TargetMatch(function, eval, attrValue,
metaData.getXACMLVersion(), masterCategory);
if ( src != null ) {
targetMatch.src = src;
src.setXACMLObject(targetMatch);
}
return targetMatch;
}
/**
* Returns the <code>Function</code> used to do the matching.
*
* @return the match function
*/
public Function getMatchFunction() {
return this.function;
}
/**
* Returns the <code>AttributeValue</code> used by the matching function.
*
* @return the <code>AttributeValue</code> for the match
*/
public AttributeValue getMatchValue() {
return this.attrValue;
}
/**
* Returns the <code>AttributeDesignator</code> or
* <code>AttributeSelector</code> used by the matching function.
*
* @return the designator or selector for the match
*/
public Evaluatable getMatchEvaluatable() {
return this.eval;
}
/**
* Returns the category of this match.
*
* @return the category of this match.
*/
public URI getCategory() {
return this.category;
}
public RuntimeInfo getRuntimeInfo() {
return this.src;
}
/**
* Determines whether this <code>TargetMatch</code> matches
* the input request (whether it is applicable)
*
* @param context the representation of the request
*
* @return the result of trying to match the TargetMatch and the request
*/
public MatchResult match(EvaluationCtx context) {
// start by evaluating the AD/AS
EvaluationResult result = this.eval.evaluate(context);
if (result.indeterminate()) {
// in this case, we don't ask the function for anything, and we
// simply return INDETERMINATE
return new MatchResult(MatchResult.INDETERMINATE,
result.getStatus());
}
// an AD/AS will always return a bag
BagAttribute bag = (BagAttribute)(result.getAttributeValue());
if (! bag.isEmpty()) {
// we got back a set of attributes, so we need to iterate through
// them, seeing if at least one matches
Iterator<AttributeValue> it = bag.iterator();
boolean atLeastOneError = false;
Status firstIndeterminateStatus = null;
while (it.hasNext()) {
ArrayList<Expression> inputs = new ArrayList<Expression>();
inputs.add(this.attrValue);
inputs.add(it.next());
// do the evaluation
MatchResult match = evaluateMatch(inputs, context);
// we only need one match for this whole thing to match
if (match.getResult() == MatchResult.MATCH) {
return match;
}
// if it was INDETERMINATE, we want to remember for later
if (match.getResult() == MatchResult.INDETERMINATE) {
atLeastOneError = true;
// there are no rules about exactly what status data
// should be returned here, so like in the combining
// algs, we'll just track the first error
if (firstIndeterminateStatus == null) {
firstIndeterminateStatus = match.getStatus();
}
}
}
// if we got here, then nothing matched, so we'll either return
// INDETERMINATE or NO_MATCH
if (atLeastOneError) {
return new MatchResult(MatchResult.INDETERMINATE,
firstIndeterminateStatus);
}
return new MatchResult(MatchResult.NO_MATCH);
}
// this is just an optimization, since the loop above will
// actually handle this case, but this is just a little
// quicker way to handle an empty bag
return new MatchResult(MatchResult.NO_MATCH);
}
/**
* Private helper that evaluates an individual match.
*/
private MatchResult evaluateMatch(List<Expression> inputs, EvaluationCtx context) {
RuntimeInfo funcSrc = null;
//set (context dependent) source locator for function
if ( src != null ) {
//funcSrc = RuntimeInfo.getIndirectSourceLocator(src, ELEMENT_TYPE.FUNCTION);
funcSrc = src.getIndirectRuntimeInfo(function, ELEMENT_TYPE.FUNCTION);
this.function.setRuntimeInfo(funcSrc);
}
// first off, evaluate the function
EvaluationResult result = this.function.evaluate(inputs, context);
//unset source locator
if ( funcSrc != null ) {
this.function.unsetRuntimeInfo(funcSrc) ;
}
// if it was indeterminate, then that's what we return immediately
if (result.indeterminate()) {
return new MatchResult(MatchResult.INDETERMINATE,
result.getStatus());
}
// otherwise, we figure out if it was a match
BooleanAttribute bool = (BooleanAttribute)(result.getAttributeValue());
if (bool.getValue()) {
return new MatchResult(MatchResult.MATCH);
}
return new MatchResult(MatchResult.NO_MATCH);
}
/**
* Encodes this <code>TargetMatch</code> into its XML representation and
* writes this encoding to the given <code>OutputStream</code> with no
* indentation.
*
* @param output a stream into which the XML-encoded data is written
* @param charsetName the character set to use in encoding of strings.
* This may be null in which case the platform
* default character set will be used.
*
* @throws UnsupportedEncodingException
*/
public void encode(OutputStream output, String charsetName)
throws UnsupportedEncodingException {
encode(output, charsetName, new Indenter(0));
}
/**
* Encodes this <code>TargetMatch</code> into its XML representation and
* writes this encoding to the given <code>OutputStream</code> with
* indentation.
*
* @param output a stream into which the XML-encoded data is written
* @param charsetName the character set to use in encoding of strings.
* This may be null in which case the platform
* default character set will be used.
* @param indenter an object that creates indentation strings
*
* @throws UnsupportedEncodingException
*/
public void encode(OutputStream output, String charsetName,
Indenter indenter)
throws UnsupportedEncodingException {
PrintStream out;
if(charsetName == null) {
out = new PrintStream(output);
} else {
out = new PrintStream(output, false, charsetName);
}
String indent = indenter.makeString();
String closingTag = null;
if (this.xacmlVersion < Constants.XACML_VERSION_3_0) {
if (this.category.equals(Constants.SUBJECT_CAT)) {
out.print(indent + "<SubjectMatch ");
closingTag = "</SubjectMatch>";
} else if (this.category.equals(Constants.RESOURCE_CAT)) {
out.print(indent + "<ResourceMatch ");
closingTag = "</ResourceMatch>";
} else if (this.category.equals(Constants.ACTION_CAT)) {
out.print(indent + "<ActionMatch ");
closingTag = "</ActionMatch>";
} else if (this.category.equals(Constants.ENVIRONMENT_CAT)) {
out.print(indent + "<EnvironmentMatch ");
closingTag = "</EnvironmentMatch>";
}
} else {
out.print(indent + "<Match ");
closingTag = "</Match>";
}
out.println("MatchId=\""
+ this.function.getIdentifier().toString()+ "\">");
indenter.in();
this.attrValue.encode(output, charsetName, indenter);
this.eval.encode(output, charsetName, indenter);
indenter.out();
out.println(indent + closingTag);
}
}