Apply manual validators even when schema was used.
[shibboleth/cpp-opensaml.git] / saml / saml2 / binding / impl / SAML2ECPDecoder.cpp
index 9d4e2fd..671e0e7 100644 (file)
@@ -1,5 +1,5 @@
 /*
- *  Copyright 2001-2007 Internet2
+ *  Copyright 2001-2009 Internet2
  * 
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #include "saml2/core/Protocols.h"
 
 #include <xmltooling/logging.h>
+#include <xmltooling/io/HTTPRequest.h>
 #include <xmltooling/soap/SOAP.h>
 #include <xmltooling/util/NDC.h>
+#include <xmltooling/util/Predicates.h>
 #include <xmltooling/validation/ValidatorSuite.h>
 
 using namespace opensaml::saml2p;
@@ -71,6 +73,9 @@ XMLObject* SAML2ECPDecoder::decode(
     Category& log = Category::getInstance(SAML_LOGCAT".MessageDecoder.SAML2ECP");
 
     log.debug("validating input");
+    const HTTPRequest* httpRequest=dynamic_cast<const HTTPRequest*>(&genericRequest);
+    if (!httpRequest)
+        throw BindingException("Unable to cast request object to HTTPRequest type.");
     string s = genericRequest.getContentType();
     if (s.find("application/vnd.paos+xml") == string::npos) {
         log.warn("ignoring incorrect content type (%s)", s.c_str() ? s.c_str() : "none");
@@ -80,6 +85,7 @@ XMLObject* SAML2ECPDecoder::decode(
     const char* data = genericRequest.getRequestBody();
     if (!data)
         throw BindingException("PAOS message had an empty request body.");
+    log.debug("received message:\n%s", data);
     istringstream is(data);
     
     // Parse and bind the document into an XMLObject.
@@ -93,8 +99,7 @@ XMLObject* SAML2ECPDecoder::decode(
     if (!env)
         throw BindingException("Decoded message was not a SOAP 1.1 Envelope.");
 
-    if (!policy.getValidating())
-        SchemaValidators.validate(env);
+    SchemaValidators.validate(env);
     
     Body* body = env->getBody();
     if (body && body->hasChildren()) {
@@ -106,23 +111,31 @@ XMLObject* SAML2ECPDecoder::decode(
             policy.reset(true);
             extractMessageDetails(*response, genericRequest, samlconstants::SAML20P_NS, policy);
             policy.evaluate(*response, &genericRequest);
-            
+
+            // Check destination URL.
+            auto_ptr_char dest(response->getDestination());
+            const char* dest2 = httpRequest->getRequestURL();
+            const char* delim = strchr(dest2, '?');
+            if (response->getSignature() && (!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() && *dest.get() && ((delim && strncmp(dest.get(), dest2, delim - dest2)) || (!delim && strcmp(dest.get(),dest2)))) {
+                log.error("PAOS response targeted at (%s), but delivered to (%s)", dest.get(), dest2);
+                throw BindingException("SAML message delivered with PAOS to incorrect server URL.");
+            }
+
             // Check for RelayState header.
             if (env->getHeader()) {
+                static const XMLCh RelayState[] = UNICODE_LITERAL_10(R,e,l,a,y,S,t,a,t,e);
                 const vector<XMLObject*>& blocks = const_cast<const Header*>(env->getHeader())->getUnknownXMLObjects();
-                for (vector<XMLObject*>::const_iterator h = blocks.begin(); h != blocks.end(); ++h) {
-                    static const XMLCh RelayState[] = UNICODE_LITERAL_10(R,e,l,a,y,S,t,a,t,e);
-                    if (XMLString::equals((*h)->getElementQName().getLocalPart(), RelayState) &&
-                            XMLString::equals((*h)->getElementQName().getNamespaceURI(), samlconstants::SAML20ECP_NS)) {
-                        const ElementProxy* ep = dynamic_cast<const ElementProxy*>(*h);
-                        if (ep) {
-                            auto_ptr_char rs(ep->getTextContent());
-                            if (rs.get()) {
-                                relayState = rs.get();
-                                break;
-                            }
-                        }
-                    }
+                vector<XMLObject*>::const_iterator h =
+                    find_if(blocks.begin(), blocks.end(), hasQName(xmltooling::QName(samlconstants::SAML20ECP_NS, RelayState)));
+                const ElementProxy* ep = dynamic_cast<const ElementProxy*>(h != blocks.end() ? *h : NULL);
+                if (ep) {
+                    auto_ptr_char rs(ep->getTextContent());
+                    if (rs.get())
+                        relayState = rs.get();
                 }
             }