/* 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.utils; import java.util.List; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.xml.namespace.QName; import nl.strohalm.cyclos.entities.Entity; import nl.strohalm.cyclos.entities.access.User; import nl.strohalm.cyclos.entities.accounts.pos.Pos; import nl.strohalm.cyclos.entities.exceptions.EntityNotFoundException; import nl.strohalm.cyclos.entities.exceptions.QueryParseException; import nl.strohalm.cyclos.entities.members.Element; import nl.strohalm.cyclos.entities.services.ServiceClient; import nl.strohalm.cyclos.exceptions.PermissionDeniedException; import nl.strohalm.cyclos.services.access.exceptions.BlockedCredentialsException; import nl.strohalm.cyclos.services.access.exceptions.InvalidCredentialsException; import nl.strohalm.cyclos.utils.logging.LoggingHandler; import nl.strohalm.cyclos.utils.logging.WebServiceLogDTO; import nl.strohalm.cyclos.utils.transaction.CurrentTransactionData; import nl.strohalm.cyclos.utils.validation.ValidationException; import nl.strohalm.cyclos.webservices.WebServiceContext; import nl.strohalm.cyclos.webservices.WebServiceContext.ContextType; import nl.strohalm.cyclos.webservices.WebServiceFault; import nl.strohalm.cyclos.webservices.WebServiceFaultsEnum; import org.apache.commons.codec.binary.Base64; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.ObjectUtils; import org.apache.commons.lang.StringUtils; import org.apache.cxf.binding.soap.SoapFault; import org.apache.cxf.binding.soap.SoapMessage; import org.apache.cxf.interceptor.Fault; import org.apache.cxf.interceptor.security.AccessDeniedException; import org.apache.cxf.transport.http.AbstractHTTPDestination; /** * Contains helper methods for web services * @author luis */ public class WebServiceHelper { private static final String CODE_PREFIX = "cyclos"; /** * Returns a SOAP fault */ public static SoapFault fault(final Throwable exception) { WebServiceFault fault; if ((exception instanceof ValidationException) || (exception instanceof IllegalArgumentException)) { fault = WebServiceFaultsEnum.INVALID_PARAMETERS; } else if (exception instanceof EntityNotFoundException) { final Class<? extends Entity> entityType = ((EntityNotFoundException) exception).getEntityType(); if (entityType != null && (Element.class.isAssignableFrom(entityType) || User.class.isAssignableFrom(entityType))) { fault = WebServiceFaultsEnum.MEMBER_NOT_FOUND; } else { fault = WebServiceFaultsEnum.INVALID_PARAMETERS; } } else if (exception instanceof QueryParseException) { fault = WebServiceFaultsEnum.QUERY_PARSE_ERROR; } else if (exception instanceof InvalidCredentialsException) { fault = WebServiceFaultsEnum.INVALID_CREDENTIALS; } else if (exception instanceof BlockedCredentialsException) { fault = WebServiceFaultsEnum.BLOCKED_CREDENTIALS; } else if (exception instanceof AccessDeniedException || exception instanceof PermissionDeniedException) { fault = WebServiceFaultsEnum.UNAUTHORIZED_ACCESS; } else { fault = WebServiceFaultsEnum.UNEXPECTED_ERROR; } return fault(fault, exception); } public static SoapFault fault(final WebServiceFault fault) { return fault(fault.code(), null); } public static SoapFault fault(final WebServiceFault fault, final String serverDetailsMessage) { return fault(fault, new Exception(serverDetailsMessage)); } /** * Throw a SoapFault with the specified fault code and the specified Throwable as the cause */ public static SoapFault fault(final WebServiceFault fault, final Throwable cause) { return fault(fault.code(), cause); } /** * Extract the username and password from the Authorization HTTP header, supporting both BASIC and USER authentications, or null when not informed */ public static String[] getCredentials(final HttpServletRequest request) { final String header = request.getHeader("Authorization"); if (StringUtils.isEmpty(header)) { return null; } final String[] parts = header.split("\\s"); // There should be 2 parts separated by a blank space: the method and the username:password section if (parts.length != 2) { return null; } final String method = parts[0]; String credentials = parts[1]; if (method.equalsIgnoreCase("basic")) { credentials = new String(Base64.decodeBase64(credentials.getBytes())); } else if (!method.equalsIgnoreCase("user")) { // Unsupported method return null; } // Return the credentials parts return credentials.split(":", 2); } @SuppressWarnings({ "unchecked", "rawtypes" }) public static <T> T getParameter(final SoapMessage message) { final List parameterValues = message.getContent(List.class); if (CollectionUtils.isNotEmpty(parameterValues)) { return (T) parameterValues.iterator().next(); } else { return (T) message.getContent(Object.class); } } /** * Initialize the POS Web Service Context. */ public static void initializeContext(final Pos pos, final SoapMessage message) { WebServiceContext.set(pos, servletContextOf(message), requestOf(message), message); } /** * Initialize the Web Service Context for all WS using Services Clients. */ public static void initializeContext(final ServiceClient client, final SoapMessage message) { WebServiceContext.set(client, servletContextOf(message), requestOf(message), message); } /** * Initialize the context with a minimal information. Used when there's neither a POS or a Service client. * @param message */ public static void initializeContext(final SoapMessage message) { WebServiceContext.set(servletContextOf(message), requestOf(message), message); } /** * @return true if the specified client's id is equals to the restricted (used in this request) client's id */ public static boolean isCurrentClient(final Long clientId) { if (WebServiceContext.getContextType() != ContextType.SERVICE_CLIENT) { return false; } else { return ObjectUtils.equals(WebServiceContext.getClient().getId(), clientId); } } /** * Checks whether the given fault was generated by Cyclos */ public static boolean isFromCyclos(final Fault fault) { return CODE_PREFIX.equals(fault.getFaultCode().getNamespaceURI()); } /** * Returns the HttpServletRequest instance for the given SOAP message */ public static HttpServletRequest requestOf(final SoapMessage message) { return (HttpServletRequest) message.get(AbstractHTTPDestination.HTTP_REQUEST); } /** * Returns the HttpServletRequest instance for the given SOAP message */ public static ServletContext servletContextOf(final SoapMessage message) { return (ServletContext) message.get(AbstractHTTPDestination.HTTP_CONTEXT); } /** * Returns a SOAP fault */ private static SoapFault fault(final String code, final Throwable th) throws SoapFault { return new SoapFault("Server error: " + code, th, faultCode(code)); } /** * Returns a qualified name for a fault code */ private static QName faultCode(final String code) { return new QName(CODE_PREFIX, code); } private LoggingHandler loggingHandler; public void error(final String error) { error(new Exception(error), false); } public void error(final Throwable th) { ValidationException valExc = null; try { if (th instanceof ValidationException) { valExc = (ValidationException) th; valExc.setShowDetailMessage(true); } error(th, true); } finally { if (valExc != null) { valExc.setShowDetailMessage(false); } } } public void setLoggingHandler(final LoggingHandler loggingHandler) { this.loggingHandler = loggingHandler; } /** * Generates a log message */ public void trace(final String message) { final WebServiceLogDTO log = WebServiceContext.newLog(); log.setMessage(message); loggingHandler.traceWebService(log); } /** * @param th * @param logStackTrace if true logs the exception's stack trace. */ private void error(final Throwable th, final boolean logStackTrace) { try { /* the context could not be initialized, for example, if there was an error in the unmarshalling phase */ final WebServiceLogDTO log = WebServiceContext.newLog(); if (logStackTrace) { log.setError(th); } else { log.setErrorMessage(th.getMessage()); } loggingHandler.traceWebService(log); } finally { // in case of a Fault we are interested in the cause to be set as the error in the TxData if (th instanceof Fault && th.getCause() != null) { CurrentTransactionData.setError(th.getCause()); } else { CurrentTransactionData.setError(th); } } } }