IdP-initiated NameID management w/ back-channel notifications.
authorcantor <cantor@cb58f699-b61c-0410-a6fe-9272a202ed29>
Tue, 2 Oct 2007 04:14:48 +0000 (04:14 +0000)
committercantor <cantor@cb58f699-b61c-0410-a6fe-9272a202ed29>
Tue, 2 Oct 2007 04:14:48 +0000 (04:14 +0000)
git-svn-id: https://svn.middleware.georgetown.edu/cpp-sp/trunk@2520 cb58f699-b61c-0410-a6fe-9272a202ed29

configs/example-metadata.xml.in
configs/shibboleth2.xml.in
schemas/shibboleth-2.0-sp-notify.xsd
shibsp/Makefile.am
shibsp/handler/impl/AbstractHandler.cpp
shibsp/handler/impl/SAML2NameIDMgmt.cpp [new file with mode: 0644]
shibsp/shibsp-lite.vcproj
shibsp/shibsp.vcproj

index 8c84925..d7a9d2c 100644 (file)
@@ -267,7 +267,16 @@ w14fpgtAk2x8xD7cpHsZ073JHxEcjEetD8PTtrFdNu6GwIrv6Sk=
                                Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"/>
                        <SingleLogoutService Location="https://sp.example.org/Shibboleth.sso/SLO/Artifact"
                                Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact"/>
-                       
+
+            <!-- This tells IdPs that NameID Management is supported and where/how to request it. -->
+            <ManageNameIDService Location="https://sp.example.org/Shibboleth.sso/NIM/SOAP"
+                Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"/>
+            <ManageNameIDService Location="https://sp.example.org/Shibboleth.sso/NIM/Redirect"
+                Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"/>
+            <ManageNameIDService Location="https://sp.example.org/Shibboleth.sso/NIM/POST"
+                Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"/>
+            <ManageNameIDService Location="https://sp.example.org/Shibboleth.sso/NIM/Artifact"
+                Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact"/>                 
                        
                        <!-- This tells IdPs that you only need transient identifiers. -->
                        <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
index 5825d7f..0195b1b 100644 (file)
                        <md:SingleLogoutService Location="/SLO/Artifact" conf:template="@-PKGSYSCONFDIR-@/bindingTemplate.html"
                                Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact"/>
 
+            <!-- md:ManageNameIDService locations handle NameID management (NIM) protocol messages. -->
+            <md:ManageNameIDService Location="/NIM/SOAP"
+                Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"/>
+            <md:ManageNameIDService Location="/NIM/Redirect" conf:template="@-PKGSYSCONFDIR-@/bindingTemplate.html"
+                Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"/>
+            <md:ManageNameIDService Location="/NIM/POST" conf:template="@-PKGSYSCONFDIR-@/bindingTemplate.html"
+                Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"/>
+            <md:ManageNameIDService Location="/NIM/Artifact" conf:template="@-PKGSYSCONFDIR-@/bindingTemplate.html"
+                Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact"/>
+
                        <!--
                        md:ArtifactResolutionService locations resolve artifacts issued when using the
                        SAML 2.0 HTTP-Artifact binding on outgoing messages, generally uses SOAP.
index 8160ed6..4994461 100644 (file)
@@ -2,6 +2,8 @@
 <schema targetNamespace="urn:mace:shibboleth:2.0:sp:notify"
         xmlns="http://www.w3.org/2001/XMLSchema"
         xmlns:notify="urn:mace:shibboleth:2.0:sp:notify"
+        xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
+        xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
         elementFormDefault="qualified">
 
     <annotation>
         </documentation>
     </annotation>
 
-    <simpleType name="string">
-        <restriction base="string">
-            <minLength value="1"/>
-        </restriction>
-    </simpleType>
+    <import namespace="urn:oasis:names:tc:SAML:2.0:assertion"
+        schemaLocation="saml-schema-assertion-2.0.xsd"/>
 
+    <import namespace="urn:oasis:names:tc:SAML:2.0:protocol"
+        schemaLocation="saml-schema-protocol-2.0.xsd"/>
+    
     <element name="OK" type="notify:OKType"/>
     <complexType name="OKType">
         <sequence/>
         </attribute>
     </complexType>
 
+    <element name="NameIDNotification" type="notify:NameIDNotificationType"/>
+    <complexType name="NameIDNotificationType">
+        <annotation>
+            <documentation>Notifies application of a NameID management event.</documentation>
+        </annotation>
+        <sequence>
+            <element ref="saml:NameID"/>
+            <choice>
+                <element ref="samlp:NewID"/>
+                <element ref="samlp:Terminate"/>
+            </choice>
+        </sequence>
+    </complexType>
+
 </schema>
index 847380f..d6bb0b3 100644 (file)
@@ -120,6 +120,7 @@ common_sources = \
        handler/impl/SAML2ArtifactResolution.cpp \
        handler/impl/SAML2Logout.cpp \
        handler/impl/SAML2LogoutInitiator.cpp \
+       handler/impl/SAML2NameIDMgmt.cpp \
        handler/impl/SAML2SessionInitiator.cpp \
        handler/impl/SAMLDSSessionInitiator.cpp \
        handler/impl/SessionInitiator.cpp \
index fa7a032..5fa02fe 100644 (file)
@@ -62,6 +62,7 @@ namespace shibsp {
     SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory LocalLogoutInitiatorFactory;
     SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory SAML2LogoutInitiatorFactory;
     SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory SAML2LogoutFactory;
+    SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory SAML2NameIDMgmtFactory;
     SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory AssertionLookupFactory;
     SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory MetadataGeneratorFactory;
 };
@@ -89,6 +90,12 @@ void SHIBSP_API shibsp::registerHandlers()
     conf.SingleLogoutServiceManager.registerFactory(SAML20_BINDING_HTTP_POST, SAML2LogoutFactory);
     conf.SingleLogoutServiceManager.registerFactory(SAML20_BINDING_HTTP_POST_SIMPLESIGN, SAML2LogoutFactory);
     conf.SingleLogoutServiceManager.registerFactory(SAML20_BINDING_HTTP_ARTIFACT, SAML2LogoutFactory);
+
+    conf.ManageNameIDServiceManager.registerFactory(SAML20_BINDING_SOAP, SAML2NameIDMgmtFactory);
+    conf.ManageNameIDServiceManager.registerFactory(SAML20_BINDING_HTTP_REDIRECT, SAML2NameIDMgmtFactory);
+    conf.ManageNameIDServiceManager.registerFactory(SAML20_BINDING_HTTP_POST, SAML2NameIDMgmtFactory);
+    conf.ManageNameIDServiceManager.registerFactory(SAML20_BINDING_HTTP_POST_SIMPLESIGN, SAML2NameIDMgmtFactory);
+    conf.ManageNameIDServiceManager.registerFactory(SAML20_BINDING_HTTP_ARTIFACT, SAML2NameIDMgmtFactory);
 }
 
 AbstractHandler::AbstractHandler(
diff --git a/shibsp/handler/impl/SAML2NameIDMgmt.cpp b/shibsp/handler/impl/SAML2NameIDMgmt.cpp
new file mode 100644 (file)
index 0000000..2649e1f
--- /dev/null
@@ -0,0 +1,620 @@
+/*
+ *  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.
+ */
+
+/**
+ * SAML2NameIDMgmt.cpp
+ * 
+ * Handles SAML 2.0 NameID management protocol messages.
+ */
+
+#include "internal.h"
+#include "exceptions.h"
+#include "Application.h"
+#include "ServiceProvider.h"
+#include "handler/AbstractHandler.h"
+#include "handler/RemotedHandler.h"
+#include "util/SPConstants.h"
+
+#ifndef SHIBSP_LITE
+# include "SessionCache.h"
+# include "security/SecurityPolicy.h"
+# include "util/TemplateParameters.h"
+# include <fstream>
+# include <saml/SAMLConfig.h>
+# include <saml/saml2/core/Protocols.h>
+# include <saml/saml2/metadata/EndpointManager.h>
+# include <saml/saml2/metadata/MetadataCredentialCriteria.h>
+# include <xmltooling/util/URLEncoder.h>
+using namespace opensaml::saml2;
+using namespace opensaml::saml2p;
+using namespace opensaml::saml2md;
+using namespace opensaml;
+#endif
+
+using namespace shibsp;
+using namespace xmltooling;
+using namespace std;
+
+namespace shibsp {
+
+#if defined (_MSC_VER)
+    #pragma warning( push )
+    #pragma warning( disable : 4250 )
+#endif
+    
+    class SHIBSP_DLLLOCAL SAML2NameIDMgmt : public AbstractHandler, public RemotedHandler
+    {
+    public:
+        SAML2NameIDMgmt(const DOMElement* e, const char* appId);
+        virtual ~SAML2NameIDMgmt() {
+#ifndef SHIBSP_LITE
+            if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
+                delete m_decoder;
+                XMLString::release(&m_outgoing);
+                for_each(m_encoders.begin(), m_encoders.end(), cleanup_pair<const XMLCh*,MessageEncoder>());
+            }
+#endif
+        }
+        
+        void receive(DDF& in, ostream& out);
+        pair<bool,long> run(SPRequest& request, bool isHandler=true) const;
+
+#ifndef SHIBSP_LITE
+        void generateMetadata(SPSSODescriptor& role, const char* handlerURL) const {
+            const char* loc = getString("Location").second;
+            string hurl(handlerURL);
+            if (*loc != '/')
+                hurl += '/';
+            hurl += loc;
+            auto_ptr_XMLCh widen(hurl.c_str());
+            ManageNameIDService* ep = ManageNameIDServiceBuilder::buildManageNameIDService();
+            ep->setLocation(widen.get());
+            ep->setBinding(getXMLString("Binding").second);
+            role.getManageNameIDServices().push_back(ep);
+            role.addSupport(samlconstants::SAML20P_NS);
+        }
+#endif
+
+    private:
+        pair<bool,long> doRequest(
+            const Application& application, const char* session_id, const HTTPRequest& httpRequest, HTTPResponse& httpResponse
+            ) const;
+
+#ifndef SHIBSP_LITE
+        bool stronglyMatches(const XMLCh* idp, const XMLCh* sp, const saml2::NameID& n1, const saml2::NameID& n2) const;
+        bool notifyBackChannel(const Application& application, const char* requestURL, const NameID& nameid, const NewID* newid) const;
+
+        pair<bool,long> sendResponse(
+            const XMLCh* requestID,
+            const XMLCh* code,
+            const XMLCh* subcode,
+            const char* msg,
+            const char* relayState,
+            const RoleDescriptor* role,
+            const Application& application,
+            HTTPResponse& httpResponse,
+            bool front
+            ) const;
+
+        QName m_role;
+        MessageDecoder* m_decoder;
+        XMLCh* m_outgoing;
+        vector<const XMLCh*> m_bindings;
+        map<const XMLCh*,MessageEncoder*> m_encoders;
+#endif
+    };
+
+#if defined (_MSC_VER)
+    #pragma warning( pop )
+#endif
+
+    Handler* SHIBSP_DLLLOCAL SAML2NameIDMgmtFactory(const pair<const DOMElement*,const char*>& p)
+    {
+        return new SAML2NameIDMgmt(p.first, p.second);
+    }
+};
+
+SAML2NameIDMgmt::SAML2NameIDMgmt(const DOMElement* e, const char* appId)
+    : AbstractHandler(e, Category::getInstance(SHIBSP_LOGCAT".NameIDMgmt.SAML2"))
+#ifndef SHIBSP_LITE
+        ,m_role(samlconstants::SAML20MD_NS, IDPSSODescriptor::LOCAL_NAME), m_decoder(NULL), m_outgoing(NULL)
+#endif
+{
+#ifndef SHIBSP_LITE
+    if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
+        SAMLConfig& conf = SAMLConfig::getConfig();
+
+        // Handle incoming binding.
+        m_decoder = conf.MessageDecoderManager.newPlugin(getString("Binding").second,make_pair(e,shibspconstants::SHIB2SPCONFIG_NS));
+        m_decoder->setArtifactResolver(SPConfig::getConfig().getArtifactResolver());
+
+        if (m_decoder->isUserAgentPresent()) {
+            // Handle front-channel binding setup.
+            pair<bool,const XMLCh*> outgoing = getXMLString("outgoingBindings", m_configNS.get());
+            if (outgoing.first) {
+                m_outgoing = XMLString::replicate(outgoing.second);
+                XMLString::trim(m_outgoing);
+            }
+            else {
+                // No override, so we'll install a default binding precedence.
+                string prec = string(samlconstants::SAML20_BINDING_HTTP_REDIRECT) + ' ' + samlconstants::SAML20_BINDING_HTTP_POST + ' ' +
+                    samlconstants::SAML20_BINDING_HTTP_POST_SIMPLESIGN + ' ' + samlconstants::SAML20_BINDING_HTTP_ARTIFACT;
+                m_outgoing = XMLString::transcode(prec.c_str());
+            }
+
+            int pos;
+            XMLCh* start = m_outgoing;
+            while (start && *start) {
+                pos = XMLString::indexOf(start,chSpace);
+                if (pos != -1)
+                    *(start + pos)=chNull;
+                m_bindings.push_back(start);
+                try {
+                    auto_ptr_char b(start);
+                    MessageEncoder * encoder = conf.MessageEncoderManager.newPlugin(b.get(),make_pair(e,shibspconstants::SHIB2SPCONFIG_NS));
+                    m_encoders[start] = encoder;
+                    m_log.debug("supporting outgoing front-channel binding (%s)", b.get());
+                }
+                catch (exception& ex) {
+                    m_log.error("error building MessageEncoder: %s", ex.what());
+                }
+                if (pos != -1)
+                    start = start + pos + 1;
+                else
+                    break;
+            }
+        }
+        else {
+            MessageEncoder* encoder = conf.MessageEncoderManager.newPlugin(
+                getString("Binding").second,make_pair(e,shibspconstants::SHIB2SPCONFIG_NS)
+                );
+            m_encoders.insert(pair<const XMLCh*,MessageEncoder*>(NULL, encoder));
+        }
+    }
+#endif
+
+    string address(appId);
+    address += getString("Location").second;
+    setAddress(address.c_str());
+}
+
+pair<bool,long> SAML2NameIDMgmt::run(SPRequest& request, bool isHandler) const
+{
+    // Get the session_id in case this is a front-channel request.
+    pair<string,const char*> shib_cookie = request.getApplication().getCookieNameProps("_shibsession_");
+    const char* session_id = request.getCookie(shib_cookie.first.c_str());
+
+    SPConfig& conf = SPConfig::getConfig();
+    if (conf.isEnabled(SPConfig::OutOfProcess)) {
+        // When out of process, we run natively and directly process the message.
+        return doRequest(request.getApplication(), session_id, request, request);
+    }
+    else {
+        // When not out of process, we remote all the message processing.
+        DDF out,in = wrap(request, NULL, true);
+        DDFJanitor jin(in), jout(out);
+        if (session_id)
+            in.addmember("session_id").string(session_id);
+        out=request.getServiceProvider().getListenerService()->send(in);
+        return unwrap(request, out);
+    }
+}
+
+void SAML2NameIDMgmt::receive(DDF& in, ostream& out)
+{
+    // Find application.
+    const char* aid=in["application_id"].string();
+    const Application* app=aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : NULL;
+    if (!app) {
+        // Something's horribly wrong.
+        m_log.error("couldn't find application (%s) for NameID mgmt", aid ? aid : "(missing)");
+        throw ConfigurationException("Unable to locate application for NameID mgmt, deleted?");
+    }
+    
+    // Unpack the request.
+    DDF ret(NULL);
+    DDFJanitor jout(ret);
+    auto_ptr<HTTPRequest> req(getRequest(in));
+    auto_ptr<HTTPResponse> resp(getResponse(ret));
+    
+    // Since we're remoted, the result should either be a throw, which we pass on,
+    // a false/0 return, which we just return as an empty structure, or a response/redirect,
+    // which we capture in the facade and send back.
+    doRequest(*app, in["session_id"].string(), *req.get(), *resp.get());
+    out << ret;
+}
+
+pair<bool,long> SAML2NameIDMgmt::doRequest(
+    const Application& application, const char* session_id, const HTTPRequest& request, HTTPResponse& response
+    ) const
+{
+#ifndef SHIBSP_LITE
+    SessionCache* cache = application.getServiceProvider().getSessionCache();
+
+    // Locate policy key.
+    pair<bool,const char*> policyId = getString("policyId", m_configNS.get());  // namespace-qualified if inside handler element
+    if (!policyId.first)
+        policyId = application.getString("policyId");   // unqualified in Application(s) element
+        
+    // Access policy properties.
+    const PropertySet* settings = application.getServiceProvider().getPolicySettings(policyId.second);
+    pair<bool,bool> validate = settings->getBool("validate");
+
+    // Lock metadata for use by policy.
+    Locker metadataLocker(application.getMetadataProvider());
+
+    // Create the policy.
+    shibsp::SecurityPolicy policy(application, &m_role, validate.first && validate.second);
+    
+    // Decode the message.
+    string relayState;
+    auto_ptr<XMLObject> msg(m_decoder->decode(relayState, request, policy));
+    const ManageNameIDRequest* mgmtRequest = dynamic_cast<ManageNameIDRequest*>(msg.get());
+    if (mgmtRequest) {
+        if (!policy.isAuthenticated())
+            throw SecurityPolicyException("Security of ManageNameIDRequest not established.");
+
+        // Message from IdP to change or terminate a NameID.
+        
+        // If this is front-channel, we have to have a session_id to use already.
+        if (m_decoder->isUserAgentPresent() && !session_id) {
+            m_log.error("no active session");
+            return sendResponse(
+                mgmtRequest->getID(),
+                StatusCode::REQUESTER, StatusCode::UNKNOWN_PRINCIPAL, "No active session found in request.",
+                relayState.c_str(),
+                policy.getIssuerMetadata(),
+                application,
+                response,
+                true
+                );
+        }
+
+        bool ownedName = false;
+        NameID* nameid = mgmtRequest->getNameID();
+        if (!nameid) {
+            // Check for EncryptedID.
+            EncryptedID* encname = mgmtRequest->getEncryptedID();
+            if (encname) {
+                CredentialResolver* cr=application.getCredentialResolver();
+                if (!cr)
+                    m_log.warn("found encrypted NameID, but no decryption credential was available");
+                else {
+                    Locker credlocker(cr);
+                    auto_ptr<MetadataCredentialCriteria> mcc(
+                        policy.getIssuerMetadata() ? new MetadataCredentialCriteria(*policy.getIssuerMetadata()) : NULL
+                        );
+                    try {
+                        auto_ptr<XMLObject> decryptedID(encname->decrypt(*cr,application.getXMLString("entityID").second,mcc.get()));
+                        nameid = dynamic_cast<NameID*>(decryptedID.get());
+                        if (nameid) {
+                            ownedName = true;
+                            decryptedID.release();
+                        }
+                    }
+                    catch (exception& ex) {
+                        m_log.error(ex.what());
+                    }
+                }
+            }
+        }
+        if (!nameid) {
+            // No NameID, so must respond with an error.
+            m_log.error("NameID not found in request");
+            return sendResponse(
+                mgmtRequest->getID(),
+                StatusCode::REQUESTER, StatusCode::UNKNOWN_PRINCIPAL, "NameID not found in request.",
+                relayState.c_str(),
+                policy.getIssuerMetadata(),
+                application,
+                response,
+                m_decoder->isUserAgentPresent()
+                );
+        }
+
+        auto_ptr<NameID> namewrapper(ownedName ? nameid : NULL);
+
+        // For a front-channel request, we have to match the information in the request
+        // against the current session.
+        EntityDescriptor* entity = policy.getIssuerMetadata() ? dynamic_cast<EntityDescriptor*>(policy.getIssuerMetadata()->getParent()) : NULL;
+        if (session_id) {
+            if (!cache->matches(session_id, entity, *nameid, NULL, application)) {
+                return sendResponse(
+                    mgmtRequest->getID(),
+                    StatusCode::REQUESTER, StatusCode::REQUEST_DENIED, "Active session did not match NameID mgmt request.",
+                    relayState.c_str(),
+                    policy.getIssuerMetadata(),
+                    application,
+                    response,
+                    true
+                    );
+            }
+
+        }
+
+        // Determine what's happening...
+        bool ownedNewID = false;
+        NewID* newid = NULL;
+        if (!mgmtRequest->getTerminate()) {
+            // Better be a NewID in there.
+            newid = mgmtRequest->getNewID();
+            if (!newid) {
+                // Check for NewEncryptedID.
+                NewEncryptedID* encnewid = mgmtRequest->getNewEncryptedID();
+                if (encnewid) {
+                    CredentialResolver* cr=application.getCredentialResolver();
+                    if (!cr)
+                        m_log.warn("found encrypted NewID, but no decryption credential was available");
+                    else {
+                        Locker credlocker(cr);
+                        auto_ptr<MetadataCredentialCriteria> mcc(
+                            policy.getIssuerMetadata() ? new MetadataCredentialCriteria(*policy.getIssuerMetadata()) : NULL
+                            );
+                        try {
+                            auto_ptr<XMLObject> decryptedID(encnewid->decrypt(*cr,application.getXMLString("entityID").second,mcc.get()));
+                            newid = dynamic_cast<NewID*>(decryptedID.get());
+                            if (newid) {
+                                ownedNewID = true;
+                                decryptedID.release();
+                            }
+                        }
+                        catch (exception& ex) {
+                            m_log.error(ex.what());
+                        }
+                    }
+                }
+            }
+
+            if (!newid) {
+                // No NewID, so must respond with an error.
+                m_log.error("NewID not found in request");
+                return sendResponse(
+                    mgmtRequest->getID(),
+                    StatusCode::REQUESTER, NULL, "NewID not found in request.",
+                    relayState.c_str(),
+                    policy.getIssuerMetadata(),
+                    application,
+                    response,
+                    m_decoder->isUserAgentPresent()
+                    );
+            }
+        }
+
+        auto_ptr<NewID> newwrapper(ownedNewID ? newid : NULL);
+
+        // TODO: maybe support in-place modification of sessions?
+        /*
+        vector<string> sessions;
+        try {
+            time_t expires = logoutRequest->getNotOnOrAfter() ? logoutRequest->getNotOnOrAfterEpoch() : 0;
+            cache->logout(entity, *nameid, &indexes, expires, application, sessions);
+
+            // Now we actually terminate everything except for the active session,
+            // if this is front-channel, for notification purposes.
+            for (vector<string>::const_iterator sit = sessions.begin(); sit != sessions.end(); ++sit)
+                if (session_id && strcmp(sit->c_str(), session_id))
+                    cache->remove(sit->c_str(), application);
+        }
+        catch (exception& ex) {
+            m_log.error("error while logging out matching sessions: %s", ex.what());
+            return sendResponse(
+                logoutRequest->getID(),
+                StatusCode::RESPONDER, NULL, ex.what(),
+                relayState.c_str(),
+                policy.getIssuerMetadata(),
+                application,
+                response,
+                m_decoder->isUserAgentPresent()
+                );
+        }
+        */
+
+        // Do back-channel app notifications.
+        // Not supporting front-channel due to privacy fears.
+        bool worked = notifyBackChannel(application, request.getRequestURL(), *nameid, newid);
+
+        return sendResponse(
+            mgmtRequest->getID(),
+            worked ? StatusCode::SUCCESS : StatusCode::RESPONDER,
+            NULL,
+            NULL,
+            relayState.c_str(),
+            policy.getIssuerMetadata(),
+            application,
+            response,
+            m_decoder->isUserAgentPresent()
+            );
+    }
+
+    // A ManageNameIDResponse completes an SP-initiated sequence, currently not supported.
+    /*
+    const ManageNameIDResponse* mgmtResponse = dynamic_cast<ManageNameIDResponse*>(msg.get());
+    if (mgmtResponse) {
+        if (!policy.isAuthenticated()) {
+            SecurityPolicyException ex("Security of ManageNameIDResponse not established.");
+            if (policy.getIssuerMetadata())
+                annotateException(&ex, policy.getIssuerMetadata()); // throws it
+            ex.raise();
+        }
+        checkError(mgmtResponse, policy.getIssuerMetadata()); // throws if Status doesn't look good...
+
+        // Return template for completion.
+        return sendLogoutPage(application, response, false, "Global logout completed.");
+    }
+    */
+
+    FatalProfileException ex("Incoming message was not a samlp:ManageNameIDRequest.");
+    if (policy.getIssuerMetadata())
+        annotateException(&ex, policy.getIssuerMetadata()); // throws it
+    ex.raise();
+    return make_pair(false,0);  // never happen, satisfies compiler
+#else
+    throw ConfigurationException("Cannot process NameID mgmt message using lite version of shibsp library.");
+#endif
+}
+
+#ifndef SHIBSP_LITE
+
+bool SAML2NameIDMgmt::stronglyMatches(const XMLCh* idp, const XMLCh* sp, const saml2::NameID& n1, const saml2::NameID& n2) const
+{
+    if (!XMLString::equals(n1.getName(), n2.getName()))
+        return false;
+    
+    const XMLCh* s1 = n1.getFormat();
+    const XMLCh* s2 = n2.getFormat();
+    if (!s1 || !*s1)
+        s1 = saml2::NameID::UNSPECIFIED;
+    if (!s2 || !*s2)
+        s2 = saml2::NameID::UNSPECIFIED;
+    if (!XMLString::equals(s1,s2))
+        return false;
+    
+    s1 = n1.getNameQualifier();
+    s2 = n2.getNameQualifier();
+    if (!s1 || !*s1)
+        s1 = idp;
+    if (!s2 || !*s2)
+        s2 = idp;
+    if (!XMLString::equals(s1,s2))
+        return false;
+
+    s1 = n1.getSPNameQualifier();
+    s2 = n2.getSPNameQualifier();
+    if (!s1 || !*s1)
+        s1 = sp;
+    if (!s2 || !*s2)
+        s2 = sp;
+    if (!XMLString::equals(s1,s2))
+        return false;
+
+    return true;
+}
+
+pair<bool,long> SAML2NameIDMgmt::sendResponse(
+    const XMLCh* requestID,
+    const XMLCh* code,
+    const XMLCh* subcode,
+    const char* msg,
+    const char* relayState,
+    const RoleDescriptor* role,
+    const Application& application,
+    HTTPResponse& httpResponse,
+    bool front
+    ) const
+{
+    // Get endpoint and encoder to use.
+    const EndpointType* ep = NULL;
+    const MessageEncoder* encoder = NULL;
+    if (front) {
+        const IDPSSODescriptor* idp = dynamic_cast<const IDPSSODescriptor*>(role);
+        for (vector<const XMLCh*>::const_iterator b = m_bindings.begin(); idp && b!=m_bindings.end(); ++b) {
+            if (ep=EndpointManager<ManageNameIDService>(idp->getManageNameIDServices()).getByBinding(*b)) {
+                map<const XMLCh*,MessageEncoder*>::const_iterator enc = m_encoders.find(*b);
+                if (enc!=m_encoders.end())
+                    encoder = enc->second;
+                break;
+            }
+        }
+        if (!ep || !encoder) {
+            auto_ptr_char id(dynamic_cast<EntityDescriptor*>(role->getParent())->getEntityID());
+            m_log.error("unable to locate compatible NIM service for provider (%s)", id.get());
+            MetadataException ex("Unable to locate endpoint at IdP ($entityID) to send ManageNameIDResponse.");
+            annotateException(&ex, role);   // throws it
+        }
+    }
+    else {
+        encoder = m_encoders.begin()->second;
+    }
+
+    // Prepare response.
+    auto_ptr<ManageNameIDResponse> nim(ManageNameIDResponseBuilder::buildManageNameIDResponse());
+    nim->setInResponseTo(requestID);
+    if (ep) {
+        const XMLCh* loc = ep->getResponseLocation();
+        if (!loc || !*loc)
+            loc = ep->getLocation();
+        nim->setDestination(loc);
+    }
+    Issuer* issuer = IssuerBuilder::buildIssuer();
+    nim->setIssuer(issuer);
+    issuer->setName(application.getXMLString("entityID").second);
+    fillStatus(*nim.get(), code, subcode, msg);
+
+    auto_ptr_char dest(nim->getDestination());
+
+    long ret = sendMessage(*encoder, nim.get(), relayState, dest.get(), role, application, httpResponse);
+    nim.release();  // freed by encoder
+    return make_pair(true,ret);
+}
+
+#include "util/SPConstants.h"
+#include <xmltooling/impl/AnyElement.h>
+#include <xmltooling/soap/SOAP.h>
+#include <xmltooling/soap/SOAPClient.h>
+using namespace soap11;
+namespace {
+    static const XMLCh NameIDNotification[] =   UNICODE_LITERAL_18(N,a,m,e,I,D,N,o,t,i,f,i,c,a,t,i,o,n);
+
+    class SHIBSP_DLLLOCAL SOAPNotifier : public soap11::SOAPClient
+    {
+    public:
+        SOAPNotifier() {}
+        virtual ~SOAPNotifier() {}
+    private:
+        void prepareTransport(SOAPTransport& transport) {
+            transport.setVerifyHost(false);
+        }
+    };
+};
+
+bool SAML2NameIDMgmt::notifyBackChannel(
+    const Application& application, const char* requestURL, const NameID& nameid, const NewID* newid
+    ) const
+{
+    unsigned int index = 0;
+    string endpoint = application.getNotificationURL(requestURL, false, index++);
+    if (endpoint.empty())
+        return true;
+
+    auto_ptr<Envelope> env(EnvelopeBuilder::buildEnvelope());
+    Body* body = BodyBuilder::buildBody();
+    env->setBody(body);
+    ElementProxy* msg = new AnyElementImpl(shibspconstants::SHIB2SPNOTIFY_NS, NameIDNotification);
+    body->getUnknownXMLObjects().push_back(msg);
+    msg->getUnknownXMLObjects().push_back(nameid.clone());
+    if (newid)
+        msg->getUnknownXMLObjects().push_back(newid->clone());
+    else
+        msg->getUnknownXMLObjects().push_back(TerminateBuilder::buildTerminate());
+
+    bool result = true;
+    SOAPNotifier soaper;
+    while (!endpoint.empty()) {
+        try {
+            soaper.send(*env.get(), SOAPTransport::Address(application.getId(), application.getId(), endpoint.c_str()));
+            delete soaper.receive();
+        }
+        catch (exception& ex) {
+            m_log.error("error notifying application of logout event: %s", ex.what());
+            result = false;
+        }
+        soaper.reset();
+        endpoint = application.getNotificationURL(requestURL, false, index++);
+    }
+    return result;
+}
+
+#endif
index 573c172..e496b33 100644 (file)
                                                >\r
                                        </File>\r
                                        <File\r
+                                               RelativePath=".\handler\impl\SAML2NameIDMgmt.cpp"\r
+                                               >\r
+                                       </File>\r
+                                       <File\r
                                                RelativePath=".\handler\impl\SAML2SessionInitiator.cpp"\r
                                                >\r
                                        </File>\r
index 523f209..d631ad3 100644 (file)
                                                >\r
                                        </File>\r
                                        <File\r
+                                               RelativePath=".\handler\impl\SAML2NameIDMgmt.cpp"\r
+                                               >\r
+                                       </File>\r
+                                       <File\r
                                                RelativePath=".\handler\impl\SAML2SessionInitiator.cpp"\r
                                                >\r
                                        </File>\r