/*
* Copyright 2001-2005 Internet2
*
* 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 gov.nih.nci.cagrid.opensaml;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import javax.xml.namespace.QName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.*;
/**
* Implementation of SOAP binding packaging methods, useful as a base class
* for full binding implementations once a transport protocol is added by
* implementing the actual binding methods.
*
* @author Scott Cantor
* @created February 12, 2005
*/
public abstract class SOAPBinding implements SAMLSOAPBinding
{
private static SAMLConfig config = SAMLConfig.instance();
private final static Logger log = LoggerFactory.getLogger(SOAPBinding.class);
private Map /* <SOAPHook,Object> */ soapHooks = Collections.synchronizedMap(new HashMap(4));
/**
* @see gov.nih.gov.nih.nci.cagrid.opensaml.SAMLSOAPBinding#addHook(gov.nih.gov.nih.nci.cagrid.opensaml.SAMLSOAPBinding.SOAPHook)
*/
public void addHook(SOAPHook h) {
addHook(h, null);
}
/**
* @see gov.nih.gov.nih.nci.cagrid.opensaml.SAMLSOAPBinding#addHook(gov.nih.gov.nih.nci.cagrid.opensaml.SAMLSOAPBinding.SOAPHook, java.lang.Object)
*/
public void addHook(SOAPHook h, Object globalCtx) {
soapHooks.put(h, globalCtx);
}
/**
* @see gov.nih.gov.nih.nci.cagrid.opensaml.SAMLSOAPBinding#sendRequest(gov.nih.gov.nih.nci.cagrid.opensaml.SAMLRequest, java.lang.Object)
*/
public Element sendRequest(SAMLRequest request, Object callCtx) throws SAMLException {
log.info("enter sendRequest");
try {
// Turn the request into a DOM, and use its document for the SOAP nodes.
Document doc=request.toDOM().getOwnerDocument();
// Build a SOAP envelope and body.
Element e=doc.createElementNS(XML.SOAP11ENV_NS, "Envelope");
e.setAttributeNS(XML.XMLNS_NS, "xmlns", XML.SOAP11ENV_NS);
Element body=doc.createElementNS(XML.SOAP11ENV_NS, "Body");
e.appendChild(body);
// Attach SAML request.
body.appendChild(request.toDOM());
if (doc.getDocumentElement()==null)
doc.appendChild(e);
else
doc.replaceChild(e, doc.getDocumentElement());
// Run the outgoing client-side SOAP hooks.
for (Iterator hooks=soapHooks.entrySet().iterator(); hooks.hasNext();) {
Entry h = (Entry)hooks.next();
if (!((SOAPHook)h.getKey()).outgoing(e, h.getValue(), callCtx)) {
body.removeChild(request.toDOM());
log.warn("SOAP processing hook returned false, aborting outgoing request");
throw new BindingException(SAMLException.REQUESTER,"SOAPBinding.sendRequest() SOAP processing hook returned false, aborted outgoing request");
}
}
return e;
}
finally {
log.info("exit sendRequest");
}
}
/**
* @see gov.nih.gov.nih.nci.cagrid.opensaml.SAMLSOAPBinding#recvResponse(org.w3c.dom.Element, java.lang.Object)
*/
public SAMLResponse recvResponse(Element envelope, Object callCtx) throws SAMLException {
log.info("enter recvResponse");
try {
// The root must be a SOAP 1.1 envelope.
if (!XML.isElementNamed(envelope,XML.SOAP11ENV_NS,"Envelope"))
throw new BindingException("SOAPBinding.recvResponse() detected an incompatible or missing SOAP envelope");
// Run the incoming client-side SOAP hooks.
for (Iterator hooks=soapHooks.entrySet().iterator(); hooks.hasNext();) {
Entry h = (Entry)hooks.next();
if (!((SOAPHook)h.getKey()).incoming(envelope, h.getValue(), callCtx)) {
log.warn("SOAP processing hook returned false, aborting incoming response");
throw new BindingException(SAMLException.REQUESTER,"SOAPBinding.recvResponse() SOAP processing hook returned false, aborted incoming response");
}
}
Element n = XML.getFirstChildElement(envelope);
if (XML.isElementNamed(n,XML.SOAP11ENV_NS,"Header")) {
// Did somebody get a look at the headers for us?
if (soapHooks.isEmpty()) {
/* Walk the children. If we encounter any headers with mustUnderstand, we have to bail.
* The thinking here is, we're not a "real" SOAP processor, but we have to emulate one that
* understands no headers. For now, assume we're the recipient.
*/
Element header=XML.getFirstChildElement(n);
while (header!=null) {
if (((Element)header).getAttributeNS(XML.SOAP11ENV_NS,"mustUnderstand")!=null &&
((Element)header).getAttributeNS(XML.SOAP11ENV_NS,"mustUnderstand").equals("1"))
throw new SOAPException(SOAPException.MUSTUNDERSTAND,"SOAPBinding.recvResponse() detected a mandatory SOAP header");
header=XML.getNextSiblingElement(header);
}
}
n = XML.getNextSiblingElement(n); // advance to body
}
if (n != null) {
// Get the first (and only) child element of the Body.
n = XML.getFirstChildElement(n);
if (n != null) {
// Is it a fault?
if (XML.isElementNamed(n,XML.SOAP11ENV_NS,"Fault")) {
// Find the faultstring element and use it in the message.
NodeList nlist = n.getElementsByTagNameNS(null,"faultstring");
String msg;
if (nlist != null && nlist.getLength() > 0)
msg = nlist.item(0).getFirstChild().getNodeValue();
else
msg = "SAMLSOAPBinding.recvResponse() detected a SOAP fault";
nlist = n.getElementsByTagNameNS(null,"faultstring");
if (nlist != null && nlist.getLength() > 0)
throw new SOAPException(XML.getQNameTextNode((Text)nlist.item(0).getFirstChild()),msg);
else
throw new SOAPException(SOAPException.SERVER,msg);
}
return new SAMLResponse(n);
}
}
throw new SOAPException(SOAPException.SERVER,"SOAPBinding.recvResponse() unable to find a SAML response or fault in SOAP body");
}
finally {
log.info("exit recvResponse");
}
}
/**
* @see gov.nih.gov.nih.nci.cagrid.opensaml.SAMLSOAPBinding#recvRequest(org.w3c.dom.Element, java.lang.Object)
*/
public SAMLRequest recvRequest(Element envelope, Object callCtx) throws SAMLException {
log.info("enter recvRequest");
try {
// The root must be a SOAP 1.1 envelope.
if (!XML.isElementNamed(envelope,XML.SOAP11ENV_NS,"Envelope"))
throw new SOAPException(SOAPException.VERSION, "SOAPBinding.recvRequest() detected an incompatible or missing SOAP envelope");
// Run the incoming server-side SOAP hooks.
for (Iterator hooks=soapHooks.entrySet().iterator(); hooks.hasNext();) {
Entry h = (Entry)hooks.next();
if (!((SOAPHook)h.getKey()).incoming(envelope, h.getValue(), callCtx)) {
log.warn("SOAP processing hook returned false, aborting incoming request");
throw new BindingException(SAMLException.REQUESTER,"SOAPBinding.recvRequest() SOAP processing hook returned false, aborted incoming request");
}
}
Element child = XML.getFirstChildElement(envelope);
if (XML.isElementNamed(child,XML.SOAP11ENV_NS,"Header")) {
// Did somebody get a look at the headers for us?
if (soapHooks.isEmpty()) {
/* Walk the children. If we encounter any headers with mustUnderstand, we have to bail.
* The thinking here is, we're not a "real" SOAP processor, but we have to emulate one that
* understands no headers. For now, assume we're the recipient.
*/
Element header = XML.getFirstChildElement(child);
while (header != null) {
if (header.getAttributeNS(XML.SOAP11ENV_NS, "mustUnderstand").equals("1"))
throw new SOAPException(SOAPException.MUSTUNDERSTAND, "SOAPBinding.recvRequest() detected a mandatory SOAP header");
header = XML.getNextSiblingElement(header);
}
}
// Advance to the Body element.
child = XML.getNextSiblingElement(child);
}
/* The element after the optional Header is the mandatory Body (the meat). The SAML
SOAP binding specifies the samlp:Request be immediately inside the body. Until
we locate a Request (which we know validated), we're still in SOAP land. A SOAP
envelope without a samlp:Request inside it is treated as a SOAP Client fault.
*/
if (child != null)
child = XML.getFirstChildElement(child);
return new SAMLRequest(child);
}
finally {
log.info("exit recvRequest");
}
}
/**
* @see gov.nih.gov.nih.nci.cagrid.opensaml.SAMLSOAPBinding#sendResponse(gov.nih.gov.nih.nci.cagrid.opensaml.SAMLResponse, gov.nih.gov.nih.nci.cagrid.opensaml.SAMLException, java.lang.Object)
*/
public Element sendResponse(SAMLResponse response, SAMLException e, Object callCtx) throws SAMLException {
log.info("enter sendResponse");
try {
Document doc = (e==null) ? response.toDOM().getOwnerDocument() : XML.parserPool.newDocument();
// Build the SOAP envelope and body for the response.
Element env = doc.createElementNS(XML.SOAP11ENV_NS, "soap:Envelope");
env.setAttributeNS(XML.XMLNS_NS,"xmlns:soap", XML.SOAP11ENV_NS);
env.setAttributeNS(XML.XMLNS_NS,"xmlns:xsd", XML.XSD_NS);
env.setAttributeNS(XML.XMLNS_NS,"xmlns:xsi", XML.XSI_NS);
if (doc.getDocumentElement()==null)
doc.appendChild(env);
else
doc.replaceChild(env, doc.getDocumentElement());
Element body = doc.createElementNS(XML.SOAP11ENV_NS, "soap:Body");
env.appendChild(body);
// If we're handed an exception, turn it into a SOAP fault.
if (e != null) {
Element fault = doc.createElementNS(XML.SOAP11ENV_NS, "soap:Fault");
body.appendChild(fault);
Element elem = doc.createElementNS(null,"faultcode");
if (e instanceof SOAPException) {
Iterator codes = e.getCodes();
if (codes.hasNext())
elem.appendChild(doc.createTextNode("soap:" + ((QName)codes.next()).getLocalPart()));
else
elem.appendChild(doc.createTextNode("soap:" + SOAPException.SERVER.getLocalPart()));
}
else
elem.appendChild(doc.createTextNode("soap:" + SOAPException.SERVER.getLocalPart()));
fault.appendChild(elem);
elem = doc.createElementNS(null,"faultstring");
fault.appendChild(elem).appendChild(doc.createTextNode(e.getMessage()));
}
else {
// Attach the SAML response.
body.appendChild(response.toDOM());
}
// Run the outgoing server-side SOAP hooks.
for (Iterator hooks=soapHooks.entrySet().iterator(); hooks.hasNext();) {
Entry h = (Entry)hooks.next();
if (!((SOAPHook)h.getKey()).outgoing(env, h.getValue(), callCtx)) {
body.removeChild(response.toDOM());
log.warn("SOAP processing hook returned false, aborting outgoing response");
throw new BindingException("SOAPBinding.sendResponse() SOAP processing hook returned false, aborted outgoing response");
}
}
return env;
}
finally {
log.info("exit sendResponse");
}
}
}