/* Copyright 2012-2015 SAP SE
*
* 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 eu.aniketos.securebpmn.xacml.pdpstate;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.Vector;
import org.apache.log4j.Logger;
import eu.aniketos.securebpmn.xacml.api.SecurityError;
import eu.aniketos.securebpmn.xacml.api.ErrorType;
import eu.aniketos.securebpmn.xacml.api.ReasonType;
import eu.aniketos.securebpmn.xacml.api.autho.AttributeIdentifier;
import eu.aniketos.securebpmn.xacml.api.autho.AuthoAttribute;
import eu.aniketos.securebpmn.xacml.api.autho.DesignatorAttribute;
import eu.aniketos.securebpmn.xacml.api.log.AccessControlRequest;
import eu.aniketos.securebpmn.xacml.pdpstate.db.AttributeDBIdentifier;
import eu.aniketos.securebpmn.xacml.pdpstate.db.AttributeType;
import eu.aniketos.securebpmn.xacml.pdpstate.db.ContextAttribute;
import eu.aniketos.securebpmn.xacml.pdpstate.db.HibernateUtil;
/**
*
* This class stores the types of known assignments and how to retrieve
* the required information from an XACML request. For example, consider a role
* assignment. The configuration might look like this:<br/>
* dependency:runEx:role:assignment <br/>
* urn:custom:subject:role -> urn:oasis:names:tc:xacml:1.0:subject:subject-id<br/>
* urn:custom:subject:role -> attribute:urn:custom:resource:role<br/>
* urn:oasis:names:tc:xacml:1.0:subject:subject-id -> urn:custom:resource:subject-id<br/>
* <br/>
* This config says:
* <ul>
* <li> define a dependency for the resource (in the XACML policy) runEx:role:assignment</li>
* <li> to retrieve a role, one needs the subject-id (those are keys
* which have to be defined in the configuration file first)</li>
* <li> the attributes for role (urn:custom:subject:role) can be retrieved by getting
* all attribute:urn:custom:resource:role attributes from the XACML request</li>
* <li> the attribute for subject-Id (urn:oasis:names:tc:xacml:1.0:subject:subject-id) can be retrieved
* by getting all urn:custom:resource:subject-id attribues from the XACML request</li>
* </ul>
*
*/
public class Dependency {
/**
* resource for which this assignment is valid (as defined in the configuration file)
* e.g., urn:role:assignment
*/
private String resourceKey;
private AttributeType type;
/**
* all pdpStateDependencies have to be retrieved from an XACML request;
* this map defines which attribute has to be retrieved from the XACML request (value)
* and assigned to which attribute stored in the db (key).
*
* Note that the attributes in the XACML have to be different, e.g., when
* assigning a role, one cannot use the role attribute to define the to be assigned roles,
* i.e., the roles have to treated as resource; so is the subject-id for the role assignment.
*/
private Map<AttributeDBIdentifier, AttributeDBIdentifier> requestDependencies;
private static Logger logger = Logger.getLogger(Dependency.class);
public Dependency(String key, AttributeType type,
Map<AttributeDBIdentifier, AttributeDBIdentifier> requestDependencies) {
this.resourceKey = key;
this.type = type;
this.requestDependencies = requestDependencies;
}
public List<ContextAttribute> getDependingAttributes() {
return type.getCtxTypes();
}
public AttributeDBIdentifier getDependingAttributeIdentifier(int index) {
return type.getCtxTypes().get(index).getAttrId();
}
public AttributeDBIdentifier getAttributeIdentifier() {
return type.getAttrType();
}
public String getResourceKey() {
return resourceKey;
}
/**
* creates a new Assignment based on the configuration file and assures that
* there are no conflicting definitions within the database.
*
* @param resource
* @param reader
* @param attributes
* @return
* @throws IOException
* @throws SyntaxError
*/
public static Dependency readAssignment(String resource, BufferedReader reader,
Map<String, AttributeDBIdentifier> attributes, HibernateUtil dbUtil) throws IOException, SyntaxError {
// get the dependency definition line, e.g., urn:custom:subject:role -> urn:oasis:names:tc:xacml:1.0:subject:subject-id
String dependency = reader.readLine();
int m = dependency.indexOf("->");
if ( m == -1 ) {
throw new SyntaxError("Missing assignment -> ");
}
// get the attribute for which the dependency is defined - key points to a attribute: definition
String key = dependency.substring(0, m).trim();
AttributeDBIdentifier dbKey = attributes.get(key);
if ( dbKey == null ) {
logger.error("Creating Dependency " + resource +", but Attribute with key " + key + " is not defined so far");
throw new SyntaxError("Attribute with key " + key + " is not defined so far");
}
if (logger.isDebugEnabled() ) {
logger.debug("Creating Dependency " + resource + " for attribute " + key + " -> " + dbKey.toString());
}
// read all dependencies for this attribute and create or get AttributeType based on those definitions
List<AttributeIdentifier> dependencies = new Vector<AttributeIdentifier>(); //bullshit, but cannot cast from List<AttributeDBIdentifier> to List<AttributeIdentifier>
// also keep track of all defined dependencies, as they are needed afterwards
List<String> dependencyKeys = new Vector<String>();
dependencyKeys.add(key);
boolean noDependency = false;
StringTokenizer tokenizer = new StringTokenizer(dependency.substring(m + 2), ",");
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken().trim();
if ( ! (token.length() == 0) ) {
if ( dependencies.size() == 0 && "null".equals(token) ) {
// some attributes may not depend on any other attribute, but they have to define this, i.e., write attributeKey -> null
noDependency = true;
} else {
AttributeDBIdentifier dependent = attributes.get(token);
if ( dependent == null ) {
logger.error("Creating Dependency " + resource +", but Attribute with key " + dependent + " is not defined so far");
throw new SyntaxError("Attribute with key " + dependent + " is not defined so far");
} else {
// dependencies_delete.add(dependent);
dependencies.add(dependent);
dependencyKeys.add(token);
if ( logger.isDebugEnabled() ) {
logger.debug("Found dependency to attribute " + token + ", referencing to " + dependent.toString());
}
}
}
}
}
if ( dependencies.size() == 0 && ! noDependency) {
logger.error("For dependency " + resource + " (attribute " + key + " no dependencies are defined");
throw new SyntaxError("No dependency is defined");
}
AttributeType type = dbUtil.addAttributeType(dbKey, dependencies);
if ( type == null ) {
logger.warn("Could not retrieve AttributeType for configuration of assignment " + dbKey + ": different definition already exists");
throw new SyntaxError("Invalid configuration: Different definition already exists");
} else if (logger.isDebugEnabled() ) {
logger.debug("Got AttributeType with id " + type.getId() + " for dependency:" + resource);
}
/* ok, we got the AttributeType definition for this dependency, now
* we have to get the information which element from a XACML request has to be retrieved
* to update the PDPState accordingly
*/
Map<AttributeDBIdentifier, AttributeDBIdentifier> requestDependencies = new HashMap<AttributeDBIdentifier, AttributeDBIdentifier>();
for ( String dep : dependencyKeys ) {
String confLine = readValueLine(reader);
m = confLine.indexOf("->");
String dbAttr, requAttr;
dbAttr = confLine.substring(0, m).trim();
requAttr = confLine.substring(m + 2, confLine.length()).trim();
if ( ! dbAttr.equals(dep)) {
throw new SyntaxError("Invalid assignment: expected assignment for " + dep + " (line: " + confLine + ")");
} else {
AttributeDBIdentifier requDBAttr = attributes.get(requAttr);
if ( requDBAttr == null ) {
throw new SyntaxError("Attribute with key " + requAttr + " is not defined so far");
} else {
requestDependencies.put(attributes.get(dbAttr), requDBAttr);
if ( logger.isDebugEnabled() ) {
logger.debug("XACML request attr " + requAttr + " is mapped to " + dbAttr + " (" + requDBAttr + " -> " + attributes.get(dbAttr) + ")");
}
}
}
}
return new Dependency(resource, type, requestDependencies);
}
private static String readValueLine(BufferedReader reader) throws IOException, SyntaxError {
String line = reader.readLine().trim();
if ( line.startsWith("#") ) {
return readValueLine(reader);
} else {
return line;
}
}
private List<String> getValues(AccessControlRequest request, AttributeDBIdentifier attr) {
List<String> values = new Vector<String>();
for ( DesignatorAttribute designAttr : request.getDesignatorAttributes()) {
if ( designAttr.getAttrId().equals(attr)) {
values.addAll(designAttr.getValues());
// break; // should be ok, as only one entry for one attr should be there.. anyhow
}
}
for ( AuthoAttribute authoAttr : request.getAttributes()) {
if ( authoAttr.getAttributeIdentifier().equals(attr)) {
values.add(authoAttr.getValue());
}
}
return values;
}
/**
* This functions receives an executed and recorded AccessControlRequest and
* reads all needed values from the xacml request and stores the according
* state changes to the PDPState
* @param request
* @param pdpState
* @throws SecurityError
*/
public void writeStateChange(AccessControlRequest request, PDPState pdpState) throws SecurityError {
// get the values we should assign from the request
List<String> assignValues = getValues(request, requestDependencies.get(type.getAttrType()));
if ( assignValues.size() == 0 ) {
logger.error("Nothing found in the request " + request.getEvaluationId().longValue());
throw new SecurityError(ErrorType.CONFIGURATION_ERROR, ReasonType.PDE_ENGINE_ERROR,
"Request " + request.getEvaluationId() + " did not contain the required information");
}
if ( logger.isDebugEnabled() ) {
StringBuffer message = new StringBuffer("Found " + assignValues.size() + " value(s) (");
for (String value : assignValues ) {
message.append(value + ", ");
}
message.append(") to write to the PDP state for " + this.resourceKey + " (" + this.type.getCtxTypes().size() + " depending ctx Attribute(s))");
logger.debug(message.toString());
}
// depending on the number of context attributes, we have to retrieve those values from the request
// and create the according assignment for the PDP state
if ( type.getCtxTypes().size() == 0 ) {
for ( String value : assignValues ) {
//pdpState.addAssignment(type, value, null, null, null);
pdpState.addAssignment(new AuthoAttribute(type.getAttrType(), value), null, null, null);
}
logger.info("Added " + assignValues.size() + " assignments for " + type.getAttrType() + " to the PDPState");
} else if ( type.getCtxTypes().size() == 1 ) {
// we have the AttributeType which tells us how much context Attributes we need;
// from this, we can get the attribute identifier which identifies the attributes in the xacml request
AttributeDBIdentifier ctxAttrType = requestDependencies.get(type.getCtxTypes().get(0).getAttrId());
// get the according values
List<String> ctxValues = getValues(request, ctxAttrType);
// if there are no values, there is something wrong; terminate
if ( ctxValues == null || ctxValues.size() == 0 ) {
logger.error("Could not find values for contextAttribute " + ctxAttrType + " in the request");
throw new SecurityError(ErrorType.CONFIGURATION_ERROR, ReasonType.PDE_ENGINE_ERROR,
"Request " + request.getEvaluationId() + " did not contain the required information");
} else if ( logger.isDebugEnabled() ) {
StringBuffer message = new StringBuffer("Found " + ctxValues.size() + " value(s) (");
for (String value : ctxValues ) {
message.append(value + ", ");
}
message.append(") for " + ctxAttrType.toString());
logger.debug(message.toString());
}
for ( String value: assignValues ) {
AuthoAttribute valueAttr = new AuthoAttribute(type.getAttrType(), value);
for ( String ctxValue : ctxValues ) {
List<AuthoAttribute> ctxAttr = new Vector<AuthoAttribute>();
ctxAttr.add(new AuthoAttribute(type.getCtxTypes().get(0).getAttrId(), ctxValue));
pdpState.addAssignment(valueAttr, null, null, ctxAttr);
}
}
logger.info("Added " + (assignValues.size()*ctxValues.size()) + " assignments for " + type.getAttrType() + " to the PDPState");
} else {
// here, we do for now only allow one value for every context attribute
List<AuthoAttribute> ctxAttrs = new Vector<AuthoAttribute>();
List<String> ctxValues;
for ( ContextAttribute ctxAttr : type.getCtxTypes() ) {
ctxValues = getValues(request, ctxAttr.getAttrId());
if ( ctxValues.size() != 1) {
logger.error("Expected 1 value for " + ctxAttr.getAttrId() + " in request " +
request.getEvaluationId() + ", but found " + ctxValues.size());
throw new SecurityError(ErrorType.CONFIGURATION_ERROR, ReasonType.PDE_ENGINE_ERROR,
"Request " + request.getEvaluationId() + " did not contain the required information");
} else {
ctxAttrs.add(new AuthoAttribute(ctxAttr.getAttrId(), ctxValues.get(0)));
}
}
for ( String value : assignValues ) {
pdpState.addAssignment(new AuthoAttribute(type.getAttrType(), value), null, null, ctxAttrs);
}
logger.info("Added " + assignValues.size() + " assignments for " + type.getAttrType() + " to the PDPState");
}
}
}