/* * 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; } }