From 26eda756f35b7ce3419fc3f508238a086b5b19a2 Mon Sep 17 00:00:00 2001 From: Scott Cantor Date: Tue, 6 Nov 2007 20:58:26 +0000 Subject: [PATCH] ECP message encoder (requests via PAOS, responses via SOAP). --- saml/Makefile.am | 1 + saml/binding/impl/MessageEncoder.cpp | 2 + saml/saml2/binding/impl/SAML2ECPEncoder.cpp | 241 ++++++++++++++++++++++++++++ saml/util/SAMLConstants.cpp | 2 + saml/util/SAMLConstants.h | 3 + 5 files changed, 249 insertions(+) create mode 100644 saml/saml2/binding/impl/SAML2ECPEncoder.cpp diff --git a/saml/Makefile.am b/saml/Makefile.am index 5b934a9..9c6cab4 100644 --- a/saml/Makefile.am +++ b/saml/Makefile.am @@ -157,6 +157,7 @@ libsaml_la_SOURCES = \ saml2/binding/impl/SAML2RedirectEncoder.cpp \ saml2/binding/impl/SAML2SOAPDecoder.cpp \ saml2/binding/impl/SAML2SOAPEncoder.cpp \ + saml2/binding/impl/SAML2ECPEncoder.cpp \ saml2/binding/impl/SAML2SOAPClient.cpp \ saml2/profile/Assertion20Validator.cpp \ saml2/profile/BrowserSSOProfile20Validator.cpp \ diff --git a/saml/binding/impl/MessageEncoder.cpp b/saml/binding/impl/MessageEncoder.cpp index 17fea00..6799c81 100644 --- a/saml/binding/impl/MessageEncoder.cpp +++ b/saml/binding/impl/MessageEncoder.cpp @@ -45,6 +45,7 @@ namespace opensaml { SAML_DLLLOCAL PluginManager< MessageEncoder,string,pair >::Factory SAML2POSTSimpleSignEncoderFactory; SAML_DLLLOCAL PluginManager< MessageEncoder,string,pair >::Factory SAML2RedirectEncoderFactory; SAML_DLLLOCAL PluginManager< MessageEncoder,string,pair >::Factory SAML2SOAPEncoderFactory; + SAML_DLLLOCAL PluginManager< MessageEncoder,string,pair >::Factory SAML2ECPEncoderFactory; }; }; @@ -59,4 +60,5 @@ void SAML_API opensaml::registerMessageEncoders() conf.MessageEncoderManager.registerFactory(samlconstants::SAML20_BINDING_HTTP_POST_SIMPLESIGN, saml2p::SAML2POSTSimpleSignEncoderFactory); conf.MessageEncoderManager.registerFactory(samlconstants::SAML20_BINDING_HTTP_REDIRECT, saml2p::SAML2RedirectEncoderFactory); conf.MessageEncoderManager.registerFactory(samlconstants::SAML20_BINDING_SOAP, saml2p::SAML2SOAPEncoderFactory); + conf.MessageEncoderManager.registerFactory(samlconstants::SAML20_BINDING_PAOS, saml2p::SAML2ECPEncoderFactory); } diff --git a/saml/saml2/binding/impl/SAML2ECPEncoder.cpp b/saml/saml2/binding/impl/SAML2ECPEncoder.cpp new file mode 100644 index 0000000..99c4ddd --- /dev/null +++ b/saml/saml2/binding/impl/SAML2ECPEncoder.cpp @@ -0,0 +1,241 @@ +/* + * Copyright 2001-2007 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. + */ + +/** + * SAML2ECPEncoder.cpp + * + * SAML 2.0 ECP profile message encoder + */ + +#include "internal.h" +#include "exceptions.h" +#include "binding/MessageEncoder.h" +#include "signature/ContentReference.h" +#include "saml1/core/Protocols.h" +#include "saml2/core/Protocols.h" + +#include +#include +#include +#include +#include +#include + +using namespace samlconstants; +using namespace opensaml::saml2p; +using namespace opensaml::saml2md; +using namespace opensaml; +using namespace xmlconstants; +using namespace xmlsignature; +using namespace soap11; +using namespace xmltooling::logging; +using namespace xmltooling; +using namespace std; + +namespace opensaml { + namespace saml2p { + + static const XMLCh ProviderName[] = UNICODE_LITERAL_12(P,r,o,v,i,d,e,r,N,a,m,e); + + class SAML_DLLLOCAL SAML2ECPEncoder : public MessageEncoder + { + public: + SAML2ECPEncoder(const DOMElement* e, const XMLCh* ns) : m_actor("http://schemas.xmlsoap.org/soap/actor/next"), + m_providerName(e ? e->getAttributeNS(ns, ProviderName) : NULL), m_idpList(NULL) { + DOMElement* child = e ? XMLHelper::getFirstChildElement(e, SAML20P_NS, IDPList::LOCAL_NAME) : NULL; + if (child) + m_idpList = dynamic_cast(XMLObjectBuilder::buildOneFromElement(child)); + } + virtual ~SAML2ECPEncoder() { + delete m_idpList; + } + + long encode( + GenericResponse& genericResponse, + XMLObject* xmlObject, + const char* destination, + const EntityDescriptor* recipient=NULL, + const char* relayState=NULL, + const ArtifactGenerator* artifactGenerator=NULL, + const Credential* credential=NULL, + const XMLCh* signatureAlg=NULL, + const XMLCh* digestAlg=NULL + ) const; + + private: + auto_ptr_XMLCh m_actor; + const XMLCh* m_providerName; + IDPList* m_idpList; + AnyElementBuilder m_anyBuilder; + }; + + MessageEncoder* SAML_DLLLOCAL SAML2ECPEncoderFactory(const pair& p) + { + return new SAML2ECPEncoder(p.first, p.second); + } + }; +}; + +long SAML2ECPEncoder::encode( + GenericResponse& genericResponse, + XMLObject* xmlObject, + const char* destination, + const EntityDescriptor* recipient, + const char* relayState, + const ArtifactGenerator* artifactGenerator, + const Credential* credential, + const XMLCh* signatureAlg, + const XMLCh* digestAlg + ) const +{ +#ifdef _DEBUG + xmltooling::NDC ndc("encode"); +#endif + Category& log = Category::getInstance(SAML_LOGCAT".MessageEncoder.SAML2ECP"); + + log.debug("validating input"); + if (xmlObject->getParent()) + throw BindingException("Cannot encode XML content with parent."); + + Response* response = NULL; + AuthnRequest* request = dynamic_cast(xmlObject); + if (!request) { + response = dynamic_cast(xmlObject); + if (!response) + throw BindingException("XML content for SAML 2.0 ECP Encoder must be a SAML 2.0 AuthnRequest or Response."); + } + + if (request && !request->getAssertionConsumerServiceURL()) + throw BindingException("AuthnRequest must carry an AssertionConsumerServiceURL by value."); + else if (response && !response->getDestination()) + throw BindingException("Response must carry a Destination attribute."); + + // PAOS request leg is a custom MIME type, SOAP response leg is just text/xml. + genericResponse.setContentType(request ? "application/vnd.paos+xml" : "text/xml"); + HTTPResponse* httpResponse = dynamic_cast(&genericResponse); + if (httpResponse) { + httpResponse->setResponseHeader("Cache-Control", "no-cache, no-store, must-revalidate, private"); + httpResponse->setResponseHeader("Pragma", "no-cache"); + } + + // Wrap it in a SOAP envelope. + Envelope* env = EnvelopeBuilder::buildEnvelope(); + Header* header = HeaderBuilder::buildHeader(); + env->setHeader(header); + Body* body = BodyBuilder::buildBody(); + env->setBody(body); + body->getUnknownXMLObjects().push_back(xmlObject); + + ElementProxy* hdrblock; + QName qMU(SOAP11ENV_NS, Header::MUSTUNDERSTAND_ATTRIB_NAME, SOAP11ENV_PREFIX); + QName qActor(SOAP11ENV_NS, Header::ACTOR_ATTRIB_NAME, SOAP11ENV_PREFIX); + + if (request) { + // Create paos:Request header. + static const XMLCh service[] = UNICODE_LITERAL_7(s,e,r,v,i,c,e); + static const XMLCh responseConsumerURL[] = UNICODE_LITERAL_19(r,e,s,p,o,n,s,e,C,o,n,s,u,m,e,r,U,R,L); + hdrblock = dynamic_cast(m_anyBuilder.buildObject(PAOS_NS, saml1p::Request::LOCAL_NAME, PAOS_PREFIX)); + hdrblock->setAttribute(qMU, XML_ONE); + hdrblock->setAttribute(qActor, m_actor.get()); + hdrblock->setAttribute(QName(NULL, service), SAML20ECP_NS); + hdrblock->setAttribute(QName(NULL, responseConsumerURL), request->getAssertionConsumerServiceURL()); + header->getUnknownXMLObjects().push_back(hdrblock); + + // Create ecp:Request header. + static const XMLCh IsPassive[] = UNICODE_LITERAL_9(I,s,P,a,s,s,i,v,e); + hdrblock = dynamic_cast(m_anyBuilder.buildObject(SAML20ECP_NS, saml1p::Request::LOCAL_NAME, SAML20ECP_PREFIX)); + hdrblock->setAttribute(qMU, XML_ONE); + hdrblock->setAttribute(qActor, m_actor.get()); + if (!request->IsPassive()) + hdrblock->setAttribute(QName(NULL,IsPassive), XML_ZERO); + if (m_providerName) + hdrblock->setAttribute(QName(NULL,ProviderName), m_providerName); + hdrblock->getUnknownXMLObjects().push_back(request->getIssuer()->clone()); + if (request->getScoping() && request->getScoping()->getIDPList()) + hdrblock->getUnknownXMLObjects().push_back(request->getScoping()->getIDPList()->clone()); + else if (m_idpList) + hdrblock->getUnknownXMLObjects().push_back(m_idpList->clone()); + header->getUnknownXMLObjects().push_back(hdrblock); + } + else { + // Create ecp:Response header. + hdrblock = dynamic_cast(m_anyBuilder.buildObject(SAML20ECP_NS, Response::LOCAL_NAME, SAML20ECP_PREFIX)); + hdrblock->setAttribute(qMU, XML_ONE); + hdrblock->setAttribute(qActor, m_actor.get()); + hdrblock->setAttribute(QName(NULL,AuthnRequest::ASSERTIONCONSUMERSERVICEURL_ATTRIB_NAME), response->getDestination()); + header->getUnknownXMLObjects().push_back(hdrblock); + } + + if (relayState) { + // Create ecp:RelayState header. + static const XMLCh RelayState[] = UNICODE_LITERAL_10(R,e,l,a,y,S,t,a,t,e); + hdrblock = dynamic_cast(m_anyBuilder.buildObject(SAML20ECP_NS, RelayState, SAML20ECP_PREFIX)); + hdrblock->setAttribute(qMU, XML_ONE); + hdrblock->setAttribute(qActor, m_actor.get()); + auto_ptr_XMLCh rs(relayState); + hdrblock->setTextContent(rs.get()); + header->getUnknownXMLObjects().push_back(hdrblock); + } + + try { + DOMElement* rootElement = NULL; + if (credential) { + if (request->getSignature()) { + log.debug("message already signed, skipping signature operation"); + rootElement = env->marshall(); + } + else { + log.debug("signing the message and marshalling the envelope"); + + // Build a Signature. + Signature* sig = SignatureBuilder::buildSignature(); + request->setSignature(sig); + if (signatureAlg) + sig->setSignatureAlgorithm(signatureAlg); + if (digestAlg) { + opensaml::ContentReference* cr = dynamic_cast(sig->getContentReference()); + if (cr) + cr->setDigestAlgorithm(digestAlg); + } + + // Sign message while marshalling. + vector sigs(1,sig); + rootElement = env->marshall((DOMDocument*)NULL,&sigs,credential); + } + } + else { + log.debug("marshalling the envelope"); + rootElement = env->marshall(); + } + + string xmlbuf; + XMLHelper::serialize(rootElement, xmlbuf); + istringstream s(xmlbuf); + log.debug("sending serialized envelope"); + long ret = genericResponse.sendResponse(s); + + // Cleanup by destroying XML. + delete env; + return ret; + } + catch (XMLToolingException&) { + // A bit weird...we have to "revert" things so that the message is isolated + // so the caller can free it. + xmlObject->getParent()->detach(); + xmlObject->detach(); + throw; + } +} diff --git a/saml/util/SAMLConstants.cpp b/saml/util/SAMLConstants.cpp index b39518c..b43dfd4 100644 --- a/saml/util/SAMLConstants.cpp +++ b/saml/util/SAMLConstants.cpp @@ -192,6 +192,8 @@ const char samlconstants::SAML1_PROFILE_BROWSER_POST[] = "urn:oasis:names:tc:SAM const char samlconstants::SAML20_BINDING_SOAP[] = "urn:oasis:names:tc:SAML:2.0:bindings:SOAP"; +const char samlconstants::SAML20_BINDING_PAOS[] = "urn:oasis:names:tc:SAML:2.0:bindings:PAOS"; + const char samlconstants::SAML20_BINDING_URI[] = "urn:oasis:names:tc:SAML:2.0:bindings:URI"; const char samlconstants::SAML20_BINDING_HTTP_ARTIFACT[] = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact"; diff --git a/saml/util/SAMLConstants.h b/saml/util/SAMLConstants.h index 608be0f..5b6774c 100644 --- a/saml/util/SAMLConstants.h +++ b/saml/util/SAMLConstants.h @@ -135,6 +135,9 @@ namespace samlconstants { /** SAML 2.0 SOAP binding ("urn:oasis:names:tc:SAML:2.0:bindings:SOAP") */ extern SAML_API const char SAML20_BINDING_SOAP[]; + /** SAML 2.0 PAOS binding ("urn:oasis:names:tc:SAML:2.0:bindings:PAOS") */ + extern SAML_API const char SAML20_BINDING_PAOS[]; + /** SAML 2.0 URI binding ("urn:oasis:names:tc:SAML:2.0:bindings:URI") */ extern SAML_API const char SAML20_BINDING_URI[]; -- 2.1.4