blob: a3ab464a2076502ade57d53114fef7455e7afd90 [file] [log] [blame]
// Copyright 2009 Google Inc.
//
// 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 com.google.enterprise.adaptor.secmgr.saml;
import org.opensaml.common.SAMLObject;
import org.opensaml.common.binding.SAMLMessageContext;
import org.opensaml.common.xml.SAMLConstants;
import org.opensaml.saml2.binding.decoding.BaseSAML2MessageDecoder;
import org.opensaml.ws.message.MessageContext;
import org.opensaml.ws.message.decoder.MessageDecodingException;
import org.opensaml.ws.soap.soap11.Envelope;
import org.opensaml.ws.soap.soap11.Header;
import org.opensaml.ws.transport.http.HTTPInTransport;
import org.opensaml.xml.AttributeExtensibleXMLObject;
import org.opensaml.xml.XMLObject;
import org.opensaml.xml.parse.ParserPool;
import org.opensaml.xml.util.DatatypeHelper;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.namespace.QName;
/**
* SAML 2.0 SOAP 1.1 over HTTP MultiContext binding decoder.
* Based on OpenSaml's HTTPSOAP11Decoder
*/
public class HTTPSOAP11MultiContextDecoder extends BaseSAML2MessageDecoder {
/** Class logger. */
private static final Logger log = Logger.getLogger(HTTPSOAP11MultiContextDecoder.class.getName());
/** QNames of understood SOAP headers. */
private final List<QName> understoodHeaders = new ArrayList<QName>();
/** QName of SOAP mustUnderstand header attribute. */
private final QName soapMustUnderstand = new QName(SAMLConstants.SOAP11ENV_NS, "mustUnderstand");
private Envelope soapMessage;
private List<XMLObject> soapBodyChildren;
private int thisChild;
/** Constructor. */
public HTTPSOAP11MultiContextDecoder() {
super();
}
/**
* Constructor.
*
* @param pool parser pool used to deserialize messages
*/
public HTTPSOAP11MultiContextDecoder(ParserPool pool) {
super(pool);
}
public String getBindingURI() {
return SAMLConstants.SAML2_SOAP11_BINDING_URI;
}
@Override
protected boolean isIntendedDestinationEndpointURIRequired(
@SuppressWarnings("rawtypes") SAMLMessageContext samlMsgCtx) {
return false;
}
/**
* Gets the SOAP header names that are understood by the application.
*
* @return SOAP header names that are understood by the application
*/
public List<QName> getUnderstoodHeaders() {
return understoodHeaders;
}
/**
* Sets the SOAP header names that are understood by the application.
*
* @param headerNames SOAP header names that are understood by the application
*/
public void setUnderstoodHeaders(List<QName> headerNames) {
understoodHeaders.clear();
if (headerNames != null) {
understoodHeaders.addAll(headerNames);
}
}
@Override
protected void doDecode(MessageContext messageContext) throws MessageDecodingException {
if (!(messageContext instanceof SAMLMessageContext<?, ?, ?>)) {
throw new MessageDecodingException(
"Invalid message context type, this decoder only support SAMLMessageContext");
}
@SuppressWarnings("unchecked")
SAMLMessageContext<SAMLObject, SAMLObject, SAMLObject> samlMsgCtx =
(SAMLMessageContext<SAMLObject, SAMLObject, SAMLObject>) messageContext;
samlMsgCtx.setInboundMessage(soapMessage);
if (soapMessage == null) {
start(samlMsgCtx);
}
if (soapBodyChildren.size() < 1) {
throw new MessageDecodingException(
"Unexpected number of children in the SOAP body, " + soapBodyChildren.size()
+ ". Unable to extract SAML message");
}
if (thisChild >= soapBodyChildren.size()) {
// indicates to the caller that there are no more messages to decode
// this should be caught and recovered from
throw new IndexOutOfBoundsException();
}
XMLObject incomingMessage = soapBodyChildren.get(thisChild);
thisChild++;
if (!(incomingMessage instanceof SAMLObject)) {
throw new MessageDecodingException(
"Unexpected SOAP body content. Expected a SAML request but recieved "
+ incomingMessage.getElementQName());
}
SAMLObject samlMessage = (SAMLObject) incomingMessage;
log.log(Level.FINE,
"Decoded SOAP messaged which included SAML message of type {}",
samlMessage.getElementQName());
samlMsgCtx.setInboundSAMLMessage(samlMessage);
populateMessageContext(samlMsgCtx);
}
/**
* Checks that all SOAP headers that require understanding are in the understood header
* list.
*
* @param headers SOAP headers to check
*
* @throws MessageDecodingException thrown if a SOAP header requires
* understanding but is not understood by the decoder
*/
protected void checkUnderstoodSOAPHeaders(List<XMLObject> headers)
throws MessageDecodingException {
if (headers == null || headers.isEmpty()) {
return;
}
AttributeExtensibleXMLObject attribExtensObject;
for (XMLObject header : headers) {
if (header instanceof AttributeExtensibleXMLObject) {
attribExtensObject = (AttributeExtensibleXMLObject) header;
if (DatatypeHelper.safeEquals("1", attribExtensObject.getUnknownAttributes().get(
soapMustUnderstand))) {
if (!understoodHeaders.contains(header.getElementQName())) {
throw new MessageDecodingException(
"SOAP decoder encountered a header, "
+ header.getElementQName()
+ ", that requires undestanding, "
+ "however this decoder does not understand that header");
}
}
}
}
}
private void start(SAMLMessageContext<SAMLObject, SAMLObject, SAMLObject> samlMsgCtx)
throws MessageDecodingException {
if (!(samlMsgCtx.getInboundMessageTransport() instanceof HTTPInTransport)) {
throw new MessageDecodingException(
"Invalid inbound message transport type, this decoder only support HTTPInTransport");
}
HTTPInTransport inTransport = (HTTPInTransport) samlMsgCtx.getInboundMessageTransport();
if (!inTransport.getHTTPMethod().equalsIgnoreCase("POST")) {
throw new MessageDecodingException(
"This message deocoder only supports the HTTP POST method");
}
log.fine("Unmarshalling SOAP message");
soapMessage = Envelope.class.cast(unmarshallMessage(inTransport.getIncomingStream()));
Header messageHeader = soapMessage.getHeader();
if (messageHeader != null) {
checkUnderstoodSOAPHeaders(soapMessage.getHeader().getUnknownXMLObjects());
}
soapBodyChildren = soapMessage.getBody().getUnknownXMLObjects();
thisChild = 0;
}
}