First set of logout base classes and non-building draft of SP-initiated logout.
authorcantor <cantor@cb58f699-b61c-0410-a6fe-9272a202ed29>
Sat, 7 Jul 2007 00:12:08 +0000 (00:12 +0000)
committercantor <cantor@cb58f699-b61c-0410-a6fe-9272a202ed29>
Sat, 7 Jul 2007 00:12:08 +0000 (00:12 +0000)
Revised cache and attribute handling APis to use more Unicode types.
Misc. fixes to handler base classes.

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

26 files changed:
schemas/shibboleth-2.0-native-sp-config.xsd
shibsp/AbstractSPRequest.cpp
shibsp/AbstractSPRequest.h
shibsp/SPConfig.cpp
shibsp/SPConfig.h
shibsp/SPRequest.h
shibsp/SessionCache.h
shibsp/attribute/filtering/BasicFilteringContext.h
shibsp/attribute/filtering/FilteringContext.h
shibsp/attribute/filtering/impl/AuthenticationMethodStringFunctor.cpp
shibsp/attribute/resolver/AttributeResolver.h
shibsp/attribute/resolver/impl/ChainingAttributeResolver.cpp
shibsp/attribute/resolver/impl/QueryAttributeResolver.cpp
shibsp/binding/impl/ArtifactResolver.cpp
shibsp/handler/AssertionConsumerService.h
shibsp/handler/Handler.h
shibsp/handler/LogoutHandler.h [new file with mode: 0644]
shibsp/handler/impl/AbstractHandler.cpp
shibsp/handler/impl/AssertionConsumerService.cpp
shibsp/handler/impl/ChainingLogoutInitiator.cpp [new file with mode: 0644]
shibsp/handler/impl/LogoutHandler.cpp [new file with mode: 0644]
shibsp/handler/impl/SAML1Consumer.cpp
shibsp/handler/impl/SAML2Consumer.cpp
shibsp/impl/RemotedSessionCache.cpp
shibsp/impl/StorageServiceSessionCache.cpp
shibsp/impl/XMLServiceProvider.cpp

index 9c3e191..a79fdfc 100644 (file)
                <complexType>\r
                        <choice minOccurs="0" maxOccurs="unbounded">\r
                                <element ref="conf:SessionInitiator"/>\r
+                               <element ref="conf:LogoutInitiator"/>\r
                                <element ref="md:AssertionConsumerService"/>\r
                                <element ref="md:ArtifactResolutionService"/>\r
                                <element ref="md:SingleLogoutService"/>\r
                        </complexContent>\r
                </complexType>\r
        </element>\r
+\r
+       <element name="LogoutInitiator">\r
+               <annotation>\r
+                       <documentation>Used to specify handlers that can issue LogoutRequests</documentation>\r
+               </annotation>\r
+               <complexType>\r
+                       <complexContent>\r
+                               <restriction base="conf:PluggableType">\r
+                                       <sequence>\r
+                                               <any namespace="##any" processContents="lax" minOccurs="0" maxOccurs="unbounded"/>\r
+                                       </sequence>\r
+                                       <attribute name="Location" type="anyURI"/>\r
+                                       <attribute name="relayState" type="conf:string"/>\r
+                                       <attribute name="outgoingBindings" type="conf:listOfURIs"/>\r
+                                       <attribute name="template" type="anyURI"/>\r
+                                       <attribute name="postArtifact" type="boolean"/>\r
+                                       <anyAttribute namespace="##any" processContents="lax"/>\r
+                               </restriction>\r
+                       </complexContent>\r
+               </complexType>\r
+       </element>\r
        \r
        <element name="Errors">\r
                <annotation>\r
index 67d00a7..d6cc6a5 100644 (file)
@@ -76,12 +76,13 @@ const Application& AbstractSPRequest::getApplication() const
     return *m_app;
 }
 
-Session* AbstractSPRequest::getSession(bool checkTimeout) const
+Session* AbstractSPRequest::getSession(bool checkTimeout, bool ignoreAddress, bool cache) const
 {
     // Only attempt this once.
-    if (m_sessionTried)
+    if (cache && m_sessionTried)
         return m_session;
-    m_sessionTried = true;
+    else if (cache)
+        m_sessionTried = true;
 
     // Get session ID from cookie.
     const Application& app = getApplication();
@@ -92,23 +93,27 @@ Session* AbstractSPRequest::getSession(bool checkTimeout) const
 
     // Need address checking and timeout settings.
     time_t timeout=0;
-    bool consistent=true;
-    const PropertySet* props=app.getPropertySet("Sessions");
-    if (props) {
-        if (checkTimeout) {
-            pair<bool,unsigned int> p=props->getUnsignedInt("timeout");
-            if (p.first)
-                timeout = p.second;
+    if (checkTimeout || !ignoreAddress) {
+        const PropertySet* props=app.getPropertySet("Sessions");
+        if (props) {
+            if (checkTimeout) {
+                pair<bool,unsigned int> p=props->getUnsignedInt("timeout");
+                if (p.first)
+                    timeout = p.second;
+            }
+            pair<bool,bool> pcheck=props->getBool("consistentAddress");
+            if (pcheck.first)
+                ignoreAddress = !pcheck.second;
         }
-        pair<bool,bool> pcheck=props->getBool("consistentAddress");
-        if (pcheck.first)
-            consistent = pcheck.second;
     }
 
     // The cache will either silently pass a session or NULL back, or throw an exception out.
-    return m_session = getServiceProvider().getSessionCache()->find(
-        session_id, app, consistent ? getRemoteAddr().c_str() : NULL, checkTimeout ? &timeout : NULL
+    Session* session = getServiceProvider().getSessionCache()->find(
+        session_id, app, ignoreAddress ? NULL : getRemoteAddr().c_str(), checkTimeout ? &timeout : NULL
         );
+    if (cache)
+        m_session = session;
+    return session;
 }
 
 const char* AbstractSPRequest::getRequestURL() const {
@@ -184,6 +189,9 @@ const char* AbstractSPRequest::getCookie(const char* name) const
 
 const char* AbstractSPRequest::getHandlerURL(const char* resource) const
 {
+    if (!resource)
+        resource = getRequestURL();
+
     if (!m_handlerURL.empty() && resource && !strcmp(getRequestURL(),resource))
         return m_handlerURL.c_str();
         
index 3dc1c68..aed5812 100644 (file)
@@ -53,7 +53,7 @@ namespace shibsp {
 
         const Application& getApplication() const;
         
-        Session* getSession(bool checkTimeout=true) const;
+        Session* getSession(bool checkTimeout=true, bool ignoreAddress=false, bool cache=true) const;
 
         const char* getRequestURL() const;
         
index ca0c879..3c9ad7f 100644 (file)
@@ -196,6 +196,7 @@ void SPInternalConfig::term()
 
     ArtifactResolutionServiceManager.deregisterFactories();
     AssertionConsumerServiceManager.deregisterFactories();
+    LogoutInitiatorManager.deregisterFactories();
     ManageNameIDServiceManager.deregisterFactories();
     SessionInitiatorManager.deregisterFactories();
     SingleLogoutServiceManager.deregisterFactories();
index b3a4e23..6898b5d 100644 (file)
@@ -228,6 +228,11 @@ namespace shibsp {
         xmltooling::PluginManager<ListenerService,std::string,const xercesc::DOMElement*> ListenerServiceManager;
 
         /**
+         * Manages factories for Handler plugins that implement LogoutInitiator functionality.
+         */
+        xmltooling::PluginManager< Handler,std::string,std::pair<const xercesc::DOMElement*,const char*> > LogoutInitiatorManager;
+
+        /**
          * Manages factories for Handler plugins that implement ManageNameIDService functionality.
          */
         xmltooling::PluginManager< Handler,std::string,std::pair<const xercesc::DOMElement*,const char*> > ManageNameIDServiceManager;
index a0b9ac4..7cc7bf7 100644 (file)
@@ -74,10 +74,12 @@ namespace shibsp {
         /**
          * Returns a locked Session associated with the request.
          *
-         * @param touch true iff the last-used timestamp should be updated and any timeout policy enforced
+         * @param checkTimeout  true iff the last-used timestamp should be updated and any timeout policy enforced
+         * @param ignoreAddress true iff all address checking should be ignored, regardless of policy
+         * @param cache         true iff the request should hold the Session lock itself and unlock during cleanup
          * @return pointer to Session, or NULL
          */
-        virtual Session* getSession(bool checkTimeout=true) const=0;
+        virtual Session* getSession(bool checkTimeout=true, bool ignoreAddress=false, bool cache=true) const=0;
 
         /**
          * Returns the effective base Handler URL for a resource,
index 954f5ea..9e2d453 100644 (file)
@@ -63,7 +63,14 @@ namespace shibsp {
          * @return the IdP's entityID
          */
         virtual const char* getEntityID() const=0;
-        
+
+        /**
+         * Returns the protocol family used to initiate the session.
+         *
+         * @return the protocol constant that represents the general SSO protocol used
+         */
+        virtual const char* getProtocol() const=0;
+
         /**
          * Returns the UTC timestamp on the authentication event at the IdP.
          * 
@@ -189,6 +196,7 @@ namespace shibsp {
          * @param application       reference to Application that owns the Session
          * @param client_addr       network address of client
          * @param issuer            issuing metadata of assertion issuer, if known
+         * @param protocol          protocol family used to initiate the session
          * @param nameid            principal identifier, normalized to SAML 2, if any
          * @param authn_instant     UTC timestamp of authentication at IdP, if known
          * @param session_index     index of session between principal and IdP, if any
@@ -203,33 +211,17 @@ namespace shibsp {
             const Application& application,
             const char* client_addr=NULL,
             const opensaml::saml2md::EntityDescriptor* issuer=NULL,
+            const XMLCh* protocol=NULL,
             const opensaml::saml2::NameID* nameid=NULL,
-            const char* authn_instant=NULL,
-            const char* session_index=NULL,
-            const char* authncontext_class=NULL,
-            const char* authncontext_decl=NULL,
+            const XMLCh* authn_instant=NULL,
+            const XMLCh* session_index=NULL,
+            const XMLCh* authncontext_class=NULL,
+            const XMLCh* authncontext_decl=NULL,
             const std::vector<const opensaml::Assertion*>* tokens=NULL,
             const std::multimap<std::string,Attribute*>* attributes=NULL
             )=0;
 
         /**
-         * Locates an existing session or sessions by subject identifier.
-         * 
-         * @param issuer        source of session(s)
-         * @param nameid        name identifier associated with the session(s) to locate
-         * @param index         index of session, or NULL for all sessions associated with other parameters
-         * @param application   reference to Application that owns the session(s)
-         * @param sessions      on exit, contains the IDs of the matching sessions
-         */
-        virtual void find(
-            const opensaml::saml2md::EntityDescriptor& issuer,
-            const opensaml::saml2::NameID& nameid,
-            const char* index,
-            const Application& application,
-            std::vector<std::string>& sessions
-            )=0;
-
-        /**
          * Deletes an existing session or sessions.
          * 
          * @param issuer        source of session(s)
@@ -239,7 +231,7 @@ namespace shibsp {
          * @param sessions      on exit, contains the IDs of the matching sessions removed
          */
         virtual void remove(
-            const opensaml::saml2md::EntityDescriptor& issuer,
+            const opensaml::saml2md::EntityDescriptor* issuer,
             const opensaml::saml2::NameID& nameid,
             const char* index,
             const Application& application,
index b05f555..721d638 100644 (file)
@@ -43,8 +43,8 @@ namespace shibsp {
             const Application& app,
             const std::multimap<std::string,Attribute*>& attributes,
             const opensaml::saml2md::RoleDescriptor* role=NULL,
-            const char* authncontext_class=NULL,
-            const char* authncontext_decl=NULL
+            const XMLCh* authncontext_class=NULL,
+            const XMLCh* authncontext_decl=NULL
             ) : m_app(app), m_attributes(attributes), m_role(role), m_issuer(NULL), m_class(authncontext_class), m_decl(authncontext_decl) {
             if (role)
                 m_issuer = dynamic_cast<opensaml::saml2md::EntityDescriptor*>(role->getParent())->getEntityID();
@@ -55,10 +55,10 @@ namespace shibsp {
         const Application& getApplication() const {
             return m_app;
         }
-        const char* getAuthnContextClassRef() const {
+        const XMLCh* getAuthnContextClassRef() const {
             return m_class;
         }
-        const char* getAuthnContextDeclRef() const {
+        const XMLCh* getAuthnContextDeclRef() const {
             return m_decl;
         }
         const XMLCh* getAttributeRequester() const {
@@ -82,8 +82,8 @@ namespace shibsp {
         const std::multimap<std::string,Attribute*>& m_attributes;
         const opensaml::saml2md::RoleDescriptor* m_role;
         const XMLCh* m_issuer;
-        const char* m_class;
-        const char* m_decl;
+        const XMLCh* m_class;
+        const XMLCh* m_decl;
     };
 };
 
index 6d3b59c..c4e3032 100644 (file)
@@ -57,14 +57,14 @@ namespace shibsp {
          * 
          * @return  a URI identifying the authentication context class
          */
-        virtual const char* getAuthnContextClassRef() const=0;
+        virtual const XMLCh* getAuthnContextClassRef() const=0;
 
         /**
          * Returns a URI containing an AuthnContextDeclRef associated with the subject.
          * 
          * @return  a URI identifying the authentication context declaration
          */
-        virtual const char* getAuthnContextDeclRef() const=0;
+        virtual const XMLCh* getAuthnContextDeclRef() const=0;
 
         /**
          * Gets the ID of the requester of the attributes, if known.
index 700bb70..69df276 100644 (file)
@@ -34,21 +34,21 @@ namespace shibsp {
      */
     class SHIBSP_DLLLOCAL AuthenticationMethodStringFunctor : public MatchFunctor
     {
-        xmltooling::auto_ptr_char m_value;
+        const XMLCh* m_value;
     public:
         AuthenticationMethodStringFunctor(const DOMElement* e) : m_value(e ? e->getAttributeNS(NULL,value) : NULL) {
-            if (!m_value.get() || !*m_value.get())
+            if (!m_value || !*m_value)
                 throw ConfigurationException("AuthenticationMethodString MatchFunctor requires non-empty value attribute.");
         }
 
         bool evaluatePolicyRequirement(const FilteringContext& filterContext) const {
-            return XMLString::equals(m_value.get(), filterContext.getAuthnContextClassRef()) ||
-                XMLString::equals(m_value.get(), filterContext.getAuthnContextDeclRef());
+            return XMLString::equals(m_value, filterContext.getAuthnContextClassRef()) ||
+                XMLString::equals(m_value, filterContext.getAuthnContextDeclRef());
         }
 
         bool evaluatePermitValue(const FilteringContext& filterContext, const Attribute& attribute, size_t index) const {
-            return XMLString::equals(m_value.get(), filterContext.getAuthnContextClassRef()) ||
-                XMLString::equals(m_value.get(), filterContext.getAuthnContextDeclRef());
+            return XMLString::equals(m_value, filterContext.getAuthnContextClassRef()) ||
+                XMLString::equals(m_value, filterContext.getAuthnContextDeclRef());
         }
     };
 
index be1a6ba..4376c89 100644 (file)
@@ -60,19 +60,21 @@ namespace shibsp {
          * 
          * @param application       reference to Application that owns the eventual Session
          * @param issuer            issuing metadata of assertion issuer, if known
+         * @param protocol          protocol used to establish Session
          * @param nameid            principal identifier, normalized to SAML 2, if any
          * @param authncontext_class    method/category of authentication event, if known
          * @param authncontext_decl specifics of authentication event, if known
-         * @param tokens            assertions initiating the session, if any
+         * @param tokens            assertions initiating the Session, if any
          * @param attributes        map of previously resolved attributes, if any
          * @return  newly created ResolutionContext, owned by caller
          */
         virtual ResolutionContext* createResolutionContext(
             const Application& application,
             const opensaml::saml2md::EntityDescriptor* issuer,
+            const XMLCh* protocol,
             const opensaml::saml2::NameID* nameid,
-            const char* authncontext_class=NULL,
-            const char* authncontext_decl=NULL,
+            const XMLCh* authncontext_class=NULL,
+            const XMLCh* authncontext_decl=NULL,
             const std::vector<const opensaml::Assertion*>* tokens=NULL,
             const std::multimap<std::string,Attribute*>* attributes=NULL
             ) const=0;
index 073e9e3..8de4e3d 100644 (file)
@@ -79,16 +79,17 @@ namespace shibsp {
         ResolutionContext* createResolutionContext(
             const Application& application,
             const EntityDescriptor* issuer,
+            const XMLCh* protocol,
             const NameID* nameid,
-            const char* authncontext_class=NULL,
-            const char* authncontext_decl=NULL,
+            const XMLCh* authncontext_class=NULL,
+            const XMLCh* authncontext_decl=NULL,
             const vector<const opensaml::Assertion*>* tokens=NULL,
             const multimap<string,shibsp::Attribute*>* attributes=NULL
             ) const {
             auto_ptr<ChainingContext> chain(new ChainingContext());
             for (vector<AttributeResolver*>::const_iterator i=m_resolvers.begin(); i!=m_resolvers.end(); ++i)
                 chain->m_contexts.push_back(
-                    (*i)->createResolutionContext(application, issuer, nameid, authncontext_class, authncontext_decl, tokens, attributes)
+                    (*i)->createResolutionContext(application, issuer, protocol, nameid, authncontext_class, authncontext_decl, tokens, attributes)
                     );
             return chain.release();
         }
index 2aa73f1..738d73b 100644 (file)
@@ -66,19 +66,23 @@ namespace shibsp {
     {
     public:
         QueryContext(const Application& application, const Session& session)
-            : m_query(true), m_app(application), m_session(&session), m_metadata(NULL), m_entity(NULL), m_nameid(NULL) {
+                : m_query(true), m_app(application), m_session(&session), m_metadata(NULL), m_entity(NULL), m_nameid(NULL) {
+            m_protocol = XMLString::transcode(session.getProtocol());
+            m_class = XMLString::transcode(session.getAuthnContextClassRef());
+            m_decl = XMLString::transcode(session.getAuthnContextDeclRef());
         }
         
         QueryContext(
             const Application& application,
             const EntityDescriptor* issuer,
+            const XMLCh* protocol,
             const NameID* nameid,
-            const char* authncontext_class=NULL,
-            const char* authncontext_decl=NULL,
+            const XMLCh* authncontext_class=NULL,
+            const XMLCh* authncontext_decl=NULL,
             const vector<const opensaml::Assertion*>* tokens=NULL,
             const multimap<string,Attribute*>* attributes=NULL
             ) : m_query(true), m_app(application), m_session(NULL), m_metadata(NULL), m_entity(issuer),
-                m_nameid(nameid), m_class(authncontext_class), m_decl(authncontext_decl) {
+                m_protocol(protocol), m_nameid(nameid), m_class(authncontext_class), m_decl(authncontext_decl) {
 
             if (tokens) {
                 for (vector<const opensaml::Assertion*>::const_iterator t = tokens->begin(); t!=tokens->end(); ++t) {
@@ -97,6 +101,11 @@ namespace shibsp {
         }
         
         ~QueryContext() {
+            if (m_session) {
+                XMLString::release((XMLCh**)&m_protocol);
+                XMLString::release((XMLCh**)&m_class);
+                XMLString::release((XMLCh**)&m_decl);
+            }
             if (m_metadata)
                 m_metadata->unlock();
             for_each(m_attributes.begin(), m_attributes.end(), cleanup_pair<string,shibsp::Attribute>());
@@ -122,14 +131,17 @@ namespace shibsp {
             }
             return NULL;
         }
+        const XMLCh* getProtocol() const {
+            return m_protocol;
+        }
         const NameID* getNameID() const {
             return m_session ? m_session->getNameID() : m_nameid;
         }
-        const char* getClassRef() const {
-            return m_session ? m_session->getAuthnContextClassRef() :  m_class;
+        const XMLCh* getClassRef() const {
+            return m_class;
         }
-        const char* getDeclRef() const {
-            return m_session ? m_session->getAuthnContextDeclRef() : m_decl;
+        const XMLCh* getDeclRef() const {
+            return m_decl;
         }
         const Session* getSession() const {
             return m_session;
@@ -147,9 +159,10 @@ namespace shibsp {
         const Session* m_session;
         mutable MetadataProvider* m_metadata;
         mutable const EntityDescriptor* m_entity;
+        const XMLCh* m_protocol;
         const NameID* m_nameid;
-        const char* m_class;
-        const char* m_decl;
+        const XMLCh* m_class;
+        const XMLCh* m_decl;
         multimap<string,shibsp::Attribute*> m_attributes;
         vector<opensaml::Assertion*> m_assertions;
     };
@@ -169,13 +182,14 @@ namespace shibsp {
         ResolutionContext* createResolutionContext(
             const Application& application,
             const EntityDescriptor* issuer,
+            const XMLCh* protocol,
             const NameID* nameid,
-            const char* authncontext_class=NULL,
-            const char* authncontext_decl=NULL,
+            const XMLCh* authncontext_class=NULL,
+            const XMLCh* authncontext_decl=NULL,
             const vector<const opensaml::Assertion*>* tokens=NULL,
             const multimap<string,shibsp::Attribute*>* attributes=NULL
             ) const {
-            return new QueryContext(application,issuer,nameid,authncontext_class,authncontext_decl,tokens,attributes);
+            return new QueryContext(application,issuer,protocol,nameid,authncontext_class,authncontext_decl,tokens,attributes);
         }
 
         ResolutionContext* createResolutionContext(const Application& application, const Session& session) const {
@@ -290,7 +304,7 @@ bool QueryResolver::SAML1Query(QueryContext& ctx) const
             request->setAttributeQuery(query);
             request->setMinorVersion(version);
 
-            SAML1SOAPClient client(soaper);
+            SAML1SOAPClient client(soaper, false);
             client.sendSAML(request, mcc, loc.get());
             response = client.receiveSAML();
         }
@@ -301,9 +315,15 @@ bool QueryResolver::SAML1Query(QueryContext& ctx) const
     }
 
     if (!response) {
-        m_log.error("unable to successfully query for attributes");
+        m_log.error("unable to obtain a SAML response from attribute authority");
         return false;
     }
+    else if (!response->getStatus() || !response->getStatus()->getStatusCode() || response->getStatus()->getStatusCode()->getValue()==NULL ||\r
+            *(response->getStatus()->getStatusCode()->getValue()) != saml1p::StatusCode::SUCCESS) {\r
+        delete response;\r
+        m_log.error("attribute authority returned a SAML error");\r
+        return true;\r
+    }\r
 
     const vector<saml1::Assertion*>& assertions = const_cast<const saml1p::Response*>(response)->getAssertions();
     if (assertions.size()>1)
@@ -395,7 +415,7 @@ bool QueryResolver::SAML2Query(QueryContext& ctx) const
             for (vector<saml2::Attribute*>::const_iterator ad = m_SAML2Designators.begin(); ad!=m_SAML2Designators.end(); ++ad)
                 query->getAttributes().push_back((*ad)->cloneAttribute());
 
-            SAML2SOAPClient client(soaper);
+            SAML2SOAPClient client(soaper, false);
             client.sendSAML(query, mcc, loc.get());
             srt = client.receiveSAML();
         }
@@ -406,7 +426,7 @@ bool QueryResolver::SAML2Query(QueryContext& ctx) const
     }
 
     if (!srt) {
-        m_log.error("unable to successfully query for attributes");
+        m_log.error("unable to obtain a SAML response from attribute authority");
         return false;
     }
     saml2p::Response* response = dynamic_cast<saml2p::Response*>(srt);
@@ -415,6 +435,12 @@ bool QueryResolver::SAML2Query(QueryContext& ctx) const
         m_log.error("message was not a samlp:Response");
         return true;
     }
+    else if (!response->getStatus() || !response->getStatus()->getStatusCode() ||
+            !XMLString::equals(response->getStatus()->getStatusCode()->getValue(), saml2p::StatusCode::SUCCESS)) {
+        delete srt;
+        m_log.error("attribute authority returned a SAML error");
+        return true;
+    }
 
     const vector<saml2::Assertion*>& assertions = const_cast<const saml2p::Response*>(response)->getAssertions();
     if (assertions.size()>1)
index eb6bd20..f457a3f 100644 (file)
@@ -73,7 +73,7 @@ saml1p::Response* ArtifactResolver::resolve(
                 request->getAssertionArtifacts().push_back(aa);
             }
 
-            SAML1SOAPClient client(soaper);
+            SAML1SOAPClient client(soaper, false);
             client.sendSAML(request, mcc, loc.get());
             response = client.receiveSAML();
         }
@@ -84,7 +84,13 @@ saml1p::Response* ArtifactResolver::resolve(
     }
 
     if (!response)
-        throw BindingException("Unable to successfully resolve artifact(s).");
+        throw BindingException("Unable to resolve artifact(s) into a SAML response.");
+    const QName* code = (response->getStatus() && response->getStatus()->getStatusCode()) ? response->getStatus()->getStatusCode()->getValue() : NULL;
+    if (!code || *code != saml1p::StatusCode::SUCCESS) {
+        delete response;
+        throw BindingException("Identity provider returned a SAML error in response to artifact(s).");
+    }
+
     return response;
 }
 
@@ -116,7 +122,7 @@ ArtifactResponse* ArtifactResolver::resolve(
             a->setArtifact(artbuf.get());
             request->setArtifact(a);
 
-            SAML2SOAPClient client(soaper);
+            SAML2SOAPClient client(soaper, false);
             client.sendSAML(request, mcc, loc.get());
             StatusResponseType* srt = client.receiveSAML();
             if (!(response = dynamic_cast<ArtifactResponse*>(srt))) {
@@ -131,6 +137,11 @@ ArtifactResponse* ArtifactResolver::resolve(
     }
 
     if (!response)
-        throw BindingException("Unable to successfully resolve artifact.");
+        throw BindingException("Unable to resolve artifact(s) into a SAML response.");
+    if (!response->getStatus() || !response->getStatus()->getStatusCode() ||
+           !XMLString::equals(response->getStatus()->getStatusCode()->getValue(), saml2p::StatusCode::SUCCESS)) {
+        delete response;
+        throw BindingException("Identity provider returned a SAML error in response to artifact.");
+    }
     return response;
 }
index e00dc8d..df28122 100644 (file)
@@ -91,6 +91,7 @@ namespace shibsp {
          * 
          * @param application   reference to application receiving message
          * @param issuer        source of SSO tokens
+         * @param protocol      SSO protocol used
          * @param nameid        identifier of principal
          * @param authncontext_class    method/category of authentication event, if known
          * @param authncontext_decl specifics of authentication event, if known
@@ -100,9 +101,10 @@ namespace shibsp {
         ResolutionContext* resolveAttributes(
             const Application& application,
             const opensaml::saml2md::EntityDescriptor* issuer=NULL,
+            const XMLCh* protocol=NULL,
             const opensaml::saml2::NameID* nameid=NULL,
-            const char* authncontext_class=NULL,
-            const char* authncontext_decl=NULL,
+            const XMLCh* authncontext_class=NULL,
+            const XMLCh* authncontext_decl=NULL,
             const std::vector<const opensaml::Assertion*>* tokens=NULL,
             const std::multimap<std::string,Attribute*>* attributes=NULL
             ) const;
index 94ccefa..1bdabd2 100644 (file)
@@ -55,6 +55,15 @@ namespace shibsp {
     
     /** Registers Handler implementations. */
     void SHIBSP_API registerHandlers();
+
+    /** LogoutInitiator that iterates through a set of protocol-specific versions. */
+    #define CHAINING_LOGOUT_INITIATOR "Chaining"
+
+    /** LogoutInitiator that supports SAML 2.0 LogoutRequests. */
+    #define SAML2_LOGOUT_INITIATOR "SAML2"
+
+    /** LogoutInitiator that supports local-only logout. */
+    #define LOCAL_LOGOUT_INITIATOR "Local"
 };
 
 #endif /* __shibsp_handler_h__ */
diff --git a/shibsp/handler/LogoutHandler.h b/shibsp/handler/LogoutHandler.h
new file mode 100644 (file)
index 0000000..1c97ba3
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ *  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.
+ */
+
+/**
+ * @file shibsp/handler/LogoutHandler.h
+ * 
+ * Base class for logout-related handlers.
+ */
+
+#ifndef __shibsp_logout_h__
+#define __shibsp_logout_h__
+
+#include <shibsp/SPRequest.h>
+#include <shibsp/handler/RemotedHandler.h>
+
+namespace shibsp {
+
+    /**
+     * Base class for logout-related handlers.
+     */
+    class SHIBSP_API LogoutHandler : public RemotedHandler
+    {
+    protected:
+        LogoutHandler() {}
+
+    public:
+        virtual ~LogoutHandler() {}
+
+        /**
+         * The base method will iteratively attempt front-channel notification
+         * of logout of the current session, and after the final round trip will
+         * perform back-channel notification. Nothing will be done unless the 
+         * handler detects that it is the "top" level logout handler.
+         * If the method returns false, then the specialized class should perform
+         * its work assuming that the notifications are completed.
+         *
+         * Note that the current session is NOT removed from the cache.
+         * 
+         * @param request   SP request context
+         * @param isHandler true iff executing in the context of a direct handler invocation
+         * @return  a pair containing a "request completed" indicator and a server-specific response code
+         */
+        std::pair<bool,long> run(SPRequest& request, bool isHandler=true) const;
+
+        /**
+         * A remoted procedure that will perform any necessary back-channel
+         * notifications. The input structure must contain an "application_id" member,
+         * and a "sessions" list containing the session keys, along with an integer
+         * member called "notify" with a value of 1.
+         * 
+         * @param in    incoming DDF message
+         * @param out   stream to write outgoing DDF message to
+         */
+        void receive(DDF& in, std::ostream& out);
+
+    protected:
+        /**
+         * Perform front-channel logout notifications for an Application.
+         *
+         * If no sessions are supplied directly, the request object's "sessions" query string
+         * parameter will be used.
+         *
+         * @param application   the Application to notify
+         * @param request       last request from browser
+         * @param response      response to use for next notification
+         * @param params        map of query string parameters to preserve across notifications, optionally with initial values
+         * @param sessions      optional array of session keys being logged out
+         * @return  indicator of a completed response along with the status code to return from the handler
+         */
+        std::pair<bool,long> notifyFrontChannel(
+            const Application& application,
+            const xmltooling::HTTPRequest& request,
+            xmltooling::HTTPResponse& response,
+            const std::map<std::string,std::string>* params=NULL,
+            const std::vector<std::string>* sessions=NULL
+            ) const;
+
+        /**
+         * Sends a response template to the user agent informing it of the results of a logout attempt.
+         *
+         * @param application   the Application to use in determining the logout template
+         * @param response      the HTTP response to use
+         * @param local         true iff the logout operation was local to the SP, false iff global
+         * @param status        optional logoutStatus key value to add to template
+         */
+        std::pair<bool,long> sendLogoutPage(
+            const Application& application, xmltooling::HTTPResponse& response, bool local=true, const char* status=NULL
+            ) const;
+
+        /**
+         * Perform back-channel logout notifications for an Application.
+         *
+         * @param application   the Application to notify
+         * @param sessions      array of session keys being logged out
+         * @return  true iff all notifications succeeded
+         */
+        bool notifyBackChannel(const Application& application, const std::vector<std::string>& sessions) const;
+    };
+
+};
+
+#endif /* __shibsp_logout_h__ */
index 034adb0..7321f03 100644 (file)
@@ -59,6 +59,9 @@ namespace shibsp {
     SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory SAML2ConsumerFactory;
     SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory SAML2ArtifactResolutionFactory;
     SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory AssertionLookupFactory;
+    SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory ChainingLogoutInitiatorFactory;
+    SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory LocalLogoutInitiatorFactory;
+    SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory SAML2LogoutInitiatorFactory;
 };
 
 void SHIBSP_API shibsp::registerHandlers()
@@ -74,6 +77,10 @@ void SHIBSP_API shibsp::registerHandlers()
     conf.ArtifactResolutionServiceManager.registerFactory(SAML20_BINDING_SOAP, SAML2ArtifactResolutionFactory);
 
     conf.HandlerManager.registerFactory(SAML20_BINDING_URI, AssertionLookupFactory);
+
+    conf.LogoutInitiatorManager.registerFactory(CHAINING_LOGOUT_INITIATOR, ChainingLogoutInitiatorFactory);
+    conf.LogoutInitiatorManager.registerFactory(LOCAL_LOGOUT_INITIATOR, LocalLogoutInitiatorFactory);
+    conf.LogoutInitiatorManager.registerFactory(SAML2_LOGOUT_INITIATOR, SAML2LogoutInitiatorFactory);
 }
 
 AbstractHandler::AbstractHandler(
@@ -171,7 +178,7 @@ long AbstractHandler::sendMessage(
     if (role && flag.first &&
         (!strcmp(flag.second, "true") ||
             (encoder.isUserAgentPresent() && !strcmp(flag.second, "front")) ||
-            ((!encoder.isUserAgentPresent() && !strcmp(flag.second, "back"))))) {
+            (!encoder.isUserAgentPresent() && !strcmp(flag.second, "back")))) {
         CredentialResolver* credResolver=application.getCredentialResolver();
         if (credResolver) {
             Locker credLocker(credResolver);
@@ -203,6 +210,9 @@ long AbstractHandler::sendMessage(
                 m_log.warn("no signing credential resolved, leaving message unsigned");
             }
         }
+        else {
+            m_log.warn("no credential resolver installed, leaving message unsigned");
+        }
     }
 
     // Unsigned request.
index 434d1f0..a936fac 100644 (file)
@@ -243,9 +243,10 @@ void AssertionConsumerService::checkAddress(
 ResolutionContext* AssertionConsumerService::resolveAttributes(
     const Application& application,
     const saml2md::EntityDescriptor* issuer,
+    const XMLCh* protocol,
     const saml2::NameID* nameid,
-    const char* authncontext_class,
-    const char* authncontext_decl,
+    const XMLCh* authncontext_class,
+    const XMLCh* authncontext_decl,
     const vector<const Assertion*>* tokens,
     const multimap<string,Attribute*>* attributes
     ) const
@@ -261,7 +262,7 @@ ResolutionContext* AssertionConsumerService::resolveAttributes(
 
         Locker locker(resolver);
         auto_ptr<ResolutionContext> ctx(
-            resolver->createResolutionContext(application, issuer, nameid, authncontext_class, authncontext_decl, tokens, attributes)
+            resolver->createResolutionContext(application, issuer, protocol, nameid, authncontext_class, authncontext_decl, tokens, attributes)
             );
         resolver->resolveAttributes(*ctx.get());
         return ctx.release();
diff --git a/shibsp/handler/impl/ChainingLogoutInitiator.cpp b/shibsp/handler/impl/ChainingLogoutInitiator.cpp
new file mode 100644 (file)
index 0000000..0285784
--- /dev/null
@@ -0,0 +1,124 @@
+/*
+ *  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.
+ */
+
+/**
+ * ChainingLogoutInitiator.cpp
+ * 
+ * Chains together multiple LogoutInitiator handlers in sequence.
+ */
+
+#include "internal.h"
+#include "exceptions.h"
+#include "handler/AbstractHandler.h"
+#include "handler/LogoutHandler.h"
+#include "util/SPConstants.h"
+
+#include <xercesc/util/XMLUniDefs.hpp>
+#include <xmltooling/util/XMLHelper.h>
+
+using namespace shibsp;
+using namespace xmltooling;
+using namespace log4cpp;
+using namespace std;
+
+namespace shibsp {
+
+#if defined (_MSC_VER)
+    #pragma warning( push )
+    #pragma warning( disable : 4250 )
+#endif
+
+    class SHIBSP_DLLLOCAL ChainingLogoutInitiator : public AbstractHandler, public LogoutHandler
+    {
+    public:
+        ChainingLogoutInitiator(const DOMElement* e, const char* appId);
+        virtual ~ChainingLogoutInitiator() {
+            for_each(m_handlers.begin(), m_handlers.end(), xmltooling::cleanup<Handler>());
+        }
+        
+        pair<bool,long> run(SPRequest& request, bool isHandler=true) const;
+
+    private:
+        vector<Handler*> m_handlers;
+    };
+
+#if defined (_MSC_VER)
+    #pragma warning( pop )
+#endif
+
+    static const XMLCh _LogoutInitiator[] =     UNICODE_LITERAL_15(L,o,g,o,u,t,I,n,i,t,i,a,t,o,r);
+    static const XMLCh _type[] =                UNICODE_LITERAL_4(t,y,p,e);
+
+    class SHIBSP_DLLLOCAL LogoutInitiatorNodeFilter : public DOMNodeFilter
+    {
+    public:
+        short acceptNode(const DOMNode* node) const {
+            if (XMLHelper::isNodeNamed(node,shibspconstants::SHIB2SPCONFIG_NS,_LogoutInitiator))
+                return FILTER_REJECT;
+            return FILTER_ACCEPT;
+        }
+    };
+
+    static SHIBSP_DLLLOCAL LogoutInitiatorNodeFilter g_LINFilter;
+
+    Handler* SHIBSP_DLLLOCAL ChainingLogoutInitiatorFactory(const pair<const DOMElement*,const char*>& p)
+    {
+        return new ChainingLogoutInitiator(p.first, p.second);
+    }
+};
+
+ChainingLogoutInitiator::ChainingLogoutInitiator(const DOMElement* e, const char* appId)
+    : AbstractHandler(e, Category::getInstance(SHIBSP_LOGCAT".LogoutInitiator"), &g_LINFilter)
+{
+    SPConfig& conf = SPConfig::getConfig();
+
+    // Load up the chain of handlers.
+    e = e ? XMLHelper::getFirstChildElement(e, _LogoutInitiator) : NULL;
+    while (e) {
+        auto_ptr_char type(e->getAttributeNS(NULL,_type));
+        if (type.get() && *(type.get())) {
+            try {
+                m_handlers.push_back(conf.LogoutInitiatorManager.newPlugin(type.get(),make_pair(e, appId)));
+                m_handlers.back()->setParent(this);
+            }
+            catch (exception& ex) {
+                m_log.error("caught exception processing embedded SessionInitiator element: %s", ex.what());
+            }
+        }
+        e = XMLHelper::getNextSiblingElement(e, _LogoutInitiator);
+    }
+
+    pair<bool,const char*> loc = getString("Location");
+    if (loc.first) {
+        string address = string(appId) + loc.second + "::run::ChainingLI";
+        setAddress(address.c_str());
+    }
+}
+
+pair<bool,long> ChainingLogoutInitiator::run(SPRequest& request, bool isHandler) const
+{
+    // Defer to base class first.
+    pair<bool,long> ret = LogoutHandler::run(request, isHandler);
+    if (ret.first)
+        return ret;
+
+    for (vector<Handler*>::const_iterator i = m_handlers.begin(); i!=m_handlers.end(); ++i) {
+        ret = (*i)->run(request, isHandler);
+        if (ret.first)
+            return ret;
+    }
+    throw ConfigurationException("None of the configured LogoutInitiators handled the request.");
+}
diff --git a/shibsp/handler/impl/LogoutHandler.cpp b/shibsp/handler/impl/LogoutHandler.cpp
new file mode 100644 (file)
index 0000000..f2a85d0
--- /dev/null
@@ -0,0 +1,211 @@
+/*
+ *  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.
+ */
+
+/**
+ * LogoutHandler.cpp
+ * 
+ * Base class for logout-related handlers.
+ */
+
+#include "internal.h"
+#include "exceptions.h"
+#include "Application.h"
+#include "ServiceProvider.h"
+#include "SessionCache.h"
+#include "handler/LogoutHandler.h"
+#include "util/TemplateParameters.h"
+
+#include <fstream>
+#include <log4cpp/Category.hh>
+#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::run(SPRequest& request, bool isHandler) const
+{
+    // If no location for this handler, we're inside a chain, so do nothing.
+    if (!getString("Location").first)
+        return make_pair(false,0);
+
+    string session_id;
+    try {
+        // Get the session, ignoring most checks and don't cache the lock.
+        Session* session = request.getSession(false,true,false);
+        if (session)
+            session_id = session->getID();
+        session->unlock();
+    }
+    catch (exception& ex) {
+        log4cpp::Category::getInstance(SHIBSP_LOGCAT".Logout").error("error accessing current session: %s", ex.what());
+    }
+
+    if (session_id.empty())
+        return sendLogoutPage(request.getApplication(), request, true, "The local logout process was only partially completed.");
+
+    // Try another front-channel notification. No extra parameters and the session is implicit.
+    pair<bool,long> ret = notifyFrontChannel(request.getApplication(), request, request);
+    if (ret.first)
+        return ret;
+
+    // Now we complete with back-channel notification, which has to be out of process.
+
+    if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
+        // When out of process, we run natively.
+        vector<string> sessions(1,session_id);
+        notifyBackChannel(request.getApplication(), sessions);
+    }
+    else {
+        // When not out of process, we remote the back channel work.
+        DDF out,in(m_address.c_str());
+        DDFJanitor jin(in), jout(out);
+        in.addmember("notify").integer(1);
+        in.addmember("application_id").string(request.getApplication().getId());
+        DDF s = DDF(NULL).string(session_id.c_str());
+        in.addmember("sessions").list().add(s);
+        out=request.getServiceProvider().getListenerService()->send(in);
+    }
+
+    return make_pair(false,0);
+}
+
+void LogoutHandler::receive(DDF& in, ostream& out)
+{
+    DDF ret(NULL);
+    DDFJanitor jout(ret);
+    if (in["notify"].integer() != 1)
+        throw ListenerException("Unsupported operation.");
+
+    // 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.
+        log4cpp::Category::getInstance(SHIBSP_LOGCAT".Logout").error("couldn't find application (%s) for logout", aid ? aid : "(missing)");
+        throw ConfigurationException("Unable to locate application for logout, deleted?");
+    }
+
+    vector<string> sessions;
+    DDF s = in["sessions"];
+    DDF temp = s.first();
+    while (temp.isstring()) {
+        sessions.push_back(temp.string());
+        temp = s.next();
+        notifyBackChannel(*app, sessions);
+    }
+
+    out << ret;
+}
+
+pair<bool,long> LogoutHandler::notifyFrontChannel(
+    const Application& application,
+    const HTTPRequest& request,
+    HTTPResponse& response,
+    const map<string,string>* params,
+    const vector<string>* sessions
+    ) const
+{
+    // Index of notification point starts at 0.
+    unsigned int index = 0;
+    const char* param = request.getParameter("index");
+    if (param)
+        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++);
+    if (loc.empty())
+        return make_pair(false,0);
+
+    const URLEncoder* encoder = XMLToolingConfig::getConfig().getURLEncoder();
+
+    // Start with an "action" telling the application what this is about.
+    loc = loc + (strchr(loc.c_str(),'?') ? '&' : '?') + "action=logout";
+
+    // Attach the "sessions" parameter, if any.
+    if (sessions) {
+        string keys;
+        for (vector<string>::const_iterator k = sessions->begin(); k!=sessions->end(); ++k) {
+            if (!keys.empty())
+                keys += ',';
+            keys += *k;
+        }
+        loc = loc + "&sessions=" + keys;
+    }
+    else if (param = request.getParameter("sessions")) {
+        loc = loc + "&sessions=" + request.getParameter("sessions");
+    }
+
+    // Now we create a second URL representing the return location back to us.
+    const char* start = request.getRequestURL();
+    const char* end = strchr(start,'?');
+    string tempstr(start, end ? end-start : strlen(start));
+    ostringstream locstr(tempstr);
+
+    // Add a signal that we're coming back from notification and the next index.
+    locstr << "?notifying=1&index=" << index;
+
+    // Now we preserve anything we're instructed to (or populate the initial values from the map).
+    if (params) {
+        for (map<string,string>::const_iterator p = params->begin(); p!=params->end(); ++p) {
+            if (!p->second.empty())
+                locstr << '&' << p->first << '=' << encoder->encode(p->second.c_str());
+            else if (param = request.getParameter(p->first.c_str()))
+                locstr << '&' << p->first << '=' << encoder->encode(param);
+        }
+    }
+
+    // Add the return parameter to the destination location and redirect.
+    loc = loc + "&return=" + encoder->encode(locstr.str().c_str());
+    return make_pair(true,response.sendRedirect(loc.c_str()));
+}
+
+bool LogoutHandler::notifyBackChannel(const Application& application, const vector<string>& sessions) const
+{
+#ifndef SHIBSP_LITE
+    return false;
+#else
+    throw ConfigurationException("Cannot perform back channel notification using lite version of shibsp library.");
+#endif
+}
+
+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 1da0cb7..5d66396 100644 (file)
@@ -204,10 +204,6 @@ string SAML1Consumer::implementProtocol(
     pair<bool,unsigned int> lifetime = sessionProps ? sessionProps->getUnsignedInt("lifetime") : pair<bool,unsigned int>(true,28800);
     if (!lifetime.first || lifetime.second == 0)
         lifetime.second = 28800;
-    auto_ptr_char authnInstant(
-        ssoStatement->getAuthenticationInstant() ? ssoStatement->getAuthenticationInstant()->getRawData() : NULL
-        );
-    auto_ptr_char authnMethod(ssoStatement->getAuthenticationMethod());
 
     // We've successfully "accepted" at least one SSO token, along with any additional valid tokens.
     // To complete processing, we need to extract and resolve attributes and then create the session.
@@ -235,7 +231,7 @@ string SAML1Consumer::implementProtocol(
 
         AttributeFilter* filter = application.getAttributeFilter();
         if (filter && !resolvedAttributes.empty()) {
-            BasicFilteringContext fc(application, resolvedAttributes, policy.getIssuerMetadata(), authnMethod.get());
+            BasicFilteringContext fc(application, resolvedAttributes, policy.getIssuerMetadata(), ssoStatement->getAuthenticationMethod());
             Locker filtlocker(filter);
             try {
                 filter->filterAttributes(fc, resolvedAttributes);
@@ -260,7 +256,17 @@ string SAML1Consumer::implementProtocol(
     const EntityDescriptor* issuerMetadata =
         policy.getIssuerMetadata() ? dynamic_cast<const EntityDescriptor*>(policy.getIssuerMetadata()->getParent()) : NULL;
     auto_ptr<ResolutionContext> ctx(
-        resolveAttributes(application, issuerMetadata, nameid.get(), authnMethod.get(), NULL, &tokens, &resolvedAttributes)
+        resolveAttributes(
+            application,
+            issuerMetadata,
+            (!response->getMinorVersion().first || response->getMinorVersion().second==1) ?
+                samlconstants::SAML11_PROTOCOL_ENUM : samlconstants::SAML10_PROTOCOL_ENUM,
+            nameid.get(),
+            ssoStatement->getAuthenticationMethod(),
+            NULL,
+            &tokens,
+            &resolvedAttributes
+            )
         );
 
     if (ctx.get()) {
@@ -281,10 +287,12 @@ string SAML1Consumer::implementProtocol(
             application,
             httpRequest.getRemoteAddr().c_str(),
             issuerMetadata,
+            (!response->getMinorVersion().first || response->getMinorVersion().second==1) ?
+                samlconstants::SAML11_PROTOCOL_ENUM : samlconstants::SAML10_PROTOCOL_ENUM,
             nameid.get(),
-            authnInstant.get(),
+            ssoStatement->getAuthenticationInstant() ? ssoStatement->getAuthenticationInstant()->getRawData() : NULL,
             NULL,
-            authnMethod.get(),
+            ssoStatement->getAuthenticationMethod(),
             NULL,
             &tokens,
             &resolvedAttributes
index fffede9..414005c 100644 (file)
@@ -336,13 +336,6 @@ string SAML2Consumer::implementProtocol(
     else
         sessionExp = min(sessionExp, now + lifetime.second);    // Use the lowest.
 
-    // Other details...
-    const AuthnContext* authnContext = ssoStatement->getAuthnContext();
-    auto_ptr_char authnClass((authnContext && authnContext->getAuthnContextClassRef()) ? authnContext->getAuthnContextClassRef()->getReference() : NULL);
-    auto_ptr_char authnDecl((authnContext && authnContext->getAuthnContextDeclRef()) ? authnContext->getAuthnContextDeclRef()->getReference() : NULL);
-    auto_ptr_char index(ssoStatement->getSessionIndex());
-    auto_ptr_char authnInstant(ssoStatement->getAuthnInstant() ? ssoStatement->getAuthnInstant()->getRawData() : NULL);
-
     multimap<string,Attribute*> resolvedAttributes;
     AttributeExtractor* extractor = application.getAttributeExtractor();
     if (extractor) {
@@ -364,9 +357,17 @@ string SAML2Consumer::implementProtocol(
         }
     }
 
+    const AuthnContext* authnContext = ssoStatement->getAuthnContext();
+
     AttributeFilter* filter = application.getAttributeFilter();
     if (filter && !resolvedAttributes.empty()) {
-        BasicFilteringContext fc(application, resolvedAttributes, policy.getIssuerMetadata(), authnClass.get(), authnDecl.get());
+        BasicFilteringContext fc(
+            application,
+            resolvedAttributes,
+            policy.getIssuerMetadata(),
+            (authnContext && authnContext->getAuthnContextClassRef()) ? authnContext->getAuthnContextClassRef()->getReference() : NULL,
+            (authnContext && authnContext->getAuthnContextDeclRef()) ? authnContext->getAuthnContextDeclRef()->getReference() : NULL
+            );
         Locker filtlocker(filter);
         try {
             filter->filterAttributes(fc, resolvedAttributes);
@@ -383,7 +384,16 @@ string SAML2Consumer::implementProtocol(
         const EntityDescriptor* issuerMetadata =
             policy.getIssuerMetadata() ? dynamic_cast<const EntityDescriptor*>(policy.getIssuerMetadata()->getParent()) : NULL;
         auto_ptr<ResolutionContext> ctx(
-            resolveAttributes(application, issuerMetadata, ssoName, authnClass.get(), authnDecl.get(), &tokens, &resolvedAttributes)
+            resolveAttributes(
+                application,
+                issuerMetadata,
+                samlconstants::SAML20P_NS,
+                ssoName,
+                (authnContext && authnContext->getAuthnContextClassRef()) ? authnContext->getAuthnContextClassRef()->getReference() : NULL,
+                (authnContext && authnContext->getAuthnContextDeclRef()) ? authnContext->getAuthnContextDeclRef()->getReference() : NULL,
+                &tokens,
+                &resolvedAttributes
+                )
             );
 
         if (ctx.get()) {
@@ -403,11 +413,12 @@ string SAML2Consumer::implementProtocol(
             application,
             httpRequest.getRemoteAddr().c_str(),
             issuerMetadata,
+            samlconstants::SAML20P_NS,
             ssoName,
-            authnInstant.get(),
-            index.get(),
-            authnClass.get(),
-            authnDecl.get(),
+            ssoStatement->getAuthnInstant() ? ssoStatement->getAuthnInstant()->getRawData() : NULL,
+            ssoStatement->getSessionIndex(),
+            (authnContext && authnContext->getAuthnContextClassRef()) ? authnContext->getAuthnContextClassRef()->getReference() : NULL,
+            (authnContext && authnContext->getAuthnContextDeclRef()) ? authnContext->getAuthnContextDeclRef()->getReference() : NULL,
             &tokens,
             &resolvedAttributes
             );
index 32c23f9..180bbc3 100644 (file)
@@ -86,6 +86,9 @@ namespace shibsp {
         const char* getEntityID() const {\r
             return m_obj["entity_id"].string();\r
         }\r
+        const char* getProtocol() const {\r
+            return m_obj["protocol"].string();\r
+        }\r
         const char* getAuthnInstant() const {\r
             return m_obj["authn_instant"].string();\r
         }\r
index 2d2f0a8..c607c39 100644 (file)
@@ -90,6 +90,9 @@ namespace shibsp {
         const char* getEntityID() const {\r
             return m_obj["entity_id"].string();\r
         }\r
+        const char* getProtocol() const {\r
+            return m_obj["protocol"].string();\r
+        }\r
         const char* getAuthnInstant() const {\r
             return m_obj["authn_instant"].string();\r
         }\r
@@ -150,34 +153,24 @@ namespace shibsp {
             const Application& application,\r
             const char* client_addr=NULL,\r
             const saml2md::EntityDescriptor* issuer=NULL,\r
+            const XMLCh* protocol=NULL,\r
             const saml2::NameID* nameid=NULL,\r
-            const char* authn_instant=NULL,\r
-            const char* session_index=NULL,\r
-            const char* authncontext_class=NULL,\r
-            const char* authncontext_decl=NULL,\r
+            const XMLCh* authn_instant=NULL,\r
+            const XMLCh* session_index=NULL,\r
+            const XMLCh* authncontext_class=NULL,\r
+            const XMLCh* authncontext_decl=NULL,\r
             const vector<const Assertion*>* tokens=NULL,\r
             const multimap<string,Attribute*>* attributes=NULL\r
             );\r
         Session* find(const char* key, const Application& application, const char* client_addr=NULL, time_t* timeout=NULL);\r
         void remove(const char* key, const Application& application);\r
-        void find(\r
-            const saml2md::EntityDescriptor& issuer,\r
-            const saml2::NameID& nameid,\r
-            const char* index,\r
-            const Application& application,\r
-            vector<string>& sessions\r
-            ) {\r
-            byname(issuer, nameid, index, application, sessions, false);\r
-        }\r
         void remove(\r
-            const saml2md::EntityDescriptor& issuer,\r
+            const saml2md::EntityDescriptor* issuer,\r
             const saml2::NameID& nameid,\r
             const char* index,\r
             const Application& application,\r
             vector<string>& sessions\r
-            ) {\r
-            byname(issuer, nameid, index, application, sessions, true);\r
-        }\r
+            );\r
 \r
         Category& m_log;\r
         StorageService* m_storage;\r
@@ -185,14 +178,6 @@ namespace shibsp {
     private:\r
         // maintain back-mappings of NameID/SessionIndex -> session key\r
         void insert(const char* key, time_t expires, const char* name, const char* index);\r
-        void byname(\r
-            const saml2md::EntityDescriptor& issuer,\r
-            const saml2::NameID& nameid,\r
-            const char* index,\r
-            const Application& application,\r
-            vector<string>& sessions,\r
-            bool logout\r
-            );\r
 \r
         bool stronglyMatches(const XMLCh* idp, const XMLCh* sp, const saml2::NameID& n1, const saml2::NameID& n2) const;\r
     };\r
@@ -494,7 +479,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.\r
     string record;\r
     time_t recordexp;\r
-    int ver = m_storage->readText("Logout", name, &record, &recordexp);\r
+    int ver = m_storage->readText("NameID", name, &record, &recordexp);\r
     if (ver > 0) {\r
         // Existing record, so we need to unmarshall it.\r
         istringstream in(record);\r
@@ -505,7 +490,7 @@ void SSCache::insert(const char* key, time_t expires, const char* name, const ch
         obj.structure();\r
     }\r
 \r
-    if (!index)\r
+    if (!index || !*index)\r
         index = "_shibnull";\r
     DDF sessions = obj.addmember(index);\r
     if (!sessions.islist())\r
@@ -519,13 +504,13 @@ void SSCache::insert(const char* key, time_t expires, const char* name, const ch
 \r
     // Try and store it back...\r
     if (ver > 0) {\r
-        ver = m_storage->updateText("Logout", name, out.str().c_str(), max(expires, recordexp), ver);\r
+        ver = m_storage->updateText("NameID", name, out.str().c_str(), max(expires, recordexp), ver);\r
         if (ver <= 0) {\r
             // Out of sync, or went missing, so retry.\r
             return insert(key, expires, name, index);\r
         }\r
     }\r
-    else if (!m_storage->createText("Logout", name, out.str().c_str(), expires)) {\r
+    else if (!m_storage->createText("NameID", name, out.str().c_str(), expires)) {\r
         // Hit a dup, so just retry, hopefully hitting the other branch.\r
         return insert(key, expires, name, index);\r
     }\r
@@ -536,11 +521,12 @@ string SSCache::insert(
     const Application& application,\r
     const char* client_addr,\r
     const saml2md::EntityDescriptor* issuer,\r
+    const XMLCh* protocol,\r
     const saml2::NameID* nameid,\r
-    const char* authn_instant,\r
-    const char* session_index,\r
-    const char* authncontext_class,\r
-    const char* authncontext_decl,\r
+    const XMLCh* authn_instant,\r
+    const XMLCh* session_index,\r
+    const XMLCh* authncontext_class,\r
+    const XMLCh* authncontext_decl,\r
     const vector<const Assertion*>* tokens,\r
     const multimap<string,Attribute*>* attributes\r
     )\r
@@ -575,14 +561,25 @@ string SSCache::insert(
         auto_ptr_char entity_id(issuer->getEntityID());\r
         obj.addmember("entity_id").string(entity_id.get());\r
     }\r
-    if (authn_instant)\r
-        obj.addmember("authn_instant").string(authn_instant);\r
+    if (protocol) {\r
+        auto_ptr_char prot(protocol);\r
+        obj.addmember("protocol").string(prot.get());\r
+    }\r
+    if (authn_instant) {\r
+        auto_ptr_char instant(authn_instant);\r
+        obj.addmember("authn_instant").string(instant.get());\r
+    }\r
+    auto_ptr_char index(session_index);\r
     if (session_index)\r
-        obj.addmember("session_index").string(session_index);\r
-    if (authncontext_class)\r
-        obj.addmember("authncontext_class").string(authncontext_class);\r
-    if (authncontext_decl)\r
-        obj.addmember("authncontext_decl").string(authncontext_decl);\r
+        obj.addmember("session_index").string(index.get());\r
+    if (authncontext_class) {\r
+        auto_ptr_char ac(authncontext_class);\r
+        obj.addmember("authncontext_class").string(ac.get());\r
+    }\r
+    if (authncontext_decl) {\r
+        auto_ptr_char ad(authncontext_decl);\r
+        obj.addmember("authncontext_decl").string(ad.get());\r
+    }\r
 \r
     if (nameid) {\r
         ostringstream namestr;\r
@@ -620,7 +617,7 @@ string SSCache::insert(
     auto_ptr_char name(nameid ? nameid->getName() : NULL);\r
     try {\r
         if (name.get())\r
-            insert(key.get(), expires, name.get(), session_index);\r
+            insert(key.get(), expires, name.get(), index.get());\r
     }\r
     catch (exception& ex) {\r
         m_log.error("error storing back mapping of NameID for logout: %s", ex.what());\r
@@ -789,25 +786,24 @@ void SSCache::remove(const char* key, const Application& application)
     xlog->log.info("Destroyed session (applicationId: %s) (ID: %s)", application.getId(), key);\r
 }\r
 \r
-void SSCache::byname(\r
-    const saml2md::EntityDescriptor& issuer,\r
+void SSCache::remove(\r
+    const saml2md::EntityDescriptor* issuer,\r
     const saml2::NameID& nameid,\r
     const char* index,\r
     const Application& application,\r
-    vector<string>& sessionsKilled,\r
-    bool logout\r
+    vector<string>& sessionsKilled\r
     )\r
 {\r
 #ifdef _DEBUG\r
     xmltooling::NDC ndc("remove");\r
 #endif\r
 \r
-    auto_ptr_char entityID(issuer.getEntityID());\r
+    auto_ptr_char entityID(issuer ? issuer->getEntityID() : NULL);\r
     auto_ptr_char name(nameid.getName());\r
 \r
     m_log.info(\r
-        "request to %s sessions from (%s) for (%s) for session index (%s)",\r
-        logout ? "logout" : "locate", entityID.get(), name.get(), index ? index : "all"\r
+        "request to logout sessions from (%s) for (%s) for session index (%s)",\r
+        entityID.get() ? entityID.get() : "unknown", name.get(), index ? index : "all"\r
         );\r
 \r
     if (strlen(name.get()) > 255)\r
@@ -815,7 +811,7 @@ void SSCache::byname(
 \r
     // Read in potentially matching sessions.\r
     string record;\r
-    int ver = m_storage->readText("Logout", name.get(), &record);\r
+    int ver = m_storage->readText("NameID", name.get(), &record);\r
     if (ver == 0) {\r
         m_log.debug("no active sessions to remove for supplied issuer and name identifier");\r
         return;\r
@@ -838,14 +834,12 @@ void SSCache::byname(
                 Locker locker(session);\r
                 if (session) {\r
                     // Same issuer?\r
-                    if (session->getEntityID() && !strcmp(session->getEntityID(), entityID.get())) {\r
+                    if (XMLString::equals(session->getEntityID(), entityID.get())) {\r
                         // Same NameID?\r
-                        if (stronglyMatches(issuer.getEntityID(), application.getXMLString("entityID").second, nameid, *session->getNameID())) {\r
-                            if (logout) {\r
-                                remove(key.string(), application);  // let this throw to detect errors in case the full logout fails?\r
-                                key.destroy();\r
-                            }\r
+                        if (stronglyMatches(issuer->getEntityID(), application.getXMLString("entityID").second, nameid, *session->getNameID())) {\r
                             sessionsKilled.push_back(key.string());\r
+                            remove(key.string(), application);  // let this throw to detect errors in case the full logout fails?\r
+                            key.destroy();\r
                         }\r
                         else {\r
                             m_log.debug("session (%s) contained a non-matching NameID, leaving it alone", key.string());\r
@@ -876,12 +870,12 @@ void SSCache::byname(
     // If possible, write back the mapping record (this isn't crucial).\r
     try {\r
         if (obj.isnull()) {\r
-            m_storage->deleteText("Logout", name.get());\r
+            m_storage->deleteText("NameID", name.get());\r
         }\r
         else if (!sessionsKilled.empty()) {\r
             ostringstream out;\r
             out << obj;\r
-            if (m_storage->updateText("Logout", name.get(), out.str().c_str(), 0, ver) <= 0)\r
+            if (m_storage->updateText("NameID", name.get(), out.str().c_str(), 0, ver) <= 0)\r
                 m_log.warn("logout mapping record changed behind us, leaving it alone");\r
         }\r
     }\r
index 4c57cdd..5f14445 100644 (file)
@@ -387,6 +387,7 @@ namespace {
     static const XMLCh Listener[] =             UNICODE_LITERAL_8(L,i,s,t,e,n,e,r);
     static const XMLCh Location[] =             UNICODE_LITERAL_8(L,o,c,a,t,i,o,n);
     static const XMLCh logger[] =               UNICODE_LITERAL_6(l,o,g,g,e,r);
+    static const XMLCh _LogoutInitiator[] =     UNICODE_LITERAL_15(L,o,g,o,u,t,I,n,i,t,i,a,t,o,r);
     static const XMLCh _ManageNameIDService[] = UNICODE_LITERAL_19(M,a,n,a,g,e,N,a,m,e,I,D,S,e,r,v,i,c,e);
     static const XMLCh MemoryListener[] =       UNICODE_LITERAL_14(M,e,m,o,r,y,L,i,s,t,e,n,e,r);
     static const XMLCh _MetadataProvider[] =    UNICODE_LITERAL_16(M,e,t,a,d,a,t,a,P,r,o,v,i,d,e,r);
@@ -594,6 +595,15 @@ XMLApplication::XMLApplication(
                             m_sessionInitDefault=sihandler;
                     }
                 }
+                else if (XMLString::equals(child->getLocalName(),_LogoutInitiator)) {
+                    auto_ptr_char type(child->getAttributeNS(NULL,_type));
+                    if (!type.get() || !*(type.get())) {
+                        log.warn("LogoutInitiator element has no type attribute, skipping it...");
+                        child = XMLHelper::getNextSiblingElement(child);
+                        continue;
+                    }
+                    handler=conf.LogoutInitiatorManager.newPlugin(type.get(),make_pair(child, getId()));
+                }
                 else if (XMLString::equals(child->getLocalName(),_ArtifactResolutionService)) {
                     auto_ptr_char bindprop(child->getAttributeNS(NULL,Binding));
                     if (!bindprop.get() || !*(bindprop.get())) {
@@ -881,9 +891,10 @@ short XMLApplication::acceptNode(const DOMNode* node) const
         XMLString::equals(name,Notify) ||
         XMLString::equals(name,_AssertionConsumerService) ||
         XMLString::equals(name,_ArtifactResolutionService) ||
-        XMLString::equals(name,_SingleLogoutService) ||
+        XMLString::equals(name,_LogoutInitiator) ||
         XMLString::equals(name,_ManageNameIDService) ||
         XMLString::equals(name,_SessionInitiator) ||
+        XMLString::equals(name,_SingleLogoutService) ||
         XMLString::equals(name,DefaultRelyingParty) ||
         XMLString::equals(name,RelyingParty) ||
         XMLString::equals(name,_MetadataProvider) ||