Imported Upstream version 2.3.1+dfsg
[shibboleth/sp.git] / shibsp / handler / impl / SAML2LogoutInitiator.cpp
index 400989c..15d38c8 100644 (file)
@@ -1,6 +1,6 @@
 /*
- *  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.
  * You may obtain a copy of the License at
@@ -16,7 +16,7 @@
 
 /**
  * SAML2LogoutInitiator.cpp
- * 
+ *
  * Triggers SP-initiated logout for SAML 2.0 sessions.
  */
 
 #include "Application.h"
 #include "ServiceProvider.h"
 #include "SessionCache.h"
+#include "SPRequest.h"
 #include "handler/AbstractHandler.h"
 #include "handler/LogoutHandler.h"
 
 #ifndef SHIBSP_LITE
 # include "binding/SOAPClient.h"
+# include "metadata/MetadataProviderCriteria.h"
+# include "security/SecurityPolicy.h"
+# include <saml/exceptions.h>
 # include <saml/SAMLConfig.h>
 # include <saml/saml2/core/Protocols.h>
 # include <saml/saml2/binding/SAML2SOAPClient.h>
 # include <saml/saml2/metadata/EndpointManager.h>
+# include <saml/saml2/metadata/Metadata.h>
 # include <saml/saml2/metadata/MetadataCredentialCriteria.h>
 using namespace opensaml::saml2;
 using namespace opensaml::saml2p;
@@ -53,7 +58,7 @@ namespace shibsp {
     #pragma warning( push )
     #pragma warning( disable : 4250 )
 #endif
-    
+
     class SHIBSP_DLLLOCAL SAML2LogoutInitiator : public AbstractHandler, public LogoutHandler
     {
     public:
@@ -66,7 +71,7 @@ namespace shibsp {
             }
 #endif
         }
-        
+
         void setParent(const PropertySet* parent);
         void receive(DDF& in, ostream& out);
         pair<bool,long> run(SPRequest& request, bool isHandler=true) const;
@@ -138,8 +143,14 @@ SAML2LogoutInitiator::SAML2LogoutInitiator(const DOMElement* e, const char* appI
                 auto_ptr_char b(start);
                 MessageEncoder * encoder =
                     SAMLConfig::getConfig().MessageEncoderManager.newPlugin(b.get(),pair<const DOMElement*,const XMLCh*>(e,NULL));
-                m_encoders[start] = encoder;
-                m_log.debug("supporting outgoing binding (%s)", b.get());
+                if (encoder->isUserAgentPresent()) {
+                    m_encoders[start] = encoder;
+                    m_log.debug("supporting outgoing binding (%s)", b.get());
+                }
+                else {
+                    delete encoder;
+                    m_log.warn("skipping outgoing binding (%s), not a front-channel mechanism", b.get());
+                }
             }
             catch (exception& ex) {
                 m_log.error("error building MessageEncoder: %s", ex.what());
@@ -229,7 +240,7 @@ void SAML2LogoutInitiator::receive(DDF& in, ostream& out)
         m_log.error("couldn't find application (%s) for logout", aid ? aid : "(missing)");
         throw ConfigurationException("Unable to locate application for logout, deleted?");
     }
-    
+
     // Unpack the request.
     auto_ptr<HTTPRequest> req(getRequest(in));
 
@@ -237,7 +248,7 @@ void SAML2LogoutInitiator::receive(DDF& in, ostream& out)
     DDF ret(NULL);
     DDFJanitor jout(ret);
     auto_ptr<HTTPResponse> resp(getResponse(ret));
-    
+
     Session* session = NULL;
     try {
          session = app->getServiceProvider().getSessionCache()->find(*app, *req.get(), NULL, NULL);
@@ -275,7 +286,7 @@ pair<bool,long> SAML2LogoutInitiator::doRequest(
     if (!notifyBackChannel(application, httpRequest.getRequestURL(), sessions, false)) {
         session->unlock();
         application.getServiceProvider().getSessionCache()->remove(application, httpRequest, &httpResponse);
-        return sendLogoutPage(application, httpRequest, httpResponse, true, "Partial logout failure.");
+        return sendLogoutPage(application, httpRequest, httpResponse, "partial");
     }
 
 #ifndef SHIBSP_LITE
@@ -284,7 +295,7 @@ pair<bool,long> SAML2LogoutInitiator::doRequest(
         // With a session in hand, we can create a LogoutRequest message, if we can find a compatible endpoint.
         MetadataProvider* m = application.getMetadataProvider();
         Locker metadataLocker(m);
-        MetadataProvider::Criteria mc(session->getEntityID(), &IDPSSODescriptor::ELEMENT_QNAME, samlconstants::SAML20P_NS);
+        MetadataProviderCriteria mc(application, session->getEntityID(), &IDPSSODescriptor::ELEMENT_QNAME, samlconstants::SAML20P_NS);
         pair<const EntityDescriptor*,const RoleDescriptor*> entity = m->getEntityDescriptor(mc);
         if (!entity.first) {
             throw MetadataException(
@@ -310,7 +321,7 @@ pair<bool,long> SAML2LogoutInitiator::doRequest(
             }
         }
         if (!ep || !encoder) {
-            m_log.warn("no compatible front channel SingleLogoutService, trying back channel...");
+            m_log.debug("no compatible front channel SingleLogoutService, trying back channel...");
             shibsp::SecurityPolicy policy(application);
             shibsp::SOAPClient soaper(policy);
             MetadataCredentialCriteria mcc(*role);
@@ -339,21 +350,33 @@ pair<bool,long> SAML2LogoutInitiator::doRequest(
                 }
             }
 
-            if (!logoutResponse)
-                ret = sendLogoutPage(application, httpRequest, httpResponse, false, "Identity provider did not respond to logout request.");
-            else if (!logoutResponse->getStatus() || !logoutResponse->getStatus()->getStatusCode() ||
-                   !XMLString::equals(logoutResponse->getStatus()->getStatusCode()->getValue(), saml2p::StatusCode::SUCCESS)) {
-                delete logoutResponse;
-                ret = sendLogoutPage(application, httpRequest, httpResponse, false, "Identity provider returned a SAML error in response to logout request.");
+            // No answer at all?
+            if (!logoutResponse) {
+                if (endpoints.empty())
+                    m_log.info("IdP doesn't support single logout protocol over a compatible binding");
+                else
+                    m_log.warn("IdP didn't respond to logout request");
+                ret = sendLogoutPage(application, httpRequest, httpResponse, "partial");
             }
             else {
+                // Check the status, looking for non-success or a partial logout code.
+                const StatusCode* sc = logoutResponse->getStatus() ? logoutResponse->getStatus()->getStatusCode() : NULL;
+                bool partial = (!sc || !XMLString::equals(sc->getValue(), StatusCode::SUCCESS));
+                if (!partial && sc->getStatusCode()) {
+                    // Success, but still need to check for partial.
+                    partial = XMLString::equals(sc->getStatusCode()->getValue(), StatusCode::PARTIAL_LOGOUT);
+                }
                 delete logoutResponse;
-                const char* returnloc = httpRequest.getParameter("return");
-                if (returnloc) {
-                    ret.second = httpResponse.sendRedirect(returnloc);
-                    ret.first = true;
+                if (partial)
+                    ret = sendLogoutPage(application, httpRequest, httpResponse, "partial");
+                else {
+                    const char* returnloc = httpRequest.getParameter("return");
+                    if (returnloc) {
+                        ret.second = httpResponse.sendRedirect(returnloc);
+                        ret.first = true;
+                    }
+                    ret = sendLogoutPage(application, httpRequest, httpResponse, "global");
                 }
-                ret = sendLogoutPage(application, httpRequest, httpResponse, false, "Logout completed successfully.");
             }
 
             if (session) {
@@ -361,6 +384,7 @@ pair<bool,long> SAML2LogoutInitiator::doRequest(
                 session = NULL;
                 application.getServiceProvider().getSessionCache()->remove(application, httpRequest, &httpResponse);
             }
+
             return ret;
         }
 
@@ -432,48 +456,8 @@ LogoutRequest* SAML2LogoutInitiator::buildRequest(
             );
         msg->setEncryptedID(encrypted.release());
     }
-
-    if (!encoder) {
-        // No encoder being used, so sign for SOAP client manually.
-        flag = relyingParty->getString("signing");
-        if (flag.first && (!strcmp(flag.second, "true") || !strcmp(flag.second, "back"))) {
-            CredentialResolver* credResolver=application.getCredentialResolver();
-            if (credResolver) {
-                Locker credLocker(credResolver);
-                // Fill in criteria to use.
-                MetadataCredentialCriteria mcc(role);
-                mcc.setUsage(Credential::SIGNING_CREDENTIAL);
-                pair<bool,const char*> keyName = relyingParty->getString("keyName");
-                if (keyName.first)
-                    mcc.getKeyNames().insert(keyName.second);
-                pair<bool,const XMLCh*> sigalg = relyingParty->getXMLString("signingAlg");
-                if (sigalg.first)
-                    mcc.setXMLAlgorithm(sigalg.second);
-                const Credential* cred = credResolver->resolve(&mcc);
-                if (cred) {
-                    xmlsignature::Signature* sig = xmlsignature::SignatureBuilder::buildSignature();
-                    msg->setSignature(sig);
-                    if (sigalg.first)
-                        sig->setSignatureAlgorithm(sigalg.second);
-                    sigalg = relyingParty->getXMLString("digestAlg");
-                    if (sigalg.first) {
-                        ContentReference* cr = dynamic_cast<ContentReference*>(sig->getContentReference());
-                        if (cr)
-                            cr->setDigestAlgorithm(sigalg.second);
-                    }
-            
-                    // Sign response while marshalling.
-                    vector<xmlsignature::Signature*> sigs(1,sig);
-                    msg->marshall((DOMDocument*)NULL,&sigs,cred);
-                }
-                else {
-                    m_log.warn("no signing credential resolved, leaving message unsigned");
-                }
-            }
-            else {
-                m_log.warn("no credential resolver installed, leaving message unsigned");
-            }
-        }
+    else {
+        msg->setNameID(nameid->cloneNameID());
     }
 
     return msg.release();