/*
* File: PEP.java
*
* Copyright 2007 Macquarie E-Learning Centre Of Excellence
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package melcoe.fedora.pep.ws;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import melcoe.fedora.pep.AuthzDeniedException;
import melcoe.fedora.pep.ContextHandler;
import melcoe.fedora.pep.ContextHandlerImpl;
import melcoe.fedora.pep.PEPException;
import melcoe.fedora.pep.ws.operations.OperationHandler;
import melcoe.fedora.pep.ws.operations.OperationHandlerException;
import org.apache.axis.AxisFault;
import org.apache.axis.MessageContext;
import org.apache.axis.description.OperationDesc;
import org.apache.axis.description.ServiceDesc;
import org.apache.axis.handlers.BasicHandler;
import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import com.sun.xacml.ctx.RequestCtx;
import com.sun.xacml.ctx.ResponseCtx;
import com.sun.xacml.ctx.Result;
import fedora.common.Constants;
/**
* This class is an Apache Axis handler. It is used as a handler on both the
* request and response. The handler examines the operation for the request and
* retrieves an appropriate handler to manage the request.
*
* @author nishen@melcoe.mq.edu.au
*/
public class PEP
extends BasicHandler {
private static final long serialVersionUID = -3435060948149239989L;
private static Logger log = Logger.getLogger(PEP.class.getName());
/**
* A list of instantiated handlers. As operations are invoked, handlers for
* those operations are created and added to this list
*/
private Map<String, Map<String, OperationHandler>> serviceHandlers = null;
/**
* The XACML context handler.
*/
ContextHandler ctxHandler = null;
/**
* A time stamp to note the time this AuthHandler was instantiated.
*/
private Date ts = null;
/**
* Default constructor that initialises the handlers map and the
* contextHandler.
*
* @throws PEPException
*/
public PEP()
throws PEPException {
super();
loadHandlers();
ctxHandler = ContextHandlerImpl.getInstance();
ts = new Date();
}
/*
* (non-Javadoc)
* @see org.apache.axis.Handler#invoke(org.apache.axis.MessageContext)
*/
public void invoke(MessageContext context) throws AxisFault {
if (log.isDebugEnabled()) {
log.debug("AuthHandler executed: " + context.getTargetService()
+ "/" + context.getOperation().getName() + " [" + ts + "]");
}
// Obtain the service details
ServiceDesc service = context.getService().getServiceDescription();
// Obtain the operation details and message type
OperationDesc operation = context.getOperation();
// Obtain a class to handle our request
OperationHandler operationHandler =
getHandler(service.getName(), operation.getName());
// If we found no handler just exit
if (operationHandler == null) {
return;
}
RequestCtx reqCtx = null;
// if we are on the request pathway, getPastPivot() == false. True on
// response pathway
try {
if (context.getPastPivot()) {
reqCtx = operationHandler.handleResponse(context);
} else {
reqCtx = operationHandler.handleRequest(context);
}
} catch (OperationHandlerException ohe) {
log.error("Error handling operation: " + operation.getName(), ohe);
throw AxisFault
.makeFault(new PEPException("Error handling operation: "
+ operation.getName(), ohe));
}
// if handler returns null, then there is no work to do (would have
// thrown exception if things went wrong).
if (reqCtx == null) {
return;
}
// if we have received a requestContext, we need to hand it over to the
// context handler for resolution.
ResponseCtx resCtx = null;
try {
resCtx = ctxHandler.evaluate(reqCtx);
} catch (PEPException pe) {
log.error("Error evaluating request", pe);
throw AxisFault
.makeFault(new PEPException("Error evaluating request (operation: "
+ operation.getName()
+ ")",
pe));
}
// TODO: set obligations
/*
* Need to set obligations in some sort of map, with UserID/SessionID +
* list of obligationIDs. Enforce will have to check that these
* obligations are met before providing access. There will need to be an
* external obligations service that this PEP can communicate with. Will
* be working on that... This service will throw an 'Obligations need to
* be met' exception for outstanding obligations
*/
// TODO: enforce will need to ensure that obligations are met.
enforce(resCtx);
}
/**
* Reads the configuration file and loads up all the handlers listed within.
* This method creates handlers for each of the services listed.
*
* @throws PEPException
*/
private void loadHandlers() throws PEPException {
serviceHandlers = new HashMap<String, Map<String, OperationHandler>>();
try {
// get the PEP configuration
File configPEPFile =
new File(Constants.FEDORA_HOME,
"server/config/config-melcoe-pep.xml");
InputStream is = new FileInputStream(configPEPFile);
if (is == null) {
throw new PEPException("Could not locate config file: config-melcoe-pep.xml");
}
DocumentBuilderFactory factory =
DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = factory.newDocumentBuilder();
Document doc = docBuilder.parse(is);
// to avoid having the same class instantiated multiple times for multiple services.
Map<String, OperationHandler> handlerMap =
new HashMap<String, OperationHandler>();
NodeList nodes = doc.getElementsByTagName("handlers-ws");
for (int x = 0; x < nodes.getLength(); x++) {
String service =
nodes.item(x).getAttributes().getNamedItem("service")
.getNodeValue();
if (service == null || "".equals(service)) {
throw new PEPException("Error in config file: service name missing.");
}
Map<String, OperationHandler> handlers =
serviceHandlers.get(service);
if (handlers == null) {
handlers = new HashMap<String, OperationHandler>();
serviceHandlers.put(service, handlers);
}
NodeList handlerNodes = nodes.item(x).getChildNodes();
for (int y = 0; y < handlerNodes.getLength(); y++) {
if (handlerNodes.item(y).getNodeType() == Node.ELEMENT_NODE) {
String opn =
handlerNodes.item(y).getAttributes()
.getNamedItem("operation")
.getNodeValue();
String cls =
handlerNodes.item(y).getAttributes()
.getNamedItem("class").getNodeValue();
if (opn == null || "".equals(opn)) {
throw new PEPException("Cannot have a missing or empty operation attribute");
}
if (cls == null || "".equals(cls)) {
throw new PEPException("Cannot have a missing or empty class attribute");
}
OperationHandler handler = handlerMap.get(cls);
if (handler == null) {
try {
Class<?> handlerClass = Class.forName(cls);
handler =
(OperationHandler) handlerClass
.newInstance();
handlerMap.put(cls, handler);
} catch (ClassNotFoundException e) {
log.debug("handlerClass not found: " + cls);
} catch (InstantiationException ie) {
log.error("Could not instantiate handler: "
+ cls);
throw new PEPException(ie);
} catch (IllegalAccessException iae) {
log.error("Could not instantiate handler: "
+ cls);
throw new PEPException(iae);
}
}
handlers.put(opn, handler);
if (log.isDebugEnabled()) {
log.debug("handler added to handler map: "
+ service + "/" + opn + "/" + cls);
}
}
}
}
} catch (Exception e) {
log.fatal("Failed to initialse the PEP for WS");
log.fatal(e.getMessage(), e);
throw new PEPException(e.getMessage(), e);
}
}
/**
* Function to try and obtain a handler using the name of the current SOAP
* service and operation.
*
* @param opName
* the name of the operation
* @return OperationHandler to handle the operation
* @throws AxisFault
*/
private OperationHandler getHandler(String serviceName, String operationName) {
if (serviceName == null) {
if (log.isDebugEnabled()) {
log.debug("Service Name was null!");
}
return null;
}
if (operationName == null) {
if (log.isDebugEnabled()) {
log.debug("Operation Name was null!");
}
return null;
}
Map<String, OperationHandler> handlers =
serviceHandlers.get(serviceName);
if (handlers == null) {
if (log.isDebugEnabled()) {
log.debug("No Service Handlers found for: " + serviceName);
}
return null;
}
OperationHandler handler = handlers.get(operationName);
if (handler == null) {
if (log.isDebugEnabled()) {
log.debug("Handler not found for: " + serviceName + "/"
+ operationName);
}
}
return handler;
}
/**
* Method to check a response and enforce any denial. This is achieved by
* throwing an AxisFault.
*
* @param res
* the ResponseCtx
* @throws AxisFault
*/
private void enforce(ResponseCtx res) throws AxisFault {
@SuppressWarnings("unchecked")
Set<Result> results = res.getResults();
for (Result r : results) {
if (r.getDecision() != Result.DECISION_PERMIT) {
if (log.isDebugEnabled()) {
log.debug("Denying access: " + r.getDecision());
}
switch (r.getDecision()) {
case Result.DECISION_DENY:
throw AxisFault
.makeFault(new AuthzDeniedException("Deny"));
case Result.DECISION_INDETERMINATE:
throw AxisFault
.makeFault(new AuthzDeniedException("Indeterminate"));
case Result.DECISION_NOT_APPLICABLE:
throw AxisFault
.makeFault(new AuthzDeniedException("NotApplicable"));
default:
}
}
}
if (log.isDebugEnabled()) {
log.debug("Permitting access!");
}
}
}