/*
* File: ContextUtil.java
*
* Copyright 2007 Macquarie E-Learning Centre Of Excellence
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fcrepo.server.security.xacml.util;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.fcrepo.common.Constants;
import org.fcrepo.server.security.Attribute;
import org.fcrepo.server.security.RequestCtx;
import org.fcrepo.server.security.impl.BasicAttribute;
import org.fcrepo.server.security.impl.BasicRequestCtx;
import org.fcrepo.server.security.impl.SingletonAttribute;
import org.fcrepo.server.security.xacml.MelcoeXacmlException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.jboss.security.xacml.sunxacml.Indenter;
import org.jboss.security.xacml.sunxacml.ParsingException;
import org.jboss.security.xacml.sunxacml.attr.AnyURIAttribute;
import org.jboss.security.xacml.sunxacml.attr.AttributeValue;
import org.jboss.security.xacml.sunxacml.attr.StringAttribute;
import org.jboss.security.xacml.sunxacml.ctx.ResponseCtx;
import org.jboss.security.xacml.sunxacml.ctx.Result;
import org.jboss.security.xacml.sunxacml.ctx.Subject;
/**
* Utility class that provides various methods for creating/converting contexts.
* This class can convert requests and responses from their string
* representations to their object representations and vice versa as well as a
* few utility methods for getting information from the contexts. It also
* contains methods for constructing requests.
*
* @author nishen@melcoe.mq.edu.au
*/
public class ContextUtil {
private static final Logger logger =
LoggerFactory.getLogger(ContextUtil.class);
private final Map<URI, URI> actionMap =
new ConcurrentHashMap<URI, URI>();
private final Map<String, String> actionValueMap =
new ConcurrentHashMap<String, String>();
public ContextUtil() {
logger.info("created");
}
public void setActionMap(Map<String,String> actions) {
for (String from: actions.keySet()){
URI key;
String to = actions.get(from);
try {
key = new URI(from);
URI value = new URI(actions.get(from));
actionMap.put(key, value);
} catch (URISyntaxException e) {
logger.warn("Mapping contained invalid URI: ["
+ from + "] / [" + to + "]");
}
}
}
public void setActionValueMap(Map<String,String> values) {
actionValueMap.putAll(values);
}
/**
* Sets up the Subject section of the request. Takes a list of Maps of
* URI/AttributeValue pairs. Each list element represents one subject which
* contains a map of URI/AttributeValues.
*
* @return a Set of Subject instances for inclusion in a Request
*/
public List<Subject> setupSubjects(List<Map<URI, List<AttributeValue>>> subjs) {
if (subjs == null || subjs.size() == 0) {
return Collections.singletonList(new Subject(new ArrayList<Attribute>()));
}
List<Subject> subjects = new ArrayList<Subject>(subjs.size());
// Go through each of the subjects
for (Map<URI, List<AttributeValue>> s : subjs) {
List<Attribute> attributes = new ArrayList<Attribute>();
// Extract and create the attributes for this subject and add them
// to the set
for (URI uri : s.keySet()) {
List<AttributeValue> attributeValues = s.get(uri);
if (attributeValues != null && attributeValues.size() > 0) {
attributes.add(
new BasicAttribute(uri, attributeValues.get(0).getType(),
null, null, attributeValues));
}
}
// Create a new subject and add the attributes for this subject
subjects.add(new Subject(attributes));
}
return subjects;
}
/**
* Creates a Resource specifying the resource-id, a required attribute.
*
* @return a Set of Attributes for inclusion in a Request
*/
public List<Attribute> setupResources(Map<URI, AttributeValue> res, RelationshipResolver relationshipResolver)
throws MelcoeXacmlException {
if (res == null || res.size() == 0) {
return new ArrayList<Attribute>();
}
List<Attribute> attributes = new ArrayList<Attribute>(res.size());
try {
String pid = null;
AttributeValue pidAttr = res.get(Constants.XACML1_RESOURCE.ID.attributeId);
if (pidAttr != null) {
pid = pidAttr.encode();
pid = relationshipResolver.buildRESTParentHierarchy(pid);
String dsid = null;
AttributeValue dsidAttr =
res.get(Constants.DATASTREAM.ID.attributeId);
if (dsidAttr != null) {
dsid = dsidAttr.encode();
if (!dsid.isEmpty()) {
pid += "/" + dsid;
}
}
res.put(Constants.XACML1_RESOURCE.ID.attributeId, new AnyURIAttribute(new URI(pid)));
}
} catch (Exception e) {
logger.error("Error finding parents.", e);
throw new MelcoeXacmlException("Error finding parents.", e);
}
for (URI uri : res.keySet()) {
attributes.add(new SingletonAttribute(uri, null, null, res.get(uri)));
}
return attributes;
}
/**
* Creates an Action specifying the action-id, an optional attribute.
*
* @return a Set of Attributes for inclusion in a Request
*/
public List<Attribute> setupAction(Map<URI, AttributeValue> a) {
if (a == null || a.size() == 0) {
return Collections.emptyList();
}
List<Attribute> actions = new ArrayList<Attribute>(a.size());
Map<URI, AttributeValue> newActions =
new HashMap<URI, AttributeValue>();
for (URI uri : a.keySet()) {
URI newUri = null;
AttributeValue newValue = null;
if (actionMap != null && actionMap.size() > 0) {
newUri = actionMap.get(uri);
}
if (actionValueMap != null && actionValueMap.size() > 0) {
String tmpValue = actionValueMap.get(a.get(uri).encode());
if (tmpValue != null) {
newValue = new StringAttribute(tmpValue);
}
}
newUri = newUri == null ? uri : newUri;
newValue = newValue == null ? a.get(uri) : newValue;
newActions.put(newUri, newValue);
}
for (URI uri : newActions.keySet()) {
actions.add(new SingletonAttribute(uri, null, null, newActions.get(uri)));
}
return actions;
}
/**
* Creates the Environment attributes.
*
* @return a Set of Attributes for inclusion in a Request
*/
public List<Attribute> setupEnvironment(Map<URI, AttributeValue> e) {
if (e == null || e.size() == 0) {
return Collections.emptyList();
}
List<Attribute> environment = new ArrayList<Attribute>(e.size());
for (URI uri : e.keySet()) {
environment.add(new SingletonAttribute(uri, null, null, e.get(uri)));
}
return environment;
}
/**
* Constructs a RequestCtx object.
*
* @param subjects
* list of Subjects
* @param actions
* list of Action attributes
* @param resources
* list of resource Attributes
* @param environment
* list of environment Attributes
* @return the RequestCtx object
* @throws MelcoeXacmlException
*/
public RequestCtx buildRequest(List<Map<URI, List<AttributeValue>>> subjects,
Map<URI, AttributeValue> actions,
Map<URI, AttributeValue> resources,
Map<URI, AttributeValue> environment,
RelationshipResolver relationshipResolver)
throws MelcoeXacmlException {
logger.debug("Building request!");
RequestCtx request = null;
// Create the new Request.
// Note that the Environment must be specified using a valid Set, even
// if that Set is empty
try {
request =
new BasicRequestCtx(setupSubjects(subjects),
setupResources(resources, relationshipResolver),
setupAction(actions),
setupEnvironment(environment));
} catch (Exception e) {
logger.error("Error creating request.", e);
throw new MelcoeXacmlException("Error creating request", e);
}
return request;
}
/**
* Converts a string based response to a ResponseCtx obejct.
*
* @param response
* the string response
* @return the ResponseCtx object
* @throws MelcoeXacmlException
*/
public ResponseCtx makeResponseCtx(String response)
throws MelcoeXacmlException {
ResponseCtx resCtx = null;
try {
ByteArrayInputStream is =
new ByteArrayInputStream(response.getBytes());
resCtx = ResponseCtx.getInstance(is);
} catch (ParsingException pe) {
throw new MelcoeXacmlException("Error parsing response.", pe);
}
return resCtx;
}
/**
* Converts a string based request to a RequestCtx obejct.
*
* @param request
* the string request
* @return the RequestCtx object
* @throws MelcoeXacmlException
*/
public RequestCtx makeRequestCtx(String request)
throws MelcoeXacmlException {
RequestCtx reqCtx = null;
try {
ByteArrayInputStream is =
new ByteArrayInputStream(request.getBytes());
reqCtx = BasicRequestCtx.getInstance(is);
} catch (ParsingException pe) {
throw new MelcoeXacmlException("Error parsing response.", pe);
}
return reqCtx;
}
/**
* Converts a RequestCtx object to its string representation.
*
* @param reqCtx
* the RequestCtx object
* @return the String representation of the RequestCtx object
*/
public String makeRequestCtx(RequestCtx reqCtx) {
ByteArrayOutputStream request = new ByteArrayOutputStream();
reqCtx.encode(request, new Indenter());
return new String(request.toByteArray());
}
/**
* Converst a ResponseCtx object to its string representation.
*
* @param resCtx
* the ResponseCtx object
* @return the String representation of the ResponseCtx object
*/
public String makeResponseCtx(ResponseCtx resCtx) {
ByteArrayOutputStream response = new ByteArrayOutputStream();
resCtx.encode(response, new Indenter());
return new String(response.toByteArray());
}
/**
* Returns a map of resource-id, result based on an XACML response.
*
* @param resCtx
* the XACML response
* @return the Map of resource-id and result
*/
public Map<String, Result> makeResultMap(ResponseCtx resCtx) {
@SuppressWarnings("unchecked")
Iterator<Result> i = resCtx.getResults().iterator();
Map<String, Result> resultMap = new HashMap<String, Result>();
while (i.hasNext()) {
Result r = i.next();
resultMap.put(r.getResource(), r);
}
return resultMap;
}
}