/* * @(#)PDP.java * * Copyright 2003-2004 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.attr.AttributeValue; import com.sun.xacml.ctx.RequestCtx; import com.sun.xacml.ctx.RequestElement; import com.sun.xacml.ctx.ResponseCtx; import com.sun.xacml.ctx.Result; import com.sun.xacml.ctx.Status; import com.sun.xacml.finder.AttributeFinder; import com.sun.xacml.finder.PolicyFinder; import com.sun.xacml.finder.PolicyFinderResult; import com.sun.xacml.finder.ResourceFinder; import com.sun.xacml.finder.ResourceFinderResult; import com.sun.xacml.finder.RevocationFinder; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URL; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Properties; import java.util.Set; import org.apache.log4j.Logger; /** * This is the core class for the XACML engine, providing the starting point * for request evaluation. To build an XACML policy engine, you start by * instantiating this object. * * @since 1.0 * @author Seth Proctor */ public class PDP { // the single attribute finder that can be used to find external values private AttributeFinder attributeFinder; // the single policy finder that will be used to resolve policies private PolicyFinder policyFinder; // the single resource finder that will be used to resolve resources private ResourceFinder resourceFinder; // the single revocation finder that will be used to find and validate // revocations private RevocationFinder revocationFinder; // the logger we'll use for all messages private static final Logger logger = Logger.getLogger(PDP.class.getName()); private static String buildBy; private static String buildTime; static { Properties manifest = new Properties(); try { String classUrl = "/" + PDP.class.getCanonicalName().replaceAll("\\.", "/") + ".class"; manifest.load(new URL(PDP.class.getResource(classUrl).toString().replace(classUrl, "/META-INF/MANIFEST.MF")).openStream()); } catch (Exception e) { // ignore.. well, doesn't work.. } if ( manifest.containsKey("Built-By") ) { buildBy = manifest.getProperty("Built-By"); } else { buildBy = "unkown"; } if ( manifest.containsKey("Built-Time") ) { buildTime = manifest.getProperty("Built-Time"); } else { buildTime = "unkown"; } } /** * Constructs a new <code>PDP</code> object with the given configuration * information. * * @param config user configuration data defining how to find policies, * resolve external attributes, etc. */ public PDP(PDPConfig config) { logger.info("creating a PDP (built by " + buildBy + " at " + buildTime + ")"); this.attributeFinder = config.getAttributeFinder(); this.policyFinder = config.getPolicyFinder(); this.policyFinder.init(config); this.resourceFinder = config.getResourceFinder(); this.revocationFinder = config.getRevocationFinder(); } /** * Attempts to evaluate the request against the policies known to this * PDP. This is really the core method of the entire XACML specification, * and for most people will provide what you want. If you need any special * handling, you should look at the version of this method that takes an * <code>EvaluationCtx</code>. * <p> * Note that if the request is somehow invalid (it was missing a required * attribute, it was using an unsupported scope, etc), then the result * will be a decision of INDETERMINATE. * * @param request the request to evaluate * * @return a response paired to the request */ public ResponseCtx evaluate(RequestCtx request) { // try to create the EvaluationCtx out of the request try { return evaluate(new BasicEvaluationCtx(request, this.attributeFinder, this.revocationFinder)); } catch (ParsingException pe) { logger.info("the PDP receieved an invalid request", pe); // there was something wrong with the request, so we return // Indeterminate with a status of syntax error...though this // may change if a more appropriate status type exists ArrayList<String> code = new ArrayList<String>(); code.add(Status.STATUS_SYNTAX_ERROR); Status status = new Status(code, pe.getMessage()); return new ResponseCtx(new Result(Result.DECISION_INDETERMINATE, status)); } } /** * Uses the given <code>EvaluationCtx</code> against the available * policies to determine a response. If you are starting with a standard * XACML Request, then you should use the version of this method that * takes a <code>RequestCtx</code>. This method should be used only if * you have a real need to directly construct an evaluation context (or * if you need to use an <code>EvaluationCtx</code> implementation other * than <code>BasicEvaluationCtx</code>). * * @param context representation of the request and the context used * for evaluation * * @return a response based on the contents of the context */ public ResponseCtx evaluate(EvaluationCtx context) { // see if we need to call the resource finder if (context.getScope() != Constants.SCOPE_IMMEDIATE) { AttributeValue parent = context.getResourceId(); ResourceFinderResult resourceResult = null; if (context.getScope() == Constants.SCOPE_CHILDREN) { resourceResult = this.resourceFinder.findChildResources(parent, context); } else if (context.getScope() == Constants.SCOPE_DESCENDANTS) { resourceResult = this.resourceFinder.findDescendantResources(parent, context); } else if (context.getScope() == Constants.SCOPE_XPATH_EXPRESSION) { resourceResult = this.resourceFinder.findXPathResources(parent, context); } else if (context.getScope() == Constants.SCOPE_ENTIRE_HIERARCHY) { resourceResult = this.resourceFinder.findHierarchyResources(parent, context); } else if (context.getScope() == Constants.SCOPE_MULTIPE_ELEMENTS) { return multipleElementsEval(context); } // see if we actually found anything if (resourceResult == null || resourceResult.isEmpty()) { // this is a problem, since we couldn't find any resources // to work on...the spec is not explicit about what kind of // error this is, so we're treating it as a processing error ArrayList<String> code = new ArrayList<String>(); code.add(Status.STATUS_PROCESSING_ERROR); String msg = "Couldn't find any resources to work on."; return new ResponseCtx(new Result(Result.DECISION_INDETERMINATE, new Status(code, msg), context)); } // setup a set to keep track of the results HashSet<Result> results = new HashSet<Result>(); // at this point, we need to go through all the resources we // successfully found and start collecting results Iterator<AttributeValue> it = resourceResult.getResources().iterator(); while (it.hasNext()) { // get the next resource, and set it in the EvaluationCtx AttributeValue resource = it.next(); context.setResourceId(resource); // do the evaluation, and set the resource in the result Result result = evaluateContext(context); result.setResource(resource.encode()); // add the result results.add(result); } // now that we've done all the successes, we add all the failures // from the finder result Map<AttributeValue, Status> failureMap = resourceResult.getFailures(); Iterator<Map.Entry<AttributeValue, Status>> it2 = failureMap.entrySet().iterator(); while (it2.hasNext()) { Map.Entry<AttributeValue, Status> entry = it2.next(); // get the next resource, and use it to get its Status data Status status = entry.getValue(); // add a new result results.add(new Result(Result.DECISION_INDETERMINATE, status, context)); } // return the set of results return new ResponseCtx(results); } // the scope was IMMEDIATE (or missing), so we can just evaluate // the request and return whatever we get back return new ResponseCtx(evaluateContext(context)); } /** * Protected helper to handle multiple elements of same category in the * request (generates several requests). * * @param context * @return Collected responses for the multiple elements evaluation. */ protected ResponseCtx multipleElementsEval(EvaluationCtx context) { // Collect the different Maps of request elements here Map<URI, Set<RequestElement>> elements = context.getRequestElements(); Iterator<URI> iter = elements.keySet().iterator(); Set<Set<RequestElement>> setOfsets = new HashSet<Set<RequestElement>>(); while (iter.hasNext()) { Set<RequestElement> reSet = context.getCategory(iter.next()); //Do the division if (setOfsets.isEmpty()) { //Create initial set of sets Iterator<RequestElement> iter2 = reSet.iterator(); while (iter2.hasNext()) { RequestElement re = iter2.next(); Set<RequestElement> newSet = new HashSet<RequestElement>(); newSet.add(re); setOfsets.add(newSet); } } else { Iterator<Set<RequestElement>> iter2 = setOfsets.iterator(); Set<Set<RequestElement>> newSetOfSets = new HashSet<Set<RequestElement>>(); while (iter2.hasNext()) { Set<RequestElement> subset = iter2.next(); Iterator<RequestElement> iter3 = reSet.iterator(); while(iter3.hasNext()) { RequestElement re = iter3.next(); Set<RequestElement> newSet = new HashSet<RequestElement>(); newSet.addAll(subset); newSet.add(re); newSetOfSets.add(newSet); } } setOfsets = newSetOfSets; } } if (setOfsets.size() < 2) { ArrayList<String> code = new ArrayList<String>(); code.add(Status.STATUS_PROCESSING_ERROR); String msg = "Scope set to 'multipe elements' but could" + " only find one/zero"; return new ResponseCtx( new Result(Result.DECISION_INDETERMINATE, new Status(code, msg), context)); } // setup a set to keep track of the results HashSet<Result> results = new HashSet<Result>(); // at this point, we need to go through all the sets we // successfully found and start collecting creating requests // and collect results Iterator<Set<RequestElement>> it = setOfsets.iterator(); while (it.hasNext()) { // get the next set of request elements, and create a // new request for it. Set<RequestElement> requestElements = it.next(); RequestCtx request = new RequestCtx(requestElements, null, null); Result result = null; try { context = new BasicEvaluationCtx(request, this.attributeFinder, this.revocationFinder); } catch (ParsingException pe) { logger.info("the PDP receieved an invalid request", pe); // there was something wrong with the request, so we collect // Indeterminate with a status of syntax error...though this // may change if a more appropriate status type exists ArrayList<String> code = new ArrayList<String>(); code.add(Status.STATUS_SYNTAX_ERROR); Status status = new Status(code, pe.getMessage()); result = new Result(Result.DECISION_INDETERMINATE, status); } if (result == null) {// do the evaluation if nothing went wrong result = evaluateContext(context); } // add the result results.add(result); } // return the set of results return new ResponseCtx(results); } /** * A protected helper routine that resolves a policy for the given * context, and then tries to evaluate based on the policy. * Can be overrider by subclasses. */ protected Result evaluateContext(EvaluationCtx context) { // first off, try to find a policy PolicyFinderResult finderResult = this.policyFinder.findPolicy(context); // see if there weren't any applicable policies if (finderResult.notApplicable()) { return new Result(Result.DECISION_NOT_APPLICABLE, context); } // see if there were any errors in trying to get a policy if (finderResult.indeterminate()) { return new Result(Result.DECISION_INDETERMINATE, finderResult.getStatus(), context); } // we found a valid policy context.newEvent(finderResult.getPolicy()); Result result = finderResult.getPolicy().evaluate(context); context.closeCurrentEvent(result); if (result == null) { return new Result(Result.DECISION_NOT_APPLICABLE, context); } return result; } /** * A utility method that wraps the functionality of the other evaluate * method with input and output streams. This is useful if you've got * a PDP that is taking inputs from some stream and is returning * responses through the same stream system. If the Request is invalid, * then this will always return a decision of INDETERMINATE. * * @deprecated As of 1.2 this method should not be used. Instead, you * should do your own stream handling, and then use one of * the other <code>evaluate</code> methods. The problem * with this method is that it often doesn't handle stream * termination correctly (eg, with sockets). * * @param input a stream that contains an XML RequestType * @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. * * @return a stream that contains an XML ResponseType * @throws UnsupportedEncodingException */ public OutputStream evaluate(InputStream input, String charsetName) throws UnsupportedEncodingException { RequestCtx request = null; ResponseCtx response = null; try { request = RequestCtx.getInstance(input); } catch (Exception pe) { // the request wasn't formed correctly ArrayList<String> code = new ArrayList<String>(); code.add(Status.STATUS_SYNTAX_ERROR); Status status = new Status(code, "invalid request: " + pe.getMessage()); response = new ResponseCtx(new Result(Result.DECISION_INDETERMINATE, status)); } // if we didn't have a problem above, then we should go ahead // with the evaluation if (response == null) { response = evaluate(request); } ByteArrayOutputStream out = new ByteArrayOutputStream(); response.encode(out, charsetName, new Indenter()); return out; } /** * @return The <code>AttributeFinder</code> * (used by subclasses of the PDP). */ protected AttributeFinder getAttributeFinder() { return this.attributeFinder; } /** * @return The <code>RevocationFinder</code> * (used by subclasses of the PDP). */ protected RevocationFinder getRevocationFinder() { return this.revocationFinder; } /** * @return The <code>PolicyFinder</code> * (used by subclasses of the PDP). */ protected PolicyFinder getPolicyFinder() { return this.policyFinder; } // public void setEmergencyLevel(EmergencyLevel level) { // this.curEmgLevel = level; // } // // public EmergencyLevel getEmergencyLevel() { // return this.curEmgLevel; // } }