/* * @(#)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 java.io.OutputStream; import java.io.PrintStream; 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; 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.Function; import com.sun.xacml.cond.FunctionFactory; import com.sun.xacml.cond.FunctionTypeException; import com.sun.xacml.ctx.Status; /** * Represents the SubjectMatch, ResourceMatch, ActionMatch, or EnvironmentMatch (in XACML 2.0 and * later) XML types in XACML, depending on the value of the type field. 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 * * Adding generic type support by Christian Mueller (geotools) * */ public class TargetMatch { /** * An integer value indicating that this class represents a SubjectMatch */ public static final int SUBJECT = 0; /** * An integer value indicating that this class represents a ResourceMatch */ public static final int RESOURCE = 1; /** * An integer value indicating that this class represents an ActionMatch */ public static final int ACTION = 2; /** * An integer value indicating that this class represents an EnvironmentMatch */ public static final int ENVIRONMENT = 3; /** * Mapping from the 4 match types to their string representations */ public static final String[] NAMES = { "Subject", "Resource", "Action", "Environment" }; // the type of this target match private int type; // the function used for matching private Function function; // the designator or selector private Evaluatable eval; // the value private AttributeValue attrValue; /** * Constructor that creates a <code>TargetMatch</code> from components. * * @param type * an integer indicating whether this class represents a SubjectMatch, ResourceMatch, * or ActionMatch * @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(int type, Function function, Evaluatable eval, AttributeValue attrValue) throws IllegalArgumentException { // check if input type is a valid value if ((type != SUBJECT) && (type != RESOURCE) && (type != ACTION) && (type != ENVIRONMENT)) throw new IllegalArgumentException("Unknown TargetMatch type"); this.type = type; this.function = function; this.eval = eval; this.attrValue = attrValue; } /** * Creates a <code>TargetMatch</code> by parsing a node, using the input prefix to determine * whether this is a SubjectMatch, ResourceMatch, or ActionMatch. * * @deprecated As of 2.0 you should avoid using this method and should instead use the version * that takes a <code>PolicyMetaData</code> instance. This method will only work for * XACML 1.x policies. * * @param root * the node to parse for the <code>TargetMatch</code> * @param prefix * a String indicating what type of <code>TargetMatch</code> to instantiate (Subject, * Resource, or Action) * @param xpathVersion * the XPath version to use in any selectors, or null if this is unspecified (ie, not * supplied in the defaults section of the policy) * * @return a new <code>TargetMatch</code> constructed by parsing * * @throws ParsingException * if there was an error during parsing * @throws IllegalArgumentException * if the input prefix isn't a valid value */ public static TargetMatch getInstance(Node root, String prefix, String xpathVersion) throws ParsingException, IllegalArgumentException { int i = 0; while ((i < NAMES.length) && (!NAMES[i].equals(prefix))) i++; if (i == NAMES.length) throw new IllegalArgumentException("Unknown TargetMatch type"); return getInstance(root, i, new PolicyMetaData(PolicyMetaData.XACML_1_0_IDENTIFIER, xpathVersion)); } /** * Creates a <code>TargetMatch</code> by parsing a node, using the input prefix to determine * whether this is a SubjectMatch, ResourceMatch, or ActionMatch. * * @param root * the node to parse for the <code>TargetMatch</code> * @param matchType * the type of <code>TargetMatch</code> as specified by the SUBJECT, RESOURCE, * ACTION, or ENVIRONMENT fields * @param metaData * the policy's meta-data * * @return a new <code>TargetMatch</code> constructed by parsing * * @throws ParsingException * if there was an error during parsing */ public static TargetMatch getInstance(Node root, int matchType, PolicyMetaData metaData) throws ParsingException { 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 = root.getAttributes().getNamedItem("MatchId").getNodeValue(); FunctionFactory factory = FunctionFactory.getTargetInstance(); try { URI funcId = new URI(funcName); function = factory.createFunction(funcId); } catch (URISyntaxException use) { throw new ParsingException("Error parsing TargetMatch", use); } catch (UnknownIdentifierException uie) { throw new ParsingException("Unknown MatchId", 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", 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); String name = node.getNodeName(); if (name.equals(NAMES[matchType] + "AttributeDesignator")) { eval = AttributeDesignator.getInstance(node, matchType, 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", uie); } } } // 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); return new TargetMatch(matchType, function, eval, attrValue); } /** * Returns the type of this <code>TargetMatch</code>, either <code>SUBJECT</code>, * <code>RESOURCE</code>, <code>ACTION</code>, or <code>ENVIRONMENT</code>. * * @return the type */ public int getType() { return type; } /** * Returns the <code>Function</code> used to do the matching. * * @return the match function */ public Function getMatchFunction() { return function; } /** * Returns the <code>AttributeValue</code> used by the matching function. * * @return the <code>AttributeValue</code> for the match */ public AttributeValue getMatchValue() { return 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 eval; } /** * 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 = 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<AttributeValue> inputs = new ArrayList<AttributeValue>(); inputs.add(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); else return new MatchResult(MatchResult.NO_MATCH); } else { // 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<AttributeValue> inputs, EvaluationCtx context) { // first off, evaluate the function EvaluationResult result = function.evaluate(inputs, context); // 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); else 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 */ public void encode(OutputStream output) { encode(output, 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 indenter * an object that creates indentation strings */ public void encode(OutputStream output, Indenter indenter) { PrintStream out = new PrintStream(output); String indent = indenter.makeString(); String tagName = NAMES[type] + "Match"; out.println(indent + "<" + tagName + " MatchId=\"" + function.getIdentifier().toString() + "\">"); indenter.in(); attrValue.encode(output, indenter); eval.encode(output, indenter); indenter.out(); out.println(indent + "</" + tagName + ">"); } }