2.0 POST binding and test
authorcantor <cantor@fb386ef7-a10c-0410-8ebf-fd3f8e989ab0>
Fri, 6 Oct 2006 03:54:56 +0000 (03:54 +0000)
committercantor <cantor@fb386ef7-a10c-0410-8ebf-fd3f8e989ab0>
Fri, 6 Oct 2006 03:54:56 +0000 (03:54 +0000)
git-svn-id: https://svn.middleware.georgetown.edu/cpp-opensaml2/trunk@96 fb386ef7-a10c-0410-8ebf-fd3f8e989ab0

12 files changed:
.cdtproject
saml/Makefile.am
saml/binding/impl/MessageDecoder.cpp
saml/saml.vcproj
saml/saml1/binding/impl/SAML1POSTDecoder.cpp
saml/saml2/binding/SAML2POSTDecoder.h [new file with mode: 0644]
saml/saml2/binding/impl/SAML2POSTDecoder.cpp [new file with mode: 0644]
samltest/Makefile.am
samltest/data/saml2/binding/SAML2Response.xml [new file with mode: 0644]
samltest/saml1/binding/SAML1POSTTest.h
samltest/saml2/binding/SAML2POSTTest.h [new file with mode: 0644]
samltest/samltest.vcproj

index 0361b54..0de9cbd 100644 (file)
 <pathentry kind="src" path="saml/signature"/>\r
 <pathentry kind="src" path="saml/util"/>\r
 <pathentry kind="src" path="samltest/signature"/>\r
-<pathentry excluding="saml1/core/impl/|signature/|saml2/core/impl/|saml2/metadata/|security/|saml1/binding/" kind="src" path="samltest"/>\r
+<pathentry excluding="saml1/core/impl/|signature/|saml2/core/impl/|saml2/metadata/|security/|saml1/binding/|saml2/binding/" kind="src" path="samltest"/>\r
 <pathentry kind="src" path="samltest/saml1/core/impl"/>\r
 <pathentry kind="src" path="samltest/saml2/core/impl"/>\r
 <pathentry kind="src" path="samltest/saml2/metadata"/>\r
 <pathentry kind="src" path="samltest/security"/>\r
 <pathentry kind="src" path="samltest/saml1/binding"/>\r
+<pathentry kind="src" path="samltest/saml2/binding"/>\r
 </item>\r
 </data>\r
 </cdtproject>\r
index d0ebc77..eb10f70 100644 (file)
@@ -64,6 +64,7 @@ saml1bindinclude_HEADERS = \
        saml1/binding/SAMLArtifactType0001.h \
        saml1/binding/SAMLArtifactType0002.h \
        saml1/binding/SAML1ArtifactEncoder.h \
+       saml1/binding/SAML1POSTDecoder.h \
        saml1/binding/SAML1POSTEncoder.h
 
 saml2coreinclude_HEADERS = \
@@ -73,8 +74,9 @@ saml2coreinclude_HEADERS = \
 saml2bindinclude_HEADERS = \
        saml2/binding/SAML2Artifact.h \
        saml2/binding/SAML2ArtifactType0004.h \
-       saml1/binding/SAML2ArtifactEncoder.h \
-       saml1/binding/SAML2POSTEncoder.h
+       saml2/binding/SAML2ArtifactEncoder.h \
+       saml2/binding/SAML2POSTDecoder.h \
+       saml2/binding/SAML2POSTEncoder.h
 
 saml2mdinclude_HEADERS = \
        saml2/metadata/AbstractMetadataProvider.h \
@@ -121,6 +123,7 @@ libsaml_la_SOURCES = \
        saml2/binding/impl/SAML2Artifact.cpp \
        saml2/binding/impl/SAML2ArtifactType0004.cpp \
        saml2/binding/impl/SAML2ArtifactEncoder.cpp \
+       saml2/binding/impl/SAML2POSTDecoder.cpp \
        saml2/binding/impl/SAML2POSTEncoder.cpp \
        encryption/EncryptedKeyResolver.cpp \
        security/impl/TrustEngine.cpp \
index 0b87270..e867ddb 100644 (file)
@@ -44,5 +44,5 @@ void SAML_API opensaml::registerMessageDecoders()
     //conf.MessageDecoderManager.registerFactory(SAML1_ARTIFACT_DECODER, saml1p::SAML1ArtifactDecoderFactory);
     conf.MessageDecoderManager.registerFactory(SAML1_POST_DECODER, saml1p::SAML1POSTDecoderFactory);
     //conf.MessageDecoderManager.registerFactory(SAML2_ARTIFACT_DECODER, saml2p::SAML2ArtifactDecoderFactory);
-    //conf.MessageDecoderManager.registerFactory(SAML2_POST_DECODER, saml2p::SAML2POSTDecoderFactory);
+    conf.MessageDecoderManager.registerFactory(SAML2_POST_DECODER, saml2p::SAML2POSTDecoderFactory);
 }
index cbfd019..135d2c9 100644 (file)
                                                        </FileConfiguration>\r
                                                </File>\r
                                                <File\r
+                                                       RelativePath=".\saml2\binding\impl\SAML2POSTDecoder.cpp"\r
+                                                       >\r
+                                               </File>\r
+                                               <File\r
                                                        RelativePath=".\saml2\binding\impl\SAML2POSTEncoder.cpp"\r
                                                        >\r
                                                </File>\r
                                                >\r
                                        </File>\r
                                        <File\r
+                                               RelativePath=".\saml2\binding\SAML2POSTDecoder.h"\r
+                                               >\r
+                                       </File>\r
+                                       <File\r
                                                RelativePath=".\saml2\binding\SAML2POSTEncoder.h"\r
                                                >\r
                                        </File>\r
index 181d6f9..83f893c 100644 (file)
@@ -98,6 +98,7 @@ Response* SAML1POSTDecoder::decode(
     if (!response)
         throw BindingException("Decoded message was not a SAML 1.x Response.");
 
+    const EntityDescriptor* provider=NULL;
     try {
         if (!m_validate)
             SchemaValidators.validate(xmlObject.get());
@@ -142,8 +143,7 @@ Response* SAML1POSTDecoder::decode(
         const vector<Assertion*>& assertions=const_cast<const Response*>(response)->getAssertions();
         if (!assertions.empty()) {
             log.debug("searching metadata for assertion issuer...");
-            const EntityDescriptor* provider=
-                metadataProvider ? metadataProvider->getEntityDescriptor(assertions.front()->getIssuer()) : NULL;
+            provider=metadataProvider ? metadataProvider->getEntityDescriptor(assertions.front()->getIssuer()) : NULL;
             if (provider) {
                 log.debug("matched assertion issuer against metadata, searching for applicable role...");
                 pair<bool,int> minor = response->getMinorVersion();
@@ -156,8 +156,10 @@ Response* SAML1POSTDecoder::decode(
                         issuerTrusted = static_cast<const TrustEngine*>(trustEngine)->validate(
                             *(response->getSignature()), *issuer, metadataProvider->getKeyResolver()
                             );
-                        if (!issuerTrusted)
+                        if (!issuerTrusted) {
                             log.error("unable to verify signature on message with supplied trust engine");
+                            throw BindingException("Message signature failed verification.");
+                        }
                     }
                     else {
                         log.warn("unable to verify integrity of the message, leaving untrusted");
@@ -186,27 +188,26 @@ Response* SAML1POSTDecoder::decode(
     }
     catch (XMLToolingException& ex) {
         // Check for an Issuer.
-        const vector<Assertion*>& assertions=const_cast<const Response*>(response)->getAssertions();
-        if (!assertions.empty()) {
-            if (!metadataProvider) {
+        if (!provider) {
+            const vector<Assertion*>& assertions=const_cast<const Response*>(response)->getAssertions();
+            if (!assertions.empty() || !metadataProvider ||
+                    !(provider=metadataProvider->getEntityDescriptor(assertions.front()->getIssuer(), false))) {
                 // Just record it.
-                auto_ptr_char issuer(assertions.front()->getIssuer());
-                if (issuer.get())
-                    ex.addProperty("entityID", issuer.get());
-                throw;  
-            }
-            // Try and locate metadata for error handling.
-            const EntityDescriptor* provider=metadataProvider->getEntityDescriptor(assertions.front()->getIssuer(),false);
-            if (provider) {
-                pair<bool,int> minor = response->getMinorVersion();
-                issuer=provider->getRoleDescriptor(
-                    *role,
-                    (minor.first && minor.second==0) ? SAMLConstants::SAML10_PROTOCOL_ENUM : SAMLConstants::SAML11_PROTOCOL_ENUM
-                    );
-                if (issuer) annotateException(&ex,issuer); // throws it
-                annotateException(&ex,provider);  // throws it
+                auto_ptr_char iname(assertions.front()->getIssuer());
+                if (iname.get())
+                    ex.addProperty("entityID", iname.get());
+                throw;
             }
         }
+        if (!issuer) {
+            pair<bool,int> minor = response->getMinorVersion();
+            issuer=provider->getRoleDescriptor(
+                *role,
+                (minor.first && minor.second==0) ? SAMLConstants::SAML10_PROTOCOL_ENUM : SAMLConstants::SAML11_PROTOCOL_ENUM
+                );
+        }
+        if (issuer) annotateException(&ex,issuer); // throws it
+        annotateException(&ex,provider);  // throws it
     }
 
     xmlObject.release();
diff --git a/saml/saml2/binding/SAML2POSTDecoder.h b/saml/saml2/binding/SAML2POSTDecoder.h
new file mode 100644 (file)
index 0000000..4e49c2b
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ *  Copyright 2001-2006 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.
+ */
+
+/**
+ * @file saml/saml2/binding/SAML2POSTDecoder.h
+ * 
+ * SAML 2.0 HTTP POST binding message decoder
+ */
+
+#include <saml/binding/MessageDecoder.h>
+
+namespace opensaml {
+    namespace saml2p {
+
+        /**
+         * SAML 2.0 HTTP POST binding message decoder
+         */
+        class SAML_API SAML2POSTDecoder : public MessageDecoder
+        {
+        public:
+            SAML2POSTDecoder(const DOMElement* e);
+            virtual ~SAML2POSTDecoder();
+            
+            xmltooling::XMLObject* decode(
+                std::string& relayState,
+                const saml2md::RoleDescriptor*& issuer,
+                bool& issuerTrusted,
+                const HTTPRequest& httpRequest,
+                const saml2md::MetadataProvider* metadataProvider=NULL,
+                const xmltooling::QName* role=NULL,
+                const X509TrustEngine* trustEngine=NULL
+                ) const;
+        };                
+
+    };
+};
diff --git a/saml/saml2/binding/impl/SAML2POSTDecoder.cpp b/saml/saml2/binding/impl/SAML2POSTDecoder.cpp
new file mode 100644 (file)
index 0000000..bf7b55d
--- /dev/null
@@ -0,0 +1,229 @@
+/*
+ *  Copyright 2001-2006 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.
+ */
+
+/**
+ * SAML2POSTDecoder.cpp
+ * 
+ * SAML 2.0 HTTP POST binding message encoder
+ */
+
+#include "internal.h"
+#include "exceptions.h"
+#include "saml/binding/ReplayCache.h"
+#include "saml2/binding/SAML2POSTDecoder.h"
+#include "saml2/core/Protocols.h"
+#include "saml2/metadata/Metadata.h"
+#include "saml2/metadata/MetadataProvider.h"
+#include "security/X509TrustEngine.h"
+
+#include <log4cpp/Category.hh>
+#include <xercesc/util/Base64.hpp>
+#include <xmltooling/util/NDC.h>
+
+using namespace opensaml::saml2md;
+using namespace opensaml::saml2p;
+using namespace opensaml::saml2;
+using namespace opensaml;
+using namespace xmlsignature;
+using namespace xmltooling;
+using namespace log4cpp;
+using namespace std;
+
+namespace opensaml {
+    namespace saml2p {              
+        MessageDecoder* SAML_DLLLOCAL SAML2POSTDecoderFactory(const DOMElement* const & e)
+        {
+            return new SAML2POSTDecoder(e);
+        }
+    };
+};
+
+SAML2POSTDecoder::SAML2POSTDecoder(const DOMElement* e) {}
+
+SAML2POSTDecoder::~SAML2POSTDecoder() {}
+
+XMLObject* SAML2POSTDecoder::decode(
+    string& relayState,
+    const RoleDescriptor*& issuer,
+    bool& issuerTrusted,
+    const HTTPRequest& httpRequest,
+    const MetadataProvider* metadataProvider,
+    const QName* role,
+    const X509TrustEngine* trustEngine
+    ) const
+{
+#ifdef _DEBUG
+    xmltooling::NDC ndc("decode");
+#endif
+    Category& log = Category::getInstance(SAML_LOGCAT".MessageDecoder.SAML2POST");
+
+    log.debug("validating input");
+    if (strcmp(httpRequest.getMethod(),"POST"))
+        return NULL;
+    const char* msg = httpRequest.getParameter("SAMLResponse");
+    if (!msg)
+        msg = httpRequest.getParameter("SAMLRequest");
+    if (!msg)
+        return NULL;
+    const char* state = httpRequest.getParameter("RelayState");
+    if (state)
+        relayState = state;
+    else
+        relayState.erase();
+
+    // Decode the base64 into SAML.
+    unsigned int x;
+    XMLByte* decoded=Base64::decode(reinterpret_cast<const XMLByte*>(msg),&x);
+    if (!decoded)
+        throw BindingException("Unable to decode base64 in POST binding message.");
+    log.debug("decoded SAML message:\n%s", decoded);
+    istringstream is(reinterpret_cast<char*>(decoded));
+    XMLString::release(&decoded);
+    
+    // Parse and bind the document into an XMLObject.
+    DOMDocument* doc = (m_validate ? XMLToolingConfig::getConfig().getValidatingParser()
+        : XMLToolingConfig::getConfig().getParser()).parse(is); 
+    XercesJanitor<DOMDocument> janitor(doc);
+    auto_ptr<XMLObject> xmlObject(XMLObjectBuilder::buildOneFromElement(doc->getDocumentElement(), true));
+    janitor.release();
+
+    StatusResponseType* response = NULL;
+    RequestAbstractType* request = dynamic_cast<RequestAbstractType*>(xmlObject.get());
+    if (!request) {
+        response = dynamic_cast<StatusResponseType*>(xmlObject.get());
+        if (!response)
+            throw BindingException("XML content for SAML 2.0 HTTP-POST Decoder must be a SAML 2.0 protocol message.");
+    }
+    
+    /* For SAML 2, the issuer can be established either from the message, or in some profiles
+     * it's possible to omit it and defer to assertions in a Response.
+     * The Issuer is later matched against metadata, and then trust checking can be applied.
+     */
+    const Issuer* claimedIssuer = request ? request->getIssuer() : response->getIssuer();
+    if (!claimedIssuer) {
+        // Check assertion option. I cannot resist the variable name, for the sake of google.
+        const Response* assbag = dynamic_cast<const Response*>(response);
+        if (assbag) {
+            const vector<Assertion*>& assertions=assbag->getAssertions();
+            if (!assertions.empty())
+                claimedIssuer = assertions.front()->getIssuer();
+        }
+    }
+
+    const EntityDescriptor* provider=NULL;
+    try {
+        if (!m_validate)
+            SchemaValidators.validate(xmlObject.get());
+        
+        Signature* signature = request ? request->getSignature() : response->getSignature();
+        
+        // Check destination URL.
+        auto_ptr_char dest(request ? request->getDestination() : response->getDestination());
+        const char* dest2 = httpRequest.getRequestURL();
+        if (signature && !dest.get() || !*(dest.get())) {
+            log.error("signed SAML message missing Destination attribute");
+            throw BindingException("Signed SAML message missing Destination attribute identifying intended destination.");
+        }
+        else if (dest.get() && (!dest2 || !*dest2 || strcmp(dest.get(),dest2))) {
+            log.error("POST targeted at (%s), but delivered to (%s)", dest.get(), dest2 ? dest2 : "none");
+            throw BindingException("SAML message delivered with POST to incorrect server URL.");
+        }
+        
+        // Check freshness.
+        time_t now = time(NULL);
+        if ((request ? request->getIssueInstant()->getEpoch() : response->getIssueInstant()->getEpoch())
+                < now-(2*XMLToolingConfig::getConfig().clock_skew_secs))
+            throw BindingException("Detected expired POST binding message.");
+        
+        // Check replay.
+        ReplayCache* replayCache = SAMLConfig::getConfig().getReplayCache();
+        if (replayCache) {
+            auto_ptr_char id(xmlObject->getXMLID());
+            if (!replayCache->check("SAML2POST", id.get(), response->getIssueInstant()->getEpoch() + (2*XMLToolingConfig::getConfig().clock_skew_secs))) {
+                log.error("replay detected of response ID (%s)", id.get());
+                throw BindingException("Rejecting replayed response ID ($1).", params(1,id.get()));
+            }
+        }
+        else
+            log.warn("replay cache was not provided, this is a serious security risk!");
+        
+        issuer = NULL;
+        issuerTrusted = false;
+        log.debug("attempting to establish issuer and integrity of message...");
+        
+        // If we can't identify the issuer, we're done, since we can't lookup or verify anything.
+        if (!claimedIssuer || !claimedIssuer->getName()) {
+            log.warn("unable to establish identity of message issuer");
+            return xmlObject.release();
+        }
+        else if (claimedIssuer->getFormat() && !XMLString::equals(claimedIssuer->getFormat(), NameIDType::ENTITY)) {
+            auto_ptr_char iformat(claimedIssuer->getFormat());
+            log.warn("message issuer was in an unsupported format (%s)", iformat.get());
+            return xmlObject.release();
+        }
+        
+        log.debug("searching metadata for assertion issuer...");
+        provider=metadataProvider ? metadataProvider->getEntityDescriptor(claimedIssuer->getName()) : NULL;
+        if (provider) {
+            log.debug("matched assertion issuer against metadata, searching for applicable role...");
+            issuer=provider->getRoleDescriptor(*role, SAMLConstants::SAML20P_NS);
+            if (issuer) {
+                if (trustEngine && signature) {
+                    issuerTrusted = static_cast<const TrustEngine*>(trustEngine)->validate(
+                        *signature, *issuer, metadataProvider->getKeyResolver()
+                        );
+                    if (!issuerTrusted) {
+                        log.error("unable to verify signature on message with supplied trust engine");
+                        throw BindingException("Message signature failed verification.");
+                    }
+                }
+                else {
+                    log.warn("unable to verify integrity of the message, leaving untrusted");
+                }
+            }
+            else {
+                log.warn("unable to find compatible SAML 2.0 role (%s) in metadata", role->toString().c_str());
+            }
+            if (log.isDebugEnabled()) {
+                auto_ptr_char iname(provider->getEntityID());
+                log.debug("message from (%s), integrity %sverified", iname.get(), issuerTrusted ? "" : "NOT ");
+            }
+        }
+        else {
+            auto_ptr_char temp(claimedIssuer->getName());
+            log.warn("no metadata found, can't establish identity of issuer (%s)", temp.get());
+        }
+    }
+    catch (XMLToolingException& ex) {
+        if (!provider) {
+            if (!claimedIssuer || !claimedIssuer->getName())
+                throw;
+            if (!metadataProvider || !(provider=metadataProvider->getEntityDescriptor(claimedIssuer->getName(), false))) {
+                // Just record it.
+                auto_ptr_char iname(claimedIssuer->getName());
+                if (iname.get())
+                    ex.addProperty("entityID", iname.get());
+                throw;
+            }
+        }
+        if (!issuer)
+            issuer=provider->getRoleDescriptor(*role, SAMLConstants::SAML20P_NS);
+        if (issuer) annotateException(&ex,issuer); // throws it
+        annotateException(&ex,provider);  // throws it
+    }
+
+    return xmlObject.release();
+}
index f4f7be6..965f91d 100644 (file)
@@ -31,6 +31,7 @@ samltest_h = \
     saml1/core/impl/AudienceRestrictionConditionTest.h \
     saml1/core/impl/AudienceTest.h \
     saml1/core/impl/AuthenticationStatementTest.h \
+    saml2/binding/impl/SAML2POSTTest.h \
     saml2/core/impl/Action20Test.h \
     saml2/core/impl/Advice20Test.h \
     saml2/core/impl/Artifact20Test.h \
diff --git a/samltest/data/saml2/binding/SAML2Response.xml b/samltest/data/saml2/binding/SAML2Response.xml
new file mode 100644 (file)
index 0000000..feaf0c4
--- /dev/null
@@ -0,0 +1,14 @@
+<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
+IssueInstant="1970-01-02T01:01:02.100Z" Version="2.0" ID="rident" Destination="https://sp.example.org/SAML/POST">
+    <saml:Issuer>https://idp.example.org/</saml:Issuer> 
+    <samlp:Status><samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/></samlp:Status>
+    <saml:Assertion ID="aident" IssueInstant="1970-01-02T01:01:02.100Z" Version="2.0">
+        <saml:Issuer>https://idp.example.org/</saml:Issuer> 
+        <saml:Subject><saml:NameID>John Doe</saml:NameID></saml:Subject>
+        <saml:AuthnStatement AuthnInstant="1970-01-02T01:01:02.100Z">
+            <saml:AuthnContext>
+                <saml:AuthnContextClassRef>foo</saml:AuthnContextClassRef>
+            </saml:AuthnContext>
+        </saml:AuthnStatement>
+    </saml:Assertion>
+</samlp:Response>
index 2d6da7a..27e96ee 100644 (file)
@@ -59,6 +59,7 @@ public:
             bool trusted=false;\r
             QName idprole(SAMLConstants::SAML20MD_NS, IDPSSODescriptor::LOCAL_NAME);\r
             auto_ptr<MessageDecoder> decoder(SAMLConfig::getConfig().MessageDecoderManager.newPlugin(SAML1_POST_DECODER, NULL));\r
+            Locker locker(m_metadata);\r
             auto_ptr<Response> response(\r
                 dynamic_cast<Response*>(\r
                     decoder->decode(relayState,issuer,trusted,*this,m_metadata,&idprole,m_trust)\r
@@ -106,6 +107,7 @@ public:
             bool trusted=false;\r
             QName idprole(SAMLConstants::SAML20MD_NS, IDPSSODescriptor::LOCAL_NAME);\r
             auto_ptr<MessageDecoder> decoder(SAMLConfig::getConfig().MessageDecoderManager.newPlugin(SAML1_POST_DECODER, NULL));\r
+            Locker locker(m_metadata);\r
             auto_ptr<Response> response(\r
                 dynamic_cast<Response*>(\r
                     decoder->decode(relayState,issuer,trusted,*this,m_metadata,&idprole)\r
diff --git a/samltest/saml2/binding/SAML2POSTTest.h b/samltest/saml2/binding/SAML2POSTTest.h
new file mode 100644 (file)
index 0000000..4e2ed8e
--- /dev/null
@@ -0,0 +1,147 @@
+/*\r
+ *  Copyright 2001-2005 Internet2\r
+ * \r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *     http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+#include "binding.h"\r
+\r
+#include <saml/saml2/core/Protocols.h>\r
+\r
+using namespace opensaml::saml2p;\r
+using namespace opensaml::saml2;\r
+\r
+class SAML2POSTTest : public CxxTest::TestSuite, public SAMLBindingBaseTestCase {\r
+public:\r
+    void setUp() {\r
+        m_fields.clear();\r
+        SAMLBindingBaseTestCase::setUp();\r
+    }\r
+\r
+    void tearDown() {\r
+        m_fields.clear();\r
+        SAMLBindingBaseTestCase::tearDown();\r
+    }\r
+\r
+    void testSAML2POSTTrusted() {\r
+        try {\r
+            // Read message to use from file.\r
+            string path = data_path + "saml2/binding/SAML2Response.xml";\r
+            ifstream in(path.c_str());\r
+            DOMDocument* doc=XMLToolingConfig::getConfig().getParser().parse(in);\r
+            XercesJanitor<DOMDocument> janitor(doc);\r
+            auto_ptr<Response> toSend(\r
+                dynamic_cast<Response*>(XMLObjectBuilder::buildOneFromElement(doc->getDocumentElement(),true))\r
+                );\r
+            janitor.release();\r
+\r
+            // Freshen timestamp.\r
+            toSend->setIssueInstant(time(NULL));\r
+    \r
+            // Encode message.\r
+            auto_ptr<MessageEncoder> encoder(SAMLConfig::getConfig().MessageEncoderManager.newPlugin(SAML2_POST_ENCODER, NULL));\r
+            encoder->encode(m_fields,toSend.get(),"https://sp.example.org/","state",m_creds);\r
+            toSend.release();\r
+            \r
+            // Decode message.\r
+            string relayState;\r
+            const RoleDescriptor* issuer=NULL;\r
+            bool trusted=false;\r
+            QName idprole(SAMLConstants::SAML20MD_NS, IDPSSODescriptor::LOCAL_NAME);\r
+            auto_ptr<MessageDecoder> decoder(SAMLConfig::getConfig().MessageDecoderManager.newPlugin(SAML2_POST_DECODER, NULL));\r
+            Locker locker(m_metadata);\r
+            auto_ptr<Response> response(\r
+                dynamic_cast<Response*>(\r
+                    decoder->decode(relayState,issuer,trusted,*this,m_metadata,&idprole,m_trust)\r
+                    )\r
+                );\r
+            \r
+            // Test the results.\r
+            TSM_ASSERT_EQUALS("RelayState was not the expected result.", relayState, "state");\r
+            TSM_ASSERT("SAML Response not decoded successfully.", response.get());\r
+            TSM_ASSERT("Message was not verified.", issuer && trusted);\r
+            auto_ptr_char entityID(dynamic_cast<const EntityDescriptor*>(issuer->getParent())->getEntityID());\r
+            TSM_ASSERT("Issuer was not expected.", !strcmp(entityID.get(),"https://idp.example.org/"));\r
+            TSM_ASSERT_EQUALS("Assertion count was not correct.", response->getAssertions().size(), 1);\r
+        }\r
+        catch (XMLToolingException& ex) {\r
+            TS_TRACE(ex.what());\r
+            throw;\r
+        }\r
+    }\r
+\r
+    void testSAML2POSTUntrusted() {\r
+        try {\r
+            // Read message to use from file.\r
+            string path = data_path + "saml2/binding/SAML2Response.xml";\r
+            ifstream in(path.c_str());\r
+            DOMDocument* doc=XMLToolingConfig::getConfig().getParser().parse(in);\r
+            XercesJanitor<DOMDocument> janitor(doc);\r
+            auto_ptr<Response> toSend(\r
+                dynamic_cast<Response*>(XMLObjectBuilder::buildOneFromElement(doc->getDocumentElement(),true))\r
+                );\r
+            janitor.release();\r
+\r
+            // Freshen timestamp and clear ID.\r
+            toSend->setIssueInstant(time(NULL));\r
+            toSend->setID(NULL);\r
+    \r
+            // Encode message.\r
+            auto_ptr<MessageEncoder> encoder(SAMLConfig::getConfig().MessageEncoderManager.newPlugin(SAML2_POST_ENCODER, NULL));\r
+            encoder->encode(m_fields,toSend.get(),"https://sp.example.org/","state");\r
+            toSend.release();\r
+            \r
+            // Decode message.\r
+            string relayState;\r
+            const RoleDescriptor* issuer=NULL;\r
+            bool trusted=false;\r
+            QName idprole(SAMLConstants::SAML20MD_NS, IDPSSODescriptor::LOCAL_NAME);\r
+            auto_ptr<MessageDecoder> decoder(SAMLConfig::getConfig().MessageDecoderManager.newPlugin(SAML2_POST_DECODER, NULL));\r
+            Locker locker(m_metadata);\r
+            auto_ptr<Response> response(\r
+                dynamic_cast<Response*>(\r
+                    decoder->decode(relayState,issuer,trusted,*this,m_metadata,&idprole)\r
+                    )\r
+                );\r
+            \r
+            // Test the results.\r
+            TSM_ASSERT_EQUALS("RelayState was not the expected result.", relayState, "state");\r
+            TSM_ASSERT("SAML Response not decoded successfully.", response.get());\r
+            TSM_ASSERT("Message was verified.", issuer && !trusted);\r
+            auto_ptr_char entityID(dynamic_cast<const EntityDescriptor*>(issuer->getParent())->getEntityID());\r
+            TSM_ASSERT("Issuer was not expected.", !strcmp(entityID.get(),"https://idp.example.org/"));\r
+            TSM_ASSERT_EQUALS("Assertion count was not correct.", response->getAssertions().size(), 1);\r
+\r
+            // Trigger a replay.\r
+            TSM_ASSERT_THROWS("Did not catch the replay.", \r
+                decoder->decode(relayState,issuer,trusted,*this,m_metadata,&idprole,m_trust),\r
+                BindingException);\r
+        }\r
+        catch (XMLToolingException& ex) {\r
+            TS_TRACE(ex.what());\r
+            throw;\r
+        }\r
+    }\r
+\r
+    const char* getMethod() const {\r
+        return "POST";\r
+    } \r
+\r
+    const char* getRequestURL() const {\r
+        return "https://sp.example.org/SAML/POST";\r
+    }\r
+    \r
+    const char* getQueryString() const {\r
+        return NULL;\r
+    }\r
+};\r
index 98d301e..b282259 100644 (file)
                                <Filter\r
                                        Name="binding"\r
                                        >\r
+                                       <File\r
+                                               RelativePath=".\saml2\binding\SAML2POSTTest.cpp"\r
+                                               >\r
+                                       </File>\r
                                </Filter>\r
                        </Filter>\r
                        <Filter\r
                                <Filter\r
                                        Name="binding"\r
                                        >\r
+                                       <File\r
+                                               RelativePath=".\saml2\binding\SAML2POSTTest.h"\r
+                                               >\r
+                                               <FileConfiguration\r
+                                                       Name="Debug|Win32"\r
+                                                       >\r
+                                                       <Tool\r
+                                                               Name="VCCustomBuildTool"\r
+                                                               CommandLine="\perl\bin\perl.exe -w \cxxtest\cxxtestgen.pl --part --have-eh --have-std --abort-on-fail -o &quot;$(InputDir)$(InputName)&quot;.cpp &quot;$(InputPath)&quot;"\r
+                                                               Outputs="&quot;$(InputDir)$(InputName)&quot;.cpp"\r
+                                                       />\r
+                                               </FileConfiguration>\r
+                                               <FileConfiguration\r
+                                                       Name="Release|Win32"\r
+                                                       >\r
+                                                       <Tool\r
+                                                               Name="VCCustomBuildTool"\r
+                                                               CommandLine="\perl\bin\perl.exe -w \cxxtest\cxxtestgen.pl --part --have-eh --have-std --abort-on-fail -o &quot;$(InputDir)$(InputName)&quot;.cpp &quot;$(InputPath)&quot;"\r
+                                                               Outputs="&quot;$(InputDir)$(InputName)&quot;.cpp"\r
+                                                       />\r
+                                               </FileConfiguration>\r
+                                       </File>\r
                                </Filter>\r
                        </Filter>\r
                        <Filter\r