/* * @(#)AbstractPolicy.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.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import com.sun.xacml.combine.CombinerElement; import com.sun.xacml.combine.CombinerParameter; import com.sun.xacml.combine.CombiningAlgFactory; import com.sun.xacml.combine.CombiningAlgorithm; import com.sun.xacml.combine.PolicyCombiningAlgorithm; import com.sun.xacml.combine.RuleCombiningAlgorithm; import com.sun.xacml.ctx.Result; /** * Represents an instance of an XACML policy. * * @since 1.0 * @author Seth Proctor * @author Marco Barreno * * Adding generic type support by Christian Mueller (geotools) */ public abstract class AbstractPolicy implements PolicyTreeElement { // atributes associated with this policy private URI idAttr; private String version; private CombiningAlgorithm combiningAlg; // the elements in the policy private String description; private Target target; // the value in defaults, or null if there was no default value private String defaultVersion; // the meta-data associated with this policy private PolicyMetaData metaData; // the child elements under this policy represented simply as the // PolicyTreeElements... private List<PolicyTreeElement> children; // ...or the CombinerElements that are passed to combining algorithms private List<CombinerElement> childElements; // any obligations held by this policy private Set<Obligation> obligations; // the list of combiner parameters private List<CombinerParameter> parameters; // the logger we'll use for all messages // private static final Logger logger = // Logger.getLogger(AbstractPolicy.class.getName()); /** * Constructor used by <code>PolicyReference</code>, which supplies its own values for the * methods in this class. */ protected AbstractPolicy() { } /** * Constructor used to create a policy from concrete components. * * @param id * the policy id * @param version * the policy version or null for the default (this is always null for pre-2.0 * policies) * @param combiningAlg * the combining algorithm to use * @param description * describes the policy or null if there is none * @param target * the policy's target */ protected AbstractPolicy(URI id, String version, CombiningAlgorithm combiningAlg, String description, Target target) { this(id, version, combiningAlg, description, target, null); } /** * Constructor used to create a policy from concrete components. * * @param id * the policy id * @param version * the policy version or null for the default (this is always null for pre-2.0 * policies) * @param combiningAlg * the combining algorithm to use * @param description * describes the policy or null if there is none * @param target * the policy's target * @param defaultVersion * the XPath version to use for selectors */ protected AbstractPolicy(URI id, String version, CombiningAlgorithm combiningAlg, String description, Target target, String defaultVersion) { this(id, version, combiningAlg, description, target, defaultVersion, null, null); } /** * Constructor used to create a policy from concrete components. * * @param id * the policy id * @param version * the policy version or null for the default (this is always null for pre-2.0 * policies) * @param combiningAlg * the combining algorithm to use * @param description * describes the policy or null if there is none * @param target * the policy's target * @param defaultVersion * the XPath version to use for selectors * @param obligations * the policy's obligations */ protected AbstractPolicy(URI id, String version, CombiningAlgorithm combiningAlg, String description, Target target, String defaultVersion, Set<Obligation> obligations, List<CombinerParameter> parameters) { idAttr = id; this.combiningAlg = combiningAlg; this.description = description; this.target = target; this.defaultVersion = defaultVersion; if (version == null) this.version = "1.0"; else this.version = version; // FIXME: this needs to fill in the meta-data correctly metaData = null; if (obligations == null) this.obligations = Collections.emptySet(); else this.obligations = Collections.unmodifiableSet(new HashSet<Obligation>(obligations)); if (parameters == null) this.parameters = Collections.emptyList(); else this.parameters = Collections.unmodifiableList(new ArrayList<CombinerParameter>( parameters)); } /** * Constructor used by child classes to initialize the shared data from a DOM root node. * * @param root * the DOM root of the policy * @param policyPrefix * either "Policy" or "PolicySet" * @param combiningName * name of the field naming the combining alg * * @throws ParsingException * if the policy is invalid */ protected AbstractPolicy(Node root, String policyPrefix, String combiningName) throws ParsingException { // get the attributes, all of which are common to Policies NamedNodeMap attrs = root.getAttributes(); try { // get the attribute Id idAttr = new URI(attrs.getNamedItem(policyPrefix + "Id").getNodeValue()); } catch (Exception e) { throw new ParsingException("Error parsing required attribute " + policyPrefix + "Id", e); } // see if there's a version Node versionNode = attrs.getNamedItem("Version"); if (versionNode != null) { version = versionNode.getNodeValue(); } else { // assign the default version version = "1.0"; } // now get the combining algorithm... try { URI algId = new URI(attrs.getNamedItem(combiningName).getNodeValue()); CombiningAlgFactory factory = CombiningAlgFactory.getInstance(); combiningAlg = factory.createAlgorithm(algId); } catch (Exception e) { throw new ParsingException("Error parsing combining algorithm" + " in " + policyPrefix, e); } // ...and make sure it's the right kind if (policyPrefix.equals("Policy")) { if (!(combiningAlg instanceof RuleCombiningAlgorithm)) throw new ParsingException("Policy must use a Rule " + "Combining Algorithm"); } else { if (!(combiningAlg instanceof PolicyCombiningAlgorithm)) throw new ParsingException("PolicySet must use a Policy " + "Combining Algorithm"); } // do an initial pass through the elements to pull out the // defaults, if any, so we can setup the meta-data NodeList children = root.getChildNodes(); // String xpathVersion = null; for (int i = 0; i < children.getLength(); i++) { Node child = children.item(i); if (child.getNodeName().equals(policyPrefix + "Defaults")) handleDefaults(child); } // with the defaults read, create the meta-data metaData = new PolicyMetaData(root.getNamespaceURI(), defaultVersion); // now read the remaining policy elements obligations = new HashSet<Obligation>(); parameters = new ArrayList<CombinerParameter>(); children = root.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { Node child = children.item(i); String cname = child.getNodeName(); if (cname.equals("Description")) { if (child.hasChildNodes()) description = child.getFirstChild().getNodeValue(); } else if (cname.equals("Target")) { target = Target.getInstance(child, metaData); } else if (cname.equals("Obligations")) { parseObligations(child); } else if (cname.equals("CombinerParameters")) { handleParameters(child); } } // finally, make sure the obligations and parameters are immutable obligations = Collections.unmodifiableSet(obligations); parameters = Collections.unmodifiableList(parameters); } /** * Helper routine to parse the obligation data */ private void parseObligations(Node root) throws ParsingException { NodeList nodes = root.getChildNodes(); for (int i = 0; i < nodes.getLength(); i++) { Node node = nodes.item(i); if (node.getNodeName().equals("Obligation")) obligations.add(Obligation.getInstance(node)); } } /** * There used to be multiple things in the defaults type, but now there's just the one string * that must be a certain value, so it doesn't seem all that useful to have a class for * this...we could always bring it back, however, if it started to do more */ private void handleDefaults(Node root) throws ParsingException { defaultVersion = null; NodeList nodes = root.getChildNodes(); for (int i = 0; i < nodes.getLength(); i++) { Node node = nodes.item(i); if (node.getNodeName().equals("XPathVersion")) defaultVersion = node.getFirstChild().getNodeValue(); } } /** * Handles all the CombinerParameters in the policy or policy set */ private void handleParameters(Node root) throws ParsingException { NodeList nodes = root.getChildNodes(); for (int i = 0; i < nodes.getLength(); i++) { Node node = nodes.item(i); if (node.getNodeName().equals("CombinerParameter")) parameters.add(CombinerParameter.getInstance(node)); } } /** * Returns the id of this policy * * @return the policy id */ public URI getId() { return idAttr; } /** * Returns the version of this policy. If this is an XACML 1.x policy then this will always * return <code>"1.0"</code>. * * @return the policy version */ public String getVersion() { return version; } /** * Returns the combining algorithm used by this policy * * @return the combining algorithm */ public CombiningAlgorithm getCombiningAlg() { return combiningAlg; } /** * Returns the list of input parameters for the combining algorithm. If this is an XACML 1.x * policy then the list will always be empty. * * @return a <code>List</code> of <code>CombinerParameter</code>s */ public List<CombinerParameter> getCombiningParameters() { return parameters; } /** * Returns the given description of this policy or null if there is no description * * @return the description or null */ public String getDescription() { return description; } /** * Returns the target for this policy * * @return the policy's target */ public Target getTarget() { return target; } /** * Returns the XPath version to use or null if none was specified * * @return XPath version or null */ public String getDefaultVersion() { return defaultVersion; } /** * Returns the <code>List</code> of children under this node in the policy tree. Depending on * what kind of policy this node represents the children will either be * <code>AbstractPolicy</code> objects or <code>Rule</code>s. * * @return a <code>List</code> of child nodes */ public List<PolicyTreeElement> getChildren() { return children; } /** * Returns the <code>List</code> of <code>CombinerElement</code>s that is provided to the * combining algorithm. This returns the same set of children that <code>getChildren</code> * provides along with any associated combiner parameters. * * @return a <code>List</code> of <code>CombinerElement</code>s */ public List<CombinerElement> getChildElements() { return childElements; } /** * Returns the Set of obligations for this policy, which may be empty * * @return the policy's obligations */ public Set<Obligation> getObligations() { return obligations; } /** * Returns the meta-data associated with this policy */ public PolicyMetaData getMetaData() { return metaData; } /** * Given the input context sees whether or not the request matches this policy. This must be * called by combining algorithms before they evaluate a policy. This is also used in the * initial policy finding operation to determine which top-level policies might apply to the * request. * * @param context * the representation of the request * * @return the result of trying to match the policy and the request */ public MatchResult match(EvaluationCtx context) { return target.match(context); } /** * Sets the child policy tree elements for this node, which are passed to the combining * algorithm on evaluation. The <code>List</code> must contain <code>CombinerElement</code>s, * which in turn will contain <code>Rule</code>s or <code>AbstractPolicy</code>s, but may not * contain both types of elements. * * @param children * a <code>List</code> of <code>CombinerElement</code>s representing the child * elements used by the combining algorithm */ protected void setChildren(List<? extends CombinerElement> children) { // we always want a concrete list, since we're going to pass it to // a combiner that expects a non-null input if (children == null) { this.children = Collections.emptyList(); } else { // NOTE: since this is only getting called by known child // classes we don't check that the types are all the same List<PolicyTreeElement> list = new ArrayList<PolicyTreeElement>(); for (CombinerElement element : children) list.add(element.getElement()); this.children = Collections.unmodifiableList(list); childElements = Collections.unmodifiableList(children); } } /** * Tries to evaluate the policy by calling the combining algorithm on the given policies or * rules. The <code>match</code> method must always be called first, and must always return * MATCH, before this method is called. * * @param context * the representation of the request * * @return the result of evaluation */ public Result evaluate(EvaluationCtx context) { // evaluate Result result = combiningAlg.combine(context, parameters, childElements); // if we have no obligations, we're done if (obligations.size() == 0) return result; // now, see if we should add any obligations to the set int effect = result.getDecision(); if ((effect == Result.DECISION_INDETERMINATE) || (effect == Result.DECISION_NOT_APPLICABLE)) { // we didn't permit/deny, so we never return obligations return result; } for (Obligation obligation : obligations) { if (obligation.getFulfillOn() == effect) result.addObligation(obligation); } // finally, return the result return result; } /** * Routine used by <code>Policy</code> and <code>PolicySet</code> to encode some common * elements. * * @param output * a stream into which the XML-encoded data is written * @param indenter * an object that creates indentation strings */ protected void encodeCommonElements(OutputStream output, Indenter indenter) { for (CombinerElement elem : childElements) elem.encode(output, indenter); if (obligations.size() != 0) { PrintStream out = new PrintStream(output); String indent = indenter.makeString(); out.println(indent + "<Obligations>"); indenter.in(); for (Obligation obligation : obligations) obligation.encode(output, indenter); out.println(indent + "</Obligations>"); indenter.out(); } } }