/*
* @(#)RequestCtx.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.ctx;
import com.sun.xacml.Constants;
import com.sun.xacml.Indenter;
import com.sun.xacml.ParsingException;
import com.sun.xacml.PolicyMetaData;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* Represents a request made to the PDP. This is the class that contains all
* the data used to start a policy evaluation.
*
* @since 1.0
* @author Seth Proctor
* @author Marco Barreno
* @author Ludwig Seitz
*/
public class RequestCtx
{
/**
* This contains the <code>requestElement</code>s.
*/
private Set<RequestElement> requestElements = null;
/**
* Hold onto the root of the document for XPath searches
*/
private Node documentRoot = null;
/**
* The XACML version.
*/
private int xacmlVersion = Constants.XACML_DEFAULT_VERSION;
/**
* Constructor that creates a <code>RequestCtx</code> from
* <code>RequestElements</code>.
*
* @param requestElements the elements of the request. E.g. subject,
* resource, action. A <code>Set</code> of
* <code>RequestElement</code>s.
* Must not be null, or empty.
* @param documentRoot the root node of the DOM tree for this request.
* Can be null.
*
* @throws IllegalArgumentException if the inputs are not well formed
*/
public RequestCtx(Set<RequestElement> requestElements, Node documentRoot,
Node content) throws IllegalArgumentException {
this(requestElements, documentRoot, Constants.XACML_DEFAULT_VERSION);
}
/**
* Constructor that creates a <code>RequestCtx</code> from
* <code>RequestElements</code>.
*
* @param requestElements the elements of the request. E.g. subject,
* resource, action. A <code>Set</code> of
* <code>RequestElement</code>s.
* Must not be null, or empty.
* @param documentRoot the root node of the DOM tree for this request.
* Can be null.
* @param xacmlVersion The version number of the XACML used (see
* <code>PolicyMetaData</code> for details).
*
* @throws IllegalArgumentException if the inputs are not well formed
*/
public RequestCtx(Set<RequestElement> requestElements, Node documentRoot,
int xacmlVersion) throws IllegalArgumentException {
//Control type of the requestElements
Iterator<RequestElement> elements = requestElements.iterator();
while (elements.hasNext()){
Object element = elements.next();
if (!(element instanceof RequestElement)) {
throw new IllegalArgumentException("First parameter in request"
+ " must be a Set of RequestElement objects");
}
}
this.requestElements = new HashSet<RequestElement>(requestElements);
// save document root node for AttributeSelectors.
this.documentRoot = documentRoot;
this.xacmlVersion = xacmlVersion;
}
/**
* Create a new <code>RequestCtx</code> by parsing a node. This
* node should be created by schema-verified parsing of an
* <code>XML</code> document.
*
* @param root the node to parse for the <code>RequestCtx</code>
*
* @return a new <code>RequestCtx</code> constructed by parsing
*
* @throws ParsingException if the DOM node is invalid
*/
public static RequestCtx getInstance(Node root) throws ParsingException {
Set<RequestElement> newRequestElements = new HashSet<RequestElement>();
// get XACML version
PolicyMetaData metaData = new PolicyMetaData(
root.getNamespaceURI(), Constants.XPATH_1_0_IDENTIFIER);
int xacmlVersion = metaData.getXACMLVersion();
// First check to be sure the node passed is indeed a Request node.
String tagName = root.getLocalName();
if (root.getNodeType() != Node.ELEMENT_NODE
|| ! tagName.equals("Request")) {
throw new ParsingException("Request cannot be constructed using " +
"type: " + root.getLocalName());
}
// Now go through its child nodes, finding the Attributes in the
// different categories. This may reset the XACML version if the
// namespace had it wrong.
NodeList children = root.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node node = children.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
String name = node.getLocalName();
if (name.equals("Attributes")) {
xacmlVersion = Constants.XACML_VERSION_3_0;
RequestElement re
= RequestElement.getInstance((Element)node, metaData);
if (re != null) {
newRequestElements.add(re);
}
} else if (name.equals("Subject") ||
name.equals("Resource") ||
name.equals("Action") ||
name.equals("Environment")) {
//compatibility code for XACML 2.0
xacmlVersion = Constants.XACML_VERSION_2_0;
RequestElement re
= RequestElement.getInstance((Element)node, metaData);
if (re != null) {
newRequestElements.add(re);
}
} else {
throw new ParsingException("Illegal element: "
+ name + " where only Attributes and Content elements"
+ " expected");
}
}
}
// Now create and return the RequestCtx from the information
// gathered
return new RequestCtx(newRequestElements, root, xacmlVersion);
}
/**
* Creates a new <code>RequestCtx</code> by parsing XML from an
* input stream. Note that this a convenience method, and it will
* not do schema validation by default. You should be parsing the data
* yourself, and then providing the root node to the other
* <code>getInstance</code> method. If you use this convenience
* method, you probably want to turn on validation by setting the
* context schema file (see the programmer guide for more information
* on this).
*
* @param input a stream providing the XML data
*
* @return a new <code>RequestCtx</code>
*
* @throws ParsingException if there is an error parsing the input
*/
public static RequestCtx getInstance(InputStream input)
throws ParsingException
{
return getInstance(InputParser.parseInput(input, "Request"));
}
/**
* Returns a <code>Set</code> containing the <code>RequestElements</code>.
*
* @return the request's RequestElements
*/
public Set<RequestElement> getRequestElements() {
return Collections.unmodifiableSet(this.requestElements);
}
/**
* @return The xacml version of this request
*/
public int getXACMLVersion() {
return this.xacmlVersion;
}
/**
* Returns the root DOM node of the document used to create this
* object, or null if this object was created by hand (ie, not through
* the <code>getInstance</code> method) or if the root node was not
* provided to the constructor.
*
* @return the root DOM node or null
*/
public Node getDocumentRoot() {
return this.documentRoot;
}
/**
* Encodes this context into its XML representation and writes this
* encoding to the given <code>OutputStream</code>. No
* indentation is used.
*
* @param output a stream into which the XML-encoded data is written
* @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.
*
* @throws UnsupportedEncodingException
*/
public void encode(OutputStream output, String charsetName)
throws UnsupportedEncodingException {
encode(output, charsetName, new Indenter(0));
}
/**
* Encodes this context 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 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.
* @param indenter an object that creates indentation strings
*
* @throws UnsupportedEncodingException
*/
public void encode(OutputStream output, String charsetName,
Indenter indenter)
throws UnsupportedEncodingException {
// Make a PrintStream for a nicer printing interface
PrintStream out;
if(charsetName == null) {
out = new PrintStream(output);
} else {
out = new PrintStream(output, false, charsetName);
}
// Prepare the indentation string
String topIndent = indenter.makeString();
out.print(topIndent + "<Request xmlns=\"");
// go in one more for next-level elements...
indenter.in();
switch (this.xacmlVersion) {
case Constants.XACML_VERSION_1_0:
/* falls through */
case Constants.XACML_VERSION_1_1:
// there is no context id for 1.1 the schema uses the 1.0 id.
out.println(Constants.XACML_1_0_CTX_ID + "\">");
HashSet<RequestElement> subjects = new HashSet<RequestElement>();
HashSet<RequestElement> resources = new HashSet<RequestElement>();
RequestElement action = null;
Iterator<RequestElement> iter = this.requestElements.iterator();
while (iter.hasNext()) {
RequestElement rE = iter.next();
if (rE.getCategory().equals(Constants.RESOURCE_CAT)) {
resources.add(rE);
} else if (rE.getCategory().equals(Constants.ACTION_CAT)) {
action = rE;
} else {
//For backwards compatibility we cast all other categories
//to subject categories.
subjects.add(rE);
}
}
//encode subjects
Iterator<RequestElement> subjectIter = subjects.iterator();
while(subjectIter.hasNext()) {
RequestElement subject = subjectIter.next();
subject.encode(output, charsetName, indenter);
}
//encode resources
Iterator<RequestElement> resourceIter = resources.iterator();
while(resourceIter.hasNext()) {
RequestElement resource = resourceIter.next();
resource.encode(output, charsetName, indenter);
}
//encode action
if (action != null) {
action.encode(output, charsetName, indenter);
}
break;
case Constants.XACML_VERSION_2_0:
out.println(Constants.XACML_2_0_CTX_ID + "\">");
subjects = new HashSet<RequestElement>();
resources = new HashSet<RequestElement>();
action = null;
RequestElement environment = null;
iter = this.requestElements.iterator();
while (iter.hasNext()) {
RequestElement rE = (RequestElement)iter.next();
if (rE.getCategory().equals(Constants.RESOURCE_CAT)) {
resources.add(rE);
} else if (rE.getCategory().equals(Constants.ACTION_CAT)) {
action = rE;
} else if (rE.getCategory().equals(
Constants.ENVIRONMENT_CAT)) {
environment = rE;
} else {
//For backwards compatibility we cast all other categories
//to subject categories.
subjects.add(rE);
}
}
//encode subjects
subjectIter = subjects.iterator();
while(subjectIter.hasNext()) {
RequestElement subject = (RequestElement)subjectIter.next();
subject.encode(output, charsetName, indenter);
}
//encode resources
resourceIter = resources.iterator();
while(resourceIter.hasNext()) {
RequestElement resource = (RequestElement)resourceIter.next();
resource.encode(output, charsetName, indenter);
}
//encode action
if (action != null) {
action.encode(output, charsetName, indenter);
}
//encode environment
if (environment != null) {
environment.encode(output, charsetName, indenter);
}
break;
case Constants.XACML_VERSION_3_0:
/* falls through */
default:
out.println(Constants.XACML_3_0_IDENTIFIER + "\">");
Iterator<RequestElement> elements = this.requestElements.iterator();
while (elements.hasNext()) {
RequestElement rEl = (RequestElement)elements.next();
rEl.encode(output, charsetName, indenter);
}
break;
}
indenter.out();
out.println(topIndent + "</Request>");
}
}