/** * Copyright (c) Codice Foundation * <p/> * This is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser * General Public License as published by the Free Software Foundation, either version 3 of the * License, or any later version. * <p/> * This program 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 * Lesser General Public License for more details. A copy of the GNU Lesser General Public License * is distributed along with this program and can be found at * <http://www.gnu.org/licenses/lgpl.html>. */ package ddf.security.pep.interceptor; import javax.xml.namespace.QName; import javax.xml.ws.handler.MessageContext; import org.apache.commons.lang.StringUtils; import org.apache.cxf.binding.soap.model.SoapOperationInfo; import org.apache.cxf.interceptor.Fault; import org.apache.cxf.interceptor.security.AccessDeniedException; import org.apache.cxf.message.Message; import org.apache.cxf.phase.AbstractPhaseInterceptor; import org.apache.cxf.phase.Phase; import org.apache.cxf.service.model.BindingOperationInfo; import org.apache.cxf.service.model.MessageInfo; import org.apache.cxf.ws.addressing.JAXWSAConstants; import org.apache.cxf.ws.addressing.Names; import org.apache.shiro.util.ThreadContext; import org.codice.ddf.platform.util.XMLUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Element; import ddf.security.SecurityConstants; import ddf.security.Subject; import ddf.security.assertion.SecurityAssertion; import ddf.security.common.audit.SecurityLogger; import ddf.security.permission.CollectionPermission; import ddf.security.permission.KeyValueCollectionPermission; import ddf.security.service.SecurityManager; import ddf.security.service.SecurityServiceException; import ddf.security.service.impl.SecurityAssertionStore; /** * Interceptor used to perform service authentication. */ public class PEPAuthorizingInterceptor extends AbstractPhaseInterceptor<Message> { private static final Logger LOGGER = LoggerFactory.getLogger(PEPAuthorizingInterceptor.class); private SecurityManager securityManager; public PEPAuthorizingInterceptor() { super(Phase.PRE_INVOKE); addAfter(org.apache.cxf.ws.policy.PolicyVerificationInInterceptor.class.getName()); } /** * Sets the security manager that will be used to create a subject. * * @param securityManager */ public void setSecurityManager(SecurityManager securityManager) { LOGGER.trace("Setting the security manager"); this.securityManager = securityManager; } /** * Intercepts a message. Interceptors should NOT invoke handleMessage or handleFault on the next * interceptor - the interceptor chain will take care of this. * * @param message */ @Override public void handleMessage(Message message) throws Fault { if (message != null) { // grab the SAML assertion associated with this Message from the // token store SecurityAssertion assertion = SecurityAssertionStore.getSecurityAssertion(message); boolean isPermitted = false; if ((assertion != null) && (assertion.getSecurityToken() != null)) { Subject user = null; CollectionPermission action = null; String actionURI = getActionUri(message); try { user = securityManager.getSubject(assertion.getSecurityToken()); if (user == null) { throw new AccessDeniedException("Unauthorized"); } if (LOGGER.isTraceEnabled()) { LOGGER.trace(format(assertion.getSecurityToken() .getToken())); } LOGGER.debug("Is user authenticated: {}", user.isAuthenticated()); LOGGER.debug("Checking for permission"); SecurityLogger.audit("Is Subject authenticated? " + user.isAuthenticated(), user); if (StringUtils.isEmpty(actionURI)) { SecurityLogger.audit("Denying access to Subject for unknown action.", user); throw new AccessDeniedException("Unauthorized"); } action = new KeyValueCollectionPermission(actionURI); LOGGER.debug("Permission: {}", action); isPermitted = user.isPermitted(action); LOGGER.debug("Result of permission: {}", isPermitted); SecurityLogger.audit("Is Subject permitted? " + isPermitted, user); // store the subject so the DDF framework can use it later ThreadContext.bind(user); message.put(SecurityConstants.SAML_ASSERTION, user); LOGGER.debug("Added assertion information to message at key {}", SecurityConstants.SAML_ASSERTION); } catch (SecurityServiceException e) { SecurityLogger.audit( "Denying access : Caught exception when trying to authenticate user for service [" + actionURI + "]", e); throw new AccessDeniedException("Unauthorized"); } if (!isPermitted) { SecurityLogger.audit( "Denying access to Subject for service: " + action.getAction(), user); throw new AccessDeniedException("Unauthorized"); } } else { SecurityLogger.audit( "Unable to retrieve the security assertion associated with the web service call."); throw new AccessDeniedException("Unauthorized"); } } else { SecurityLogger.audit( "Unable to retrieve the current message associated with the web service call."); throw new AccessDeniedException("Unauthorized"); } } /** * This method is an implementation of the WSA-M and WSA-W specs for determining the action URI.<br> * <ul> * <li>http://www.w3.org/TR/ws-addr-metadata/#actioninwsdl</li> * <li>http://www.w3.org/TR/ws-addr-wsdl/#actioninwsdl</li> * </ul> * Adapted from {@link org.apache.cxf.ws.addressing.impl.MAPAggregatorImpl} and * {@link org.apache.cxf.ws.addressing.impl.InternalContextUtils} * * @param message * @return */ private String getActionUri(Message message) { String actionURI = null; /** * See if the action is explicitly defined in the WSDL message service model. Retrieves one * of the Action attribute in the wsdl:input message. */ MessageInfo msgInfo = (MessageInfo) message.get(MessageInfo.class.getName()); if (msgInfo != null && msgInfo.getExtensionAttributes() != null) { // wsaw:Action Object attr = msgInfo.getExtensionAttribute(JAXWSAConstants.WSAW_ACTION_QNAME); // wsam:Action if (attr == null) { attr = msgInfo.getExtensionAttribute(JAXWSAConstants.WSAM_ACTION_QNAME); } // support for older usages if (attr == null) { attr = msgInfo.getExtensionAttributes() .get(new QName(JAXWSAConstants.NS_WSA, Names.WSAW_ACTION_NAME)); } if (attr == null) { attr = msgInfo.getExtensionAttributes() .get(new QName(Names.WSA_NAMESPACE_WSDL_NAME_OLD, Names.WSAW_ACTION_NAME)); } if (attr instanceof QName) { actionURI = ((QName) attr).getLocalPart(); } else { actionURI = attr == null ? null : attr.toString(); } } /** * See if the action is explicitly defined in the WSDL operation service model. Retrieves * the operation soap:soapAction property. */ if (StringUtils.isEmpty(actionURI)) { BindingOperationInfo bindingOpInfo = message.getExchange() .get(BindingOperationInfo.class); SoapOperationInfo soi = null; if (bindingOpInfo != null) { soi = bindingOpInfo.getExtensor(SoapOperationInfo.class); if (soi == null && bindingOpInfo.isUnwrapped()) { soi = bindingOpInfo.getWrappedOperation() .getExtensor(SoapOperationInfo.class); } } actionURI = soi == null ? null : soi.getAction(); actionURI = StringUtils.isEmpty(actionURI) ? null : actionURI; } /** * If the service model doesn't explicitly defines the action, we'll construct the default * URI string. */ if (StringUtils.isEmpty(actionURI)) { QName op = (QName) message.get(MessageContext.WSDL_OPERATION); QName port = (QName) message.get(MessageContext.WSDL_PORT); if (op != null && port != null) { actionURI = port.getNamespaceURI(); actionURI = addPath(actionURI, port.getLocalPart()); actionURI = addPath(actionURI, op.getLocalPart() + "Request"); } } return actionURI; } private String addPath(String uri, String path) { StringBuilder builder = new StringBuilder(uri); String delimiter = uri.startsWith("urn") ? ":" : "/"; if (!uri.endsWith(delimiter) && !path.startsWith(delimiter)) { builder.append(delimiter); } builder.append(path); return builder.toString(); } private String format(Element unformattedXml) { if (unformattedXml == null) { LOGGER.debug("Unable to transform xml: null"); return null; } return XMLUtils.prettyFormat(unformattedXml); } }