/*
This file is part of Cyclos (www.cyclos.org).
A project of the Social Trade Organisation (www.socialtrade.org).
Cyclos is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
Cyclos 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with Cyclos; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package nl.strohalm.cyclos.webservices.interceptor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.xml.namespace.QName;
import nl.strohalm.cyclos.entities.exceptions.EntityNotFoundException;
import nl.strohalm.cyclos.entities.services.ServiceClient;
import nl.strohalm.cyclos.entities.services.ServiceOperation;
import nl.strohalm.cyclos.services.application.ApplicationServiceLocal;
import nl.strohalm.cyclos.services.services.ServiceClientServiceLocal;
import nl.strohalm.cyclos.utils.access.LoggedUser;
import nl.strohalm.cyclos.webservices.CyclosWebServicesClientFactory;
import nl.strohalm.cyclos.webservices.Permission;
import nl.strohalm.cyclos.webservices.WebServiceContext;
import nl.strohalm.cyclos.webservices.WebServiceContext.ContextType;
import nl.strohalm.cyclos.webservices.WebServiceFaultsEnum;
import nl.strohalm.cyclos.webservices.utils.WebServiceHelper;
import org.apache.commons.lang.StringUtils;
import org.apache.cxf.binding.soap.SoapFault;
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.binding.soap.interceptor.AbstractSoapInterceptor;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.phase.Phase;
import org.apache.cxf.service.model.MessageInfo;
import org.apache.cxf.service.model.OperationInfo;
import org.apache.cxf.transport.http.AbstractHTTPDestination;
/**
* A CXF interceptor that will process authentication & authorization for web services
*
* @author luis
*/
public class AuthInterceptor extends AbstractSoapInterceptor {
private static final String[] BLANK_CREDENTIALS = { "", "" };
private ServiceClientServiceLocal serviceClientServiceLocal;
private ApplicationServiceLocal applicationServiceLocal;
private final Map<QName, ServiceOperation[]> cachedOperations = new HashMap<QName, ServiceOperation[]>();
public AuthInterceptor() {
super(Phase.PRE_INVOKE);
}
@Override
public void handleMessage(final SoapMessage message) throws Fault {
final HttpServletRequest request = WebServiceHelper.requestOf(message);
request.setAttribute(ContextType.class.getName(), ContextType.SERVICE_CLIENT);
ServiceClient client = null;
final ServletContext servletContext = servletContextOf(message);
try {
if (!applicationServiceLocal.isOnline()) {
throw WebServiceHelper.fault(WebServiceFaultsEnum.APPLICATION_OFFLINE);
}
// Check non-secure access when HTTP is enabled
if (Boolean.TRUE.equals(servletContext.getAttribute("cyclos.httpEnabled"))) {
final String protocol = StringUtils.split(request.getRequestURL().toString(), "://")[0];
if (!"https".equalsIgnoreCase(protocol)) {
throw WebServiceHelper.fault(WebServiceFaultsEnum.SECURE_ACCESS_REQUIRED);
}
}
boolean allowed = false;
// Find the service client
client = resolveClient(request);
if (client != null) {
// Find the requested operation
final ServiceOperation[] operations = resolveOperations(message);
if (operations.length == 0) {
// When there are no operations, access is granted to anyone
allowed = true;
} else {
// Check whether the client has access to the requested operation
final Set<ServiceOperation> permissions = client.getPermissions();
for (final ServiceOperation serviceOperation : operations) {
if (permissions.contains(serviceOperation)) {
allowed = true;
break;
}
}
}
}
if (!allowed) {
throw WebServiceHelper.fault(WebServiceFaultsEnum.UNAUTHORIZED_ACCESS);
}
// Initialize the logged user
LoggedUser.init(client, request.getRemoteAddr(), null);
// Initialize the context
WebServiceContext.set(client, servletContext, request, message);
} catch (Exception e) {
WebServiceHelper.initializeContext(message);
if (e instanceof SoapFault) {
throw (SoapFault) e;
} else {
throw WebServiceHelper.fault(e);
}
}
}
public void setApplicationServiceLocal(final ApplicationServiceLocal applicationService) {
applicationServiceLocal = applicationService;
}
public void setServiceClientServiceLocal(final ServiceClientServiceLocal serviceClientService) {
serviceClientServiceLocal = serviceClientService;
}
/**
* Find a matching {@link ServiceClient} for the given request
*/
private ServiceClient resolveClient(final HttpServletRequest request) {
final String address = request.getRemoteAddr();
String[] credentials = WebServiceHelper.getCredentials(request);
if (credentials == null) {
credentials = BLANK_CREDENTIALS;
}
try {
return serviceClientServiceLocal.findByAddressAndCredentials(address, credentials[0], credentials[1]);
} catch (final EntityNotFoundException e) {
return null;
}
}
/**
* Resolve the possible operations for the current request
*/
private ServiceOperation[] resolveOperations(final SoapMessage message) {
final MessageInfo messageInfo = message.get(MessageInfo.class);
final OperationInfo operation = messageInfo.getOperation();
final QName operationQName = operation.getName();
// Try to find the operations in the cache
ServiceOperation[] operations = cachedOperations.get(operationQName);
if (operations == null) {
// Cache miss... find the interface method
final String operationName = operationQName.getLocalPart();
final String serviceName = operation.getInterface().getService().getName().getLocalPart();
final Class<?> serviceInterface = CyclosWebServicesClientFactory.serviceInterfaceForName(serviceName);
for (final Method m : serviceInterface.getMethods()) {
if (m.getName().equals(operationName)) {
final Permission permission = m.getAnnotation(Permission.class);
operations = permission == null ? new ServiceOperation[0] : permission.value();
break;
}
}
// Store the operations on the cache for further access
cachedOperations.put(operationQName, operations);
}
return operations;
}
/**
* Returns the SessionContext instance for the given SOAP message
*/
private ServletContext servletContextOf(final SoapMessage message) {
return (ServletContext) message.get(AbstractHTTPDestination.HTTP_CONTEXT);
}
}