/* The contents of this file are subject to the license and copyright terms
* detailed in the license directory at the root of the source tree (also
* available online at http://fedora-commons.org/license/).
*/
package org.fcrepo.server.security;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.xml.transform.Transformer;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.fcrepo.common.Constants;
import org.fcrepo.server.Server;
import org.fcrepo.server.config.ModuleConfiguration;
import org.fcrepo.server.errors.GeneralException;
import org.fcrepo.server.validation.ValidationUtility;
import org.fcrepo.utilities.FileUtils;
import org.fcrepo.utilities.XmlTransformUtility;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.jboss.security.xacml.sunxacml.AbstractPolicy;
import org.jboss.security.xacml.sunxacml.EvaluationCtx;
import org.jboss.security.xacml.sunxacml.PolicySet;
import org.jboss.security.xacml.sunxacml.attr.AttributeValue;
import org.jboss.security.xacml.sunxacml.attr.BagAttribute;
import org.jboss.security.xacml.sunxacml.attr.StringAttribute;
import org.jboss.security.xacml.sunxacml.combine.OrderedDenyOverridesPolicyAlg;
import org.jboss.security.xacml.sunxacml.combine.PolicyCombiningAlgorithm;
import org.jboss.security.xacml.sunxacml.cond.EvaluationResult;
import org.jboss.security.xacml.sunxacml.ctx.Status;
import org.jboss.security.xacml.sunxacml.finder.PolicyFinder;
import org.jboss.security.xacml.sunxacml.finder.PolicyFinderResult;
/**
* XACML PolicyFinder for Fedora.
* <p>
* This provides repository-wide policies and object-specific policies,
* when available.
*/
public class PolicyFinderModule
extends org.jboss.security.xacml.sunxacml.finder.PolicyFinderModule {
private static final Logger logger =
LoggerFactory.getLogger(PolicyFinderModule.class);
private static final List<String> ERROR_CODE_LIST = new ArrayList<String>(1);
static {
ERROR_CODE_LIST.add(Status.STATUS_PROCESSING_ERROR);
}
private static final String DEFAULT = "default";
private static final String DEFAULT_XACML_COMBINING_ALGORITHM = "org.jboss.security.xacml.sunxacml.combine.OrderedDenyOverridesPolicyAlg";
private static final String XACML_DIST_BASE = "fedora-internal-use";
private static final String DEFAULT_REPOSITORY_POLICIES_DIRECTORY =
XACML_DIST_BASE
+ "/fedora-internal-use-repository-policies-approximating-2.0";
private static final String BACKEND_POLICIES_ACTIVE_DIRECTORY =
XACML_DIST_BASE + "/fedora-internal-use-backend-service-policies";
private static final String BE_SECURITY_XML_LOCATION =
"config/beSecurity.xml";
private static final String BACKEND_POLICIES_XSL_LOCATION =
XACML_DIST_BASE + "/build-backend-policy.xsl";
private static final String COMBINING_ALGORITHM_KEY = "XACML-COMBINING-ALGORITHM";
private static final String REPOSITORY_POLICIES_DIRECTORY_KEY =
"REPOSITORY-POLICIES-DIRECTORY";
private static final String POLICY_SCHEMA_PATH_KEY = "POLICY-SCHEMA-PATH";
private static final String VALIDATE_REPOSITORY_POLICIES_KEY =
"VALIDATE-REPOSITORY-POLICIES";
private static final String VALIDATE_OBJECT_POLICIES_FROM_DATASTREAM_KEY =
"VALIDATE-OBJECT-POLICIES-FROM-DATASTREAM";
private static final URI STRING_ATTRIBUTE = URI.create(StringAttribute.identifier);
private static final URI EMPTY_URI = URI.create("");
@SuppressWarnings("unchecked")
private static final PolicySet EMPTY_SET = toPolicySet(Collections.EMPTY_LIST, new OrderedDenyOverridesPolicyAlg());
private final PolicyCombiningAlgorithm m_combiningAlgorithm;
private final String m_serverHome;
private final String m_repositoryPolicyDirectoryPath;
private final String m_repositoryBackendPolicyDirectoryPath;
private final boolean m_validateRepositoryPolicies;
private final boolean m_validateObjectPoliciesFromDatastream;
private final PolicyParser m_policyParser;
private final PolicyLoader m_policyLoader;
private final List<AbstractPolicy> m_repositoryPolicies;
private PolicySet m_repositoryPolicySet = EMPTY_SET;
public PolicyFinderModule(Server server,
PolicyLoader policyLoader,
ModuleConfiguration authorizationConfig)
throws GeneralException {
m_serverHome = server.getHomeDir().getAbsolutePath();
m_policyLoader = policyLoader;
m_repositoryBackendPolicyDirectoryPath = m_serverHome + File.separator
+ BACKEND_POLICIES_ACTIVE_DIRECTORY;
String repositoryPolicyDirectoryPath =
authorizationConfig.getParameter(REPOSITORY_POLICIES_DIRECTORY_KEY, true);
if (repositoryPolicyDirectoryPath == null) repositoryPolicyDirectoryPath = "";
m_repositoryPolicyDirectoryPath = repositoryPolicyDirectoryPath;
String combAlgClass = authorizationConfig.getParameter(COMBINING_ALGORITHM_KEY);
if (combAlgClass == null) combAlgClass = DEFAULT_XACML_COMBINING_ALGORITHM;
try {
m_combiningAlgorithm =
(PolicyCombiningAlgorithm) Class
.forName(combAlgClass).newInstance();
} catch (Exception e) {
throw new GeneralException(e.getMessage(), e);
}
String validatePolicies = authorizationConfig.getParameter(VALIDATE_REPOSITORY_POLICIES_KEY);
try {
m_validateRepositoryPolicies = (validatePolicies != null) ? Boolean.parseBoolean(validatePolicies) : false;
} catch (Exception e) {
throw new GeneralException("bad init parm boolean value for "
+ VALIDATE_REPOSITORY_POLICIES_KEY, e);
}
validatePolicies = authorizationConfig.getParameter(VALIDATE_OBJECT_POLICIES_FROM_DATASTREAM_KEY);
try {
m_validateObjectPoliciesFromDatastream = (validatePolicies != null) ? Boolean.parseBoolean(validatePolicies) : false;
} catch (Exception e) {
throw new GeneralException("bad init parm boolean value for "
+ VALIDATE_OBJECT_POLICIES_FROM_DATASTREAM_KEY, e);
}
// Initialize the policy parser given the POLICY_SCHEMA_PATH_KEY
String schemaPath = authorizationConfig.getParameter(POLICY_SCHEMA_PATH_KEY);
if (schemaPath != null) {
File schema;
if (schemaPath.startsWith(File.separator)){ // absolute
schema = new File(schemaPath);
} else {
schema = new File(new File(m_serverHome), schemaPath);
}
try {
FileInputStream in = new FileInputStream(schema);
m_policyParser = new PolicyParser(in);
ValidationUtility.setPolicyParser(m_policyParser);
} catch (Exception e) {
throw new GeneralException("Error loading policy"
+ " schema: " + schema.getAbsolutePath(), e);
}
} else {
throw new GeneralException("Policy schema path not"
+ " specified. Must be given as " + POLICY_SCHEMA_PATH_KEY);
}
m_repositoryPolicies = new ArrayList<AbstractPolicy>();
}
/**
* Does nothing at init time.
*/
@Override
public void init(PolicyFinder finder) {
try {
logger.info("Loading repository policies...");
setupActivePolicyDirectories();
m_repositoryPolicies.clear();
Map<String,AbstractPolicy> repositoryPolicies =
m_policyLoader.loadPolicies(m_policyParser,
m_validateRepositoryPolicies,
new File(m_repositoryBackendPolicyDirectoryPath));
repositoryPolicies.putAll(
m_policyLoader.loadPolicies(m_policyParser,
m_validateRepositoryPolicies,
new File(m_repositoryPolicyDirectoryPath)));
m_repositoryPolicies.addAll(repositoryPolicies.values());
m_repositoryPolicySet = toPolicySet(m_repositoryPolicies, m_combiningAlgorithm);
} catch (Throwable t) {
logger.error("Error loading repository policies: " + t.toString(), t);
}
}
private final void generateBackendPolicies() throws Exception {
logger.info("Generating backend policies...");
FileUtils.deleteContents(new File(m_repositoryBackendPolicyDirectoryPath));
BackendPolicies backendPolicies =
new BackendPolicies(m_serverHome + File.separator
+ BE_SECURITY_XML_LOCATION);
Hashtable<String, String> tempfiles = backendPolicies.generateBackendPolicies();
try {
Iterator<String> iterator = tempfiles.keySet().iterator();
Transformer transformer = null;
while (iterator.hasNext()) {
if (transformer == null) {
File f =
new File(m_serverHome + File.separator
+ BACKEND_POLICIES_XSL_LOCATION); // <<stylesheet
// location
StreamSource ss = new StreamSource(f);
transformer = XmlTransformUtility.getTransformer(ss); // xformPath
} else {
transformer.reset();
}
String key = iterator.next();
File infile = new File(tempfiles.get(key));
FileInputStream fis = new FileInputStream(infile);
FileOutputStream fos =
new FileOutputStream(m_repositoryBackendPolicyDirectoryPath
+ File.separator + key);
transformer.transform(new StreamSource(fis),
new StreamResult(fos));
}
} finally {
// we're done with temp files now, so delete them
Iterator<String> iter = tempfiles.keySet().iterator();
while (iter.hasNext()) {
File tempFile = new File(tempfiles.get(iter.next()));
tempFile.delete();
}
}
}
private void setupActivePolicyDirectories() throws Exception {
File repoPolicyDir = new File(m_repositoryPolicyDirectoryPath + File.separator + DEFAULT);
if (!repoPolicyDir.exists()){
repoPolicyDir.mkdirs();
File source = new File(m_serverHome + File.separator + DEFAULT_REPOSITORY_POLICIES_DIRECTORY);
FileUtils.copy(source, repoPolicyDir);
}
generateBackendPolicies();
}
/**
* Always returns true, indicating that this impl supports finding policies
* based on a request.
*/
@Override
public boolean isRequestSupported() {
return true;
}
/**
* Gets a deny-biased policy set that includes all repository-wide and
* object-specific policies.
*/
@Override
public PolicyFinderResult findPolicy(EvaluationCtx context) {
PolicyFinderResult policyFinderResult = null;
PolicySet policySet = m_repositoryPolicySet;
try {
String pid = getPid(context);
if (pid != null && !pid.isEmpty()) {
AbstractPolicy objectPolicyFromObject =
m_policyLoader.loadObjectPolicy(m_policyParser.copy(),
pid,
m_validateObjectPoliciesFromDatastream);
if (objectPolicyFromObject != null) {
List<AbstractPolicy> policies = new ArrayList<AbstractPolicy>(m_repositoryPolicies);
policies.add(objectPolicyFromObject);
policySet = toPolicySet(policies, m_combiningAlgorithm);
}
}
policyFinderResult = new PolicyFinderResult(policySet);
} catch (Exception e) {
logger.warn("PolicyFinderModule seriously failed to evaluate a policy ", e);
policyFinderResult =
new PolicyFinderResult(new Status(ERROR_CODE_LIST, e
.getMessage()));
}
return policyFinderResult;
}
// get the pid from the context, or null if unable
public static String getPid(EvaluationCtx context) {
EvaluationResult attribute
= context.getResourceAttribute(STRING_ATTRIBUTE,
Constants.OBJECT.PID.attributeId,
null);
BagAttribute element = getAttributeFromEvaluationResult(attribute);
if (element == null) {
logger.debug("PolicyFinderModule:getPid exit on can't get pid on request callback");
return null;
}
if (!(element.getType().equals(STRING_ATTRIBUTE))) {
logger.debug("PolicyFinderModule:getPid exit on couldn't get pid from xacml request non-string returned");
return null;
}
return (element.size() == 1) ? (String) element.getValue() : null;
}
// copy of code in AttributeFinderModule; consider refactoring
private static final BagAttribute getAttributeFromEvaluationResult(EvaluationResult attribute) {
if (attribute.indeterminate()) {
return null;
}
if (attribute.getStatus() != null
&& !Status.STATUS_OK.equals(attribute.getStatus())) {
return null;
}
AttributeValue attributeValue = attribute.getAttributeValue();
if (!(attributeValue instanceof BagAttribute)) {
return null;
}
return (BagAttribute) attributeValue;
}
private static PolicySet toPolicySet(List<AbstractPolicy> policies, PolicyCombiningAlgorithm alg) {
return new PolicySet(EMPTY_URI,
alg,
null /*
* no general target beyond those of
* multiplexed individual policies
*/,
policies);
}
}