1154 lines
44 KiB
Java
1154 lines
44 KiB
Java
/*
|
|
* @(#)BasicEvaluationCtx.java
|
|
*
|
|
* Copyright 2004-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;
|
|
|
|
import com.sun.xacml.attr.AttributeValue;
|
|
import com.sun.xacml.attr.BagAttribute;
|
|
import com.sun.xacml.attr.DateAttribute;
|
|
import com.sun.xacml.attr.DateTimeAttribute;
|
|
import com.sun.xacml.attr.DecisionAttribute;
|
|
import com.sun.xacml.attr.StringAttribute;
|
|
import com.sun.xacml.attr.TimeAttribute;
|
|
|
|
import com.sun.xacml.cond.EvaluationResult;
|
|
|
|
import com.sun.xacml.ctx.Attribute;
|
|
import com.sun.xacml.ctx.RequestCtx;
|
|
import com.sun.xacml.ctx.RequestElement;
|
|
import com.sun.xacml.ctx.Result;
|
|
|
|
import com.sun.xacml.finder.AttributeFinder;
|
|
import com.sun.xacml.finder.RevocationFinder;
|
|
import com.sun.xacml.reduction.ReductionGraph;
|
|
|
|
import java.net.URI;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Date;
|
|
import java.util.Collections;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
import java.util.Stack;
|
|
|
|
import org.apache.log4j.Logger;
|
|
|
|
import org.w3c.dom.Node;
|
|
|
|
|
|
/**
|
|
* A basic implementation of <code>EvaluationCtx</code> that is created from
|
|
* an XACML Request and falls back on an AttributeFinder if a requested
|
|
* value isn't available in the Request.
|
|
* <p>
|
|
* Note that this class can do some optional caching for current date, time,
|
|
* and dateTime values (defined by a boolean flag to the constructors). The
|
|
* XACML specification requires that these values always be available, but it
|
|
* does not specify whether or not they must remain constant over the course
|
|
* of an evaluation if the values are being generated by the PDP (if the
|
|
* values are provided in the Request, then obviously they will remain
|
|
* constant). The default behavior is for these environment values to be
|
|
* cached, so that (for example) the current time remains constant over the
|
|
* course of an evaluation.
|
|
*
|
|
* @since 1.2
|
|
* @author Seth Proctor
|
|
* @author Ludwig Seitz
|
|
*/
|
|
public class BasicEvaluationCtx implements EvaluationCtx, Cloneable
|
|
{
|
|
/**
|
|
* the finder to use if a value isn't in the request
|
|
*/
|
|
private AttributeFinder aFinder;
|
|
|
|
/**
|
|
* flag that indicates if the attribute finder is deactivated.
|
|
*/
|
|
private boolean afinderActive = true;
|
|
|
|
/**
|
|
* the revocation finder to use
|
|
*/
|
|
private RevocationFinder rFinder;
|
|
|
|
/**
|
|
* flag that indicates if the revocation finder is deactivated.
|
|
*/
|
|
private boolean rfinderActive = true;
|
|
/**
|
|
* the DOM root the original RequestContext document.
|
|
*/
|
|
private Node requestRoot;
|
|
|
|
/**
|
|
* The request data. A map of <code>Set</code>s of
|
|
* <code>RequestElement</code>s keyed by the attribute category
|
|
* <code>String</code>s.
|
|
*/
|
|
private HashMap<URI, Set<RequestElement>> requestElements = null;
|
|
|
|
/**
|
|
* the cached delegation decision value
|
|
*/
|
|
private int delegationDecision;
|
|
|
|
/**
|
|
* the delegation depth value
|
|
*/
|
|
private int delegationDepth;
|
|
|
|
/**
|
|
* the resource id
|
|
*/
|
|
private AttributeValue resourceId;
|
|
|
|
/**
|
|
* the <code>Set</code> of <code>RequestElements</code> containing
|
|
* only the attributes that should be included in the result.
|
|
*/
|
|
private Set<RequestElement> includedAttributes;
|
|
|
|
/**
|
|
* the resource scope
|
|
*/
|
|
private int scope;
|
|
|
|
/**
|
|
* the cached current date, which we may or may
|
|
* not be using depending on how this object was constructed
|
|
*/
|
|
private DateAttribute currentDate;
|
|
|
|
/**
|
|
* the cached current time, which we may or may
|
|
* not be using depending on how this object was constructed
|
|
*/
|
|
private TimeAttribute currentTime;
|
|
|
|
/**
|
|
* the cached current dateTime, which we may or may
|
|
* not be using depending on how this object was constructed
|
|
*/
|
|
private DateTimeAttribute currentDateTime;
|
|
|
|
/**
|
|
* a flag indicating that we should use the cached date/time values.
|
|
*/
|
|
private boolean useCachedEnvValues;
|
|
|
|
/**
|
|
* a stack of parent <code>PolicySet</code>s.
|
|
*/
|
|
private Stack<PolicyTreeElement> parentPolicySets;
|
|
|
|
/**
|
|
* a stack of <code>ReductionGraph</code>s.
|
|
*/
|
|
private Stack<ReductionGraph> reductionGraphs;
|
|
|
|
/**
|
|
* the set of deactivated policies
|
|
*/
|
|
private Set<URI> inactivePolicyIds;
|
|
|
|
/**
|
|
* the logger we'll use for all messages
|
|
*/
|
|
private static final Logger logger =
|
|
Logger.getLogger(BasicEvaluationCtx.class.getName());
|
|
|
|
/**
|
|
* Constructs a new <code>BasicEvaluationCtx</code> based on the given
|
|
* request. The resulting context will cache current date, time, and
|
|
* dateTime values so they remain constant for this evaluation.
|
|
*
|
|
* @param request the request
|
|
*
|
|
* @throws ParsingException if a required attribute is missing, or if there
|
|
* are any problems dealing with the request data
|
|
*/
|
|
public BasicEvaluationCtx(RequestCtx request)
|
|
throws ParsingException {
|
|
this(request, null, null, true);
|
|
}
|
|
|
|
/**
|
|
* Constructs a new <code>BasicEvaluationCtx</code> based on the given
|
|
* request.
|
|
*
|
|
* @param request the request
|
|
* @param cacheEnvValues whether or not to cache the current time, date,
|
|
* and dateTime so they are constant for the scope
|
|
* of this evaluation
|
|
*
|
|
* @throws ParsingException if a required attribute is missing, or if there
|
|
* are any problems dealing with the request data
|
|
*/
|
|
public BasicEvaluationCtx(RequestCtx request, boolean cacheEnvValues)
|
|
throws ParsingException {
|
|
this(request, null, null, cacheEnvValues);
|
|
}
|
|
|
|
/**
|
|
* Constructs a new <code>BasicEvaluationCtx</code> based on the given
|
|
* request, and supports looking outside the original request for attribute
|
|
* values using the <code>AttributeFinder</code>. The resulting context
|
|
* will cache current date, time, and dateTime values so they remain
|
|
* constant for this evaluation.
|
|
*
|
|
* @param request the request
|
|
* @param aFinder an <code>AttributeFinder</code> to use in looking for
|
|
* attributes that aren't in the request
|
|
* @param rFinder an <code>RevocationFinder</code> to use in looking for
|
|
* revocations
|
|
*
|
|
* @throws ParsingException if a required attribute is missing, or if there
|
|
* are any problems dealing with the request data
|
|
*/
|
|
public BasicEvaluationCtx(RequestCtx request, AttributeFinder aFinder,
|
|
RevocationFinder rFinder)
|
|
throws ParsingException {
|
|
this(request, aFinder, rFinder, true);
|
|
}
|
|
|
|
/**
|
|
* Constructs a new <code>BasicEvaluationCtx</code> based on the given
|
|
* request, and supports looking outside the original request for attribute
|
|
* values using the <code>AttributeFinder</code>.
|
|
*
|
|
* @param request the request
|
|
* @param aFinder an <code>AttributeFinder</code> to use in looking for
|
|
* @param rFinder an <code>RevocationFinder</code> to use in looking for
|
|
* revocations
|
|
* attributes that aren't in the request
|
|
* @param cacheEnvValues whether or not to cache the current time, date,
|
|
* and dateTime so they are constant for the scope
|
|
* of this evaluation
|
|
*
|
|
* @throws ParsingException if a required attribute is missing, or if there
|
|
* are any problems dealing with the request data
|
|
*/
|
|
public BasicEvaluationCtx(RequestCtx request, AttributeFinder aFinder,
|
|
RevocationFinder rFinder,
|
|
boolean cacheEnvValues)
|
|
throws ParsingException {
|
|
// keep track of the attribute finder
|
|
this.aFinder = aFinder;
|
|
// keep track of the revocation finder
|
|
this.rFinder = rFinder;
|
|
|
|
// remember the root of the DOM tree for XPath queries
|
|
this.requestRoot = request.getDocumentRoot();
|
|
|
|
// initialize the cached date/time values so it's clear we haven't
|
|
// retrieved them yet
|
|
this.useCachedEnvValues = cacheEnvValues;
|
|
this.currentDate = null;
|
|
this.currentTime = null;
|
|
this.currentDateTime = null;
|
|
|
|
// parse the request elements into a map
|
|
// and collect the IncludeInResult attributes
|
|
this.requestElements = new HashMap<URI, Set<RequestElement>>();
|
|
this.includedAttributes = new HashSet<RequestElement>();
|
|
Iterator<RequestElement> iter = request.getRequestElements().iterator();
|
|
while (iter.hasNext()){
|
|
RequestElement re = iter.next();
|
|
URI category = re.getCategory();
|
|
if (!re.getIncludeInResultSet().isEmpty()) {
|
|
this.includedAttributes.add(new RequestElement(
|
|
re.getCategory(), re.getIncludeInResultSet()));
|
|
}
|
|
if (this.requestElements.containsKey(category)) {
|
|
this.scope = Constants.SCOPE_MULTIPE_ELEMENTS;
|
|
Set<RequestElement> set = this.requestElements.get(category);
|
|
set.add(re);
|
|
} else {
|
|
Set<RequestElement> set = new HashSet<RequestElement>();
|
|
set.add(re);
|
|
this.requestElements.put(category, set);
|
|
}
|
|
}
|
|
|
|
//Compatibility code for XACML 2.0 requestId handling
|
|
if (request.getXACMLVersion() < Constants.XACML_VERSION_3_0
|
|
&& !this.includedAttributes.isEmpty()) {
|
|
throw new ParsingException("Can't have included attributes"
|
|
+ " in XACML < v 3.0");
|
|
}
|
|
|
|
//Compatibility code for old Resource handling.
|
|
if (this.requestElements.containsKey(Constants.RESOURCE_CAT)) {
|
|
Iterator<RequestElement> reIt = this.requestElements.get(
|
|
Constants.RESOURCE_CAT).iterator();
|
|
while(reIt.hasNext()) {
|
|
RequestElement re = (RequestElement)reIt.next();
|
|
checkResource(re.getAttributes());
|
|
}
|
|
}
|
|
|
|
//this will only be set in administrative requests.
|
|
this.delegationDecision = Result.INVALID_DECISION;
|
|
this.delegationDepth = 0;
|
|
|
|
this.parentPolicySets = new Stack<PolicyTreeElement>();
|
|
this.reductionGraphs = new Stack<ReductionGraph>();
|
|
|
|
// this is empty when the evaluation context is first created
|
|
// it is used during the reduction of untrusted policies
|
|
this.inactivePolicyIds = new HashSet<URI>();
|
|
}
|
|
|
|
/**
|
|
* The clone method
|
|
*
|
|
* @return a copy of this object.
|
|
*
|
|
*/
|
|
public Object clone() {
|
|
try {
|
|
BasicEvaluationCtx clone = (BasicEvaluationCtx)super.clone();
|
|
if (this.aFinder != null) {
|
|
clone.aFinder = (AttributeFinder)this.aFinder.clone();
|
|
}
|
|
clone.afinderActive = this.afinderActive;
|
|
if(this.rFinder != null) {
|
|
clone.rFinder = (RevocationFinder)this.rFinder.clone();
|
|
}
|
|
clone.rfinderActive = this.rfinderActive;
|
|
if(this.requestRoot != null) {
|
|
clone.requestRoot = this.requestRoot.cloneNode(true);
|
|
} else {
|
|
clone.requestRoot = null;
|
|
}
|
|
|
|
// deep copy of the requestElements
|
|
clone.requestElements = new HashMap<URI, Set<RequestElement>>();
|
|
Iterator<URI> iter = this.requestElements.keySet().iterator();
|
|
while(iter.hasNext()) {
|
|
URI key = URI.create(iter.next().toString());
|
|
Set<RequestElement> res = new HashSet<RequestElement>();
|
|
Iterator<RequestElement> iter2 = this.requestElements.get(key).iterator();
|
|
while(iter2.hasNext()) {
|
|
res.add((RequestElement)iter2.next().clone());
|
|
}
|
|
clone.requestElements.put(key, res);
|
|
}
|
|
|
|
clone.delegationDecision = this.delegationDecision;
|
|
clone.delegationDepth = this.delegationDepth;
|
|
clone.resourceId = this.resourceId;
|
|
|
|
//deep copy of the included attributes
|
|
clone.includedAttributes = new HashSet<RequestElement>();
|
|
Iterator<RequestElement> iter3 = this.includedAttributes.iterator();
|
|
while(iter3.hasNext()) {
|
|
RequestElement element = iter3.next();
|
|
clone.includedAttributes.add( (RequestElement) element.clone());
|
|
}
|
|
synchronized (clone) {
|
|
clone.scope = this.scope;
|
|
clone.currentDate = this.currentDate;
|
|
clone.currentTime = this.currentTime;
|
|
clone.currentDateTime = this.currentDateTime;
|
|
clone.useCachedEnvValues = this.useCachedEnvValues;
|
|
}
|
|
//clone.parentPolicySets = (Stack<PolicyTreeElement>) this.parentPolicySets.clone(); //
|
|
clone.parentPolicySets = new Stack<PolicyTreeElement>();
|
|
Collections.copy(parentPolicySets, this.parentPolicySets);
|
|
//clone.reductionGraphs = (Stack<ReductionGraph>) this.reductionGraphs.clone();
|
|
clone.reductionGraphs = new Stack<ReductionGraph>();
|
|
Collections.copy(reductionGraphs, this.reductionGraphs);
|
|
clone.inactivePolicyIds = new HashSet<URI>(this.inactivePolicyIds);
|
|
return clone;
|
|
} catch (CloneNotSupportedException e) {//This should never happen
|
|
throw new RuntimeException("Couldn't clone BasicEvaluationCtx");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a context for an administrative request from this context.
|
|
*
|
|
* @param decision The decision code corresponding to those in the
|
|
* <code>Result</code> class.
|
|
* @param delegate The delegate in this request (a set containing a
|
|
* single <code>RequestElement</code>).
|
|
*
|
|
* @return An administrative context for this context.
|
|
*/
|
|
public EvaluationCtx createAdminCtx(int decision, Set<RequestElement> delegate) {
|
|
if (decision == Result.INVALID_DECISION
|
|
|| decision == Result.DECISION_INDETERMINATE
|
|
|| decision == Result.DECISION_NOT_APPLICABLE) {
|
|
throw new ProcessingException("Invalid decision value for an"
|
|
+ " administrative request");
|
|
}
|
|
|
|
//First copy the old context.
|
|
BasicEvaluationCtx adminCtx = null;
|
|
adminCtx = (BasicEvaluationCtx)this.clone();
|
|
|
|
//Now set the new values ...
|
|
adminCtx.delegationDecision = decision;
|
|
adminCtx.delegationDepth = this.delegationDepth + 1;
|
|
|
|
//... and modify the requestElements accordingly
|
|
HashMap<URI, Set<RequestElement>> newREmap = new HashMap<URI, Set<RequestElement>>();
|
|
|
|
//get out the delegation relevant information
|
|
// and remove the old
|
|
adminCtx.requestElements.remove(Constants.DELEGATION_INFO);
|
|
adminCtx.requestElements.remove(Constants.DELEGATE);
|
|
|
|
//modify the rest so category * becomes category
|
|
// urn:oasis:names:tc:xacml:3.0:attribute-category:delegated:*
|
|
Iterator<Map.Entry<URI, Set<RequestElement>>> iter = adminCtx.requestElements.entrySet().iterator();
|
|
while(iter.hasNext()) {
|
|
Map.Entry<URI, Set<RequestElement>> entry = iter.next();
|
|
Set<RequestElement> requestElementCategory = entry.getValue();
|
|
Iterator<RequestElement> iter2 = requestElementCategory.iterator();
|
|
HashSet<RequestElement> newREset = new HashSet<RequestElement>();
|
|
URI category = null;
|
|
while(iter2.hasNext()) {
|
|
RequestElement oldRe = iter2.next();
|
|
category = oldRe.getCategory();
|
|
if (category.toString().startsWith(Constants.DELEGATED)) {
|
|
newREset.add(oldRe);
|
|
} else { // add the delegated part to the category
|
|
category = URI.create(Constants.DELEGATED
|
|
+ category.toString());
|
|
// this URI must be valid, since the old category was
|
|
RequestElement newRe = new RequestElement(category,
|
|
oldRe.getAttributes());
|
|
newREset.add(newRe);
|
|
}
|
|
}
|
|
newREmap.put(category, newREset);
|
|
}
|
|
|
|
//put in the new delegate
|
|
newREmap.put(Constants.DELEGATE, delegate);
|
|
|
|
//put in the new delegation info
|
|
|
|
// then create the correct DelegationInfo category
|
|
DecisionAttribute delegationDecision = new DecisionAttribute(decision);
|
|
Set<Attribute> delegationInfoAttrs = new HashSet<Attribute>();
|
|
delegationInfoAttrs.add(delegationDecision);
|
|
RequestElement delegationInfo = new RequestElement(
|
|
Constants.DELEGATION_INFO, delegationInfoAttrs);
|
|
Set<RequestElement> delegationInfoSet = new HashSet<RequestElement>();
|
|
delegationInfoSet.add(delegationInfo);
|
|
// and put it into the new request elements map
|
|
newREmap.put(Constants.DELEGATION_INFO, delegationInfoSet);
|
|
|
|
//now we are done, copy the newREmap to the requestElements
|
|
adminCtx.requestElements = newREmap;
|
|
|
|
return adminCtx;
|
|
}
|
|
|
|
/**
|
|
* Creates a copy of this context with disabled attribute finder.
|
|
*
|
|
* @return A copy of this context with disabled attribute finder.
|
|
*/
|
|
public EvaluationCtx copyWithoutAttributeFinder() {
|
|
BasicEvaluationCtx ctx = (BasicEvaluationCtx)this.clone();
|
|
ctx.aFinder = null;
|
|
ctx.afinderActive = false;
|
|
return ctx;
|
|
}
|
|
|
|
/**
|
|
* This basically does the same thing that the other types need
|
|
* to do, except that we also look for a resource-id attribute, not
|
|
* because we're going to use, but only to make sure that it's not
|
|
* double, and for the optional scope attribute, to see what the scope
|
|
* of the attribute is.
|
|
*
|
|
* @param resourceAttr A <code>Map</code> keyed by attribute id
|
|
* <code>URI</code>s, containing <code>Set</code>s
|
|
* of <code>Attribute</code>s.
|
|
* @param xacmlVersion The XACML version number.
|
|
*
|
|
* @throws ParsingException
|
|
*
|
|
*/
|
|
private void checkResource(Map<URI, Set<Attribute>> resourceAttr)
|
|
throws ParsingException {
|
|
|
|
// make sure there's only one value for resource-id
|
|
Set<Attribute> rSet = resourceAttr.get(Constants.RESOURCE_ID);
|
|
if (rSet != null) {
|
|
if (rSet.size() > 1) {
|
|
System.err.println("Resource may contain "
|
|
+ "only one resource-id Attribute");
|
|
throw new ParsingException("too many resource-id attrs");
|
|
}
|
|
// keep track of the resource-id attribute
|
|
this.resourceId = ((Attribute)(rSet.iterator().next()))
|
|
.getValue();
|
|
}
|
|
|
|
|
|
// see if a resource-scope attribute was included
|
|
if (resourceAttr.containsKey(Constants.RESOURCE_SCOPE)) {
|
|
Set<Attribute> set = resourceAttr.get(Constants.RESOURCE_SCOPE);
|
|
|
|
// make sure there's only one value for resource-scope
|
|
if (set.size() > 1) {
|
|
System.err.println("Resource may contain "
|
|
+ "only one resource-scope Attribute");
|
|
throw new ParsingException("too many resource-scope attrs");
|
|
}
|
|
|
|
Attribute attr = (Attribute)(set.iterator().next());
|
|
AttributeValue attrValue = attr.getValue();
|
|
|
|
// scope must be a string, so throw an exception otherwise
|
|
if (! attrValue.getType().toString().
|
|
equals(StringAttribute.identifier)) {
|
|
throw new ParsingException("scope attr must be a string");
|
|
}
|
|
|
|
String value = ((StringAttribute)attrValue).getValue();
|
|
|
|
if (value.equals("Immediate")) {
|
|
this.scope = Constants.SCOPE_IMMEDIATE;
|
|
} else if (value.equals("Children")) {
|
|
this.scope = Constants.SCOPE_CHILDREN;
|
|
} else if (value.equals("Descendants")) {
|
|
this.scope = Constants.SCOPE_DESCENDANTS;
|
|
} else if (value.equals("XPath-expression")) {
|
|
this.scope = Constants.SCOPE_XPATH_EXPRESSION;
|
|
} else if (value.equals("EntireHierarchy")) {
|
|
this.scope = Constants.SCOPE_ENTIRE_HIERARCHY;
|
|
} else {
|
|
System.err.println("Unknown scope type: " + value);
|
|
throw new ParsingException("invalid scope type: " + value);
|
|
}
|
|
} else {
|
|
// by default, the scope is always Immediate
|
|
this.scope = Constants.SCOPE_IMMEDIATE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the DOM root of the original RequestType XML document.
|
|
*
|
|
* @return the DOM root node
|
|
*/
|
|
public Node getRequestRoot() {
|
|
return this.requestRoot;
|
|
}
|
|
|
|
/**
|
|
* Returns the resource scope of the request, which will be one of the
|
|
* three fields denoting Immediate, Children, or Descendants.
|
|
*
|
|
* @return the scope of the resource in the request
|
|
*/
|
|
public int getScope() {
|
|
return this.scope;
|
|
}
|
|
|
|
/**
|
|
* Returns the resource named in the request as resource-id.
|
|
*
|
|
* @return the resource
|
|
*/
|
|
public AttributeValue getResourceId() {
|
|
return this.resourceId;
|
|
}
|
|
|
|
/**
|
|
* @return The <code>Set</code> of <code>RequestElement</code>s
|
|
* representing the included attributes.
|
|
*/
|
|
public Set<RequestElement> getIncludedAttributes() {
|
|
return this.includedAttributes;
|
|
}
|
|
|
|
/**
|
|
* Changes the value of the resource-id attribute in this context. This
|
|
* is useful when you have multiple resources (ie, a scope other than
|
|
* IMMEDIATE), and you need to keep changing only the resource-id to
|
|
* evaluate the different effective requests.
|
|
*
|
|
* @param resourceId the new resource-id value
|
|
*/
|
|
public void setResourceId(AttributeValue resourceId) {
|
|
this.resourceId = resourceId;
|
|
|
|
// there will always be exactly one value for this attribute
|
|
Iterator<RequestElement> rIt = this.requestElements.get(Constants.RESOURCE_CAT).iterator();
|
|
while (rIt.hasNext()) {
|
|
RequestElement re = rIt.next();
|
|
Map<URI, Set<Attribute>> attrMap = re.getAttributes();
|
|
Set<Attribute> resId = attrMap.get(Constants.RESOURCE_ID);
|
|
Attribute attr = (Attribute)(resId.iterator().next());
|
|
// remove the old value...
|
|
resId.remove(attr);
|
|
Attribute newAttr = new Attribute(attr.getId(), attr.getIssuer(),
|
|
resourceId,
|
|
attr.getVersion(),
|
|
attr.includeInResult());
|
|
// if it was marked to be included in the result remove this too
|
|
if (attr.includeInResult()) {
|
|
Iterator<RequestElement> iter = this.includedAttributes.iterator();
|
|
while (iter.hasNext()) {
|
|
RequestElement ire = iter.next();
|
|
if (ire.getCategory().equals(Constants.RESOURCE_CAT)) {
|
|
this.includedAttributes.remove(ire);
|
|
Map<URI, Set<Attribute>> newAttrs = new HashMap<URI, Set<Attribute>>();
|
|
newAttrs.putAll(ire.getAttributes());
|
|
newAttrs.remove(Constants.RESOURCE_ID);
|
|
Set<Attribute> newResourceId = new HashSet<Attribute>();
|
|
newResourceId.add(newAttr);
|
|
newAttrs.put(Constants.RESOURCE_ID, newResourceId);
|
|
this.includedAttributes.add(new RequestElement(
|
|
ire.getCategory(), newAttrs));
|
|
}
|
|
}
|
|
}
|
|
// ...and insert the new value
|
|
resId.add(newAttr);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the value for the current time. The current time, current
|
|
* date, and current dateTime are consistent, so that they all
|
|
* represent the same moment. If this is the first time that one
|
|
* of these three values has been requested, and caching is enabled,
|
|
* then the three values will be resolved and stored.
|
|
* <p>
|
|
* Note that the value supplied here applies only to dynamically
|
|
* resolved values, not those supplied in the Request. In other words,
|
|
* this always returns a dynamically resolved value local to the PDP,
|
|
* even if a different value was supplied in the Request. This is
|
|
* handled correctly when the value is requested by its identifier.
|
|
*
|
|
* @return the current time
|
|
*/
|
|
public synchronized TimeAttribute getCurrentTime() {
|
|
long millis = dateTimeHelper();
|
|
if (this.useCachedEnvValues) {
|
|
return this.currentTime;
|
|
}
|
|
return new TimeAttribute(new Date(millis));
|
|
}
|
|
|
|
/**
|
|
* Returns the value for the current date. The current time, current
|
|
* date, and current dateTime are consistent, so that they all
|
|
* represent the same moment. If this is the first time that one
|
|
* of these three values has been requested, and caching is enabled,
|
|
* then the three values will be resolved and stored.
|
|
* <p>
|
|
* Note that the value supplied here applies only to dynamically
|
|
* resolved values, not those supplied in the Request. In other words,
|
|
* this always returns a dynamically resolved value local to the PDP,
|
|
* even if a different value was supplied in the Request. This is
|
|
* handled correctly when the value is requested by its identifier.
|
|
*
|
|
* @return the current date
|
|
*/
|
|
public synchronized DateAttribute getCurrentDate() {
|
|
long millis = dateTimeHelper();
|
|
|
|
if (this.useCachedEnvValues) {
|
|
return this.currentDate;
|
|
}
|
|
return new DateAttribute(new Date(millis));
|
|
}
|
|
|
|
/**
|
|
* Returns the value for the current dateTime. The current time, current
|
|
* date, and current dateTime are consistent, so that they all
|
|
* represent the same moment. If this is the first time that one
|
|
* of these three values has been requested, and caching is enabled,
|
|
* then the three values will be resolved and stored.
|
|
* <p>
|
|
* Note that the value supplied here applies only to dynamically
|
|
* resolved values, not those supplied in the Request. In other words,
|
|
* this always returns a dynamically resolved value local to the PDP,
|
|
* even if a different value was supplied in the Request. This is
|
|
* handled correctly when the value is requested by its identifier.
|
|
*
|
|
* @return the current dateTime
|
|
*/
|
|
public synchronized DateTimeAttribute getCurrentDateTime() {
|
|
long millis = dateTimeHelper();
|
|
|
|
if (this.useCachedEnvValues) {
|
|
return this.currentDateTime;
|
|
}
|
|
return new DateTimeAttribute(new Date(millis));
|
|
}
|
|
|
|
/**
|
|
* Private helper that figures out if we need to resolve new values,
|
|
* and returns either the current moment (if we're not caching) or
|
|
* -1 (if we are caching)
|
|
*
|
|
* @return returns the current moment or -1.
|
|
*/
|
|
private long dateTimeHelper() {
|
|
// if we already have current values, then we can stop (note this
|
|
// always means that we're caching)
|
|
if (this.currentTime != null) {
|
|
return -1;
|
|
}
|
|
|
|
// get the current moment
|
|
Date time = new Date();
|
|
long millis = time.getTime();
|
|
|
|
// if we're not caching then we just return the current moment
|
|
if (! this.useCachedEnvValues) {
|
|
return millis;
|
|
}
|
|
// we're caching, so resolve all three values, making sure
|
|
// to use clean copies of the date object since it may be
|
|
// modified when creating the attributes
|
|
this.currentTime = new TimeAttribute(time);
|
|
this.currentDate = new DateAttribute(new Date(millis));
|
|
this.currentDateTime = new DateTimeAttribute(new Date(millis));
|
|
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Return available attribute values of the selected category.
|
|
*
|
|
* @param category the category the attribute value(s) must be in
|
|
* @param type the type of the attribute value(s) to find
|
|
* @param id the id of the attribute value(s) to find
|
|
* @param issuer the issuer of the attribute value(s) to find or null
|
|
*
|
|
* @return a result containing a bag either empty because no values were
|
|
* found or containing at least one value, or status associated with an
|
|
* Indeterminate result
|
|
*/
|
|
public EvaluationResult getAttribute(URI category, URI type, URI id,
|
|
URI issuer) {
|
|
Set<RequestElement> elements = this.requestElements.get(category);
|
|
if (elements == null) {
|
|
elements = RequestElement.EMPTY_SET;
|
|
}
|
|
return getGenericAttributes(category, type, id, issuer,
|
|
elements, false);
|
|
}
|
|
|
|
/**
|
|
* Helper function for the resource, action, environment and delegate
|
|
* methods to get an attribute.
|
|
*
|
|
* @param category the category the attribute value(s) must be in
|
|
* @param type the type of the attribute value(s) to find
|
|
* @param id the id of the attribute value(s) to find
|
|
* @param issuer the issuer of the attribute value(s) to find or null
|
|
* @param elements a set of <code>RequestElement</code>s to search for
|
|
* the requested attribute.
|
|
* @param local get only local attributes, don't call the
|
|
* AttributeFinder.
|
|
*
|
|
* @return a result containing a bag either empty because no values were
|
|
* found or containing at least one value, or status associated with an
|
|
* Indeterminate result.
|
|
*
|
|
*/
|
|
private EvaluationResult getGenericAttributes(URI category, URI type,
|
|
URI id, URI issuer, Set<RequestElement> elements, boolean local) {
|
|
|
|
Iterator<RequestElement> iter = elements.iterator();
|
|
Set<Attribute> attrSet = new HashSet<Attribute>();
|
|
while (iter.hasNext()) {
|
|
RequestElement element = iter.next();
|
|
// try to find the id
|
|
Map<URI, Set<Attribute>> map = element.getAttributes();
|
|
if (map.containsKey(id)) {
|
|
attrSet.addAll(map.get(id));
|
|
}
|
|
}
|
|
|
|
if (attrSet.isEmpty()) {
|
|
if (local) {
|
|
return new EvaluationResult(BagAttribute.createEmptyBag(type));
|
|
}
|
|
// the request didn't have an attribute with that id, so we should
|
|
// try asking the attribute finder
|
|
return callHelper(category, type, id, issuer, elements);
|
|
}
|
|
|
|
// now go through each, considering each Attribute object
|
|
List<AttributeValue> attributes = new ArrayList<AttributeValue>();
|
|
Iterator<Attribute> it = attrSet.iterator();
|
|
|
|
while (it.hasNext()) {
|
|
Attribute attr = it.next();
|
|
|
|
// make sure the type and issuer are correct
|
|
if ((attr.getValue().getType().equals(type)) &&
|
|
((issuer == null) ||
|
|
((attr.getIssuer() != null) &&
|
|
(attr.getIssuer().equals(issuer.toString()))))) {
|
|
|
|
// if we got here, then we found a match, so we want to pull
|
|
// out the values and put them in out list
|
|
attributes.add(attr.getValue());
|
|
}
|
|
}
|
|
|
|
// see if we found any acceptable attributes
|
|
if (attributes.size() == 0) {
|
|
if (local) {
|
|
return new EvaluationResult(BagAttribute.createEmptyBag(type));
|
|
}
|
|
// we failed to find any that matched the type/issuer, or all the
|
|
// Attribute types were empty...so ask the finder
|
|
// if (logger.isLoggable(Level.FINE)) {
|
|
// logger.fine("Attribute not in request: " + id.toString() +
|
|
// " ... querying AttributeFinder");
|
|
// }
|
|
if (logger.isDebugEnabled()) {
|
|
logger.debug("Attribute not in request: " + id.toString() + " category " + category +
|
|
" ... querying AttributeFinder");
|
|
}
|
|
|
|
return callHelper(category, type, id, issuer, elements);
|
|
}
|
|
|
|
// if we got here, then we found at least one useful AttributeValue
|
|
return new EvaluationResult(new BagAttribute(type, attributes));
|
|
}
|
|
|
|
/**
|
|
* Protected helper that calls the finder if it's non-null, or else returns
|
|
* an empty bag. (Proctected so it can be overridden by subclasses).
|
|
*
|
|
* @param category the category the attribute value(s) must be in or null
|
|
* @param type the type of the attribute value(s) to find or null
|
|
* @param id the id of the attribute value(s) to find or null
|
|
* @param issuer the issuer of the attribute value(s) to find or null
|
|
* @param elements a set of <code>RequestElement</code>s to search for the
|
|
* requested attribute.
|
|
*
|
|
* @return an <code>EvaluationResult</code> containing the requested
|
|
* attribute or an empty bag.
|
|
*/
|
|
protected EvaluationResult callHelper(URI category, URI type, URI id,
|
|
URI issuer, Set<RequestElement> elements) {
|
|
if (this.aFinder != null) {
|
|
return this.aFinder.findAttribute(category, type, id, issuer,
|
|
this);
|
|
}
|
|
if (this.afinderActive) {
|
|
logger.warn("Context tried to invoke AttributeFinder but was "
|
|
+ "not configured with one");
|
|
}
|
|
return new EvaluationResult(BagAttribute.createEmptyBag(type));
|
|
}
|
|
|
|
/**
|
|
* Returns the attribute value(s) retrieved using the given XPath
|
|
* expression.
|
|
*
|
|
* @param contextPath the XPath expression to search
|
|
* @param namespaceNode the DOM node defining namespace mappings to use,
|
|
* or null if mappings come from the context root
|
|
* @param type the type of the attribute value(s) to find
|
|
* @param xpathVersion the version of XPath to use
|
|
*
|
|
* @return a result containing a bag either empty because no values were
|
|
* found or containing at least one value, or status associated with an
|
|
* Indeterminate result
|
|
*/
|
|
public EvaluationResult getAttribute(String contextPath,
|
|
Node namespaceNode, URI type,
|
|
String xpathVersion) {
|
|
if (this.aFinder != null) {
|
|
return this.aFinder.findAttribute(contextPath, namespaceNode,
|
|
type, this, xpathVersion);
|
|
}
|
|
logger.warn("Context tried to invoke AttributeFinder but was " +
|
|
"not configured with one");
|
|
|
|
return new EvaluationResult(BagAttribute.createEmptyBag(type));
|
|
}
|
|
|
|
/**
|
|
* Get the decision.
|
|
*
|
|
* @return The <code>int</code> value of the decision according to
|
|
* the <code>Result</code> class.
|
|
*/
|
|
public int getDecision() {
|
|
return this.delegationDecision;
|
|
}
|
|
|
|
/**
|
|
* Get the delegation depth.
|
|
*
|
|
* @return The <code>int</code> value specifying the number of nodes
|
|
* in the reduction graph until now (not including this one).
|
|
*/
|
|
public int getDelegationDepth() {
|
|
return this.delegationDepth;
|
|
}
|
|
|
|
/**
|
|
* Get a whole category.
|
|
*
|
|
* @param category The name of the category. If the category does
|
|
* not exist return an empty set
|
|
*
|
|
* @return The <code>Set</code> of <code>RequestElement</code>s with
|
|
* the matching category.
|
|
*/
|
|
public Set<RequestElement> getCategory(URI category) {
|
|
if (this.requestElements.containsKey(category)) {
|
|
return new HashSet<RequestElement>(this.requestElements.get(category));
|
|
}
|
|
return RequestElement.EMPTY_SET;
|
|
}
|
|
|
|
/**
|
|
* @return the <code>Set</code> of <code>RequestElements</code>
|
|
* containing the attributes that should be included
|
|
* in the result.
|
|
*/
|
|
public Set<RequestElement> getIncludedAttrs() {
|
|
return this.includedAttributes;
|
|
}
|
|
|
|
/**
|
|
* @return the <code>Map</code> of <code>RequestElements</code>
|
|
* defining this request.
|
|
*/
|
|
public Map<URI, Set<RequestElement>> getRequestElements() {
|
|
return Collections.unmodifiableMap(this.requestElements);
|
|
}
|
|
|
|
/**
|
|
* Save the parent <code>PolicySet</code> in this evaluation context
|
|
* for doing reduction of delegated policies if that becomes necessary.
|
|
*
|
|
* @param pps the parent policy set
|
|
*/
|
|
public void saveParentPolicySet(AbstractPolicy pps) {
|
|
if (pps instanceof PolicySet) {
|
|
this.parentPolicySets.push(pps);
|
|
} else if (pps instanceof PolicyReference) {
|
|
PolicyReference pr = (PolicyReference)pps;
|
|
if (pr.getReferenceType() != PolicyReference.POLICYSET_REFERENCE) {
|
|
//this should never happen
|
|
throw new RuntimeException("Tried to save a Reference "
|
|
+ "to a Policy as a PolicySet");
|
|
}
|
|
this.parentPolicySets.push(pps);
|
|
} else {
|
|
throw new RuntimeException("Tried to save "
|
|
+ this.getClass().getName() + " as a PolicySet");
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Create a reduction graph for the current parent PolicySet.
|
|
*
|
|
*/
|
|
public void createReductionGraph() {
|
|
this.reductionGraphs.push(new ReductionGraph(getParentPolicySet()));
|
|
}
|
|
|
|
|
|
/**
|
|
* @return The current reduction graph or null if there is none.
|
|
*/
|
|
public ReductionGraph getReductionGraph() {
|
|
if (this.reductionGraphs != null && !this.reductionGraphs.empty()) {
|
|
return (ReductionGraph)this.reductionGraphs.peek();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Remove the current <code>ReductionGraph</code> from the stack.
|
|
*/
|
|
public void popReductionGraph() {
|
|
if (!this.reductionGraphs.isEmpty()) {
|
|
this.reductionGraphs.pop();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the root <code>PolicySet</code> for this evaluation context.
|
|
* If there is none, return null.
|
|
*
|
|
* @return the root policy set or null.
|
|
*/
|
|
public AbstractPolicy getParentPolicySet() {
|
|
if (!this.parentPolicySets.empty()) {
|
|
return (AbstractPolicy) this.parentPolicySets.peek();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Remove the current parent <code>PolicySet</code> from the stack
|
|
* of parent policy sets.
|
|
*/
|
|
public void popParentPolicySet() {
|
|
if (!this.parentPolicySets.isEmpty()) {
|
|
this.parentPolicySets.pop();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks whether a <code>Policy</code> or <code>PolicySet</code>
|
|
* supports a revocation of a specific Policy of PolicySet
|
|
* in this context.
|
|
*
|
|
* @param supporting The policy or policy set that could support
|
|
* a revocation.
|
|
* @param candidate The id of the policy or policy set that is candidate
|
|
* for revocation.
|
|
*
|
|
* @return true if the policy/policy set supports a revocation,
|
|
* false otherwise.
|
|
*/
|
|
public boolean supportsRevocation(AbstractPolicy supporting,
|
|
URI candidate) {
|
|
if (this.rFinder != null && this.rfinderActive) {
|
|
//deactivate revocation finder to avoid infinite loops
|
|
// of policies revoking themselves
|
|
this.rfinderActive = false;
|
|
boolean result = this.rFinder.supportsRevocation(supporting,
|
|
candidate, this);
|
|
this.rfinderActive = true;
|
|
return result;
|
|
}
|
|
// since there is no revocation finder, no policy can be
|
|
// revoked.
|
|
logger.warn("Context tried to invoke RevocationFinder but was "
|
|
+ "not configured with one");
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Add new inactive PolicyId to the Map
|
|
* @param policyId the id of the new inactive policy
|
|
*/
|
|
public void addInactivePolicyId(URI policyId) {
|
|
this.inactivePolicyIds.add(policyId);
|
|
}
|
|
|
|
/**
|
|
* Return an unmodifiable <code>Set</code> of <code>URI</code>s of
|
|
* inactive policies
|
|
* @return the inactive policies
|
|
*/
|
|
public Set<URI> getInactivePolicyIds() {
|
|
return Collections.unmodifiableSet(
|
|
new HashSet<URI>(this.inactivePolicyIds));
|
|
}
|
|
|
|
/**
|
|
* Signal a new event to this EvaluationCtx.
|
|
* BasicEvaluationCtx does nothing with this signal.
|
|
*
|
|
* @param element The new event.
|
|
*/
|
|
public void newEvent(Object element) {
|
|
//BasicEvaluationCtx does nothing with this signal.
|
|
}
|
|
|
|
/**
|
|
* Signal that an event has finished and pass the result.
|
|
* BasicEvaluationCtx does nothing with this signal.
|
|
*
|
|
* @param result The result of the finished event.
|
|
*/
|
|
public void closeCurrentEvent(Result result) {
|
|
//BasicEvaluationCtx does nothing with this signal.
|
|
}
|
|
|
|
/**
|
|
* Signal that an event has finished and pass the result.
|
|
* BasicEvaluationCtx does nothing with this signal.
|
|
*
|
|
* @param result The result of the finished event.
|
|
*/
|
|
public void closeCurrentEvent(MatchResult result) {
|
|
//BasicEvaluationCtx does nothing with this signal.
|
|
}
|
|
|
|
/**
|
|
* Signal that an event has finished and pass the result
|
|
* which is a <code>EvaluationResult</code>
|
|
* BasicEvaluationCtx does nothing with this signal.
|
|
*
|
|
* @param result The result of the finished event.
|
|
*/
|
|
public void closeCurrentEvent(EvaluationResult result) {
|
|
//BasicEvaluationCtx does nothing with this signal.
|
|
}
|
|
|
|
/**
|
|
* Signal that an event has finished with a <code>String</code> message.
|
|
* BasicEvaluationCtx does nothing with this signal.
|
|
*
|
|
* @param message The message.
|
|
*/
|
|
public void closeCurrentEvent(String message) {
|
|
//BasicEvaluationCtx does nothing with this signal.
|
|
}
|
|
|
|
/**
|
|
* Signal that an event has finished with no result.
|
|
* BasicEvaluationCtx does nothing with this signal.
|
|
*/
|
|
public void closeCurrentEvent() {
|
|
//BasicEvaluationCtx does nothing with this signal.
|
|
}
|
|
} |