/* * @(#)PolicyCollection.java * * Copyright 2006 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.support.finder; import com.sun.xacml.AbstractPolicy; import com.sun.xacml.EvaluationCtx; import com.sun.xacml.MatchResult; import com.sun.xacml.Policy; import com.sun.xacml.PolicyMetaData; import com.sun.xacml.PolicyReference; import com.sun.xacml.PolicySet; import com.sun.xacml.Target; import com.sun.xacml.TargetMatch; import com.sun.xacml.TargetSection; import com.sun.xacml.VersionConstraints; import com.sun.xacml.combine.PolicyCombiningAlgorithm; import com.sun.xacml.ctx.Status; import java.net.URI; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.StringTokenizer; import java.util.TreeSet; /** * This class handles collections of <code>AbstractPolicy</code> instances, * and provides some commonly useful operations. Specifically, it lets you * retrieve matching policies (based on reference or context), it optionally * handles wrapping multiple matches under a single PolicySet, and it manages * different versions of policies correctly. This class is intended for use * as a backing store to <code>PolicyFinderModule</code>s, but in practice * may have many uses. * <p> * Note that this class will accept multiple versions of the same policy. This * means that when you retieve a policy by reference, you will get the * correct version. It also means that when you retrieve a policy based on * context, there may be multiple revisions of the same policy, any number * of which may apply. Generally speaking, the correct behavior here is not * to return all of these policies, since they are (virtually speaking) the * same policy, but may have conflicting rules. So, as a simplification, and * to handle the most common cases, only the most recent version of a policy * is returned in these cases. If you need a more complex solution, you * will need to implement it yourself. Because the support modules use this * class as their backing store, this is true also of those modules. * <p> * Note that this is not a heavily optimized class. It is intended more as * an example, support code for the finder modules, and a starting utility * for other programmers than as an enterprise-quality implementation. That * said, it is fully functional, and should be useful for many applications. * * @since 2.0 * @author Seth Proctor */ public class PolicyCollection { // the actual collection of policies private HashMap policies; // the single instance of the comparator we'll use for managing versions private VersionComparator versionComparator = new VersionComparator(); // the optional combining algorithm used when wrapping multiple policies private PolicyCombiningAlgorithm combiningAlg; // the optional policy id used when wrapping multiple policies private URI parentId; // default target that matches anything, used in wrapping policies private static final Target target; /** * This static initializer just sets up the default target, which is * used by all wrapping policy sets. */ static { target = new Target(new TargetSection(null, TargetMatch.SUBJECT, PolicyMetaData.XACML_VERSION_2_0), new TargetSection(null, TargetMatch.RESOURCE, PolicyMetaData.XACML_VERSION_2_0), new TargetSection(null, TargetMatch.ACTION, PolicyMetaData.XACML_VERSION_2_0), new TargetSection(null, TargetMatch.ENVIRONMENT, PolicyMetaData.XACML_VERSION_2_0)); }; /** * Creates a new <code>PolicyCollection</code> that will return errors * when multiple policies match for a given request. */ public PolicyCollection() { policies = new HashMap(); combiningAlg = null; } /** * Creates a new <code>PolicyCollection</code> that will create a new * top-level PolicySet when multiple policies match for a given request. * * @param combiningAlg the algorithm to use in a new PolicySet when more * than one policy applies * @param parentPolicyId the identifier to use for the new PolicySet */ public PolicyCollection(PolicyCombiningAlgorithm combiningAlg, URI parentPolicyId) { policies = new HashMap(); this.combiningAlg = combiningAlg; this.parentId = parentPolicyId; } /** * Adds a new policy to the collection, and uses the policy's identifier * as the reference identifier. If this identifier already exists in the * collection, and this policy does not represent a new version of the * policy, then the policy is not added. * * @param policy the policy to add * * @return true if the policy was added, false otherwise */ public boolean addPolicy(AbstractPolicy policy) { return addPolicy(policy, policy.getId().toString()); } /** * Adds a new policy to the collection using the given identifier as * the reference identifier. If this identifier already exists in the * collection, and this policy does not represent a new version of the * policy, then the policy is not added. * * @param policy the policy to add * @param identifier the identifier to use when referencing this policy * * @return true if the policy was added, false otherwise */ public boolean addPolicy(AbstractPolicy policy, String identifier) { if (policies.containsKey(identifier)) { // this identifier is already is use, so see if this version is // already in the set TreeSet set = (TreeSet)(policies.get(identifier)); return set.add(policy); } else { // this identifier isn't already being used, so create a new // set in the map for it, and add the policy TreeSet set = new TreeSet(versionComparator); policies.put(identifier, set); return set.add(policy); } } /** * Attempts to retrieve a policy based on the given context. If multiple * policies match then this will either throw an exception or wrap the * policies under a new PolicySet (depending on how this instance was * constructed). If no policies match, then this will return null. See * the comment in the class header about how this behaves when multiple * versions of the same policy exist. * * @param context representation of a request * * @return a matching policy, or null if no policy matches * * @throws TopLevelPolicyException if multiple policies match but this * instance wasn't setup to wrap policies */ public AbstractPolicy getPolicy(EvaluationCtx context) throws TopLevelPolicyException { // setup a list of matching policies ArrayList list = new ArrayList(); // get an iterator over all the identifiers Iterator it = policies.values().iterator(); while (it.hasNext()) { // for each identifier, get only the most recent policy AbstractPolicy policy = (AbstractPolicy)(((TreeSet)(it.next())).first()); // see if we match MatchResult match = policy.match(context); int result = match.getResult(); // if there was an error, we stop right away if (result == MatchResult.INDETERMINATE) throw new TopLevelPolicyException(match.getStatus()); // if we matched, we keep track of the matching policy... if (result == MatchResult.MATCH) { // ...first checking if this is the first match and if // we automaticlly nest policies if ((combiningAlg == null) && (list.size() > 0)) { ArrayList code = new ArrayList(); code.add(Status.STATUS_PROCESSING_ERROR); Status status = new Status(code, "too many applicable" + " top-level policies"); throw new TopLevelPolicyException(status); } list.add(policy); } } // no errors happened during the search, so now take the right // action based on how many policies we found switch (list.size()) { case 0: return null; case 1: return ((AbstractPolicy)(list.get(0))); default: return new PolicySet(parentId, combiningAlg, target, list); } } /** * Attempts to retrieve a policy based on the given identifier and other * constraints. If there are multiple versions of the identified policy * that meet the version constraints, then the most recent version is * returned. * * @param identifier an identifier specifying some policy * @param type type of reference (policy or policySet) as identified by * the fields in <code>PolicyReference</code> * @param constraints any optional constraints on the version of the * referenced policy (this will never be null, but * it may impose no constraints, and in fact will * never impose constraints when used from a pre-2.0 * XACML policy) */ public AbstractPolicy getPolicy(String identifier, int type, VersionConstraints constraints) { TreeSet set = (TreeSet)(policies.get(identifier)); // if we don't know about this identifier then there's nothing to do if (set == null) return null; // walk through the set starting with the most recent version, looking // for a match until we exhaust all known versions Iterator it = set.iterator(); while (it.hasNext()) { AbstractPolicy policy = (AbstractPolicy)(it.next()); if (constraints.meetsConstraint(policy.getVersion())) { // we found a valid version, so see if it's the right kind, // and if it is then we return it if (type == PolicyReference.POLICY_REFERENCE) { if (policy instanceof Policy) return policy; } else { if (policy instanceof PolicySet) return policy; } } } // we didn't find a match return null; } /** * A <code>Comparator</code> that is used within this class to maintain * ordering amongst different versions of the same policy. Note that * it actually maintains reverse-ordering, since we want to traverse the * sets in decreasing, not increasing order. * <p> * Note that this comparator is only used when there are multiple versions * of the same policy, which in practice will probably happen far less * (from this class' point of view) than additions or fetches. */ class VersionComparator implements Comparator { public int compare(Object o1, Object o2) { // we swap the parameters so that sorting goes largest to smallest String v1 = ((AbstractPolicy)o2).getVersion(); String v2 = ((AbstractPolicy)o1).getVersion(); // do a quick check to see if the strings are equal (note that // even if the strings aren't equal, the versions can still // be equal) if (v1.equals(v2)) return 0; // setup tokenizers, and walk through both strings one set of // numeric values at a time StringTokenizer tok1 = new StringTokenizer(v1, "."); StringTokenizer tok2 = new StringTokenizer(v2, "."); while (tok1.hasMoreTokens()) { // if there's nothing left in tok2, then v1 is bigger if (! tok2.hasMoreTokens()) return 1; // get the next elements in the version, convert to numbers, // and compare them (continuing with the loop only if the // two values were equal) int num1 = Integer.parseInt(tok1.nextToken()); int num2 = Integer.parseInt(tok2.nextToken()); if (num1 > num2) return 1; if (num1 < num2) return -1; } // if there's still something left in tok2, then it's bigger if (tok2.hasMoreTokens()) return -1; // if we got here it means both versions had the same number of // elements and all the elements were equal, so the versions // are in fact equal return 0; } } }