/* 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 fedora.server.security;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import com.sun.xacml.AbstractPolicy;
import com.sun.xacml.EvaluationCtx;
import com.sun.xacml.PolicySet;
import com.sun.xacml.attr.AttributeValue;
import com.sun.xacml.attr.BagAttribute;
import com.sun.xacml.attr.StringAttribute;
import com.sun.xacml.combine.PolicyCombiningAlgorithm;
import com.sun.xacml.cond.EvaluationResult;
import com.sun.xacml.ctx.Status;
import com.sun.xacml.finder.PolicyFinder;
import com.sun.xacml.finder.PolicyFinderResult;
import org.apache.log4j.Logger;
import fedora.common.Constants;
import fedora.common.FaultException;
import fedora.server.ReadOnlyContext;
import fedora.server.Server;
import fedora.server.errors.GeneralException;
import fedora.server.errors.ObjectNotInLowlevelStorageException;
import fedora.server.errors.ServerException;
import fedora.server.errors.ValidationException;
import fedora.server.storage.DOReader;
import fedora.server.storage.RepositoryReader;
import fedora.server.storage.types.Datastream;
/**
* XACML PolicyFinder for Fedora.
* <p>
* This provides repository-wide policies and object-specific policies,
* when available.
*/
public class PolicyFinderModule
extends com.sun.xacml.finder.PolicyFinderModule {
/** Logger for this class. */
private static final Logger LOG =
Logger.getLogger(PolicyFinderModule.class.getName());
private static final List<String> ERROR_CODE_LIST = new ArrayList<String>(1);
static {
ERROR_CODE_LIST.add(Status.STATUS_PROCESSING_ERROR);
}
private final String m_combiningAlgorithm;
private final RepositoryReader m_repoReader;
private final boolean m_validateRepositoryPolicies;
private final boolean m_validateObjectPoliciesFromDatastream;
private final PolicyParser m_policyParser;
private final List<AbstractPolicy> m_repositoryPolicies;
public PolicyFinderModule(String combiningAlgorithm,
String repositoryPolicyDirectoryPath,
String repositoryBackendPolicyDirectoryPath,
String repositoryPolicyGuiToolDirectoryPath,
RepositoryReader repoReader,
boolean validateRepositoryPolicies,
boolean validateObjectPoliciesFromDatastream,
PolicyParser policyParser)
throws GeneralException {
m_combiningAlgorithm = combiningAlgorithm;
m_repoReader = repoReader;
m_validateRepositoryPolicies = validateRepositoryPolicies;
m_validateObjectPoliciesFromDatastream = validateObjectPoliciesFromDatastream;
m_policyParser = policyParser;
LOG.info("Loading repository policies...");
m_repositoryPolicies = new ArrayList<AbstractPolicy>();
try {
m_repositoryPolicies.addAll(
loadPolicies(m_policyParser,
m_validateRepositoryPolicies,
new File(repositoryPolicyDirectoryPath)));
m_repositoryPolicies.addAll(
loadPolicies(m_policyParser,
m_validateRepositoryPolicies,
new File(repositoryBackendPolicyDirectoryPath)));
} catch (Exception e) {
throw new GeneralException("Error loading repository policies", e);
}
}
/**
* Does nothing at init time.
*/
@Override
public void init(PolicyFinder finder) {
}
/**
* 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;
try {
List<AbstractPolicy> policies = new ArrayList<AbstractPolicy>(m_repositoryPolicies);
String pid = getPid(context);
if (pid != null && !"".equals(pid)) {
AbstractPolicy objectPolicyFromObject = loadObjectPolicy(pid);
if (objectPolicyFromObject != null) {
policies.add(objectPolicyFromObject);
}
}
PolicyCombiningAlgorithm policyCombiningAlgorithm =
(PolicyCombiningAlgorithm) Class
.forName(m_combiningAlgorithm).newInstance();
PolicySet policySet =
new PolicySet(new URI(""),
policyCombiningAlgorithm,
null /*
* no general target beyond those of
* multiplexed individual policies
*/,
policies);
policyFinderResult = new PolicyFinderResult(policySet);
} catch (Exception e) {
policyFinderResult =
new PolicyFinderResult(new Status(ERROR_CODE_LIST, e
.getMessage()));
}
return policyFinderResult;
}
// if the object exists and has a POLICY datastream, parse and return it
private AbstractPolicy loadObjectPolicy(String pid) throws ServerException {
try {
DOReader reader = m_repoReader.getReader(Server.USE_DEFINITIVE_STORE,
ReadOnlyContext.EMPTY,
pid);
Datastream ds = reader.GetDatastream("POLICY", null);
if (ds != null) {
LOG.debug("Using POLICY for " + pid);
return m_policyParser
.copy().parse(ds.getContentStream(),
m_validateObjectPoliciesFromDatastream);
} else {
return null;
}
} catch (ObjectNotInLowlevelStorageException e) {
return null;
}
}
// get the pid from the context, or null if unable
private static String getPid(EvaluationCtx context) {
URI resourceIdType = null;
URI resourceIdId = null;
try {
resourceIdType = new URI(StringAttribute.identifier);
resourceIdId = new URI(Constants.OBJECT.PID.uri);
} catch (URISyntaxException e) {
throw new FaultException("Bad URI syntax", e);
}
EvaluationResult attribute
= context.getResourceAttribute(resourceIdType,
resourceIdId,
null);
Object element = getAttributeFromEvaluationResult(attribute);
if (element == null) {
LOG.debug("PolicyFinderModule:getPid exit on "
+ "can't get contextId on request callback");
return null;
}
if (!(element instanceof StringAttribute)) {
LOG.debug("PolicyFinderModule:getPid exit on "
+ "couldn't get contextId from xacml request "
+ "non-string returned");
return null;
}
return ((StringAttribute) element).getValue();
}
// copy of code in AttributeFinderModule; consider refactoring
private static final Object 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;
}
BagAttribute bag = (BagAttribute) attributeValue;
if (1 != bag.size()) {
return null;
} else {
return bag.iterator().next();
}
}
// load and parse all policies (*.xml) from a given directory, recursively
private static List<AbstractPolicy> loadPolicies(PolicyParser parser,
boolean validate,
File dir)
throws IOException, ValidationException {
List<AbstractPolicy> policies = new ArrayList<AbstractPolicy>();
for (File file: dir.listFiles()) {
if (file.isDirectory()) {
policies.addAll(loadPolicies(parser, validate, file));
} else {
if (file.getName().endsWith(".xml")) {
LOG.info("Loading policy: " + file.getPath());
InputStream policyStream = new FileInputStream(file);
policies.add(parser.parse(policyStream, validate));
}
}
}
return policies;
}
}