PIPBase.java
/*
* CNR - IIT (2015-2016)
*
* @authors Fabio Bindi and Filippo Lauria
*/
package it.cnr.iit.ucs.pip;
import it.cnr.iit.ucs.constants.ENTITIES;
import it.cnr.iit.ucs.exceptions.PIPException;
import it.cnr.iit.ucs.message.attributechange.AttributeChangeMessage;
import it.cnr.iit.ucs.properties.components.PipProperties;
import it.cnr.iit.ucs.requestmanager.RequestManagerInterface;
import it.cnr.iit.utility.errorhandling.Reject;
import it.cnr.iit.xacml.Attribute;
import it.cnr.iit.xacml.Category;
import it.cnr.iit.xacml.DataType;
import oasis.names.tc.xacml.core.schema.wd_17.RequestType;
import java.util.*;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* General PIP abstract class
*
* @author Fabio Bindi and Filippo Lauria and Antonio La Marra and Alessandro Rosetti
*/
public abstract class PIPBase implements PIPCHInterface, PIPOMInterface {
private RequestManagerInterface requestManager;
/**
* Map having the attributeId as key and an Attribute object as value
*/
private final HashMap<String, Attribute> attributesMap = new HashMap<>();
/**
* List that stores the attributes on which a 'subscribe' has been performed
*/
public final BlockingQueue<Attribute> subscriptions = new LinkedBlockingQueue<>();
// Whenever a PIP has to retrieve some information related to an attribute
// that is stored inside the request, it has to know in advance all the
// information to retrieve that attribute. E.g. if this PIP has to retrieve
// information about the subject, it has to know in advance which is the
// attribute id qualifying the subject, its category and the data-type used,
// otherwise it is not able to retrieve the value of that attribute, hence it
// would not be able to communicate with the AM properly
/**
* Map having attributeId as key and the expected category as value
*/
public final Map<String, Category> expectedCategoryMap = new HashMap<>();
private final PipProperties properties;
public PIPBase(PipProperties properties) {
Reject.ifNull(properties);
this.properties = properties;
}
public boolean init(PipProperties properties) {
try {
for (Map<String, String> attributeMap : properties.getAttributes()) {
Attribute attribute = new Attribute();
buildAttribute(attribute, attributeMap);
addAttribute(attribute);
// timer for polling the value of the attribute
PIPSubscriberTimer subscriberTimer = new PIPSubscriberTimer(this);
subscriberTimer.setRate(properties.getRefreshRate());
subscriberTimer.start();
}
return true;
} catch (Exception e) {
return false;
}
}
/**
* Use the information in the PipProperties to build an attribute
*
* @param attribute the attribute to build
* @param attributeMap the part of the PipProperties representing an attribute
*/
public void buildAttribute(Attribute attribute, Map<String, String> attributeMap) {
String attributeId = attributeMap.get(PIPKeywords.ATTRIBUTE_ID);
attribute.setAttributeId(attributeId);
Category category = Category.toCATEGORY(attributeMap.get(PIPKeywords.CATEGORY));
attribute.setCategory(category);
DataType dataType = DataType.toDATATYPE(attributeMap.get(PIPKeywords.DATA_TYPE));
attribute.setDataType(dataType);
if (attribute.getCategory() != Category.ENVIRONMENT) {
Category expectedCategory = Category.toCATEGORY(attributeMap.get(PIPKeywords.EXPECTED_CATEGORY));
Reject.ifNull(expectedCategory, "missing expected category");
expectedCategoryMap.put(attribute.getAttributeId(), expectedCategory);
}
}
/**
* Performs the retrieve operation.
* The retrieve operation is a very basic operation in which the PIP simply
* asks the AttributeManager the value in which it is interested into. Once
* that value has been retrieved, the PIP will fatten the request.
*
* @param request this is an in/out parameter
*/
@Override
public void retrieve(RequestType request) throws PIPException {
Reject.ifNull(request);
for (Attribute attribute : getAttributes()) {
if (!isEnvironmentCategory(attribute)) {
addAdditionalInformation(request, attribute);
}
try {
String value = retrieve(attribute);
request.addAttribute(attribute, value);
} catch (Exception e) {
//TODO: handle exception
System.err.println(e.getMessage());
}
}
}
/**
* Performs the subscribe operation. The request passed as input is used
* to retrieve the information related to either the subject, resource, or action.
* The attributeId is used to select the right attribute from those this PIP
* is monitoring.
*
* @param request the XACML request containing at least subject-id, resource-id and action-id
* @param attributeId the attributeId of the attribute we want this PIP to subscribe to.
*/
@Override
public void subscribe(RequestType request, String attributeId) {
Reject.ifNull(request);
Attribute attribute = getAttributes().stream()
.filter(attr -> attr.getAttributeId().equals(attributeId)).findAny().orElse(null);
Reject.ifNull(attribute);
if (!isEnvironmentCategory(attribute)) {
addAdditionalInformation(request, attribute);
}
try {
String value = subscribe(attribute);
request.addAttribute(attribute, value);
} catch (Exception e) {
//TODO: handle exception
System.err.println(e.getMessage());
}
}
/**
* Performs the subscribe operation. This operation is very similar to the
* retrieve operation. The only difference is that in this case we have to
* signal to the thread in charge of performing the polling that it has to
* poll a new attribute
*
* @param request IN/OUT parameter
*/
@Override
public void subscribe(RequestType request) throws PIPException {
Reject.ifNull(request);
for (Attribute attribute : getAttributes()) {
if (!isEnvironmentCategory(attribute)) {
addAdditionalInformation(request, attribute);
}
try {
String value = subscribe(attribute);
request.addAttribute(attribute, value);
} catch (Exception e) {
//TODO: handle exception
System.err.println(e.getMessage());
}
}
}
/**
* This is the function called by the context handler whenever we have a
* remote retrieve request
*/
@Override
public String subscribe(Attribute attribute) throws PIPException {
Reject.ifNull(attribute);
String value = retrieve(attribute);
addSubscription(attribute);
return value;
}
/**
* Add the attribute passed as argument to the list of subscriptions
*
* @param attribute the attribute to add to the list
*/
public void addSubscription(Attribute attribute) {
if (!subscriptions.contains(attribute)) {
subscriptions.add(attribute);
}
}
/**
* Given a list of attributes as input, remove from the subscriptions list the attributes
* that match.
* For the environment attributes, only the attributeId must match in order for an attribute
* to be removed from the subscriptions list.
* For attributes of other categories, both the attributeId and the additionalInformation must
* match in order for an attribute to be removed from the subscriptions list.
* This is because, for example, the PIP could be monitoring the attributeId 'subject-role'
* for the subject with subject-id 'User1' and for the subject with subject-id 'User2'.
* The information 'User1' or 'User2' is contained within the additionalInformation field.
*
* @param unsubAttributes the list of attributes that must be unsubscribed
*/
@Override
public boolean unsubscribe(List<Attribute> unsubAttributes) throws PIPException {
Reject.ifEmpty(unsubAttributes);
boolean atLeastOneAttributeRemoved = false;
// for each attribute to unsubscribe
for (Attribute attributeToUnsubscribe : unsubAttributes) {
// for each attribute the PIP is subscribed to
for (Attribute subscribedAttribute : subscriptions) {
// if the attributeId matches one of the attributes the PIP has an active subscription
if (subscribedAttribute.getAttributeId().equals(attributeToUnsubscribe.getAttributeId())) {
// if the attribute is of category Environment,
// or if the additionalInformation matches
if (subscribedAttribute.getCategory() == Category.ENVIRONMENT ||
subscribedAttribute.getAdditionalInformation()
.equals(attributeToUnsubscribe.getAdditionalInformation())) {
atLeastOneAttributeRemoved = removeAttribute(subscribedAttribute);
break;
}
}
}
}
return atLeastOneAttributeRemoved;
}
/**
* Retrieve the value of the
*/
public void checkSubscriptions() {
for (Attribute attribute : subscriptions) {
// first, get the old value as memorized in the attribute
String oldValue = attribute.getAttributeValues(attribute.getDataType()).get(0);
//get(0) assumes that the attribute has only one value
String value;
// log.log( Level.INFO, "Polling on value of the attribute " + attribute.getAttributeId() + " for change." );
// then, retrieve the new value.
// Note that the retrieve method updates the attribute with the value just retrieved
try {
value = retrieve(attribute);
} catch (PIPException e) {
getLogger().log(Level.WARNING, "Error reading attribute "
+ attribute.getAttributeId() + e.getMessage());
continue;
}
if (!oldValue.equals(value)) { // if the attribute has changed
getLogger().log(Level.INFO,
"Attribute {0}={1}:{2} changed at {3}",
new Object[]{attribute.getAttributeId(), value,
attribute.getAdditionalInformation(),
System.currentTimeMillis()});
attribute.setValue(attribute.getDataType(), value);
notifyRequestManager(attribute);
}
}
}
/**
* Remove the attribute passed as argument from the list of subscriptions
*
* @param subscribedAttribute the attribute to remove from the list
*/
private boolean removeAttribute(Attribute subscribedAttribute) {
if (!subscriptions.remove(subscribedAttribute)) {
throw new IllegalStateException("Unable to remove attribute from list");
}
return true;
}
/**
* Check if the attribute's category is 'environment'
*
* @param attribute the attribute to examine
* @return true is the category of the attribute is 'environment',
* false otherwise
*/
public boolean isEnvironmentCategory(Attribute attribute) {
return attribute.getCategory() == Category.ENVIRONMENT;
}
/**
* Add information coming from the request to the attribute.
* In particular, the attribute is added the value of either the 'subject-id',
* the 'resource-id', or the 'action-id' attribute. This information
* is stored in the additionalInformation field of the attribute.
*
* @param request The XACML request
* @param attribute the attribute to which the additionalInformation has to be set
*/
private void addAdditionalInformation(RequestType request, Attribute attribute) {
String filter = request.getAttributeValue(expectedCategoryMap.get(attribute.getAttributeId()));
attribute.setAdditionalInformation(filter);
}
@Override
public final ArrayList<String> getAttributeIds() {
return new ArrayList<>(attributesMap.keySet());
}
@Override
public final ArrayList<Attribute> getAttributes() {
return new ArrayList<>(attributesMap.values());
}
@Override
public final HashMap<String, Attribute> getAttributesCharacteristics() {
return attributesMap;
}
@Override
public RequestManagerInterface getRequestManager() {
Reject.ifNull(requestManager, "request manager is null");
return requestManager;
}
@Override
public void setRequestManager(RequestManagerInterface requestManager) {
Reject.ifNull(requestManager);
this.requestManager = requestManager;
}
protected final boolean addAttribute(Attribute attribute) {
Reject.ifNull(attribute);
if (attributesMap.containsKey(attribute.getAttributeId())) {
return false;
}
attributesMap.put(attribute.getAttributeId(), attribute);
return true;
}
/**
* Send a message to the Request Manager to notify it that an attribute
* value changed.
*
* @param attribute the attribute whose value changed
*/
public void notifyRequestManager(Attribute attribute) {
AttributeChangeMessage attrChangeMessage = new AttributeChangeMessage(ENTITIES.PIP.toString(), ENTITIES.CH.toString());
ArrayList<Attribute> attrList = new ArrayList<>(Arrays.asList(attribute));
attrChangeMessage.setAttributes(attrList);
getRequestManager().sendMessage(attrChangeMessage);
}
/**
* Get the queue containing the subscriptions
*
* @return the queue containing the subscriptions
*/
public BlockingQueue<Attribute> getSubscriptions() {
return subscriptions;
}
protected abstract Logger getLogger();
}