/* 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.BufferedInputStream;
import java.io.File;
import java.security.InvalidParameterException;
import java.sql.Timestamp;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import javax.xml.parsers.DocumentBuilderFactory;
import org.apache.log4j.Logger;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.w3c.dom.Document;
import eu.aniketos.securebpmn.xacml.api.autho.AttributeIdentifier;
import eu.aniketos.securebpmn.xacml.api.autho.AuthoAttribute;
import eu.aniketos.securebpmn.xacml.pdpstate.InvalidAssignmentException.Reason;
import eu.aniketos.securebpmn.xacml.pdpstate.db.AttributeAssignment;
import eu.aniketos.securebpmn.xacml.pdpstate.db.AttributeType;
import eu.aniketos.securebpmn.xacml.pdpstate.db.ContextAttribute;
import eu.aniketos.securebpmn.xacml.pdpstate.db.ContextAttributeAssignment;
import eu.aniketos.securebpmn.xacml.pdpstate.db.HibernateUtil;
/**
*
* Manages the state of the PDP as it can be retrieved from the PDPStateModule.
*
* Changes have to be done through this class ans especially through the HibernateUtil,
* as here some caching is implemented. Bypassing this caching mechanism will result in
* strange errors.
*
*/
public class PDPState {
private static PDPState instance;
public static final String CONFFILE_NAME = "hibernate.pdpState.cfg.xml";
private SessionFactory factory;
private HibernateUtil hibernateUtil;
private static final Logger logger = Logger.getLogger(PDPState.class);
private static final String ATTRTYPE = "attrType",
VALIDAT = "validAt",
VALIDFROM = "validFrom",
VALIDTO = "validTo",
VALUE = "value",
CTXATTRTYP0 = "ctxAttrTypeNull",
CTXATTR0 = "ctxAttrNull",
CTXATTRTYP1 = "ctxAttrTypeOne",
CTXATTR1 = "ctxAttrOne";
public static PDPState getInstance() {
if ( instance == null ) {
createInstance();
}
return instance;
}
private static synchronized void createInstance() {
if ( instance == null ) {
instance = new PDPState();
}
}
private PDPState() {
logger.debug("Starting PDPState Server: Creating SessionFactory");
factory = buildSessionFactory();
factory.openSession();
logger.debug("Creating HibernateUtil");
hibernateUtil = HibernateUtil.createHibernateUtil(factory);
}
private SessionFactory buildSessionFactory() {
try {
File confFile = new File(CONFFILE_NAME);
if ( ! confFile.exists() ) {
// try with conf before...
confFile = new File("conf/" +CONFFILE_NAME);
}
if ( ! confFile.exists() ) {
// try from mvn default test location
confFile = new File("src/test/resources/" +CONFFILE_NAME);
}
if ( confFile.exists()) {
logger.debug("Reading PDPState hibernate configuration from " + confFile.getAbsolutePath());
return new Configuration().configure(confFile).buildSessionFactory();
}
else {
logger.warn("Reading PDPState hibernate configuration from jar; You might define a more accurate " + CONFFILE_NAME);
BufferedInputStream bIs = new BufferedInputStream(this.getClass().getResourceAsStream( "/" + CONFFILE_NAME));
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(false); // do not validate with DTD (and, therefore, do not die offline with an unkown host exception
factory.setFeature("http://xml.org/sax/features/namespaces", false);
factory.setFeature("http://xml.org/sax/features/validation", false);
factory.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false);
factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
Document confDoc = factory.newDocumentBuilder().parse(bIs).getDocumentElement().getOwnerDocument();
return new Configuration().configure(confDoc).buildSessionFactory();
}
}
catch (Throwable ex) {
ex.printStackTrace();
logger.error("Initial SessionFactory creation failed." + ex.getClass().getName() + " - " + ex.getMessage());
throw new ExceptionInInitializerError(ex);
}
}
public HibernateUtil getHibernateUtil() {
return this.hibernateUtil;
}
/**
* adds a new attribute type. values in AuthoAttribute are ignored
* @param attributeId
* @param contextInformation
*/
public AttributeType addAttributeType(AuthoAttribute attribute, List<AuthoAttribute> contextAttributes) {
List<AttributeIdentifier> contextIds = null;
if ( contextAttributes != null && contextAttributes.size() > 0 ) {
contextIds = new Vector<AttributeIdentifier>();
for (AuthoAttribute attr : contextAttributes ) {
contextIds.add(attr.getAttributeIdentifier());
}
}
return hibernateUtil.addAttributeType(attribute.getAttributeIdentifier(), contextIds);
}
public List<String> getAttribute(AttributeIdentifier attrId, List<AuthoAttribute> contextAttrValues) {
return getAttribute(attrId, contextAttrValues, new java.util.Date());
}
public List<String> getAttribute(AttributeIdentifier attrId, List<AuthoAttribute> contextAttrValues, Date validAt) {
return getAttribute(attrId, contextAttrValues, new Timestamp(validAt.getTime()));
}
public List<String> getAttribute(AttributeIdentifier attrId, List<AuthoAttribute> contextAttrValues, Timestamp validAt) {
AttributeType attrType = hibernateUtil.getAttributeType(attrId);
List<ContextAttributeAssignment> contextDBAttrs = transformCtxAttr(attrType, contextAttrValues);
return getAttribute(attrType, contextDBAttrs, validAt);
}
/**
* WARNING: This function performs NO sanity checks, thus, you may get weird behavior
* if you provide unchecked and inconsistent parameters.
* <br/>
*
* @param attrType
* @param contextDBAttrs
* @param validAt
* @return
*/
public List<String> getAttribute(AttributeType attrType, List<ContextAttributeAssignment> contextDBAttrs) {
return getAttribute(attrType, contextDBAttrs, new Timestamp(new java.util.Date().getTime()));
}
/**
* WARNING: This function performs NO sanity checks, thus, you may get weird behavior
* if you provide unchecked and inconsistent parameters.
* <br/>
*
* @param attrType
* @param contextDBAttrs
* @param validAt
* @return
*/
@SuppressWarnings("unchecked") // needed for List<String> list = query.list();
public List<String> getAttribute(AttributeType attrType,
List<ContextAttributeAssignment> contextDBAttrs, Timestamp validAt) {
Session session = factory.getCurrentSession();
Transaction t = session.beginTransaction();
Query query = null;
//if ( attrType.getCtxTypes().size() == 0 ) {
if ( contextDBAttrs == null || contextDBAttrs.size() == 0 ) {
//no context attributes are required, easy case!
query = session.getNamedQuery("getAttribute0");
} else if ( contextDBAttrs.size() == 1 ) {
//only one context attribute is required
query = session.getNamedQuery("getAttribute1");
query.setParameter(CTXATTRTYP0, contextDBAttrs.get(0).getCtxAttribute());
query.setParameter(CTXATTR0, contextDBAttrs.get(0).getValue());
} else if ( contextDBAttrs.size() == 2 ) {
query = session.getNamedQuery("getAttribute2");
query.setParameter(CTXATTRTYP0, contextDBAttrs.get(0).getCtxAttribute());
query.setParameter(CTXATTR0, contextDBAttrs.get(0).getValue());
query.setParameter(CTXATTRTYP1, contextDBAttrs.get(1).getCtxAttribute());
query.setParameter(CTXATTR1, contextDBAttrs.get(1).getValue());
} else {
throw new RuntimeException("Only two constraining attributes are supported");
}
query.setParameter(ATTRTYPE, attrType);
query.setParameter(VALIDAT, validAt);
List<String> list = query.list();
t.commit();
return list;
}
public List<Long> getAssignmentIds(AttributeIdentifier attrId, String value,
List<AuthoAttribute> contextAttrValues,
Date validFrom, Date validTo) {
return getAssignmentIds(attrId, value, contextAttrValues, new Date(validFrom.getTime()), new Date(validTo.getTime()));
}
public List<Long> getAssignmentIds(AttributeIdentifier attrId, String value,
List<AuthoAttribute> contextAttrValues, Timestamp validFrom, Timestamp validTo) {
AttributeType attrType = hibernateUtil.getAttributeType(attrId);
List<ContextAttributeAssignment> contextDBAttrs = transformCtxAttr(attrType, contextAttrValues);
return getAssignmentIds(attrType, value, contextDBAttrs, validFrom, validTo);
}
@SuppressWarnings("unchecked")
public List<Long> getAssignmentIds(AttributeType attrType, String value,
List<ContextAttributeAssignment> contextDBAttrs, Date validFrom, Date validTo) {
/* Task: get all IDs which are within the boundaries defined by :validFrom and :validTo
* Idea:
* exclude those which are not within the boundaries, i.e., one of (OR)
* 1) both a.validFrom and a.validTo lie before :validFrom
* 2) both a.validFrom and a.validTo lie after :validTo
* thus,
* NOT ( ( a.validFrom < :validFrom AND a.validTo < :validTo) OR
* ( a.validFrom > :validFrom AND a.validTo > :validTo) )
* resolving the NOT:
* (a.validFrom >= :validFrom OR a.validTo >= :validTo) AND
* (a.validFrom <= :validFrom OR a.validTo <= :validTo)
*
* TODO make version from arni
* a.validFrom <= :validTo AND a.validTo >= :validFrom
*/
Session session = factory.getCurrentSession();
Transaction t = session.beginTransaction();
Query query = null;
if ( contextDBAttrs == null || contextDBAttrs.size() == 0 ) {
query = session.getNamedQuery("getAssignmentIds0");
} else if ( contextDBAttrs.size() == 1) {
query = session.getNamedQuery("getAssignmentIds1");
query.setParameter(CTXATTRTYP0, contextDBAttrs.get(0).getCtxAttribute());
query.setParameter(CTXATTR0, contextDBAttrs.get(0).getValue());
} else if ( contextDBAttrs.size() == 2) {
query = session.getNamedQuery("getAssignmentIds2");
query.setParameter(CTXATTRTYP0, contextDBAttrs.get(0).getCtxAttribute());
query.setParameter(CTXATTR0, contextDBAttrs.get(0).getValue());
query.setParameter(CTXATTRTYP1, contextDBAttrs.get(1).getCtxAttribute());
query.setParameter(CTXATTR1, contextDBAttrs.get(1).getValue());
}else {
throw new RuntimeException("Only two constraining attributes are supported");
}
query.setParameter(ATTRTYPE, attrType);
query.setParameter(VALIDFROM, validFrom);
query.setParameter(VALIDTO, validTo);
query.setParameter(VALUE, value);
List<Long> list = query.list();
t.commit();
return list;
}
public AttributeAssignment addAssignment(AuthoAttribute attribute,
Date validFrom, Date validTo, List<AuthoAttribute> contextAttributes) {
return addAssignment(attribute,
validFrom == null ? null : new Timestamp(validFrom.getTime()),
validTo == null ? null : new Timestamp(validTo.getTime()), contextAttributes);
//return addAssignment(attribute, new Date(validFrom.getTime()), new Date(validTo.getTime()), contextAttributes);
}
public AttributeAssignment addAssignment(AuthoAttribute attribute,
Timestamp validFrom, Timestamp validTo, List<AuthoAttribute> contextAttributes)
throws InvalidAssignmentException {
AttributeType attrType = hibernateUtil.getAttributeType(attribute.getAttributeIdentifier());
if ( attrType == null ) {
logger.info("AttributeType is not jet known by PDPState. Creating new AttributeType for attribute " + attribute.getAttributeIdentifier());
attrType = addAttributeType(attribute, contextAttributes);
}
// transform input to values with db IDs
List<ContextAttributeAssignment> contextDBAttrs = transformCtxAttr(attrType, contextAttributes);
return addAssignment(attrType, attribute.getValue(), validFrom, validTo, contextDBAttrs);
}
public AttributeAssignment addAssignment(AttributeType attrType,
String value, Timestamp validFrom, Timestamp validTo,
List<ContextAttributeAssignment> contextDBAttrs) {
AttributeAssignment assignment = new AttributeAssignment();
assignment.setValue(value);
assignment.setValidFrom(validFrom);
assignment.setValidTo(validTo);
if ( assignment.getValidFrom().after(assignment.getValidTo()) ) {
throw new InvalidAssignmentException(Reason.INVALID_DATE_NO_TIMEFRAME);
}
if ( new Date(new java.util.Date().getTime() - 60*1000).after(assignment.getValidFrom())) {
throw new InvalidAssignmentException(Reason.INVALID_DATE_MODIFICATION_OF_PAST);
}
logger.info("Adding assignment with value \"" + value + "\" for " + attrType.getAttrType().toString());
logger.debug("Checking that there is no existing attribute in the defined time period");
List<Long> assignedIds = getAssignmentIds(attrType, value, contextDBAttrs, validFrom, validTo);
if ( assignedIds != null && assignedIds.size() > 0 ) {
String message = "There are already " + assignedIds.size() + " assignment(s) within the defined scope and time frame";
logger.error(message);
throw new RuntimeException(message);
}
assignment.setAttrType(attrType);
assignment.setCtxAttrAssignments(contextDBAttrs);
if ( contextDBAttrs != null ) {
for ( ContextAttributeAssignment attr : contextDBAttrs ) {
attr.setAttrAssignment(assignment);
}
}
Session session = factory.getCurrentSession();
Transaction tx = session.beginTransaction();
session.save(assignment);
tx.commit();
return assignment;
}
public void endAssignment(AuthoAttribute attribute, Date validTo,
List<AuthoAttribute> contextAttrValues) {
endAssignment(attribute, validTo == null ? null : new Date(validTo.getTime()), contextAttrValues);
}
public void endAssignment(AuthoAttribute attribute, Timestamp validTo,
List<AuthoAttribute> contextAttrValues) {
AttributeType attrType = hibernateUtil.getAttributeType(attribute.getAttributeIdentifier());
List<ContextAttributeAssignment> contextDBAttrs = transformCtxAttr(attrType, contextAttrValues);
endAssignment(attrType, attribute.getValue(), validTo, contextDBAttrs);
}
public void endAssignment(AttributeType attrType,
String value, Timestamp validTo,
List<ContextAttributeAssignment> contextDBAttrs){
if ( validTo == null ) {
//per default, end assignment with now
validTo = new Timestamp(new java.util.Date().getTime());
} else if ( new Date(new java.util.Date().getTime() - 1000).after(validTo)) {
logger.error("It is not permitted to end an assignment in the past");
throw new InvalidAssignmentException(Reason.INVALID_DATE_MODIFICATION_OF_PAST);
}
//Date now = new Date(new java.util.Date().getTime());
List<Long> ids = getAssignmentIds(attrType, value, contextDBAttrs, validTo, validTo);
if ( ids.size() == 0 ) {
logger.error("There is no assignment which could be ended");
throw new InvalidAssignmentException(Reason.NO_ASSIGNMENT_AVAILABLE);
} else if (ids.size() > 1) {
logger.fatal("There are several assignments valid at one time: TODO log details");
throw new InvalidAssignmentException(Reason.INVALID_STATE);
}
Session session = factory.getCurrentSession();
Transaction tx = session.beginTransaction();
//TODO check before in the same session?
AttributeAssignment attrAssign = (AttributeAssignment) session.get(AttributeAssignment.class, ids.get(0));
attrAssign.setValidTo(validTo);
session.update(attrAssign);
tx.commit();
}
public String replaceValue() {
//TODO for assignment where only one value at one point in time is valid
// e.g. active policy version
//
//return old value
return null;
}
/**
* This function does mainly two things: first, the attribute definitions which are contained in
* contextAttributes are transfered to a List of ContextAttributeAssignment (i.e., containing
* a reference to the actual ContextAttribute). Second, a sanity check is done, i.e., if the attributes
* defined by contextAttributes match to the required contextAttributes defined for the
* attributeType
* @param attributeType
* @param contextAttributes
* @return
*/
protected List<ContextAttributeAssignment> transformCtxAttr(AttributeType attributeType, List<AuthoAttribute> contextAttributes) {
// check if meta info defined by AttributeType defines same size of contextAttributes as provided
if ( (attributeType.getCtxTypes() == null ? 0 : attributeType.getCtxTypes().size())
!= (contextAttributes == null ? 0 : contextAttributes.size())) {
String message = "AttributeType for " + attributeType.getAttrType() + " defines " +
(attributeType.getCtxTypes() == null ? 0 : attributeType.getCtxTypes().size()) + " context attributes, but received " +
(contextAttributes == null ? 0 : contextAttributes.size());
logger.error("Invalid Assignment: " + message);
throw new InvalidParameterException(message);
}
AttributeAssignment assignment = new AttributeAssignment();
if ( contextAttributes != null && contextAttributes.size() > 0 ) {
List<ContextAttributeAssignment> contextDBAttrs = new Vector<ContextAttributeAssignment>();
//in most cases there will be only one ctx Attribute; implement "fast way"
if ( contextAttributes.size() == 1 ) {
AuthoAttribute provAttr = contextAttributes.get(0);
ContextAttribute ctxAttr = attributeType.getCtxTypes().get(0);
//check if required IDs match
if ( provAttr.getAttributeIdentifier().hashCode() !=
ctxAttr.getAttrId().hashCode()) {
String message = "AttributeType for " + attributeType.getAttrType() + " defines " +
"another required context attribute (" + ctxAttr.getAttrId() +
") than provided (" + provAttr.getAttributeIdentifier();
logger.error("Invalid Assignment: " + message);
throw new InvalidParameterException(message);
} else {
contextDBAttrs.add(new ContextAttributeAssignment(provAttr.getValue(), ctxAttr, assignment));
return contextDBAttrs;
}
} else {
Map<AttributeIdentifier, ContextAttribute> tmpMap = new HashMap<AttributeIdentifier, ContextAttribute>();
for ( ContextAttribute ctxAttr : attributeType.getCtxTypes() ) {
tmpMap.put(ctxAttr.getAttrId(), ctxAttr);
}
for ( AuthoAttribute attrValue : contextAttributes ) {
if ( tmpMap.containsKey(attrValue.getAttributeIdentifier()) ) {
ContextAttribute ctxAttr = tmpMap.get(attrValue.getAttributeIdentifier());
contextDBAttrs.add(new ContextAttributeAssignment(attrValue.getValue(), ctxAttr, assignment));
} else {
String message = "AttributeType " + attributeType.getAttrType() +
" does not require the context attribute " + attrValue.getAttributeIdentifier();
logger.error("Invalid Assignment: " + message);
throw new InvalidParameterException(message);
}
}
return contextDBAttrs;
}
} else {
return null;
}
}
// session.createQuery("SELECT a.id FROM " +
// AttributeAssignment.class.getName() + " AS a " +
// " INNER JOIN a.ctxAttrAssignments as c " +
// " WHERE a.attrType = :attrType " +
// " AND ( ( a.validFrom >= :validFrom OR a.validTo >= :validTo ) AND " +
// " ( a.validFrom <= :validFrom OR a.validTo <= :validTo ) ) " +
// " AND a.value = :value " +
// " AND c.ctxAttribute = :ctxAttrTypeNull " +
// " AND c.value = :ctxAttrNull");
}