/**
* Copyright (c) Codice Foundation
* <p>
* This is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser
* General Public License as published by the Free Software Foundation, either version 3 of the
* License, or any later version.
* <p>
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details. A copy of the GNU Lesser General Public License
* is distributed along with this program and can be found at
* <http://www.gnu.org/licenses/lgpl.html>.
*/
package ddf.security.pdp.realm.xacml;
import java.util.List;
import org.apache.commons.validator.EmailValidator;
import org.apache.commons.validator.UrlValidator;
import org.apache.commons.validator.routines.CalendarValidator;
import org.apache.commons.validator.routines.DateValidator;
import org.apache.commons.validator.routines.DoubleValidator;
import org.apache.commons.validator.routines.IntegerValidator;
import org.apache.commons.validator.routines.TimeValidator;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.Permission;
import org.apache.shiro.util.CollectionUtils;
import org.bouncycastle.asn1.x500.X500Name;
import org.codice.ddf.parser.Parser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.net.InetAddresses;
import ddf.security.common.audit.SecurityLogger;
import ddf.security.pdp.realm.xacml.processor.PdpException;
import ddf.security.pdp.realm.xacml.processor.XacmlClient;
import ddf.security.permission.CollectionPermission;
import ddf.security.permission.KeyValueCollectionPermission;
import ddf.security.permission.KeyValuePermission;
import oasis.names.tc.xacml._3_0.core.schema.wd_17.AttributeType;
import oasis.names.tc.xacml._3_0.core.schema.wd_17.AttributeValueType;
import oasis.names.tc.xacml._3_0.core.schema.wd_17.AttributesType;
import oasis.names.tc.xacml._3_0.core.schema.wd_17.DecisionType;
import oasis.names.tc.xacml._3_0.core.schema.wd_17.RequestType;
import oasis.names.tc.xacml._3_0.core.schema.wd_17.ResponseType;
/**
* Performs authorization backed by a XACML-based PDP.
*/
public class XacmlPdp {
private static final Logger LOGGER = LoggerFactory.getLogger(XacmlPdp.class);
private XacmlClient pdp;
private List<String> environmentAttributes;
/**
* Creates a general
*/
public XacmlPdp(String dirPath, Parser parser, List<String> environmentAttributes)
throws PdpException {
super();
pdp = new XacmlClient(dirPath, parser);
this.environmentAttributes = environmentAttributes;
LOGGER.debug("Creating new PDP-backed Authorizing Realm");
}
public boolean isPermitted(String primaryPrincipal, AuthorizationInfo info,
KeyValueCollectionPermission curPermission) {
boolean curResponse;
LOGGER.debug("Checking if {} has access for action {}",
primaryPrincipal,
curPermission.getAction());
SecurityLogger.audit("Checking if [" + primaryPrincipal + "] has access for action "
+ curPermission.getAction());
if (CollectionUtils.isEmpty(info.getObjectPermissions())
&& CollectionUtils.isEmpty(info.getStringPermissions()) && CollectionUtils.isEmpty(
info.getRoles())
&& !CollectionUtils.isEmpty(curPermission.getKeyValuePermissionList())) {
return false;
}
if ((!CollectionUtils.isEmpty(info.getObjectPermissions())
|| !CollectionUtils.isEmpty(info.getStringPermissions())
|| !CollectionUtils.isEmpty(info.getRoles())) && CollectionUtils.isEmpty(
curPermission.getKeyValuePermissionList())) {
return true;
}
LOGGER.debug("Received authZ info, creating XACML request.");
RequestType curRequest = createXACMLRequest(primaryPrincipal, info, curPermission);
LOGGER.debug("Created XACML request, calling PDP.");
curResponse = isPermitted(curRequest);
return curResponse;
}
protected RequestType createXACMLRequest(String subject, AuthorizationInfo info,
CollectionPermission permission) {
LOGGER.debug("Creating XACML request for subject: {} and metacard permissions {}",
subject,
permission);
RequestType xacmlRequestType = new RequestType();
xacmlRequestType.setCombinedDecision(false);
xacmlRequestType.setReturnPolicyIdList(false);
// Adding filter action
AttributesType actionAttributes = new AttributesType();
actionAttributes.setCategory(XACMLConstants.ACTION_CATEGORY);
AttributeType actionAttribute = new AttributeType();
actionAttribute.setAttributeId(XACMLConstants.ACTION_ID);
actionAttribute.setIncludeInResult(false);
AttributeValueType actionValue = new AttributeValueType();
actionValue.setDataType(XACMLConstants.STRING_DATA_TYPE);
LOGGER.trace("Adding action: {} for subject: {}", XACMLConstants.FILTER_ACTION, subject);
actionValue.getContent()
.add(permission.getAction());
actionAttribute.getAttributeValue()
.add(actionValue);
actionAttributes.getAttribute()
.add(actionAttribute);
xacmlRequestType.getAttributes()
.add(actionAttributes);
// Adding permissions for the calling subject
AttributesType subjectAttributes = createSubjectAttributes(subject, info);
xacmlRequestType.getAttributes()
.add(subjectAttributes);
// Adding permissions for the resource
AttributesType metadataAttributes = new AttributesType();
metadataAttributes.setCategory(XACMLConstants.RESOURCE_CATEGORY);
AttributesType environmentAttributesType = new AttributesType();
environmentAttributesType.setCategory(XACMLConstants.ENVIRONMENT_CATEGORY);
if (!CollectionUtils.isEmpty(environmentAttributes)) {
for (String envAttr : environmentAttributes) {
String[] attr = envAttr.split("=");
if (attr.length == 2) {
AttributeType attributeType = new AttributeType();
attributeType.setAttributeId(attr[0].trim());
String[] attrVals = attr[1].split(",");
for (String attrVal : attrVals) {
AttributeValueType attributeValueType = new AttributeValueType();
attributeValueType.setDataType(XACMLConstants.STRING_DATA_TYPE);
attributeValueType.getContent()
.add(attrVal.trim());
attributeType.getAttributeValue()
.add(attributeValueType);
}
environmentAttributesType.getAttribute()
.add(attributeType);
}
}
}
if (permission instanceof KeyValueCollectionPermission) {
List<KeyValuePermission> tmpList =
((KeyValueCollectionPermission) permission).getKeyValuePermissionList();
for (KeyValuePermission curPermission : tmpList) {
AttributeType resourceAttribute = new AttributeType();
resourceAttribute.setAttributeId(curPermission.getKey());
resourceAttribute.setIncludeInResult(false);
if (curPermission.getValues()
.size() > 0) {
for (String curPermValue : curPermission.getValues()) {
AttributeValueType resourceAttributeValue = new AttributeValueType();
resourceAttributeValue.setDataType(getXacmlDataType(curPermValue));
LOGGER.trace("Adding permission: {}:{} for incoming resource",
new Object[] {curPermission.getKey(), curPermValue});
resourceAttributeValue.getContent()
.add(curPermValue);
resourceAttribute.getAttributeValue()
.add(resourceAttributeValue);
}
metadataAttributes.getAttribute()
.add(resourceAttribute);
}
}
xacmlRequestType.getAttributes()
.add(metadataAttributes);
if (!CollectionUtils.isEmpty(environmentAttributes)) {
xacmlRequestType.getAttributes()
.add(environmentAttributesType);
}
} else {
LOGGER.warn(
"Permission on the resource need to be of type KeyValueCollectionPermission, cannot process this resource.");
}
return xacmlRequestType;
}
protected boolean isPermitted(RequestType xacmlRequest) {
boolean permitted;
ResponseType xacmlResponse;
try {
LOGGER.debug("Calling PDP to evaluate XACML request.");
xacmlResponse = pdp.evaluate(xacmlRequest);
LOGGER.debug("Received response from PDP.");
permitted = xacmlResponse != null && xacmlResponse.getResult()
.get(0)
.getDecision() == DecisionType.PERMIT;
LOGGER.debug("Permitted: {}", permitted);
} catch (PdpException e) {
LOGGER.debug(e.getMessage(), e);
permitted = false;
}
return permitted;
}
private AttributesType createSubjectAttributes(String subject, AuthorizationInfo info) {
AttributesType subjectAttributes = new AttributesType();
subjectAttributes.setCategory(XACMLConstants.ACCESS_SUBJECT_CATEGORY);
AttributeType subjectAttribute = new AttributeType();
subjectAttribute.setAttributeId(XACMLConstants.SUBJECT_ID);
subjectAttribute.setIncludeInResult(false);
AttributeValueType subjectValue = new AttributeValueType();
subjectValue.setDataType(XACMLConstants.STRING_DATA_TYPE);
LOGGER.debug("Adding subject: {}", subject);
subjectValue.getContent()
.add(subject);
subjectAttribute.getAttributeValue()
.add(subjectValue);
subjectAttributes.getAttribute()
.add(subjectAttribute);
AttributeType roleAttribute = new AttributeType();
roleAttribute.setAttributeId(XACMLConstants.ROLE_CLAIM);
roleAttribute.setIncludeInResult(false);
if (info.getRoles()
.size() > 0) {
for (String curRole : info.getRoles()) {
AttributeValueType roleValue = new AttributeValueType();
roleValue.setDataType(XACMLConstants.STRING_DATA_TYPE);
LOGGER.trace("Adding role: {} for subject: {}", curRole, subject);
roleValue.getContent()
.add(curRole);
roleAttribute.getAttributeValue()
.add(roleValue);
}
subjectAttributes.getAttribute()
.add(roleAttribute);
}
for (Permission curPermission : info.getObjectPermissions()) {
if (curPermission instanceof KeyValuePermission) {
AttributeType subjAttr = new AttributeType();
subjAttr.setAttributeId(((KeyValuePermission) curPermission).getKey());
subjAttr.setIncludeInResult(false);
if (((KeyValuePermission) curPermission).getValues()
.size() > 0) {
for (String curPermValue : ((KeyValuePermission) curPermission).getValues()) {
AttributeValueType subjAttrValue = new AttributeValueType();
subjAttrValue.setDataType(getXacmlDataType(curPermValue));
LOGGER.trace("Adding permission: {}:{} for subject: {}",
((KeyValuePermission) curPermission).getKey(),
curPermValue,
subject);
subjAttrValue.getContent()
.add(curPermValue);
subjAttr.getAttributeValue()
.add(subjAttrValue);
}
subjectAttributes.getAttribute()
.add(subjAttr);
}
} else {
LOGGER.warn(
"Permissions for subject were not of type KeyValuePermission, cannot add any subject permissions to the request.");
}
}
return subjectAttributes;
}
protected String getXacmlDataType(String curPermValue) {
if ("false".equalsIgnoreCase(curPermValue) || "true".equalsIgnoreCase(curPermValue)) {
return XACMLConstants.BOOLEAN_DATA_TYPE;
} else if (IntegerValidator.getInstance()
.validate(curPermValue) != null) {
return XACMLConstants.INTEGER_DATA_TYPE;
} else if (DoubleValidator.getInstance()
.validate(curPermValue) != null) {
return XACMLConstants.DOUBLE_DATA_TYPE;
} else if (TimeValidator.getInstance()
.validate(curPermValue, "H:mm:ss") != null || TimeValidator.getInstance()
.validate(curPermValue, "H:mm:ss.SSS") != null || TimeValidator.getInstance()
.validate(curPermValue, "H:mm:ssXXX") != null || TimeValidator.getInstance()
.validate(curPermValue, "H:mm:ss.SSSXXX") != null) {
return XACMLConstants.TIME_DATA_TYPE;
} else if (DateValidator.getInstance()
.validate(curPermValue, "yyyy-MM-dd") != null || DateValidator.getInstance()
.validate(curPermValue, "yyyy-MM-ddXXX") != null) {
return XACMLConstants.DATE_DATA_TYPE;
} else if (CalendarValidator.getInstance()
.validate(curPermValue, "yyyy-MM-dd:ss'T'H:mm") != null ||
CalendarValidator.getInstance()
.validate(curPermValue, "yyyy-MM-dd'T'H:mm:ssXXX") != null ||
CalendarValidator.getInstance()
.validate(curPermValue, "yyyy-MM-dd'T'H:mm:ss.SSS") != null ||
CalendarValidator.getInstance()
.validate(curPermValue, "yyyy-MM-dd'T'H:mm:ss.SSSXXX") != null ||
CalendarValidator.getInstance()
.validate(curPermValue, "yyyy-MM-dd'T'H:mm:ss") != null) {
return XACMLConstants.DATE_TIME_DATA_TYPE;
} else if (EmailValidator.getInstance()
.isValid(curPermValue)) {
return XACMLConstants.RFC822_NAME_DATA_TYPE;
} else if (new UrlValidator().isValid(curPermValue)) {
return XACMLConstants.URI_DATA_TYPE;
} else if (InetAddresses.isUriInetAddress(curPermValue)) {
return XACMLConstants.IP_ADDRESS_DATA_TYPE;
} else {
try {
if (new X500Name(curPermValue).getRDNs().length > 0) {
return XACMLConstants.X500_NAME_DATA_TYPE;
}
} catch (IllegalArgumentException e) {
}
}
return XACMLConstants.STRING_DATA_TYPE;
}
}