[cpp-sp] branch master updated: SSPCPP-798 - Administrative logout

Scott Cantor cantor.2 at osu.edu
Wed Apr 25 20:02:22 EDT 2018


This is an automated email from the git hooks/post-receive script.

scantor pushed a commit to branch master
in repository cpp-sp.

View the commit online:
http://git.shibboleth.net/view/?p=cpp-sp.git;a=commit;h=dd51ee26c29d29304288bde6b7d13ae45f26355b

The following commit(s) were added to refs/heads/master by this push:
       new  dd51ee2   SSPCPP-798 - Administrative logout
dd51ee2 is described below

commit dd51ee26c29d29304288bde6b7d13ae45f26355b
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Wed Apr 25 20:01:24 2018 -0400

    SSPCPP-798 - Administrative logout
    
    https://issues.shibboleth.net/jira/browse/SSPCPP-798
    
    Missing file, plus revocation features to prevent recovery.
---
 adfs/adfs.cpp                                   |  15 +-
 shibsp/Makefile.am                              |  22 +-
 shibsp/SessionCache.h                           |  11 +-
 shibsp/handler/impl/AdminLogoutInitiator.cpp    | 467 ++++++++++++++++++++++++
 shibsp/handler/impl/AttributeCheckerHandler.cpp |  18 +-
 shibsp/handler/impl/LocalLogoutInitiator.cpp    |   7 +-
 shibsp/handler/impl/SAML2Logout.cpp             |  10 +-
 shibsp/handler/impl/SAML2LogoutInitiator.cpp    |  22 +-
 shibsp/impl/StorageServiceSessionCache.cpp      | 113 ++++--
 shibsp/impl/StorageServiceSessionCache.h        |  20 +-
 10 files changed, 620 insertions(+), 85 deletions(-)

diff --git a/adfs/adfs.cpp b/adfs/adfs.cpp
index ed3d681..0158b03 100644
--- a/adfs/adfs.cpp
+++ b/adfs/adfs.cpp
@@ -918,8 +918,9 @@ void ADFSLogoutInitiator::receive(DDF& in, ostream& out)
         }
         else {
             m_log.error("no issuing entityID found in session");
+            time_t revocationExp = session->getExpiration();
             session->unlock();
-            app->getServiceProvider().getSessionCache()->remove(*app, *req, resp.get());
+            app->getServiceProvider().getSessionCache()->remove(*app, *req, resp.get(), revocationExp);
         }
     }
     out << ret;
@@ -944,9 +945,10 @@ pair<bool,long> ADFSLogoutInitiator::doRequest(
             application.getServiceProvider().getTransactionLog()->write(*logout_event);
         }
 #endif
+        time_t revocationExp = session->getExpiration();
         sessionLocker.assign();
         session = nullptr;
-        application.getServiceProvider().getSessionCache()->remove(application, httpRequest, &httpResponse);
+        application.getServiceProvider().getSessionCache()->remove(application, httpRequest, &httpResponse, revocationExp);
         return sendLogoutPage(application, httpRequest, httpResponse, "partial");
     }
 
@@ -1008,16 +1010,17 @@ pair<bool,long> ADFSLogoutInitiator::doRequest(
         ret.first = true;
 
         if (session) {
+            time_t revocationExp = session->getExpiration();
             sessionLocker.assign();
             session = nullptr;
-            application.getServiceProvider().getSessionCache()->remove(application, httpRequest, &httpResponse);
+            application.getServiceProvider().getSessionCache()->remove(application, httpRequest, &httpResponse, revocationExp);
         }
     }
-    catch (MetadataException& mex) {
+    catch (const MetadataException& mex) {
         // Less noise for IdPs that don't support logout
         m_log.info("unable to issue ADFS logout request: %s", mex.what());
     }
-    catch (std::exception& ex) {
+    catch (const std::exception& ex) {
         m_log.error("error issuing ADFS logout request: %s", ex.what());
     }
 
@@ -1070,7 +1073,7 @@ pair<bool,long> ADFSLogout::run(SPRequest& request, bool isHandler) const
         try {
             app.getServiceProvider().getSessionCache()->remove(app, request, &request);
         }
-        catch (std::exception& ex) {
+        catch (const std::exception& ex) {
             m_log.error("error removing session (%s): %s", session_id.c_str(), ex.what());
         }
     }
diff --git a/shibsp/Makefile.am b/shibsp/Makefile.am
index 5a19269..f35f86b 100644
--- a/shibsp/Makefile.am
+++ b/shibsp/Makefile.am
@@ -75,7 +75,7 @@ handinclude_HEADERS = \
 	handler/LogoutHandler.h \
 	handler/LogoutInitiator.h \
 	handler/RemotedHandler.h \
-    handler/SecuredHandler.h \
+	handler/SecuredHandler.h \
 	handler/SessionInitiator.h
 
 liteinclude_HEADERS = \
@@ -105,8 +105,8 @@ utilinclude_HEADERS = \
 
 noinst_HEADERS = \
 	internal.h \
-    impl/StoredSession.h \
-    impl/StorageServiceSessionCache.h \
+	impl/StoredSession.h \
+	impl/StorageServiceSessionCache.h \
 	remoting/impl/SocketListener.h
 
 common_sources = \
@@ -114,7 +114,7 @@ common_sources = \
 	Application.cpp \
 	ServiceProvider.cpp \
 	SPConfig.cpp \
-    version.cpp \
+	version.cpp \
 	attribute/Attribute.cpp \
 	attribute/BinaryAttribute.cpp \
 	attribute/ExtensibleAttribute.cpp \
@@ -124,15 +124,15 @@ common_sources = \
 	attribute/XMLAttribute.cpp \
 	binding/impl/XMLProtocolProvider.cpp \
 	handler/impl/AbstractHandler.cpp \
-	handler/impl/AdminLogout.cpp \
+	handler/impl/AdminLogoutInitiator.cpp \
 	handler/impl/AssertionConsumerService.cpp \
 	handler/impl/AssertionLookup.cpp \
-    handler/impl/AttributeCheckerHandler.cpp \
+	handler/impl/AttributeCheckerHandler.cpp \
 	handler/impl/ChainingLogoutInitiator.cpp \
 	handler/impl/ChainingSessionInitiator.cpp \
 	handler/impl/CookieSessionInitiator.cpp \
 	handler/impl/DiscoveryFeed.cpp \
-    handler/impl/ExternalAuthHandler.cpp \
+	handler/impl/ExternalAuthHandler.cpp \
 	handler/impl/FormSessionInitiator.cpp \
 	handler/impl/LocalLogoutInitiator.cpp \
 	handler/impl/LogoutHandler.cpp \
@@ -147,7 +147,7 @@ common_sources = \
 	handler/impl/SAML2NameIDMgmt.cpp \
 	handler/impl/SAML2SessionInitiator.cpp \
 	handler/impl/SAMLDSSessionInitiator.cpp \
-    handler/impl/SecuredHandler.cpp \
+	handler/impl/SecuredHandler.cpp \
 	handler/impl/SessionHandler.cpp \
 	handler/impl/SessionInitiator.cpp \
 	handler/impl/Shib1SessionInitiator.cpp \
@@ -155,8 +155,8 @@ common_sources = \
 	handler/impl/TransformSessionInitiator.cpp \
 	handler/impl/WAYFSessionInitiator.cpp \
 	impl/ChainingAccessControl.cpp \
-    impl/StoredSession.cpp \
-    impl/StorageServiceSessionCache.cpp \
+	impl/StoredSession.cpp \
+	impl/StorageServiceSessionCache.cpp \
 	impl/XMLAccessControl.cpp \
 	impl/XMLRequestMapper.cpp \
 	impl/XMLServiceProvider.cpp \
@@ -215,7 +215,7 @@ libshibsp_la_SOURCES = \
 	attribute/filtering/impl/AttributeIssuerEntityMatcherFunctor.cpp \
 	attribute/filtering/impl/AttributeRequesterEntityMatcherFunctor.cpp \
 	attribute/filtering/impl/AttributeMatchesShibMDScopeFunctor.cpp \
-    attribute/filtering/impl/RegistrationAuthorityFunctor.cpp \
+	attribute/filtering/impl/RegistrationAuthorityFunctor.cpp \
 	attribute/resolver/impl/ChainingAttributeResolver.cpp \
 	attribute/resolver/impl/QueryAttributeResolver.cpp \
 	attribute/resolver/impl/SimpleAggregationAttributeResolver.cpp \
diff --git a/shibsp/SessionCache.h b/shibsp/SessionCache.h
index 4977dba..4ca7cb8 100644
--- a/shibsp/SessionCache.h
+++ b/shibsp/SessionCache.h
@@ -355,14 +355,18 @@ namespace shibsp {
         /**
          * Deletes an existing session bound to a request.
          *
+         * <p>Revocation may be supported by some implementations.</p>
+         *
          * @param application   reference to Application that owns the Session
          * @param request       request from client containing session, or a reference to it
          * @param response      optional response to client enabling removal of session or reference
+         * @param revocationExp optional indicator for length of time to track revocation of this session
          */
         virtual void remove(
             const Application& application,
             const xmltooling::HTTPRequest& request,
-            xmltooling::HTTPResponse* response=nullptr
+            xmltooling::HTTPResponse* response=nullptr,
+            time_t revocationExp=0
         )=0;
 
         /**
@@ -377,10 +381,13 @@ namespace shibsp {
         /**
         * Deletes an existing session.
         *
+        * <p>Revocation may be supported by some implementations.</p>
+        *
         * @param application   reference to Application that owns the Session
         * @param key           session key
+        * @param revocationExp optional indicator for length of time to track revocation of this session
         */
-        virtual void remove(const Application& application, const char* key)=0;
+        virtual void remove(const Application& application, const char* key, time_t revocationExp=0)=0;
     };
 
     /** SessionCache implementation backed by a StorageService. */
diff --git a/shibsp/handler/impl/AdminLogoutInitiator.cpp b/shibsp/handler/impl/AdminLogoutInitiator.cpp
new file mode 100644
index 0000000..4b9da9b
--- /dev/null
+++ b/shibsp/handler/impl/AdminLogoutInitiator.cpp
@@ -0,0 +1,467 @@
+/**
+ * Licensed to the University Corporation for Advanced Internet
+ * Development, Inc. (UCAID) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ *
+ * UCAID licenses this file to you 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.
+ */
+
+/**
+ * AdminLogoutInitiator.cpp
+ *
+ * Triggers administrative logout of a session.
+ */
+
+#include "internal.h"
+#include "exceptions.h"
+#include "Application.h"
+#include "ServiceProvider.h"
+#include "SessionCache.h"
+#include "handler/SecuredHandler.h"
+#include "handler/LogoutInitiator.h"
+
+#ifndef SHIBSP_LITE
+# include "binding/SOAPClient.h"
+# include "metadata/MetadataProviderCriteria.h"
+# include "security/SecurityPolicy.h"
+# include <boost/algorithm/string.hpp>
+# include <boost/iterator/indirect_iterator.hpp>
+# 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;
+using namespace opensaml::saml2md;
+using namespace opensaml;
+#else
+# include "lite/SAMLConstants.h"
+#endif
+
+using namespace shibsp;
+using namespace xmltooling;
+using namespace boost;
+using namespace std;
+
+namespace shibsp {
+
+#if defined (_MSC_VER)
+    #pragma warning( push )
+    #pragma warning( disable : 4250 )
+#endif
+
+    class SHIBSP_DLLLOCAL AdminLogoutInitiator : public SecuredHandler, public LogoutInitiator
+    {
+    public:
+        AdminLogoutInitiator(const DOMElement* e, const char* appId);
+        virtual ~AdminLogoutInitiator() {}
+
+        void init(const char* location);    // encapsulates actions that need to run either in the c'tor or setParent
+
+        void setParent(const PropertySet* parent);
+        void receive(DDF& in, ostream& out);
+        pair<bool,long> run(SPRequest& request, bool isHandler=true) const;
+
+    private:
+        pair<bool,long> doRequest(const Application& application, const HTTPRequest& request, HTTPResponse& httpResponse) const;
+
+        string m_appId;
+#ifndef SHIBSP_LITE
+        auto_ptr_char m_protocol;
+        auto_ptr<LogoutRequest> buildRequest(
+            const Application& application,
+            const Session& session,
+            const RoleDescriptor& role,
+            const XMLCh* endpoint
+            ) const;
+#endif
+    };
+
+#if defined (_MSC_VER)
+    #pragma warning( pop )
+#endif
+
+    Handler* SHIBSP_DLLLOCAL AdminLogoutInitiatorFactory(const pair<const DOMElement*,const char*>& p)
+    {
+        return new AdminLogoutInitiator(p.first, p.second);
+    }
+};
+
+AdminLogoutInitiator::AdminLogoutInitiator(const DOMElement* e, const char* appId)
+    : SecuredHandler(e, Category::getInstance(SHIBSP_LOGCAT ".LogoutInitiator.Admin")), m_appId(appId)
+#ifndef SHIBSP_LITE
+        ,m_protocol(samlconstants::SAML20P_NS)
+#endif
+{
+    // If Location isn't set, defer initialization until the setParent call.
+    pair<bool,const char*> loc = getString("Location");
+    if (loc.first) {
+        init(loc.second);
+    }
+}
+
+void AdminLogoutInitiator::setParent(const PropertySet* parent)
+{
+    DOMPropertySet::setParent(parent);
+    pair<bool,const char*> loc = getString("Location");
+    init(loc.second);
+}
+
+void AdminLogoutInitiator::init(const char* location)
+{
+    if (location) {
+        string address = m_appId + location + "::run::AdminLI";
+        setAddress(address.c_str());
+    }
+    else {
+        m_log.warn("no Location property in Admin LogoutInitiator (or parent), can't register as remoted handler");
+    }
+}
+
+
+pair<bool,long> AdminLogoutInitiator::run(SPRequest& request, bool isHandler) const
+{
+    // No front-channel notifications, so skip calling base class.
+
+    if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
+        // When out of process, we run natively.
+        return doRequest(request.getApplication(), request, request);
+    }
+    else {
+        // When not out of process, we remote the request.
+        vector<string> headers(1, "User-Agent");
+        DDF out, in = wrap(request, &headers);
+        DDFJanitor jin(in), jout(out);
+        out = send(request, in);
+        return unwrap(request, out);
+    }
+}
+
+void AdminLogoutInitiator::receive(DDF& in, ostream& out)
+{
+#ifndef SHIBSP_LITE
+    // Find application.
+    const char* aid=in["application_id"].string();
+    const Application* app=aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : nullptr;
+    if (!app) {
+        // Something's horribly wrong.
+        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.
+    scoped_ptr<HTTPRequest> req(getRequest(in));
+
+    // Set up a response shim.
+    DDF ret(nullptr);
+    DDFJanitor jout(ret);
+    scoped_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, *req, *resp);
+
+    out << ret;
+#else
+    throw ConfigurationException("Cannot perform logout using lite version of shibsp library.");
+#endif
+}
+
+pair<bool,long> AdminLogoutInitiator::doRequest(const Application& application, const HTTPRequest& httpRequest, HTTPResponse& httpResponse) const
+{
+    const char* sessionId = httpRequest.getParameter("session");
+    if (!sessionId || !*sessionId) {
+        // Something's horribly wrong.
+        m_log.error("no session parameter supplied for request");
+        istringstream msg("NO SESSION PARAMETER");
+        return make_pair(true, httpResponse.sendResponse(msg, HTTPResponse::XMLTOOLING_HTTP_STATUS_BADREQUEST));
+    }
+
+    Session* session = nullptr;
+    try {
+        session = application.getServiceProvider().getSessionCache()->find(application, sessionId);
+    }
+    catch (const std::exception& ex) {
+        m_log.error("error accessing designated session: %s", ex.what());
+    }
+
+    // With no session, we return a 404 after "revoking" the session just to be safe.
+    if (!session) {
+        application.getServiceProvider().getSessionCache()->remove(application, sessionId);
+        istringstream msg("NOT FOUND");
+        return make_pair(true, httpResponse.sendResponse(msg, HTTPResponse::XMLTOOLING_HTTP_STATUS_NOTFOUND));
+    }
+
+    time_t revocationExp = session->getExpiration();
+
+    Locker sessionLocker(session, false);
+#ifndef SHIBSP_LITE
+    scoped_ptr<LogoutEvent> logout_event(newLogoutEvent(application, &httpRequest, session));
+#endif
+
+    bool doSAML = false;
+
+#ifndef SHIBSP_LITE
+    if (XMLString::equals(session->getProtocol(), m_protocol.get())) {
+        if (!session->getEntityID() || !session->getNameID()) {
+            m_log.info("skipping SAML 2.0 logout attempt, no NameID or issuing entityID found in session");
+        }
+        else {
+            doSAML = true;
+        }
+    }
+    else {
+        m_log.info("skipping global logout for non-SAML2 session");
+    }
+#endif
+
+    // Do back channel notification.
+    vector<string> sessions(1, session->getID());
+    if (!notifyBackChannel(application, httpRequest.getRequestURL(), sessions, true)) {
+#ifndef SHIBSP_LITE
+        if (logout_event) {
+            logout_event->m_logoutType = LogoutEvent::LOGOUT_EVENT_PARTIAL;
+            application.getServiceProvider().getTransactionLog()->write(*logout_event);
+        }
+#endif
+        sessionLocker.assign();
+        session = nullptr;
+        application.getServiceProvider().getSessionCache()->remove(application, sessionId, revocationExp);
+        
+        istringstream msg("PARTIAL");
+        return make_pair(true, httpResponse.sendResponse(msg, 206)); // misuse of an HTTP code, but whatever
+    }
+
+    if (!doSAML) {
+#ifndef SHIBSP_LITE
+        if (logout_event) {
+            logout_event->m_logoutType = LogoutEvent::LOGOUT_EVENT_LOCAL;
+            application.getServiceProvider().getTransactionLog()->write(*logout_event);
+        }
+#endif
+        sessionLocker.assign();
+        session = nullptr;
+        application.getServiceProvider().getSessionCache()->remove(application, sessionId, revocationExp);
+
+        istringstream msg("OK");
+        return make_pair(true, httpResponse.sendResponse(msg, HTTPResponse::XMLTOOLING_HTTP_STATUS_OK));
+    }
+
+#ifndef SHIBSP_LITE
+    pair<bool,long> ret = make_pair(false, 0L);
+    try {
+        // With a session in hand, we can create a LogoutRequest message, if we can find a compatible SOAP endpoint.
+        MetadataProvider* m = application.getMetadataProvider();
+        Locker metadataLocker(m);
+        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(
+                "Unable to locate metadata for identity provider ($entityID)", namedparams(1, "entityID", session->getEntityID())
+                );
+        }
+        else if (!entity.second) {
+            throw MetadataException(
+                "Unable to locate SAML 2.0 IdP role for identity provider ($entityID).", namedparams(1, "entityID", session->getEntityID())
+                );
+        }
+
+        const IDPSSODescriptor* role = dynamic_cast<const IDPSSODescriptor*>(entity.second);
+        if (role->getSingleLogoutServices().empty()) {
+            throw MetadataException(
+                "No SingleLogoutService endpoints in metadata for identity provider ($entityID).", namedparams(1, "entityID", session->getEntityID())
+                );
+        }
+
+        shibsp::SecurityPolicy policy(application);
+        shibsp::SOAPClient soaper(policy);
+        MetadataCredentialCriteria mcc(*role);
+
+        bool requestSent = false;
+        LogoutResponse* logoutResponse = nullptr;
+        scoped_ptr<StatusResponseType> srt;
+        auto_ptr_XMLCh binding(samlconstants::SAML20_BINDING_SOAP);
+        const vector<SingleLogoutService*>& endpoints = role->getSingleLogoutServices();
+        for (indirect_iterator<vector<SingleLogoutService*>::const_iterator> epit = make_indirect_iterator(endpoints.begin());
+                !logoutResponse && epit != make_indirect_iterator(endpoints.end()); ++epit) {
+            try {
+                if (!XMLString::equals(epit->getBinding(), binding.get()))
+                    continue;
+
+                requestSent = true;
+                auto_ptr<LogoutRequest> msg(buildRequest(application, *session, *role, epit->getLocation()));
+
+                // Log the request.
+                if (logout_event) {
+                    logout_event->m_logoutType = LogoutEvent::LOGOUT_EVENT_UNKNOWN;
+                    logout_event->m_saml2Request = msg.get();
+                    application.getServiceProvider().getTransactionLog()->write(*logout_event);
+                    logout_event->m_saml2Request = nullptr;
+                }
+
+                SAML2SOAPClient client(soaper, false);
+                auto_ptr_char dest(epit->getLocation());
+                client.sendSAML(msg.release(), application.getId(), mcc, dest.get());
+                srt.reset(client.receiveSAML());
+                if (!(logoutResponse = dynamic_cast<LogoutResponse*>(srt.get()))) {
+                    break;
+                }
+            }
+            catch (const std::exception& ex) {
+                m_log.error("error sending LogoutRequest message: %s", ex.what());
+                soaper.reset();
+            }
+        }
+
+        // No answer at all?
+        if (!logoutResponse) {
+            if (!requestSent)
+                m_log.info("IdP (%s) doesn't support SOAP-based single logout protocol", session->getEntityID());
+
+            // Log the end result.
+            if (logout_event) {
+                logout_event->m_logoutType = LogoutEvent::LOGOUT_EVENT_PARTIAL;
+                application.getServiceProvider().getTransactionLog()->write(*logout_event);
+            }
+        }
+        else {
+            // Check the status, looking for non-success or a partial logout code.
+            const StatusCode* sc = logoutResponse->getStatus() ? logoutResponse->getStatus()->getStatusCode() : nullptr;
+            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);
+            }
+
+            // Log the end result.
+            if (logout_event) {
+                logout_event->m_logoutType = partial ? LogoutEvent::LOGOUT_EVENT_PARTIAL : LogoutEvent::LOGOUT_EVENT_GLOBAL;
+                logout_event->m_saml2Response = logoutResponse;
+                application.getServiceProvider().getTransactionLog()->write(*logout_event);
+            }
+
+            if (!partial) {
+                sessionLocker.assign();
+                session = nullptr;
+                application.getServiceProvider().getSessionCache()->remove(application, sessionId, revocationExp);
+                istringstream msg("OK");
+                ret = make_pair(true, httpResponse.sendResponse(msg, HTTPResponse::XMLTOOLING_HTTP_STATUS_OK));
+            }
+        }
+    }
+    catch (const MetadataException& mex) {
+        // Less noise for IdPs that don't support logout (i.e. most)
+        m_log.info("unable to attempt SAML 2.0 logout: %s", mex.what());
+        if (logout_event) {
+            logout_event->m_logoutType = LogoutEvent::LOGOUT_EVENT_PARTIAL;
+            application.getServiceProvider().getTransactionLog()->write(*logout_event);
+        }
+    }
+    catch (const std::exception& ex) {
+        m_log.error("error issuing SAML 2.0 logout request: %s", ex.what());
+        if (logout_event) {
+            logout_event->m_logoutType = LogoutEvent::LOGOUT_EVENT_PARTIAL;
+            application.getServiceProvider().getTransactionLog()->write(*logout_event);
+        }
+    }
+
+    if (session) {
+        sessionLocker.assign();
+        session = nullptr;
+        application.getServiceProvider().getSessionCache()->remove(application, sessionId, revocationExp);
+    }
+
+    if (ret.first)
+        return ret;
+
+    istringstream msg("PARTIAL");
+    return make_pair(true, httpResponse.sendResponse(msg, 206)); // misuse of an HTTP code, but whatever
+
+#else
+    throw ConfigurationException("Cannot perform SAML logout using lite version of shibsp library.");
+#endif
+}
+
+#ifndef SHIBSP_LITE
+
+auto_ptr<LogoutRequest> AdminLogoutInitiator::buildRequest(
+    const Application& application,
+    const Session& session,
+    const RoleDescriptor& role,
+    const XMLCh* endpoint) const
+{
+    const PropertySet* relyingParty = application.getRelyingParty(dynamic_cast<EntityDescriptor*>(role.getParent()));
+
+    auto_ptr<LogoutRequest> msg(LogoutRequestBuilder::buildLogoutRequest());
+    Issuer* issuer = IssuerBuilder::buildIssuer();
+    msg->setIssuer(issuer);
+    issuer->setName(relyingParty->getXMLString("entityID").second);
+    auto_ptr_XMLCh index(session.getSessionIndex());
+    if (index.get() && *index.get()) {
+        SessionIndex* si = SessionIndexBuilder::buildSessionIndex();
+        msg->getSessionIndexs().push_back(si);
+        si->setSessionIndex(index.get());
+    }
+
+    const NameID* nameid = session.getNameID();
+    pair<bool, const char*> flag = getString("encryption");
+    if (!flag.first)
+        flag = relyingParty->getString("encryption");
+    auto_ptr_char dest(endpoint);
+    if (SPConfig::shouldSignOrEncrypt(flag.first ? flag.second : "conditional", dest.get(), false)) {
+        try {
+            auto_ptr<EncryptedID> encrypted(EncryptedIDBuilder::buildEncryptedID());
+            MetadataCredentialCriteria mcc(role);
+            encrypted->encrypt(
+                *nameid,
+                *(application.getMetadataProvider()),
+                mcc,
+                false,
+                relyingParty->getXMLString("encryptionAlg").second
+            );
+            msg->setEncryptedID(encrypted.get());
+            encrypted.release();
+        }
+        catch (const std::exception& ex) {
+            // If we're encrypting deliberately, failure should be fatal.
+            if (flag.first && strcmp(flag.second, "conditional")) {
+                throw;
+            }
+            // If opportunistically, just log and move on.
+            m_log.info("Conditional encryption of NameID in LogoutRequest failed: %s", ex.what());
+            auto_ptr<NameID> namewrapper(nameid->cloneNameID());
+            msg->setNameID(namewrapper.get());
+            namewrapper.release();
+        }
+    }
+    else {
+        auto_ptr<NameID> namewrapper(nameid->cloneNameID());
+        msg->setNameID(namewrapper.get());
+        namewrapper.release();
+    }
+
+    XMLCh* msgid = SAMLConfig::getConfig().generateIdentifier();
+    msg->setID(msgid);
+    XMLString::release(&msgid);
+    msg->setIssueInstant(time(nullptr));
+
+    return msg;
+}
+
+#endif
diff --git a/shibsp/handler/impl/AttributeCheckerHandler.cpp b/shibsp/handler/impl/AttributeCheckerHandler.cpp
index fb61caa..085de02 100644
--- a/shibsp/handler/impl/AttributeCheckerHandler.cpp
+++ b/shibsp/handler/impl/AttributeCheckerHandler.cpp
@@ -76,11 +76,11 @@ namespace shibsp {
         pair<bool,long> run(SPRequest& request, bool isHandler=true) const;
 
     private:
-        void flushSession(SPRequest& request) const {
+        void flushSession(SPRequest& request, time_t exp) const {
             try {
-                request.getApplication().getServiceProvider().getSessionCache()->remove(request.getApplication(), request, &request);
+                request.getApplication().getServiceProvider().getSessionCache()->remove(request.getApplication(), request, &request, exp);
             }
-            catch (std::exception&) {
+            catch (const std::exception&) {
             }
         }
 
@@ -151,7 +151,7 @@ pair<bool,long> AttributeCheckerHandler::run(SPRequest& request, bool isHandler)
         if (!session)
             request.log(SPRequest::SPWarn, "AttributeChecker found session unavailable immediately after creation");
     }
-    catch (std::exception& ex) {
+    catch (const std::exception& ex) {
         request.log(SPRequest::SPWarn, string("AttributeChecker caught exception accessing session immediately after creation: ") + ex.what());
     }
 
@@ -191,16 +191,18 @@ pair<bool,long> AttributeCheckerHandler::run(SPRequest& request, bool isHandler)
         tp.m_request = &request;
         stringstream str;
         XMLToolingConfig::getConfig().getTemplateEngine()->run(infile, str, tp);
-        if (m_flushSession) {
+        if (m_flushSession && session) {
+            time_t revocationExp = session->getExpiration();
             sessionLocker.assign(); // unlock the session
-            flushSession(request);
+            flushSession(request, revocationExp);
         }
         return make_pair(true, request.sendError(str));
     }
 
-    if (m_flushSession) {
+    if (m_flushSession && session) {
+        time_t revocationExp = session->getExpiration();
         sessionLocker.assign(); // unlock the session
-        flushSession(request);
+        flushSession(request, revocationExp);
     }
     m_log.error("could not process error template (%s)", m_template.c_str());
     istringstream msg("Internal Server Error. Please contact the site administrator.");
diff --git a/shibsp/handler/impl/LocalLogoutInitiator.cpp b/shibsp/handler/impl/LocalLogoutInitiator.cpp
index fe8146f..4c8b304 100644
--- a/shibsp/handler/impl/LocalLogoutInitiator.cpp
+++ b/shibsp/handler/impl/LocalLogoutInitiator.cpp
@@ -112,7 +112,7 @@ pair<bool,long> LocalLogoutInitiator::run(SPRequest& request, bool isHandler) co
         try {
             session = request.getSession(false, true, false);  // don't cache it and ignore all checks
         }
-        catch (std::exception& ex) {
+        catch (const std::exception& ex) {
             m_log.error("error accessing current session: %s", ex.what());
         }
         return doRequest(request.getApplication(), request, request, session);
@@ -156,7 +156,7 @@ void LocalLogoutInitiator::receive(DDF& in, ostream& out)
     try {
          session = app->getServiceProvider().getSessionCache()->find(*app, *req, nullptr, nullptr);
     }
-    catch (std::exception& ex) {
+    catch (const std::exception& ex) {
         m_log.error("error accessing current session: %s", ex.what());
     }
 
@@ -188,8 +188,9 @@ pair<bool,long> LocalLogoutInitiator::doRequest(
             application.getServiceProvider().getTransactionLog()->write(*logout_event);
         }
 #endif
+        time_t revocationExp = session->getExpiration();
         locker.assign();    // unlock the session
-        application.getServiceProvider().getSessionCache()->remove(application, httpRequest, &httpResponse);
+        application.getServiceProvider().getSessionCache()->remove(application, httpRequest, &httpResponse, revocationExp);
         if (!result)
             return sendLogoutPage(application, httpRequest, httpResponse, "partial");
     }
diff --git a/shibsp/handler/impl/SAML2Logout.cpp b/shibsp/handler/impl/SAML2Logout.cpp
index 5d649e5..fe763d3 100644
--- a/shibsp/handler/impl/SAML2Logout.cpp
+++ b/shibsp/handler/impl/SAML2Logout.cpp
@@ -190,7 +190,7 @@ SAML2Logout::SAML2Logout(const DOMElement* e, const char* appId)
                         m_log.warn("skipping outgoing binding (%s), not a SAML 2.0 front-channel mechanism", b->c_str());
                     }
                 }
-                catch (std::exception& ex) {
+                catch (const std::exception& ex) {
                     m_log.error("error building MessageEncoder: %s", ex.what());
                 }
             }
@@ -286,7 +286,7 @@ pair<bool,long> SAML2Logout::doRequest(const Application& application, HTTPReque
                 cache->remove(application, request, &response);
                 worked2 = true;
             }
-            catch (std::exception& ex) {
+            catch (const std::exception& ex) {
                 m_log.error("error removing session (%s): %s", session_id.c_str(), ex.what());
             }
         }
@@ -406,7 +406,7 @@ pair<bool,long> SAML2Logout::doRequest(const Application& application, HTTPReque
                             );
                         nameid = dynamic_cast<NameID*>(decryptedID.get());
                     }
-                    catch (std::exception& ex) {
+                    catch (const std::exception& ex) {
                         m_log.error(ex.what());
                     }
                 }
@@ -471,7 +471,7 @@ pair<bool,long> SAML2Logout::doRequest(const Application& application, HTTPReque
                 if (*sit != session_id)
                     cache->remove(application, sit->c_str()); // using the ID-based removal operation
         }
-        catch (std::exception& ex) {
+        catch (const std::exception& ex) {
             m_log.error("error while logging out matching sessions: %s", ex.what());
             if (logout_event) {
                 logout_event->m_nameID = nameid;
@@ -562,7 +562,7 @@ pair<bool,long> SAML2Logout::doRequest(const Application& application, HTTPReque
         try {
             checkError(logoutResponse, policy->getIssuerMetadata()); // throws if Status doesn't look good...
         }
-        catch (std::exception& ex) {
+        catch (const std::exception& ex) {
             if (logout_event) {
                 logout_event->m_exception = &ex;
                 application.getServiceProvider().getTransactionLog()->write(*logout_event);
diff --git a/shibsp/handler/impl/SAML2LogoutInitiator.cpp b/shibsp/handler/impl/SAML2LogoutInitiator.cpp
index 04fac01..c315f1f 100644
--- a/shibsp/handler/impl/SAML2LogoutInitiator.cpp
+++ b/shibsp/handler/impl/SAML2LogoutInitiator.cpp
@@ -180,7 +180,7 @@ void SAML2LogoutInitiator::init(const char* location)
                     m_log.warn("skipping outgoing binding (%s), not a SAML 2.0 front-channel mechanism", b->c_str());
                 }
             }
-            catch (std::exception& ex) {
+            catch (const std::exception& ex) {
                 m_log.error("error building MessageEncoder: %s", ex.what());
             }
         }
@@ -211,7 +211,7 @@ pair<bool,long> SAML2LogoutInitiator::run(SPRequest& request, bool isHandler) co
             return make_pair(false, 0L);
         }
     }
-    catch (std::exception& ex) {
+    catch (const std::exception& ex) {
         m_log.error("error accessing current session: %s", ex.what());
         return make_pair(false, 0L);
     }
@@ -272,9 +272,10 @@ void SAML2LogoutInitiator::receive(DDF& in, ostream& out)
             doRequest(*app, *req, *resp, session);
         }
         else {
+            time_t revocationExp = session->getExpiration();
             session->unlock();
             m_log.log(getParent() ? Priority::WARN : Priority::ERROR, "bypassing SAML 2.0 logout, no NameID or issuing entityID found in session");
-            app->getServiceProvider().getSessionCache()->remove(*app, *req, resp.get());
+            app->getServiceProvider().getSessionCache()->remove(*app, *req, resp.get(), revocationExp);
         }
     }
     out << ret;
@@ -301,9 +302,10 @@ pair<bool,long> SAML2LogoutInitiator::doRequest(
             application.getServiceProvider().getTransactionLog()->write(*logout_event);
         }
 #endif
+        time_t revocationExp = session->getExpiration();
         sessionLocker.assign();
         session = nullptr;
-        application.getServiceProvider().getSessionCache()->remove(application, httpRequest, &httpResponse);
+        application.getServiceProvider().getSessionCache()->remove(application, httpRequest, &httpResponse, revocationExp);
         return sendLogoutPage(application, httpRequest, httpResponse, "partial");
     }
 
@@ -378,7 +380,7 @@ pair<bool,long> SAML2LogoutInitiator::doRequest(
                         break;
                     }
                 }
-                catch (std::exception& ex) {
+                catch (const std::exception& ex) {
                     m_log.error("error sending LogoutRequest message: %s", ex.what());
                     soaper.reset();
                 }
@@ -439,9 +441,10 @@ pair<bool,long> SAML2LogoutInitiator::doRequest(
             }
 
             if (session) {
+                time_t revocationExp = session->getExpiration();
                 sessionLocker.assign();
                 session = nullptr;
-                application.getServiceProvider().getSessionCache()->remove(application, httpRequest, &httpResponse);
+                application.getServiceProvider().getSessionCache()->remove(application, httpRequest, &httpResponse, revocationExp);
             }
 
             return ret;
@@ -474,16 +477,17 @@ pair<bool,long> SAML2LogoutInitiator::doRequest(
         msg.release();  // freed by encoder
 
         if (session) {
+            time_t revocationExp = session->getExpiration();
             sessionLocker.assign();
             session = nullptr;
-            application.getServiceProvider().getSessionCache()->remove(application, httpRequest, &httpResponse);
+            application.getServiceProvider().getSessionCache()->remove(application, httpRequest, &httpResponse, revocationExp);
         }
     }
-    catch (MetadataException& mex) {
+    catch (const MetadataException& mex) {
         // Less noise for IdPs that don't support logout (i.e. most)
         m_log.info("unable to issue SAML 2.0 logout request: %s", mex.what());
     }
-    catch (std::exception& ex) {
+    catch (const std::exception& ex) {
         m_log.error("error issuing SAML 2.0 logout request: %s", ex.what());
     }
 
diff --git a/shibsp/impl/StorageServiceSessionCache.cpp b/shibsp/impl/StorageServiceSessionCache.cpp
index bda1f1b..3d926d8 100644
--- a/shibsp/impl/StorageServiceSessionCache.cpp
+++ b/shibsp/impl/StorageServiceSessionCache.cpp
@@ -94,11 +94,12 @@ SessionCache::~SessionCache()
 }
 
 SSCache::SSCache(const DOMElement* e)
-    : m_log(Category::getInstance(SHIBSP_LOGCAT ".SessionCache")), inproc(true),
+    :
 #ifndef SHIBSP_LITE
-      m_storage(nullptr), m_storage_lite(nullptr), m_cacheAssertions(true), m_reverseIndex(true),
+      m_storage(nullptr), m_storage_lite(nullptr), m_cacheAssertions(true), m_reverseIndex(true), m_softRevocation(true), m_reverseIndexMaxSize(0),
 #endif
-      m_root(e), m_inprocTimeout(900), m_cacheTimeout(0), m_cacheAllowance(0), shutdown(false)
+      m_root(e), m_inprocTimeout(900), m_cacheTimeout(0), m_cacheAllowance(0),
+      m_log(Category::getInstance(SHIBSP_LOGCAT ".SessionCache")), inproc(true), shutdown(false)
 {
     SPConfig& conf = SPConfig::getConfig();
     inproc = conf.isEnabled(SPConfig::InProcess);
@@ -111,6 +112,8 @@ SSCache::SSCache(const DOMElement* e)
     static const XMLCh inprocTimeout[] =        UNICODE_LITERAL_13(i,n,p,r,o,c,T,i,m,e,o,u,t);
     static const XMLCh inboundHeader[] =        UNICODE_LITERAL_13(i,n,b,o,u,n,d,H,e,a,d,e,r);
     static const XMLCh maintainReverseIndex[] = UNICODE_LITERAL_20(m,a,i,n,t,a,i,n,R,e,v,e,r,s,e,I,n,d,e,x);
+    static const XMLCh reverseIndexMaxSize[] =  UNICODE_LITERAL_19(r,e,v,e,r,s,e,I,n,d,e,x,M,a,x,S,i,z,e);
+    static const XMLCh softRevocation[] =       UNICODE_LITERAL_14(s,o,f,t,R,e,v,o,c,a,t,i,o,n);
     static const XMLCh outboundHeader[] =       UNICODE_LITERAL_14(o,u,t,b,o,u,n,d,H,e,a,d,e,r);
     static const XMLCh _StorageService[] =      UNICODE_LITERAL_14(S,t,o,r,a,g,e,S,e,r,v,i,c,e);
     static const XMLCh _StorageServiceLite[] =  UNICODE_LITERAL_18(S,t,o,r,a,g,e,S,e,r,v,i,c,e,L,i,t,e);
@@ -158,8 +161,10 @@ SSCache::SSCache(const DOMElement* e)
             m_storage_lite = m_storage;
         }
 
+        m_softRevocation = XMLHelper::getAttrBool(e, true, softRevocation);
         m_cacheAssertions = XMLHelper::getAttrBool(e, true, cacheAssertions);
         m_reverseIndex = XMLHelper::getAttrBool(e, true, maintainReverseIndex);
+        m_reverseIndexMaxSize = XMLHelper::getAttrInt(e, 0, reverseIndexMaxSize);
         const XMLCh* excludedNames = e ? e->getAttributeNS(nullptr, excludeReverseIndex) : nullptr;
         if (excludedNames && *excludedNames) {
             XMLStringTokenizer toks(excludedNames);
@@ -324,10 +329,11 @@ void SSCache::insert(const char* key, time_t expires, const char* name, const ch
     if (!index || !*index)
         index = "_shibnull";
     DDF sessions = obj.addmember(index);
-    if (!sessions.islist())
-        sessions.list();
-    DDF session = DDF(nullptr).string(key);
-    sessions.add(session);
+    if (!sessions.isstruct())
+        sessions.structure();
+    else if (sessions.integer() == m_reverseIndexMaxSize)
+        sessions.first().destroy();
+    sessions.addmember(key);
 
     // Remarshall the record.
     ostringstream out;
@@ -492,7 +498,7 @@ void SSCache::insert(
         try {
             insert(key.get(), expires, name.get(), index.get());
         }
-        catch (std::exception& ex) {
+        catch (const std::exception& ex) {
             m_log.error("error storing back mapping of NameID for logout: %s", ex.what());
         }
     }
@@ -509,7 +515,7 @@ void SSCache::insert(
                     throw IOException("Duplicate assertion ID ($1)", params(1, tokenid.get()));
             }
         }
-        catch (std::exception& ex) {
+        catch (const std::exception& ex) {
             m_log.error("error storing assertion along with session: %s", ex.what());
         }
     }
@@ -603,7 +609,7 @@ void SSCache::persist(const Application& app, HTTPResponse& httpResponse, DDF& s
         }
         httpResponse.setCookie(shib_cookie.first.c_str(), sealed.c_str());
     }
-    catch (std::exception& e) {
+    catch (const std::exception& e) {
         m_log.error("failed to wrap session (%s) with DataSealer: %s", session.name(), e.what());
     }
 }
@@ -627,7 +633,7 @@ bool SSCache::matches(
             }
         }
     }
-    catch (std::exception& ex) {
+    catch (const std::exception& ex) {
         m_log.error("error while matching session: %s", ex.what());
     }
     return false;
@@ -733,20 +739,20 @@ vector<string>::size_type SSCache::_logout(
     istringstream in(record);
     in >> obj;
 
-    // The record contains child lists for each known session index.
+    // The record contains child structs for each known session index.
     DDF key;
     DDF sessions = obj.first();
-    while (sessions.islist()) {
+    while (sessions.isstruct()) {
         if (!indexes || indexes->empty() || indexes->count(sessions.name())) {
             key = sessions.first();
-            while (key.isstring()) {
+            while (!key.isnull()) {
                 // Fetch the session for comparison.
                 Session* session = nullptr;
                 try {
-                    session = find(app, key.string());
+                    session = find(app, key.name());
                 }
-                catch (std::exception& ex) {
-                    m_log.error("error locating session (%s): %s", key.string(), ex.what());
+                catch (const std::exception& ex) {
+                    m_log.error("error locating session (%s): %s", key.name(), ex.what());
                 }
 
                 if (session) {
@@ -755,15 +761,15 @@ vector<string>::size_type SSCache::_logout(
                     if (XMLString::equals(session->getEntityID(), entityID.get())) {
                         // Same NameID?
                         if (stronglyMatches(issuer->getEntityID(), app.getRelyingParty(issuer)->getXMLString("entityID").second, nameid, *session->getNameID())) {
-                            sessionsKilled.push_back(key.string());
+                            sessionsKilled.push_back(key.name());
                             key.destroy();
                         }
                         else {
-                            m_log.debug("session (%s) contained a non-matching NameID, leaving it alone", key.string());
+                            m_log.debug("session (%s) contained a non-matching NameID, leaving it alone", key.name());
                         }
                     }
                     else {
-                        m_log.debug("session (%s) established by different IdP, leaving it alone", key.string());
+                        m_log.debug("session (%s) established by different IdP, leaving it alone", key.name());
                     }
                 }
                 else {
@@ -771,7 +777,7 @@ vector<string>::size_type SSCache::_logout(
                     // To be conservative, we'll leave it alone. This isn't really increasing our security
                     // risk, because if we can't lookup the session, it's unlikely the calling logout code
                     // can either, so there's no chance of removing the session anyway.
-                    m_log.warn("session (%s) not accessible for logout, may be gone, or associated with a different application", key.string());
+                    m_log.warn("session (%s) not accessible for logout, may be gone, or associated with a different application", key.name());
                 }
                 key = sessions.next();
             }
@@ -798,7 +804,7 @@ vector<string>::size_type SSCache::_logout(
                 m_log.warn("logout mapping record changed behind us, leaving it alone");
         }
     }
-    catch (std::exception& ex) {
+    catch (const std::exception& ex) {
         m_log.error("error updating logout mapping record: %s", ex.what());
     }
 
@@ -856,7 +862,7 @@ LogoutEvent* SSCache::newLogoutEvent(const Application& app) const
             m_log.warn("unable to audit event, log event object was of an incorrect type");
         }
     }
-    catch (std::exception& ex) {
+    catch (const std::exception& ex) {
         m_log.warn("exception auditing event: %s", ex.what());
     }
     return nullptr;
@@ -990,7 +996,7 @@ Session* SSCache::_find(const Application& app, const char* key, const char* rec
                 try {
                     m_storage->updateContext(key, now + cacheTimeout);
                 }
-                catch (std::exception& ex) {
+                catch (const std::exception& ex) {
                     m_log.error("failed to update session expiration: %s", ex.what());
                 }
             }
@@ -1078,7 +1084,7 @@ Session* SSCache::find(const Application& app, HTTPRequest& request, const char*
             response->setCookie(shib_cookie.first.c_str(), exp.c_str());
         }
     }
-    catch (std::exception&) {
+    catch (const std::exception&) {
         HTTPResponse* response = dynamic_cast<HTTPResponse*>(&request);
         if (response) {
             if (!m_outboundHeader.empty())
@@ -1124,6 +1130,22 @@ bool SSCache::recover(const Application& app, const char* key, const char* data)
     else {
         // We're out of process, so we can recover the session.
 #ifndef SHIBSP_LITE
+        m_log.debug("checking for revocation of session (%s)", key);
+        try {
+            if (m_storage_lite->readString("Revoked", key) > 0) {
+                m_log.warn("blocked recovery of revoked session (%s)", key);
+                return false;
+            }
+        }
+        catch (const std::exception& ex) {
+            if (m_softRevocation)
+                m_log.warn("ignoring failed check for revocation of session (%s): %s", ex.what());
+            else {
+                m_log.warn("check for revocation of session (%s) failed, treating as revoked: %s", ex.what());
+                return false;
+            }
+        }
+
         m_log.debug("attempting recovery of session (%s)", key);
 
         DDF obj;
@@ -1140,7 +1162,7 @@ bool SSCache::recover(const Application& app, const char* key, const char* data)
             stringstream str(unwrapped);
             str >> obj;
         }
-        catch (std::exception& e) {
+        catch (const std::exception& e) {
             if (dup)
                 free(dup);
             m_log.error("failed to unwrap sealed session data with DataSealer: %s", e.what());
@@ -1183,7 +1205,7 @@ bool SSCache::recover(const Application& app, const char* key, const char* data)
                     insert(key, iso.getEpoch(), name.get(), obj["session_index"].string());
                 }
             }
-            catch (std::exception& ex) {
+            catch (const std::exception& ex) {
                 m_log.error("error storing back mapping of NameID for logout: %s", ex.what());
             }
         }
@@ -1200,7 +1222,7 @@ bool SSCache::recover(const Application& app, const char* key, const char* data)
     return true;
 }
 
-void SSCache::remove(const Application& app, const HTTPRequest& request, HTTPResponse* response)
+void SSCache::remove(const Application& app, const HTTPRequest& request, HTTPResponse* response, time_t revocationExp)
 {
 #ifdef _DEBUG
     xmltooling::NDC ndc("remove");
@@ -1227,11 +1249,11 @@ void SSCache::remove(const Application& app, const HTTPRequest& request, HTTPRes
             shib_cookie = app.getCookieNameProps("_shibsealed_");
             response->setCookie(shib_cookie.first.c_str(), exp.c_str());
         }
-        remove(app, session_id.c_str());
+        remove(app, session_id.c_str(), revocationExp);
     }
 }
 
-void SSCache::remove(const Application& app, const char* key)
+void SSCache::remove(const Application& app, const char* key, time_t revocationExp)
 {
 #ifdef _DEBUG
     xmltooling::NDC ndc("remove");
@@ -1245,6 +1267,24 @@ void SSCache::remove(const Application& app, const char* key)
 #ifndef SHIBSP_LITE
         m_storage->deleteContext(key);
         m_log.info("removed session (%s)", key);
+
+        if (!m_persistedAttributeIds.empty()) {
+            if (!revocationExp) {
+                const PropertySet* props = app.getPropertySet("Sessions");
+                if (props)
+                    revocationExp = props->getUnsignedInt("lifetime").second;
+                if (!revocationExp)
+                    revocationExp = 28800;
+                revocationExp += time(nullptr);
+            }
+            try {
+                if (!m_storage_lite->createString("Revoked", key, "1", revocationExp))
+                    m_log.warn("duplicate insertion of revocation for session (%s)", key);
+            }
+            catch (const std::exception& ex) {
+                m_log.warn("error recording revocation of session (%s): %s", key, ex.what());
+            }
+        }
 #else
         throw ConfigurationException("SessionCache removal requires a StorageService.");
 #endif
@@ -1445,7 +1485,7 @@ void SSCache::receive(DDF& in, ostream& out)
             try {
                 m_storage->updateContext(key, now + cacheTimeout);
             }
-            catch (std::exception& ex) {
+            catch (const std::exception& ex) {
                 m_log.error("failed to update session expiration: %s", ex.what());
             }
         }
@@ -1496,7 +1536,7 @@ void SSCache::receive(DDF& in, ostream& out)
         try {
             m_storage->updateContext(key, now + cacheTimeout);
         }
-        catch (std::exception& ex) {
+        catch (const std::exception& ex) {
             m_log.error("failed to update session expiration: %s", ex.what());
         }
 
@@ -1573,8 +1613,15 @@ void SSCache::receive(DDF& in, ostream& out)
         const char* key=in["key"].string();
         if (!key)
             throw ListenerException("Required parameter missing for session removal.");
+        time_t revocationExp = 0;
+        auto_ptr_XMLCh dt(in["revocationExp"].string());
+        if (dt.get()) {
+            XMLDateTime dtobj(dt.get());
+            dtobj.parseDateTime();
+            revocationExp = dtobj.getEpoch();
+        }
 
-        remove(*app, key);
+        remove(*app, key, revocationExp);
         DDF ret(nullptr);
         DDFJanitor jan(ret);
         out << ret;
diff --git a/shibsp/impl/StorageServiceSessionCache.h b/shibsp/impl/StorageServiceSessionCache.h
index d2411ba..5e18062 100644
--- a/shibsp/impl/StorageServiceSessionCache.h
+++ b/shibsp/impl/StorageServiceSessionCache.h
@@ -104,12 +104,18 @@ namespace shibsp {
 #endif
         std::string active(const Application& app, const xmltooling::HTTPRequest& request);
         Session* find(const Application& app, xmltooling::HTTPRequest& request, const char* client_addr=nullptr, time_t* timeout=nullptr);
-        void remove(const Application& app, const xmltooling::HTTPRequest& request, xmltooling::HTTPResponse* response=nullptr);
+
+        void remove(
+            const Application& app,
+            const xmltooling::HTTPRequest& request,
+            xmltooling::HTTPResponse* response=nullptr,
+            time_t revocationExp=0
+            );
 
         Session* find(const Application& app, const char* key) {
             return _find(app, key, nullptr, nullptr, nullptr);
         }
-        void remove(const Application& app, const char* key);
+        void remove(const Application& app, const char* key, time_t revocationExp=0);
         void test();
 
         unsigned long getCacheTimeout(const Application& app) const;
@@ -138,7 +144,10 @@ namespace shibsp {
         bool stronglyMatches(const XMLCh* idp, const XMLCh* sp, const opensaml::saml2::NameID& n1, const opensaml::saml2::NameID& n2) const;
         LogoutEvent* newLogoutEvent(const Application& app) const;
 
-        bool m_cacheAssertions,m_reverseIndex;
+        xmltooling::StorageService* m_storage;
+        xmltooling::StorageService* m_storage_lite;
+        bool m_cacheAssertions,m_reverseIndex,m_softRevocation;
+        unsigned long m_reverseIndexMaxSize;
         std::set<xmltooling::xstring> m_excludedNames;
         std::set<std::string> m_persistedAttributeIds;
 #endif
@@ -162,11 +171,6 @@ namespace shibsp {
 
         xmltooling::logging::Category& m_log;
         bool inproc;
-#ifndef SHIBSP_LITE
-        xmltooling::StorageService* m_storage;
-        xmltooling::StorageService* m_storage_lite;
-#endif
-
         bool shutdown;
         boost::scoped_ptr<xmltooling::CondWait> shutdown_wait;
         boost::scoped_ptr<xmltooling::Thread> cleanup_thread;

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list