/** * 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 org.codice.ddf.security.interceptor; import java.util.Set; import java.util.UUID; import java.util.concurrent.TimeUnit; import javax.servlet.http.HttpServletRequest; import javax.xml.soap.SOAPElement; import javax.xml.soap.SOAPException; import javax.xml.soap.SOAPFactory; import javax.xml.soap.SOAPMessage; import org.apache.commons.lang.StringUtils; import org.apache.cxf.binding.soap.SoapBindingConstants; import org.apache.cxf.binding.soap.SoapMessage; import org.apache.cxf.binding.soap.saaj.SAAJInInterceptor; import org.apache.cxf.interceptor.Fault; import org.apache.cxf.message.Message; import org.apache.cxf.message.MessageUtils; import org.apache.cxf.phase.Phase; import org.apache.cxf.transport.http.AbstractHTTPDestination; import org.apache.cxf.ws.addressing.AddressingProperties; import org.apache.cxf.ws.addressing.AttributedURIType; import org.apache.cxf.ws.addressing.EndpointReferenceType; import org.apache.cxf.ws.addressing.policy.MetadataConstants; import org.apache.cxf.ws.policy.AssertionInfoMap; import org.apache.cxf.ws.security.SecurityConstants; import org.apache.cxf.ws.security.tokenstore.SecurityToken; import org.apache.cxf.ws.security.wss4j.AbstractWSS4JInterceptor; import org.apache.cxf.ws.security.wss4j.PolicyBasedWSS4JInInterceptor; import org.apache.cxf.ws.security.wss4j.PolicyBasedWSS4JOutInterceptor; import org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor; import org.apache.shiro.subject.PrincipalCollection; import org.apache.wss4j.common.ext.WSSecurityException; import org.apache.wss4j.dom.handler.WSHandlerConstants; import org.apache.wss4j.dom.util.WSSecurityUtil; import org.codice.ddf.platform.util.XMLUtils; import org.codice.ddf.security.common.Security; import org.codice.ddf.security.policy.context.ContextPolicyManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Element; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import ddf.security.Subject; import ddf.security.assertion.SecurityAssertion; import ddf.security.encryption.EncryptionService; import ddf.security.service.SecurityManager; import ddf.security.sts.client.configuration.STSClientConfiguration; /** * Interceptor for guest access to SOAP endpoints. */ public class GuestInterceptor extends AbstractWSS4JInterceptor { private static final Logger LOGGER = LoggerFactory.getLogger(GuestInterceptor.class); private EncryptionService encryptionService; private SecurityManager securityManager; private STSClientConfiguration stsClientConfiguration; private ContextPolicyManager contextPolicyManager; private boolean guestAccessDenied = false; private static final Cache<String, Subject> CACHED_GUEST_SUBJECT = CacheBuilder.newBuilder() .expireAfterWrite(1, TimeUnit.DAYS) .maximumSize(1000) .build(); private Security security; private final PolicyBasedWSS4JOutInterceptor.PolicyBasedWSS4JOutInterceptorInternal policyBasedWss4jOutInterceptor = new PolicyBasedWSS4JOutInterceptor().createEndingInterceptor(); public GuestInterceptor(SecurityManager securityManager, ContextPolicyManager contextPolicyManager, Security security) { super(); LOGGER.trace("Constructing GuestInterceptor"); this.securityManager = securityManager; this.contextPolicyManager = contextPolicyManager; this.security = security; setPhase(Phase.PRE_PROTOCOL); //make sure this interceptor runs before the WSS4J one in the same Phase, otherwise it won't work Set<String> before = getBefore(); before.add(WSS4JInInterceptor.class.getName()); before.add(PolicyBasedWSS4JInInterceptor.class.getName()); LOGGER.trace("Exiting GuestInterceptor constructor."); } @Override public void handleMessage(SoapMessage message) throws Fault { if (guestAccessDenied) { LOGGER.debug("Guest Access not enabled - no message checking performed."); return; } if (message == null) { LOGGER.debug("Incoming SOAP message is null - guest interceptor makes no sense."); return; } SOAPMessage soapMessage = getSOAPMessage(message); internalHandleMessage(message, soapMessage); if (LOGGER.isTraceEnabled()) { try { LOGGER.trace("SOAP request after guest interceptor: {}", XMLUtils.prettyFormat(soapMessage.getSOAPHeader() .getParentNode())); } catch (SOAPException e) { //ignore } } } private void internalHandleMessage(SoapMessage message, SOAPMessage soapMessage) throws Fault { //Check if security header exists; if not, execute GuestInterceptor logic String actor = (String) getOption(WSHandlerConstants.ACTOR); if (actor == null) { actor = (String) message.getContextualProperty(SecurityConstants.ACTOR); } Element existingSecurityHeader = null; try { LOGGER.debug("Checking for security header."); existingSecurityHeader = WSSecurityUtil.getSecurityHeader(soapMessage.getSOAPPart(), actor); } catch (WSSecurityException e1) { LOGGER.debug("Issue with getting security header", e1); } if (existingSecurityHeader != null) { LOGGER.debug( "SOAP message contains security header, no action taken by the GuestInterceptor."); return; } LOGGER.debug("Current request has no security header, continuing with GuestInterceptor"); AssertionInfoMap assertionInfoMap = message.get(AssertionInfoMap.class); boolean hasAddressingAssertion = assertionInfoMap.entrySet() .stream() .flatMap(p -> p.getValue() .stream()) .filter(info -> MetadataConstants.ADDRESSING_ASSERTION_QNAME.equals(info.getAssertion() .getName())) .findFirst() .isPresent(); if (hasAddressingAssertion) { createAddressing(message, soapMessage); } LOGGER.debug("Creating guest security token."); HttpServletRequest request = (HttpServletRequest) message.get(AbstractHTTPDestination.HTTP_REQUEST); SecurityToken securityToken = createSecurityToken(request.getRemoteAddr()); message.put(SecurityConstants.TOKEN, securityToken); if (!MessageUtils.isRequestor(message)) { try { message.put(Message.REQUESTOR_ROLE, true); policyBasedWss4jOutInterceptor.handleMessage(message); } finally { message.remove(Message.REQUESTOR_ROLE); } } else { policyBasedWss4jOutInterceptor.handleMessage(message); } } private SecurityToken createSecurityToken(String ipAddress) { SecurityToken securityToken = null; Subject subject = getSubject(ipAddress); LOGGER.trace("Attempting to create Security token."); if (subject != null) { PrincipalCollection principals = subject.getPrincipals(); if (principals != null) { SecurityAssertion securityAssertion = principals.oneByType(SecurityAssertion.class); if (securityAssertion != null) { securityToken = securityAssertion.getSecurityToken(); } else { LOGGER.info( "Subject did not contain a security assertion, could not add assertion to the security header."); } } else { LOGGER.info( "Subject did not contain any principals, could not create security token."); } } return securityToken; } private Subject getSubject(String ipAddress) { Subject subject = CACHED_GUEST_SUBJECT.getIfPresent(ipAddress); if (security.tokenAboutToExpire(subject)) { subject = security.getGuestSubject(ipAddress); CACHED_GUEST_SUBJECT.put(ipAddress, subject); } else { LOGGER.debug("Using cached Guest user token for {}", ipAddress); } return subject; } private void createAddressing(SoapMessage message, SOAPMessage soapMessage) { SOAPFactory soapFactory; try { soapFactory = SOAPFactory.newInstance(); } catch (SOAPException e) { LOGGER.debug("Could not create a SOAPFactory.", e); return; // can't add anything if we can't create it } String addressingProperty = org.apache.cxf.ws.addressing.JAXWSAConstants.CLIENT_ADDRESSING_PROPERTIES_INBOUND; AddressingProperties addressingProperties = new AddressingProperties(); try { SOAPElement action = soapFactory.createElement(org.apache.cxf.ws.addressing.Names.WSA_ACTION_NAME, org.apache.cxf.ws.addressing.JAXWSAConstants.WSA_PREFIX, org.apache.cxf.ws.security.wss4j.DefaultCryptoCoverageChecker.WSA_NS); action.addTextNode((String) message.get(org.apache.cxf.message.Message.REQUEST_URL)); AttributedURIType attributedString = new AttributedURIType(); String actionValue = StringUtils.defaultIfEmpty((String) message.get( SoapBindingConstants.SOAP_ACTION), ""); attributedString.setValue(actionValue); addressingProperties.setAction(attributedString); soapMessage.getSOAPHeader() .addChildElement(action); } catch (SOAPException e) { LOGGER.debug("Unable to add addressing action.", e); } try { SOAPElement messageId = soapFactory.createElement(org.apache.cxf.ws.addressing.Names.WSA_MESSAGEID_NAME, org.apache.cxf.ws.addressing.JAXWSAConstants.WSA_PREFIX, org.apache.cxf.ws.security.wss4j.DefaultCryptoCoverageChecker.WSA_NS); String uuid = "urn:uuid:" + UUID.randomUUID() .toString(); messageId.addTextNode(uuid); AttributedURIType attributedString = new AttributedURIType(); attributedString.setValue(uuid); addressingProperties.setMessageID(attributedString); soapMessage.getSOAPHeader() .addChildElement(messageId); } catch (SOAPException e) { LOGGER.debug("Unable to add addressing messageId.", e); } try { SOAPElement to = soapFactory.createElement(org.apache.cxf.ws.addressing.Names.WSA_TO_NAME, org.apache.cxf.ws.addressing.JAXWSAConstants.WSA_PREFIX, org.apache.cxf.ws.security.wss4j.DefaultCryptoCoverageChecker.WSA_NS); to.addTextNode((String) message.get(org.apache.cxf.message.Message.REQUEST_URL)); EndpointReferenceType endpointReferenceType = new EndpointReferenceType(); AttributedURIType attributedString = new AttributedURIType(); attributedString.setValue((String) message.get(org.apache.cxf.message.Message.REQUEST_URL)); endpointReferenceType.setAddress(attributedString); addressingProperties.setTo(endpointReferenceType); soapMessage.getSOAPHeader() .addChildElement(to); } catch (SOAPException e) { LOGGER.debug("Unable to add addressing to.", e); } try { SOAPElement replyTo = soapFactory.createElement(org.apache.cxf.ws.addressing.Names.WSA_REPLYTO_NAME, org.apache.cxf.ws.addressing.JAXWSAConstants.WSA_PREFIX, org.apache.cxf.ws.security.wss4j.DefaultCryptoCoverageChecker.WSA_NS); SOAPElement address = soapFactory.createElement(org.apache.cxf.ws.addressing.Names.WSA_ADDRESS_NAME, org.apache.cxf.ws.addressing.JAXWSAConstants.WSA_PREFIX, org.apache.cxf.ws.security.wss4j.DefaultCryptoCoverageChecker.WSA_NS); address.addTextNode(org.apache.cxf.ws.addressing.Names.WSA_ANONYMOUS_ADDRESS); replyTo.addChildElement(address); soapMessage.getSOAPHeader() .addChildElement(replyTo); } catch (SOAPException e) { LOGGER.debug("Unable to add addressing replyTo.", e); } message.put(addressingProperty, addressingProperties); } private SOAPMessage getSOAPMessage(SoapMessage msg) { SAAJInInterceptor.INSTANCE.handleMessage(msg); return msg.getContent(SOAPMessage.class); } public EncryptionService getEncryptionService() { return encryptionService; } public void setEncryptionService(EncryptionService encryptionService) { this.encryptionService = encryptionService; } public STSClientConfiguration getStsClientConfiguration() { return stsClientConfiguration; } public void setStsClientConfiguration(STSClientConfiguration stsClientConfiguration) { this.stsClientConfiguration = stsClientConfiguration; } public ContextPolicyManager getContextPolicyManager() { return contextPolicyManager; } public void setContextPolicyManager(ContextPolicyManager contextPolicyManager) { this.contextPolicyManager = contextPolicyManager; } public boolean isGuestAccessDenied() { return guestAccessDenied; } public void setGuestAccessDenied(boolean deny) { guestAccessDenied = deny; } }