From: cantor Date: Sat, 7 Jul 2007 00:12:08 +0000 (+0000) Subject: First set of logout base classes and non-building draft of SP-initiated logout. X-Git-Tag: 2.4~858 X-Git-Url: http://www.project-moonshot.org/gitweb/?p=shibboleth%2Fsp.git;a=commitdiff_plain;h=d2b54916e13bac5fbb0c3e8d48d90622430685cc First set of logout base classes and non-building draft of SP-initiated logout. 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 --- diff --git a/schemas/shibboleth-2.0-native-sp-config.xsd b/schemas/shibboleth-2.0-native-sp-config.xsd index 9c3e191..a79fdfc 100644 --- a/schemas/shibboleth-2.0-native-sp-config.xsd +++ b/schemas/shibboleth-2.0-native-sp-config.xsd @@ -416,6 +416,7 @@ + @@ -482,6 +483,27 @@ + + + + Used to specify handlers that can issue LogoutRequests + + + + + + + + + + + + + + + + + diff --git a/shibsp/AbstractSPRequest.cpp b/shibsp/AbstractSPRequest.cpp index 67d00a7..d6cc6a5 100644 --- a/shibsp/AbstractSPRequest.cpp +++ b/shibsp/AbstractSPRequest.cpp @@ -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 p=props->getUnsignedInt("timeout"); - if (p.first) - timeout = p.second; + if (checkTimeout || !ignoreAddress) { + const PropertySet* props=app.getPropertySet("Sessions"); + if (props) { + if (checkTimeout) { + pair p=props->getUnsignedInt("timeout"); + if (p.first) + timeout = p.second; + } + pair pcheck=props->getBool("consistentAddress"); + if (pcheck.first) + ignoreAddress = !pcheck.second; } - pair 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(); diff --git a/shibsp/AbstractSPRequest.h b/shibsp/AbstractSPRequest.h index 3dc1c68..aed5812 100644 --- a/shibsp/AbstractSPRequest.h +++ b/shibsp/AbstractSPRequest.h @@ -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; diff --git a/shibsp/SPConfig.cpp b/shibsp/SPConfig.cpp index ca0c879..3c9ad7f 100644 --- a/shibsp/SPConfig.cpp +++ b/shibsp/SPConfig.cpp @@ -196,6 +196,7 @@ void SPInternalConfig::term() ArtifactResolutionServiceManager.deregisterFactories(); AssertionConsumerServiceManager.deregisterFactories(); + LogoutInitiatorManager.deregisterFactories(); ManageNameIDServiceManager.deregisterFactories(); SessionInitiatorManager.deregisterFactories(); SingleLogoutServiceManager.deregisterFactories(); diff --git a/shibsp/SPConfig.h b/shibsp/SPConfig.h index b3a4e23..6898b5d 100644 --- a/shibsp/SPConfig.h +++ b/shibsp/SPConfig.h @@ -228,6 +228,11 @@ namespace shibsp { xmltooling::PluginManager ListenerServiceManager; /** + * Manages factories for Handler plugins that implement LogoutInitiator functionality. + */ + xmltooling::PluginManager< Handler,std::string,std::pair > LogoutInitiatorManager; + + /** * Manages factories for Handler plugins that implement ManageNameIDService functionality. */ xmltooling::PluginManager< Handler,std::string,std::pair > ManageNameIDServiceManager; diff --git a/shibsp/SPRequest.h b/shibsp/SPRequest.h index a0b9ac4..7cc7bf7 100644 --- a/shibsp/SPRequest.h +++ b/shibsp/SPRequest.h @@ -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, diff --git a/shibsp/SessionCache.h b/shibsp/SessionCache.h index 954f5ea..9e2d453 100644 --- a/shibsp/SessionCache.h +++ b/shibsp/SessionCache.h @@ -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* tokens=NULL, const std::multimap* 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& 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, diff --git a/shibsp/attribute/filtering/BasicFilteringContext.h b/shibsp/attribute/filtering/BasicFilteringContext.h index b05f555..721d638 100644 --- a/shibsp/attribute/filtering/BasicFilteringContext.h +++ b/shibsp/attribute/filtering/BasicFilteringContext.h @@ -43,8 +43,8 @@ namespace shibsp { const Application& app, const std::multimap& 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(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& 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; }; }; diff --git a/shibsp/attribute/filtering/FilteringContext.h b/shibsp/attribute/filtering/FilteringContext.h index 6d3b59c..c4e3032 100644 --- a/shibsp/attribute/filtering/FilteringContext.h +++ b/shibsp/attribute/filtering/FilteringContext.h @@ -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. diff --git a/shibsp/attribute/filtering/impl/AuthenticationMethodStringFunctor.cpp b/shibsp/attribute/filtering/impl/AuthenticationMethodStringFunctor.cpp index 700bb70..69df276 100644 --- a/shibsp/attribute/filtering/impl/AuthenticationMethodStringFunctor.cpp +++ b/shibsp/attribute/filtering/impl/AuthenticationMethodStringFunctor.cpp @@ -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()); } }; diff --git a/shibsp/attribute/resolver/AttributeResolver.h b/shibsp/attribute/resolver/AttributeResolver.h index be1a6ba..4376c89 100644 --- a/shibsp/attribute/resolver/AttributeResolver.h +++ b/shibsp/attribute/resolver/AttributeResolver.h @@ -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* tokens=NULL, const std::multimap* attributes=NULL ) const=0; diff --git a/shibsp/attribute/resolver/impl/ChainingAttributeResolver.cpp b/shibsp/attribute/resolver/impl/ChainingAttributeResolver.cpp index 073e9e3..8de4e3d 100644 --- a/shibsp/attribute/resolver/impl/ChainingAttributeResolver.cpp +++ b/shibsp/attribute/resolver/impl/ChainingAttributeResolver.cpp @@ -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* tokens=NULL, const multimap* attributes=NULL ) const { auto_ptr chain(new ChainingContext()); for (vector::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(); } diff --git a/shibsp/attribute/resolver/impl/QueryAttributeResolver.cpp b/shibsp/attribute/resolver/impl/QueryAttributeResolver.cpp index 2aa73f1..738d73b 100644 --- a/shibsp/attribute/resolver/impl/QueryAttributeResolver.cpp +++ b/shibsp/attribute/resolver/impl/QueryAttributeResolver.cpp @@ -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* tokens=NULL, const multimap* 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_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()); @@ -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 m_attributes; vector 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* tokens=NULL, const multimap* 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 || + *(response->getStatus()->getStatusCode()->getValue()) != saml1p::StatusCode::SUCCESS) { + delete response; + m_log.error("attribute authority returned a SAML error"); + return true; + } const vector& assertions = const_cast(response)->getAssertions(); if (assertions.size()>1) @@ -395,7 +415,7 @@ bool QueryResolver::SAML2Query(QueryContext& ctx) const for (vector::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(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& assertions = const_cast(response)->getAssertions(); if (assertions.size()>1) diff --git a/shibsp/binding/impl/ArtifactResolver.cpp b/shibsp/binding/impl/ArtifactResolver.cpp index eb6bd20..f457a3f 100644 --- a/shibsp/binding/impl/ArtifactResolver.cpp +++ b/shibsp/binding/impl/ArtifactResolver.cpp @@ -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(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; } diff --git a/shibsp/handler/AssertionConsumerService.h b/shibsp/handler/AssertionConsumerService.h index e00dc8d..df28122 100644 --- a/shibsp/handler/AssertionConsumerService.h +++ b/shibsp/handler/AssertionConsumerService.h @@ -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* tokens=NULL, const std::multimap* attributes=NULL ) const; diff --git a/shibsp/handler/Handler.h b/shibsp/handler/Handler.h index 94ccefa..1bdabd2 100644 --- a/shibsp/handler/Handler.h +++ b/shibsp/handler/Handler.h @@ -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 index 0000000..1c97ba3 --- /dev/null +++ b/shibsp/handler/LogoutHandler.h @@ -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 +#include + +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 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 notifyFrontChannel( + const Application& application, + const xmltooling::HTTPRequest& request, + xmltooling::HTTPResponse& response, + const std::map* params=NULL, + const std::vector* 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 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& sessions) const; + }; + +}; + +#endif /* __shibsp_logout_h__ */ diff --git a/shibsp/handler/impl/AbstractHandler.cpp b/shibsp/handler/impl/AbstractHandler.cpp index 034adb0..7321f03 100644 --- a/shibsp/handler/impl/AbstractHandler.cpp +++ b/shibsp/handler/impl/AbstractHandler.cpp @@ -59,6 +59,9 @@ namespace shibsp { SHIBSP_DLLLOCAL PluginManager< Handler,string,pair >::Factory SAML2ConsumerFactory; SHIBSP_DLLLOCAL PluginManager< Handler,string,pair >::Factory SAML2ArtifactResolutionFactory; SHIBSP_DLLLOCAL PluginManager< Handler,string,pair >::Factory AssertionLookupFactory; + SHIBSP_DLLLOCAL PluginManager< Handler,string,pair >::Factory ChainingLogoutInitiatorFactory; + SHIBSP_DLLLOCAL PluginManager< Handler,string,pair >::Factory LocalLogoutInitiatorFactory; + SHIBSP_DLLLOCAL PluginManager< Handler,string,pair >::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. diff --git a/shibsp/handler/impl/AssertionConsumerService.cpp b/shibsp/handler/impl/AssertionConsumerService.cpp index 434d1f0..a936fac 100644 --- a/shibsp/handler/impl/AssertionConsumerService.cpp +++ b/shibsp/handler/impl/AssertionConsumerService.cpp @@ -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* tokens, const multimap* attributes ) const @@ -261,7 +262,7 @@ ResolutionContext* AssertionConsumerService::resolveAttributes( Locker locker(resolver); auto_ptr 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 index 0000000..0285784 --- /dev/null +++ b/shibsp/handler/impl/ChainingLogoutInitiator.cpp @@ -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 +#include + +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()); + } + + pair run(SPRequest& request, bool isHandler=true) const; + + private: + vector 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& 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 loc = getString("Location"); + if (loc.first) { + string address = string(appId) + loc.second + "::run::ChainingLI"; + setAddress(address.c_str()); + } +} + +pair ChainingLogoutInitiator::run(SPRequest& request, bool isHandler) const +{ + // Defer to base class first. + pair ret = LogoutHandler::run(request, isHandler); + if (ret.first) + return ret; + + for (vector::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 index 0000000..f2a85d0 --- /dev/null +++ b/shibsp/handler/impl/LogoutHandler.cpp @@ -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 +#include +#include +#include + +#ifndef SHIBSP_LITE +#endif + +using namespace shibsp; +using namespace xmltooling; +using namespace std; + +pair 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 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 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 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 LogoutHandler::notifyFrontChannel( + const Application& application, + const HTTPRequest& request, + HTTPResponse& response, + const map* params, + const vector* 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::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::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& sessions) const +{ +#ifndef SHIBSP_LITE + return false; +#else + throw ConfigurationException("Cannot perform back channel notification using lite version of shibsp library."); +#endif +} + +pair LogoutHandler::sendLogoutPage(const Application& application, HTTPResponse& response, bool local, const char* status) const +{ + pair 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)); +} diff --git a/shibsp/handler/impl/SAML1Consumer.cpp b/shibsp/handler/impl/SAML1Consumer.cpp index 1da0cb7..5d66396 100644 --- a/shibsp/handler/impl/SAML1Consumer.cpp +++ b/shibsp/handler/impl/SAML1Consumer.cpp @@ -204,10 +204,6 @@ string SAML1Consumer::implementProtocol( pair lifetime = sessionProps ? sessionProps->getUnsignedInt("lifetime") : pair(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(policy.getIssuerMetadata()->getParent()) : NULL; auto_ptr 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 diff --git a/shibsp/handler/impl/SAML2Consumer.cpp b/shibsp/handler/impl/SAML2Consumer.cpp index fffede9..414005c 100644 --- a/shibsp/handler/impl/SAML2Consumer.cpp +++ b/shibsp/handler/impl/SAML2Consumer.cpp @@ -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 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(policy.getIssuerMetadata()->getParent()) : NULL; auto_ptr 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 ); diff --git a/shibsp/impl/RemotedSessionCache.cpp b/shibsp/impl/RemotedSessionCache.cpp index 32c23f9..180bbc3 100644 --- a/shibsp/impl/RemotedSessionCache.cpp +++ b/shibsp/impl/RemotedSessionCache.cpp @@ -86,6 +86,9 @@ namespace shibsp { const char* getEntityID() const { return m_obj["entity_id"].string(); } + const char* getProtocol() const { + return m_obj["protocol"].string(); + } const char* getAuthnInstant() const { return m_obj["authn_instant"].string(); } diff --git a/shibsp/impl/StorageServiceSessionCache.cpp b/shibsp/impl/StorageServiceSessionCache.cpp index 2d2f0a8..c607c39 100644 --- a/shibsp/impl/StorageServiceSessionCache.cpp +++ b/shibsp/impl/StorageServiceSessionCache.cpp @@ -90,6 +90,9 @@ namespace shibsp { const char* getEntityID() const { return m_obj["entity_id"].string(); } + const char* getProtocol() const { + return m_obj["protocol"].string(); + } const char* getAuthnInstant() const { return m_obj["authn_instant"].string(); } @@ -150,34 +153,24 @@ namespace shibsp { const Application& application, const char* client_addr=NULL, const saml2md::EntityDescriptor* issuer=NULL, + const XMLCh* protocol=NULL, const 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 vector* tokens=NULL, const multimap* attributes=NULL ); Session* find(const char* key, const Application& application, const char* client_addr=NULL, time_t* timeout=NULL); void remove(const char* key, const Application& application); - void find( - const saml2md::EntityDescriptor& issuer, - const saml2::NameID& nameid, - const char* index, - const Application& application, - vector& sessions - ) { - byname(issuer, nameid, index, application, sessions, false); - } void remove( - const saml2md::EntityDescriptor& issuer, + const saml2md::EntityDescriptor* issuer, const saml2::NameID& nameid, const char* index, const Application& application, vector& sessions - ) { - byname(issuer, nameid, index, application, sessions, true); - } + ); Category& m_log; StorageService* m_storage; @@ -185,14 +178,6 @@ namespace shibsp { private: // maintain back-mappings of NameID/SessionIndex -> session key void insert(const char* key, time_t expires, const char* name, const char* index); - void byname( - const saml2md::EntityDescriptor& issuer, - const saml2::NameID& nameid, - const char* index, - const Application& application, - vector& sessions, - bool logout - ); bool stronglyMatches(const XMLCh* idp, const XMLCh* sp, const saml2::NameID& n1, const saml2::NameID& n2) const; }; @@ -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. string record; time_t recordexp; - int ver = m_storage->readText("Logout", name, &record, &recordexp); + int ver = m_storage->readText("NameID", name, &record, &recordexp); if (ver > 0) { // Existing record, so we need to unmarshall it. istringstream in(record); @@ -505,7 +490,7 @@ void SSCache::insert(const char* key, time_t expires, const char* name, const ch obj.structure(); } - if (!index) + if (!index || !*index) index = "_shibnull"; DDF sessions = obj.addmember(index); if (!sessions.islist()) @@ -519,13 +504,13 @@ void SSCache::insert(const char* key, time_t expires, const char* name, const ch // Try and store it back... if (ver > 0) { - ver = m_storage->updateText("Logout", name, out.str().c_str(), max(expires, recordexp), ver); + ver = m_storage->updateText("NameID", name, out.str().c_str(), max(expires, recordexp), ver); if (ver <= 0) { // Out of sync, or went missing, so retry. return insert(key, expires, name, index); } } - else if (!m_storage->createText("Logout", name, out.str().c_str(), expires)) { + else if (!m_storage->createText("NameID", name, out.str().c_str(), expires)) { // Hit a dup, so just retry, hopefully hitting the other branch. return insert(key, expires, name, index); } @@ -536,11 +521,12 @@ string SSCache::insert( const Application& application, const char* client_addr, const saml2md::EntityDescriptor* issuer, + const XMLCh* protocol, const saml2::NameID* nameid, - const char* authn_instant, - const char* session_index, - const char* authncontext_class, - const char* authncontext_decl, + const XMLCh* authn_instant, + const XMLCh* session_index, + const XMLCh* authncontext_class, + const XMLCh* authncontext_decl, const vector* tokens, const multimap* attributes ) @@ -575,14 +561,25 @@ string SSCache::insert( auto_ptr_char entity_id(issuer->getEntityID()); obj.addmember("entity_id").string(entity_id.get()); } - if (authn_instant) - obj.addmember("authn_instant").string(authn_instant); + if (protocol) { + auto_ptr_char prot(protocol); + obj.addmember("protocol").string(prot.get()); + } + if (authn_instant) { + auto_ptr_char instant(authn_instant); + obj.addmember("authn_instant").string(instant.get()); + } + auto_ptr_char index(session_index); if (session_index) - obj.addmember("session_index").string(session_index); - if (authncontext_class) - obj.addmember("authncontext_class").string(authncontext_class); - if (authncontext_decl) - obj.addmember("authncontext_decl").string(authncontext_decl); + obj.addmember("session_index").string(index.get()); + if (authncontext_class) { + auto_ptr_char ac(authncontext_class); + obj.addmember("authncontext_class").string(ac.get()); + } + if (authncontext_decl) { + auto_ptr_char ad(authncontext_decl); + obj.addmember("authncontext_decl").string(ad.get()); + } if (nameid) { ostringstream namestr; @@ -620,7 +617,7 @@ string SSCache::insert( auto_ptr_char name(nameid ? nameid->getName() : NULL); try { if (name.get()) - insert(key.get(), expires, name.get(), session_index); + insert(key.get(), expires, name.get(), index.get()); } catch (exception& ex) { m_log.error("error storing back mapping of NameID for logout: %s", ex.what()); @@ -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); } -void SSCache::byname( - const saml2md::EntityDescriptor& issuer, +void SSCache::remove( + const saml2md::EntityDescriptor* issuer, const saml2::NameID& nameid, const char* index, const Application& application, - vector& sessionsKilled, - bool logout + vector& sessionsKilled ) { #ifdef _DEBUG xmltooling::NDC ndc("remove"); #endif - auto_ptr_char entityID(issuer.getEntityID()); + auto_ptr_char entityID(issuer ? issuer->getEntityID() : NULL); auto_ptr_char name(nameid.getName()); m_log.info( - "request to %s sessions from (%s) for (%s) for session index (%s)", - logout ? "logout" : "locate", entityID.get(), name.get(), index ? index : "all" + "request to logout sessions from (%s) for (%s) for session index (%s)", + entityID.get() ? entityID.get() : "unknown", name.get(), index ? index : "all" ); if (strlen(name.get()) > 255) @@ -815,7 +811,7 @@ void SSCache::byname( // Read in potentially matching sessions. string record; - int ver = m_storage->readText("Logout", name.get(), &record); + int ver = m_storage->readText("NameID", name.get(), &record); if (ver == 0) { m_log.debug("no active sessions to remove for supplied issuer and name identifier"); return; @@ -838,14 +834,12 @@ void SSCache::byname( Locker locker(session); if (session) { // Same issuer? - if (session->getEntityID() && !strcmp(session->getEntityID(), entityID.get())) { + if (XMLString::equals(session->getEntityID(), entityID.get())) { // Same NameID? - if (stronglyMatches(issuer.getEntityID(), application.getXMLString("entityID").second, nameid, *session->getNameID())) { - if (logout) { - remove(key.string(), application); // let this throw to detect errors in case the full logout fails? - key.destroy(); - } + if (stronglyMatches(issuer->getEntityID(), application.getXMLString("entityID").second, nameid, *session->getNameID())) { sessionsKilled.push_back(key.string()); + remove(key.string(), application); // let this throw to detect errors in case the full logout fails? + key.destroy(); } else { m_log.debug("session (%s) contained a non-matching NameID, leaving it alone", key.string()); @@ -876,12 +870,12 @@ void SSCache::byname( // If possible, write back the mapping record (this isn't crucial). try { if (obj.isnull()) { - m_storage->deleteText("Logout", name.get()); + m_storage->deleteText("NameID", name.get()); } else if (!sessionsKilled.empty()) { ostringstream out; out << obj; - if (m_storage->updateText("Logout", name.get(), out.str().c_str(), 0, ver) <= 0) + if (m_storage->updateText("NameID", name.get(), out.str().c_str(), 0, ver) <= 0) m_log.warn("logout mapping record changed behind us, leaving it alone"); } } diff --git a/shibsp/impl/XMLServiceProvider.cpp b/shibsp/impl/XMLServiceProvider.cpp index 4c57cdd..5f14445 100644 --- a/shibsp/impl/XMLServiceProvider.cpp +++ b/shibsp/impl/XMLServiceProvider.cpp @@ -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) ||