First cut at logout race detection in cache.
authorcantor <cantor@cb58f699-b61c-0410-a6fe-9272a202ed29>
Tue, 17 Jul 2007 22:22:45 +0000 (22:22 +0000)
committercantor <cantor@cb58f699-b61c-0410-a6fe-9272a202ed29>
Tue, 17 Jul 2007 22:22:45 +0000 (22:22 +0000)
Back-channel logout notifier using SOAP.

git-svn-id: https://svn.middleware.georgetown.edu/cpp-sp/trunk@2362 cb58f699-b61c-0410-a6fe-9272a202ed29

13 files changed:
schemas/Makefile.am
schemas/catalog.xml.in
schemas/shibboleth-2.0-sp-notify.xsd [new file with mode: 0644]
shibsp/Application.h
shibsp/handler/LogoutHandler.h
shibsp/handler/impl/LocalLogoutInitiator.cpp
shibsp/handler/impl/LogoutHandler.cpp
shibsp/handler/impl/SAML2Logout.cpp
shibsp/handler/impl/SAML2LogoutInitiator.cpp
shibsp/impl/StorageServiceSessionCache.cpp
shibsp/impl/XMLServiceProvider.cpp
shibsp/util/SPConstants.cpp
shibsp/util/SPConstants.h

index 7a52a98..3fd6989 100644 (file)
@@ -12,6 +12,7 @@ schemafiles = \
     shibboleth-2.0-afp-mf-basic.xsd \
     shibboleth-2.0-afp-mf-saml.xsd \
     shibboleth-2.0-attribute-map.xsd \
+    shibboleth-2.0-sp-notify.xsd \
     metadata_v12_to_v13.xsl \
     metadata_v13_to_v12.xsl \
     trust_v13_to_v12.xsl
index e633dd3..115473e 100644 (file)
@@ -6,5 +6,6 @@
     <system systemId="urn:mace:shibboleth:2.0:afp:mf:basic" uri="@-PKGXMLDIR-@/shibboleth-2.0-afp-mf-basic.xsd"/>
     <system systemId="urn:mace:shibboleth:2.0:afp:mf:saml" uri="@-PKGXMLDIR-@/shibboleth-2.0-afp-mf-saml.xsd"/>
     <system systemId="urn:mace:shibboleth:2.0:attribute-map" uri="@-PKGXMLDIR-@/shibboleth-2.0-attribute-map.xsd"/>
+    <system systemId="urn:mace:shibboleth:2.0:sp:notify" uri="@-PKGXMLDIR-@/shibboleth-2.0-sp-notify.xsd"/>
     <system systemId="urn:mace:shibboleth:1.0" uri="@-PKGXMLDIR-@/shibboleth.xsd"/>
 </catalog>
diff --git a/schemas/shibboleth-2.0-sp-notify.xsd b/schemas/shibboleth-2.0-sp-notify.xsd
new file mode 100644 (file)
index 0000000..8160ed6
--- /dev/null
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-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"
+        elementFormDefault="qualified">
+
+    <annotation>
+        <documentation>
+            Defines local application notification protocol used by SP.
+        </documentation>
+    </annotation>
+
+    <simpleType name="string">
+        <restriction base="string">
+            <minLength value="1"/>
+        </restriction>
+    </simpleType>
+
+    <element name="OK" type="notify:OKType"/>
+    <complexType name="OKType">
+        <sequence/>
+    </complexType>
+
+    <element name="LogoutNotification" type="notify:LogoutNotificationType"/>
+    <complexType name="LogoutNotificationType">
+        <annotation>
+            <documentation>Notifies application of a logout event.</documentation>
+        </annotation>
+        <sequence>
+            <element name="SessionID" type="notify:string" maxOccurs="unbounded"/>
+        </sequence>
+        <attribute name="type">
+            <simpleType>
+                <restriction base="string">
+                    <enumeration value="local"/>
+                    <enumeration value="global"/>
+                </restriction>
+            </simpleType>
+        </attribute>
+    </complexType>
+
+</schema>
index ae6e76f..118467f 100644 (file)
@@ -156,12 +156,12 @@ namespace shibsp {
         /**
          * Returns the designated notification URL, or an empty string if no more locations are specified.
          *
-         * @param request   SP request to use to fill in missing pieces of URL
+         * @param request   requested URL to use to fill in missing pieces of notification URL
          * @param front     true iff front channel notification is desired, false iff back channel is desired
          * @param index     zero-based index of URL to return
          * @return  the designated URL, or an empty string
          */
-        virtual std::string getNotificationURL(const xmltooling::HTTPRequest& request, bool front, unsigned int index) const=0;
+        virtual std::string getNotificationURL(const char* request, bool front, unsigned int index) const=0;
 
         /**
          * Returns a set of attribute IDs to use as a REMOTE_USER value.
index 8bcf05e..8c79666 100644 (file)
@@ -109,10 +109,14 @@ namespace shibsp {
          * Perform back-channel logout notifications for an Application.
          *
          * @param application   the Application to notify
+         * @param requestURL    requestURL that resulted in method call
          * @param sessions      array of session keys being logged out
+         * @param local         true iff the logout operation is local to the SP, false iff global
          * @return  true iff all notifications succeeded
          */
-        bool notifyBackChannel(const Application& application, const std::vector<std::string>& sessions) const;
+        bool notifyBackChannel(
+            const Application& application, const char* requestURL, const std::vector<std::string>& sessions, bool local
+            ) const;
     };
 
 #if defined (_MSC_VER)
index 687dd32..aa51435 100644 (file)
@@ -99,7 +99,7 @@ pair<bool,long> LocalLogoutInitiator::run(SPRequest& request, bool isHandler) co
     if (session_id) {
         // Do back channel notification.
         vector<string> sessions(1, session_id);
-        if (!notifyBackChannel(request.getApplication(), sessions)) {
+        if (!notifyBackChannel(request.getApplication(), request.getRequestURL(), sessions, true)) {
             request.getApplication().getServiceProvider().getSessionCache()->remove(session_id, request.getApplication());
             return sendLogoutPage(request.getApplication(), request, true, "Partial logout failure.");
         }
index e363c05..901e45b 100644 (file)
 #include <xmltooling/XMLToolingConfig.h>
 #include <xmltooling/util/URLEncoder.h>
 
-#ifndef SHIBSP_LITE
-#endif
-
 using namespace shibsp;
 using namespace xmltooling;
 using namespace std;
 
+pair<bool,long> LogoutHandler::sendLogoutPage(const Application& application, HTTPResponse& response, bool local, const char* status) const
+{
+    pair<bool,const char*> prop = application.getString(local ? "localLogout" : "globalLogout");
+    if (prop.first) {
+        response.setContentType("text/html");
+        response.setResponseHeader("Expires","01-Jan-1997 12:00:00 GMT");
+        response.setResponseHeader("Cache-Control","private,no-store,no-cache");
+        ifstream infile(prop.second);
+        if (!infile)
+            throw ConfigurationException("Unable to access $1 HTML template.", params(1,local ? "localLogout" : "globalLogout"));
+        TemplateParameters tp;
+        tp.setPropertySet(application.getPropertySet("Errors"));
+        if (status)
+            tp.m_map["logoutStatus"] = status;
+        stringstream str;
+        XMLToolingConfig::getConfig().getTemplateEngine()->run(infile, str, tp);
+        return make_pair(true,response.sendResponse(str));
+    }
+    prop = application.getString("homeURL");
+    if (!prop.first)
+        prop.second = "/";
+    return make_pair(true,response.sendRedirect(prop.second));
+}
+
 pair<bool,long> LogoutHandler::run(SPRequest& request, bool isHandler) const
 {
     // If we're inside a chain, so do nothing.
@@ -80,7 +101,7 @@ void LogoutHandler::receive(DDF& in, ostream& out)
     while (temp.isstring()) {
         sessions.push_back(temp.string());
         temp = s.next();
-        if (notifyBackChannel(*app, sessions))
+        if (notifyBackChannel(*app, in["url"].string(), sessions, in["local"].integer()==1))
             ret.integer(1);
     }
 
@@ -101,7 +122,7 @@ pair<bool,long> LogoutHandler::notifyFrontChannel(
         index = atoi(param);
 
     // Fetch the next front notification URL and bump the index for the next round trip.
-    string loc = application.getNotificationURL(request, true, index++);
+    string loc = application.getNotificationURL(request.getRequestURL(), true, index++);
     if (loc.empty())
         return make_pair(false,0);
 
@@ -137,11 +158,71 @@ pair<bool,long> LogoutHandler::notifyFrontChannel(
     return make_pair(true,response.sendRedirect(loc.c_str()));
 }
 
-bool LogoutHandler::notifyBackChannel(const Application& application, const vector<string>& sessions) const
+#ifndef SHIBSP_LITE
+#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 LogoutNotification[] =   UNICODE_LITERAL_18(L,o,g,o,u,t,N,o,t,i,f,i,c,a,t,i,o,n);
+    static const XMLCh SessionID[] =            UNICODE_LITERAL_9(S,e,s,s,i,o,n,I,D);
+    static const XMLCh _type[] =                UNICODE_LITERAL_4(t,y,p,e);
+    static const XMLCh _local[] =               UNICODE_LITERAL_5(l,o,c,a,l);
+    static const XMLCh _global[] =              UNICODE_LITERAL_6(g,l,o,b,a,l);
+
+    class SHIBSP_DLLLOCAL SOAPNotifier : public soap11::SOAPClient
+    {
+    public:
+        SOAPNotifier() {}
+        virtual ~SOAPNotifier() {}
+    private:
+        void prepareTransport(SOAPTransport& transport) {
+            transport.setVerifyHost(false);
+        }
+    };
+};
+#endif
+
+bool LogoutHandler::notifyBackChannel(
+    const Application& application, const char* requestURL, const vector<string>& sessions, bool local
+    ) const
 {
+    unsigned int index = 0;
+    string endpoint = application.getNotificationURL(requestURL, false, index++);
+    if (endpoint.empty())
+        return true;
+
     if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
 #ifndef SHIBSP_LITE
-        return true;
+        auto_ptr<Envelope> env(EnvelopeBuilder::buildEnvelope());
+        Body* body = BodyBuilder::buildBody();
+        env->setBody(body);
+        ElementProxy* msg = new AnyElementImpl(shibspconstants::SHIB2SPNOTIFY_NS, LogoutNotification);
+        body->getUnknownXMLObjects().push_back(msg);
+        msg->setAttribute(QName(NULL, _type), local ? _local : _global);
+        for (vector<string>::const_iterator s = sessions.begin(); s!=sessions.end(); ++s) {
+            auto_ptr_XMLCh temp(s->c_str());
+            ElementProxy* child = new AnyElementImpl(shibspconstants::SHIB2SPNOTIFY_NS, SessionID);
+            child->setTextContent(temp.get());
+            msg->getUnknownXMLObjects().push_back(child);
+        }
+
+        bool result = true;
+        SOAPNotifier soaper;
+        while (!endpoint.empty()) {
+            try {
+                soaper.send(*env.get(), application.getId(), endpoint.c_str());
+                delete soaper.receive();
+            }
+            catch (exception& ex) {
+                log4cpp::Category::getInstance(SHIBSP_LOGCAT".Logout").error("error notifying application of logout event: %s", ex.what());
+                result = false;
+            }
+            soaper.reset();
+            endpoint = application.getNotificationURL(requestURL, false, index++);
+        }
+        return result;
 #else
         return false;
 #endif
@@ -152,6 +233,9 @@ bool LogoutHandler::notifyBackChannel(const Application& application, const vect
     DDFJanitor jin(in), jout(out);
     in.addmember("notify").integer(1);
     in.addmember("application_id").string(application.getId());
+    in.addmember("url").string(requestURL);
+    if (local)
+        in.addmember("local").integer(1);
     DDF s = in.addmember("sessions").list();
     for (vector<string>::const_iterator i = sessions.begin(); i!=sessions.end(); ++i) {
         DDF temp = DDF(NULL).string(i->c_str());
@@ -160,27 +244,3 @@ bool LogoutHandler::notifyBackChannel(const Application& application, const vect
     out=application.getServiceProvider().getListenerService()->send(in);
     return (out.integer() == 1);
 }
-
-pair<bool,long> LogoutHandler::sendLogoutPage(const Application& application, HTTPResponse& response, bool local, const char* status) const
-{
-    pair<bool,const char*> prop = application.getString(local ? "localLogout" : "globalLogout");
-    if (prop.first) {
-        response.setContentType("text/html");
-        response.setResponseHeader("Expires","01-Jan-1997 12:00:00 GMT");
-        response.setResponseHeader("Cache-Control","private,no-store,no-cache");
-        ifstream infile(prop.second);
-        if (!infile)
-            throw ConfigurationException("Unable to access $1 HTML template.", params(1,local ? "localLogout" : "globalLogout"));
-        TemplateParameters tp;
-        tp.setPropertySet(application.getPropertySet("Errors"));
-        if (status)
-            tp.m_map["logoutStatus"] = status;
-        stringstream str;
-        XMLToolingConfig::getConfig().getTemplateEngine()->run(infile, str, tp);
-        return make_pair(true,response.sendResponse(str));
-    }
-    prop = application.getString("homeURL");
-    if (!prop.first)
-        prop.second = "/";
-    return make_pair(true,response.sendRedirect(prop.second));
-}
index 906b9b8..6bd06cc 100644 (file)
@@ -252,7 +252,7 @@ pair<bool,long> SAML2Logout::doRequest(
         bool worked1 = false,worked2 = false;
         if (session_id) {
             vector<string> sessions(1,session_id);
-            worked1 = notifyBackChannel(application, sessions);
+            worked1 = notifyBackChannel(application, request.getRequestURL(), sessions, false);
             try {
                 cache->remove(session_id, application);
                 worked2 = true;
@@ -452,7 +452,7 @@ pair<bool,long> SAML2Logout::doRequest(
         
         // For back-channel requests, or if no front-channel notification is needed...
         bool worked1 = false,worked2 = false;
-        worked1 = notifyBackChannel(application, sessions);
+        worked1 = notifyBackChannel(application, request.getRequestURL(), sessions, false);
         if (session_id) {
             // One last session to yoink...
             try {
index c8b410b..dac3946 100644 (file)
@@ -73,7 +73,7 @@ namespace shibsp {
         pair<bool,long> run(SPRequest& request, bool isHandler=true) const;
 
     private:
-        pair<bool,long> doRequest(const Application& application, Session* session_id, HTTPResponse& httpResponse) const;
+        pair<bool,long> doRequest(const Application& application, const char* requestURL, Session* session_id, HTTPResponse& httpResponse) const;
 
         string m_appId;
 #ifndef SHIBSP_LITE
@@ -194,7 +194,7 @@ pair<bool,long> SAML2LogoutInitiator::run(SPRequest& request, bool isHandler) co
 
     if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
         // When out of process, we run natively.
-        return doRequest(request.getApplication(), session, request);
+        return doRequest(request.getApplication(), request.getRequestURL(), session, request);
     }
     else {
         // When not out of process, we remote the request.
@@ -203,6 +203,7 @@ pair<bool,long> SAML2LogoutInitiator::run(SPRequest& request, bool isHandler) co
         DDFJanitor jin(in), jout(out);
         in.addmember("application_id").string(request.getApplication().getId());
         in.addmember("session_id").string(session->getID());
+        in.addmember("url").string(request.getRequestURL());
         out=request.getServiceProvider().getListenerService()->send(in);
         return unwrap(request, out);
     }
@@ -243,7 +244,7 @@ void SAML2LogoutInitiator::receive(DDF& in, ostream& out)
             // 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, session, *resp.get());
+            doRequest(*app, in["url"].string(), session, *resp.get());
         }
         else {
              m_log.error("no NameID or issuing entityID found in session");
@@ -258,11 +259,13 @@ void SAML2LogoutInitiator::receive(DDF& in, ostream& out)
 #endif
 }
 
-pair<bool,long> SAML2LogoutInitiator::doRequest(const Application& application, Session* session, HTTPResponse& response) const
+pair<bool,long> SAML2LogoutInitiator::doRequest(
+    const Application& application, const char* requestURL, Session* session, HTTPResponse& response
+    ) const
 {
     // Do back channel notification.
     vector<string> sessions(1, session->getID());
-    if (!notifyBackChannel(application, sessions)) {
+    if (!notifyBackChannel(application, requestURL, sessions, false)) {
         session->unlock();
         application.getServiceProvider().getSessionCache()->remove(sessions.front().c_str(), application);
         return sendLogoutPage(application, response, true, "Partial logout failure.");
index b64f7d3..c41f599 100644 (file)
@@ -487,7 +487,7 @@ void SSCache::insert(const char* key, time_t expires, const char* name, const ch
     // Since we can't guarantee uniqueness, check for an existing record.
     string record;
     time_t recordexp;
-    int ver = m_storage->readText("Logout", name, &record, &recordexp);
+    int ver = m_storage->readText("NameID", name, &record, &recordexp);
     if (ver > 0) {
         // Existing record, so we need to unmarshall it.
         istringstream in(record);
@@ -512,13 +512,13 @@ void SSCache::insert(const char* key, time_t expires, const char* name, const ch
 
     // Try and store it back...
     if (ver > 0) {
-        ver = m_storage->updateText("Logout", name, out.str().c_str(), max(expires, recordexp), ver);
+        ver = m_storage->updateText("NameID", name, out.str().c_str(), max(expires, recordexp), ver);
         if (ver <= 0) {
             // Out of sync, or went missing, so retry.
             return insert(key, expires, name, index);
         }
     }
-    else if (!m_storage->createText("Logout", name, out.str().c_str(), expires)) {
+    else if (!m_storage->createText("NameID", name, out.str().c_str(), expires)) {
         // Hit a dup, so just retry, hopefully hitting the other branch.
         return insert(key, expires, name, index);
     }
@@ -545,6 +545,38 @@ string SSCache::insert(
 
     m_log.debug("creating new session");
 
+    time_t now = time(NULL);
+    auto_ptr_char index(session_index);
+    auto_ptr_char entity_id(issuer ? issuer->getEntityID() : NULL);
+    auto_ptr_char name(nameid ? nameid->getName() : NULL);
+
+    if (nameid) {
+        // Check for a pending logout.
+        if (strlen(name.get()) > 255)
+            const_cast<char*>(name.get())[255] = 0;
+        string pending;
+        int ver = m_storage->readText("Logout", name.get(), &pending);
+        if (ver > 0) {
+            DDF pendobj;
+            DDFJanitor jpend(pendobj);
+            istringstream pstr(pending);
+            pstr >> pendobj;
+            // IdP.SP.index contains logout expiration, if any.
+            DDF deadmenwalking = pendobj[issuer ? entity_id.get() : "_shibnull"][application.getString("entityID").second];
+            const char* logexpstr = deadmenwalking[session_index ? index.get() : "_shibnull"].string();
+            if (!logexpstr && session_index)    // we tried an exact session match, now try for NULL
+                logexpstr = deadmenwalking["_shibnull"].string();
+            if (logexpstr) {
+                auto_ptr_XMLCh dt(logexpstr);
+                DateTime dtobj(dt.get());
+                dtobj.parseDateTime();
+                time_t logexp = dtobj.getEpoch();
+                if (now - XMLToolingConfig::getConfig().clock_skew_secs < logexp)
+                    throw FatalProfileException("A logout message from your identity provider has blocked your login attempt.");
+            }
+        }
+    }
+
     auto_ptr_char key(SAMLConfig::getConfig().generateIdentifier());
 
     // Store session properties in DDF.
@@ -552,7 +584,7 @@ string SSCache::insert(
     obj.addmember("version").integer(1);
     obj.addmember("application_id").string(application.getId());
 
-    // On 64-bit Windows, time_t doesn't fit in a long, so I'm using ISO timestamps.  
+    // On 64-bit Windows, time_t doesn't fit in a long, so I'm using ISO timestamps.
 #ifndef HAVE_GMTIME_R
     struct tm* ptime=gmtime(&expires);
 #else
@@ -565,10 +597,8 @@ string SSCache::insert(
 
     if (client_addr)
         obj.addmember("client_addr").string(client_addr);
-    if (issuer) {
-        auto_ptr_char entity_id(issuer->getEntityID());
+    if (issuer)
         obj.addmember("entity_id").string(entity_id.get());
-    }
     if (protocol) {
         auto_ptr_char prot(protocol);
         obj.addmember("protocol").string(prot.get());
@@ -577,7 +607,6 @@ string SSCache::insert(
         auto_ptr_char instant(authn_instant);
         obj.addmember("authn_instant").string(instant.get());
     }
-    auto_ptr_char index(session_index);
     if (session_index)
         obj.addmember("session_index").string(index.get());
     if (authncontext_class) {
@@ -617,14 +646,12 @@ string SSCache::insert(
     record << obj;
     
     m_log.debug("storing new session...");
-    time_t now = time(NULL);
     if (!m_storage->createText(key.get(), "session", record.str().c_str(), now + m_cacheTimeout))
         throw FatalProfileException("Attempted to create a session with a duplicate key.");
     
     // Store the reverse mapping for logout.
-    auto_ptr_char name(nameid ? nameid->getName() : NULL);
     try {
-        if (name.get())
+        if (nameid)
             insert(key.get(), expires, name.get(), index.get());
     }
     catch (exception& ex) {
@@ -662,7 +689,7 @@ string SSCache::insert(
         ") at (ClientAddress: " <<
             (client_addr ? client_addr : "none") <<
         ") with (NameIdentifier: " <<
-            (name.get() ? name.get() : "none") <<
+            (nameid ? name.get() : "none") <<
         ")";
     
     if (attributes) {
@@ -840,16 +867,70 @@ vector<string>::size_type SSCache::logout(
     if (strlen(name.get()) > 255)
         const_cast<char*>(name.get())[255] = 0;
 
-    // Read in potentially matching sessions.
+    DDF obj;
+    DDFJanitor jobj(obj);
     string record;
-    int ver = m_storage->readText("Logout", name.get(), &record);
+    int ver;
+
+    if (expires) {
+        // Record the logout to prevent post-delivered assertions.
+        // On 64-bit Windows, time_t doesn't fit in a long, so I'm using ISO timestamps.
+#ifndef HAVE_GMTIME_R
+        struct tm* ptime=gmtime(&expires);
+#else
+        struct tm res;
+        struct tm* ptime=gmtime_r(&expires,&res);
+#endif
+        char timebuf[32];
+        strftime(timebuf,32,"%Y-%m-%dT%H:%M:%SZ",ptime);
+
+        time_t oldexp = 0;
+        ver = m_storage->readText("Logout", name.get(), &record, &oldexp);
+        if (ver > 0) {
+            istringstream lin(record);
+            lin >> obj;
+        }
+        else {
+            obj = DDF(NULL).structure();
+        }
+
+        // Structure is keyed by the IdP and SP, with a member per session index containing the expiration.
+        DDF root = obj.addmember(issuer ? entityID.get() : "_shibnull").addmember(application.getString("entityID").second);
+        if (indexes) {
+            for (set<string>::const_iterator x = indexes->begin(); x!=indexes->end(); ++x)
+                root.addmember(x->c_str()).string(timebuf);
+        }
+        else {
+            root.addmember("_shibnull").string(timebuf);
+        }
+
+        // Write it back.
+        ostringstream lout;
+        lout << obj;
+
+        if (ver > 0) {
+            ver = m_storage->updateText("Logout", name.get(), lout.str().c_str(), max(expires, oldexp), ver);
+            if (ver <= 0) {
+                // Out of sync, or went missing, so retry.
+                return logout(issuer, nameid, indexes, expires, application, sessionsKilled);
+            }
+        }
+        else if (!m_storage->createText("Logout", name.get(), lout.str().c_str(), expires)) {
+            // Hit a dup, so just retry, hopefully hitting the other branch.
+            return logout(issuer, nameid, indexes, expires, application, sessionsKilled);
+        }
+
+        obj.destroy();
+        record.erase();
+    }
+
+    // Read in potentially matching sessions.
+    ver = m_storage->readText("NameID", name.get(), &record);
     if (ver == 0) {
         m_log.debug("no active sessions to logout for supplied issuer and subject");
         return 0;
     }
 
-    DDF obj;
-    DDFJanitor jobj(obj);
     istringstream in(record);
     in >> obj;
 
@@ -907,12 +988,12 @@ vector<string>::size_type SSCache::logout(
     // If possible, write back the mapping record (this isn't crucial).
     try {
         if (obj.isnull()) {
-            m_storage->deleteText("Logout", name.get());
+            m_storage->deleteText("NameID", name.get());
         }
         else if (!sessionsKilled.empty()) {
             ostringstream out;
             out << obj;
-            if (m_storage->updateText("Logout", name.get(), out.str().c_str(), 0, ver) <= 0)
+            if (m_storage->updateText("NameID", name.get(), out.str().c_str(), 0, ver) <= 0)
                 m_log.warn("logout mapping record changed behind us, leaving it alone");
         }
     }
index 39c1df4..cdbbefc 100644 (file)
@@ -129,7 +129,7 @@ namespace {
             return (m_audiences.empty() && m_base) ? m_base->getAudiences() : m_audiences;
         }
 #endif
-        string getNotificationURL(const HTTPRequest& request, bool front, unsigned int index) const;
+        string getNotificationURL(const char* resource, bool front, unsigned int index) const;
 
         const set<string>& getRemoteUserAttributeIds() const {
             return (m_remoteUsers.empty() && m_base) ? m_base->getRemoteUserAttributeIds() : m_remoteUsers;
@@ -949,15 +949,14 @@ const PropertySet* XMLApplication::getRelyingParty(const EntityDescriptor* provi
 
 #endif
 
-string XMLApplication::getNotificationURL(const HTTPRequest& request, bool front, unsigned int index) const
+string XMLApplication::getNotificationURL(const char* resource, bool front, unsigned int index) const
 {
     const vector<string>& locs = front ? m_frontLogout : m_backLogout;
     if (locs.empty())
-        return m_base ? m_base->getNotificationURL(request, front, index) : string();
+        return m_base ? m_base->getNotificationURL(resource, front, index) : string();
     else if (index >= locs.size())
         return string();
 
-    const char* resource = request.getRequestURL();
 #ifdef HAVE_STRCASECMP
     if (!resource || (strncasecmp(resource,"http://",7) && strncasecmp(resource,"https://",8)))
 #else
index 48d991c..78c140a 100644 (file)
@@ -56,6 +56,13 @@ const XMLCh shibspconstants::SHIB2ATTRIBUTEMAP_NS[] = // urn:mace:shibboleth:2.0
   chLatin_m, chLatin_a, chLatin_p, chNull\r
 };\r
 \r
+const XMLCh shibspconstants::SHIB2SPNOTIFY_NS[] = // urn:mace:shibboleth:2.0:sp:notify\r
+{ chLatin_u, chLatin_r, chLatin_n, chColon, chLatin_m, chLatin_a, chLatin_c, chLatin_e, chColon,\r
+  chLatin_s, chLatin_h, chLatin_i, chLatin_b, chLatin_b, chLatin_o, chLatin_l, chLatin_e, chLatin_t, chLatin_h, chColon,\r
+  chDigit_2, chPeriod, chDigit_0, chColon, chLatin_s, chLatin_p, chColon,\r
+  chLatin_n, chLatin_o, chLatin_t, chLatin_i, chLatin_f, chLatin_y, chNull\r
+};\r
+\r
 const XMLCh shibspconstants::SHIB2ATTRIBUTEFILTER_NS[] = // urn:mace:shibboleth:2.0:afp\r
 { chLatin_u, chLatin_r, chLatin_n, chColon, chLatin_m, chLatin_a, chLatin_c, chLatin_e, chColon,\r
   chLatin_s, chLatin_h, chLatin_i, chLatin_b, chLatin_b, chLatin_o, chLatin_l, chLatin_e, chLatin_t, chLatin_h, chColon,\r
index 31c2097..92689a0 100644 (file)
@@ -47,6 +47,9 @@ namespace shibspconstants {
     /** Shibboleth 2.0 attribute mapping namespace ("urn:mace:shibboleth:2.0:attribute-map") */
     extern SHIBSP_API const XMLCh SHIB2ATTRIBUTEMAP_NS[];
 
+    /** Shibboleth 2.0 notification namespace ("urn:mace:shibboleth:2.0:sp:notify") */
+    extern SHIBSP_API const XMLCh SHIB2SPNOTIFY_NS[];
+
     /** Shibboleth 2.0 attribute filter policy namespace ("urn:mace:shibboleth:2.0:afp") */
     extern SHIBSP_API const XMLCh SHIB2ATTRIBUTEFILTER_NS[];