/* 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.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.servlet.ServletContext;
import com.sun.xacml.PDP;
import com.sun.xacml.PDPConfig;
import com.sun.xacml.attr.StringAttribute;
import com.sun.xacml.ctx.Attribute;
import com.sun.xacml.ctx.RequestCtx;
import com.sun.xacml.ctx.ResponseCtx;
import com.sun.xacml.ctx.Result;
import com.sun.xacml.ctx.Subject;
import com.sun.xacml.finder.AttributeFinder;
import com.sun.xacml.finder.PolicyFinder;
import org.apache.log4j.Logger;
import fedora.common.Constants;
import fedora.server.Context;
import fedora.server.errors.authorization.AuthzDeniedException;
import fedora.server.errors.authorization.AuthzException;
import fedora.server.errors.authorization.AuthzOperationalException;
import fedora.server.errors.authorization.AuthzPermittedException;
import fedora.server.storage.DOManager;
import fedora.utilities.Log4JRedirectFilter;
/**
* @author Bill Niebel
*/
public class PolicyEnforcementPoint {
/** Logger for this class. */
private static final Logger LOG =
Logger.getLogger(PolicyEnforcementPoint.class.getName());
public static final String SUBACTION_SEPARATOR = "//";
public static final String SUBRESOURCE_SEPARATOR = "//";
private static PolicyEnforcementPoint singleton = null;
private static int count = 0;
private String enforceMode = ENFORCE_MODE_ENFORCE_POLICIES;
static final String ENFORCE_MODE_ENFORCE_POLICIES = "enforce-policies";
static final String ENFORCE_MODE_PERMIT_ALL_REQUESTS =
"permit-all-requests";
static final String ENFORCE_MODE_DENY_ALL_REQUESTS = "deny-all-requests";
public static final String XACML_SUBJECT_ID =
"urn:oasis:names:tc:xacml:1.0:subject:subject-id";
public static final String XACML_ACTION_ID =
"urn:oasis:names:tc:xacml:1.0:action:action-id";
public static final String XACML_RESOURCE_ID =
"urn:oasis:names:tc:xacml:1.0:resource:resource-id";
private final URI XACML_SUBJECT_ID_URI;
private final URI XACML_ACTION_ID_URI;
private final URI XACML_RESOURCE_ID_URI;
private final URI SUBJECT_ID_URI;
private final URI ACTION_ID_URI;
private final URI ACTION_API_URI;
private final URI ACTION_CONTEXT_URI;
private final URI RESOURCE_ID_URI;
private final URI RESOURCE_NAMESPACE_URI;
static {
// force com.sun.xacml logging to use Log4J
Log4JRedirectFilter.apply("com.sun.xacml.finder.AttributeFinder");
}
private PolicyEnforcementPoint() {
URI xacmlSubjectIdUri = null;
URI xacmlActionIdUri = null;
URI xacmlResourceIdUri = null;
URI subjectIdUri = null;
URI actionIdUri = null;
URI actionApiUri = null;
URI contextUri = null;
URI pidUri = null;
URI namespaceUri = null;
try {
xacmlSubjectIdUri = new URI(XACML_SUBJECT_ID);
xacmlActionIdUri = new URI(XACML_ACTION_ID);
xacmlResourceIdUri = new URI(XACML_RESOURCE_ID);
subjectIdUri = new URI(Constants.SUBJECT.LOGIN_ID.uri);
actionIdUri = new URI(Constants.ACTION.ID.uri);
actionApiUri = new URI(Constants.ACTION.API.uri);
contextUri = new URI(Constants.ACTION.CONTEXT_ID.uri);
pidUri = new URI(Constants.OBJECT.PID.uri);
namespaceUri = new URI(Constants.OBJECT.NAMESPACE.uri);
} catch (URISyntaxException e) {
LOG.fatal("Bad URI syntax", e);
} finally {
XACML_SUBJECT_ID_URI = xacmlSubjectIdUri;
XACML_ACTION_ID_URI = xacmlActionIdUri;
XACML_RESOURCE_ID_URI = xacmlResourceIdUri;
SUBJECT_ID_URI = subjectIdUri;
ACTION_ID_URI = actionIdUri;
ACTION_API_URI = actionApiUri;
ACTION_CONTEXT_URI = contextUri;
RESOURCE_ID_URI = pidUri;
RESOURCE_NAMESPACE_URI = namespaceUri;
}
}
public static final PolicyEnforcementPoint getInstance() {
if (singleton == null) {
singleton = new PolicyEnforcementPoint();
}
count++;
LOG.debug("***another use (" + count + ") of XACMLPep singleton");
return singleton;
}
/**
* xacml pdp
*/
private PDP pdp = null;
/**
* available during init(); keep as logging hook
*/
private ServletContext servletContext = null;
private ContextAttributeFinderModule contextAttributeFinder;
public final void newPdp() throws Exception {
AttributeFinder attrFinder = new AttributeFinder();
List<AttributeFinderModule> attrModules =
new ArrayList<AttributeFinderModule>();
ResourceAttributeFinderModule resourceAttributeFinder =
ResourceAttributeFinderModule.getInstance();
resourceAttributeFinder.setServletContext(servletContext);
resourceAttributeFinder.setDOManager(manager);
resourceAttributeFinder.setOwnerIdSeparator(ownerIdSeparator);
attrModules.add(resourceAttributeFinder);
try {
LOG.debug("about to set contextAttributeFinder in original");
contextAttributeFinder = ContextAttributeFinderModule.getInstance();
} catch (Throwable t) {
enforceMode = ENFORCE_MODE_DENY_ALL_REQUESTS;
LOG.error("Error in newPdp", t);
if (t instanceof Exception) {
throw (Exception) t;
}
throw new Exception("wrapped", t);
}
LOG.debug("just set contextAttributeFinder=" + contextAttributeFinder);
contextAttributeFinder.setServletContext(servletContext);
attrModules.add(contextAttributeFinder);
attrFinder.setModules(attrModules);
LOG.debug("before building policy finder");
PolicyFinder policyFinder = new PolicyFinder();
Set<PolicyFinderModule> policyModules =
new HashSet<PolicyFinderModule>();
PolicyFinderModule combinedPolicyModule = null;
combinedPolicyModule =
new PolicyFinderModule(combiningAlgorithm,
globalPolicyConfig,
globalBackendPolicyConfig,
globalPolicyGuiToolConfig,
manager,
validateRepositoryPolicies,
validateObjectPoliciesFromDatastream,
policyParser);
LOG.debug("after constucting fedora policy finder module");
LOG
.debug("before adding fedora policy finder module to policy finder hashset");
policyModules.add(combinedPolicyModule);
LOG
.debug("after adding fedora policy finder module to policy finder hashset");
LOG.debug("o before setting policy finder hashset into policy finder");
policyFinder.setModules(policyModules);
LOG.debug("o after setting policy finder hashset into policy finder");
PDP pdp = new PDP(new PDPConfig(attrFinder, policyFinder, null));
synchronized (this) {
this.pdp = pdp;
//so enforce() will wait, if this pdp update is in progress
}
}
String combiningAlgorithm = null;
String globalPolicyConfig = null;
String globalBackendPolicyConfig = null;
String globalPolicyGuiToolConfig = null;
DOManager manager = null;
boolean validateRepositoryPolicies = false;
boolean validateObjectPoliciesFromDatastream = false;
PolicyParser policyParser;
String ownerIdSeparator = ",";
public void initPep(String enforceMode,
String combiningAlgorithm,
String globalPolicyConfig,
String globalBackendPolicyConfig,
String globalPolicyGuiToolConfig,
DOManager manager,
boolean validateRepositoryPolicies,
boolean validateObjectPoliciesFromDatastream,
PolicyParser policyParser,
String ownerIdSeparator) throws Exception {
LOG.debug("in initPep()");
destroy();
this.policyParser = policyParser;
this.enforceMode = enforceMode;
if (ENFORCE_MODE_ENFORCE_POLICIES.equals(enforceMode)) {
} else if (ENFORCE_MODE_PERMIT_ALL_REQUESTS.equals(enforceMode)) {
} else if (ENFORCE_MODE_DENY_ALL_REQUESTS.equals(enforceMode)) {
} else {
throw new AuthzOperationalException("invalid enforceMode from config");
}
this.combiningAlgorithm = combiningAlgorithm;
this.globalPolicyConfig = globalPolicyConfig;
this.globalBackendPolicyConfig = globalBackendPolicyConfig;
this.globalPolicyGuiToolConfig = globalPolicyGuiToolConfig;
this.manager = manager;
this.validateRepositoryPolicies = validateRepositoryPolicies;
this.validateObjectPoliciesFromDatastream =
validateObjectPoliciesFromDatastream;
this.ownerIdSeparator = ownerIdSeparator;
newPdp();
}
public void inactivate() {
destroy();
}
public void destroy() {
servletContext = null;
pdp = null;
}
private final Set wrapSubjects(String subjectLoginId) {
LOG.debug("wrapSubjectIdAsSubjects(): " + subjectLoginId);
StringAttribute stringAttribute = new StringAttribute("");
Attribute subjectAttribute =
new Attribute(XACML_SUBJECT_ID_URI, null, null, stringAttribute);
LOG.debug("wrapSubjectIdAsSubjects(): subjectAttribute, id="
+ subjectAttribute.getId() + ", type="
+ subjectAttribute.getType() + ", value="
+ subjectAttribute.getValue());
Set<Attribute> subjectAttributes = new HashSet<Attribute>();
subjectAttributes.add(subjectAttribute);
if (subjectLoginId != null && !"".equals(subjectLoginId)) {
stringAttribute = new StringAttribute(subjectLoginId);
subjectAttribute =
new Attribute(SUBJECT_ID_URI, null, null, stringAttribute);
LOG.debug("wrapSubjectIdAsSubjects(): subjectAttribute, id="
+ subjectAttribute.getId() + ", type="
+ subjectAttribute.getType() + ", value="
+ subjectAttribute.getValue());
}
subjectAttributes.add(subjectAttribute);
Subject singleSubject = new Subject(subjectAttributes);
Set<Subject> subjects = new HashSet<Subject>();
subjects.add(singleSubject);
return subjects;
}
private final Set wrapActions(String actionId,
String actionApi,
String contextIndex) {
Set<Attribute> actions = new HashSet<Attribute>();
Attribute action =
new Attribute(XACML_ACTION_ID_URI,
null,
null,
new StringAttribute(""));
actions.add(action);
action =
new Attribute(ACTION_ID_URI,
null,
null,
new StringAttribute(actionId));
actions.add(action);
action =
new Attribute(ACTION_API_URI,
null,
null,
new StringAttribute(actionApi));
actions.add(action);
action =
new Attribute(ACTION_CONTEXT_URI,
null,
null,
new StringAttribute(contextIndex));
actions.add(action);
return actions;
}
private final Set wrapResources(String pid, String namespace)
throws AuthzOperationalException {
Set<Attribute> resources = new HashSet<Attribute>();
Attribute attribute = null;
attribute =
new Attribute(XACML_RESOURCE_ID_URI,
null,
null,
new StringAttribute(""));
resources.add(attribute);
attribute =
new Attribute(RESOURCE_ID_URI,
null,
null,
new StringAttribute(pid));
resources.add(attribute);
attribute =
new Attribute(RESOURCE_NAMESPACE_URI,
null,
null,
new StringAttribute(namespace));
resources.add(attribute);
return resources;
}
private int n = 0;
private synchronized int next() {
return n++;
}
private final Set NULL_SET = new HashSet();
public final void enforce(String subjectId,
String action,
String api,
String pid,
String namespace,
Context context) throws AuthzException {
long enforceStartTime = System.currentTimeMillis();
try {
synchronized (this) {
//wait, if pdp update is in progress
}
if (ENFORCE_MODE_PERMIT_ALL_REQUESTS.equals(enforceMode)) {
LOG
.debug("permitting request because enforceMode==ENFORCE_MODE_PERMIT_ALL_REQUESTS");
} else if (ENFORCE_MODE_DENY_ALL_REQUESTS.equals(enforceMode)) {
LOG
.debug("denying request because enforceMode==ENFORCE_MODE_DENY_ALL_REQUESTS");
throw new AuthzDeniedException("all requests are currently denied");
} else if (!ENFORCE_MODE_ENFORCE_POLICIES.equals(enforceMode)) {
LOG.debug("denying request because enforceMode is invalid");
throw new AuthzOperationalException("invalid enforceMode from config");
} else {
ResponseCtx response = null;
String contextIndex = null;
try {
contextIndex = (new Integer(next())).toString();
LOG.debug("context index set=" + contextIndex);
Set subjects = wrapSubjects(subjectId);
Set actions = wrapActions(action, api, contextIndex);
Set resources = wrapResources(pid, namespace);
RequestCtx request =
new RequestCtx(subjects,
resources,
actions,
NULL_SET);
Set tempset = request.getAction();
Iterator tempit = tempset.iterator();
while (tempit.hasNext()) {
Attribute tempobj = (Attribute) tempit.next();
LOG.debug("request action has " + tempobj.getId() + "="
+ tempobj.getValue().toString());
}
LOG.debug("about to ref contextAttributeFinder="
+ contextAttributeFinder);
contextAttributeFinder.registerContext(contextIndex,
context);
long st = System.currentTimeMillis();
try {
response = pdp.evaluate(request);
} finally {
long dur = System.currentTimeMillis() - st;
LOG.debug("Policy evaluation took " + dur + "ms.");
}
LOG.debug("in pep, after evaluate() called");
} catch (Throwable t) {
LOG.error("Error evaluating policy", t);
throw new AuthzOperationalException("");
} finally {
contextAttributeFinder.unregisterContext(contextIndex);
}
LOG.debug("in pep, before denyBiasedAuthz() called");
if (!denyBiasedAuthz(response.getResults())) {
throw new AuthzDeniedException("");
}
}
if (context.getNoOp()) {
throw new AuthzPermittedException("noOp");
}
} finally {
long dur = System.currentTimeMillis() - enforceStartTime;
LOG.debug("Policy enforcement took " + dur + "ms.");
}
}
private static final boolean denyBiasedAuthz(Set set) {
int nPermits = 0; //explicit permit returned
int nDenies = 0; //explicit deny returned
int nNotApplicables = 0; //no targets matched
int nIndeterminates = 0; //for targets matched, no rules matched
int nWrongs = 0; //none of the above, i.e., unreported failure, should not happen
Iterator it = set.iterator();
while (it.hasNext()) {
Result result = (Result) it.next();
int decision = result.getDecision();
switch (decision) {
case Result.DECISION_PERMIT:
nPermits++;
break;
case Result.DECISION_DENY:
nDenies++;
break;
case Result.DECISION_INDETERMINATE:
nIndeterminates++;
break;
case Result.DECISION_NOT_APPLICABLE:
nNotApplicables++;
break;
default:
nWrongs++;
break;
}
}
LOG.debug("AUTHZ: permits=" + nPermits + " denies=" + nDenies
+ " indeterminates=" + nIndeterminates + " notApplicables="
+ nNotApplicables + " unexpecteds=" + nWrongs);
return nPermits >= 1 && nDenies == 0 && nIndeterminates == 0
&& nWrongs == 0; // don't care about NotApplicables
}
}