X-Git-Url: http://www.project-moonshot.org/gitweb/?a=blobdiff_plain;f=shibsp%2Fhandler%2Fimpl%2FSAML2Logout.cpp;h=be80cb100cc21305271fc2dbf09694dd4b002119;hb=392d1448deb48beb75f219532ac248b4776f16db;hp=9f4108a4894f27f8d795004a4a099a469e30b088;hpb=60519c8e7c4ffcda66ba01a6e46d5adb93f870b0;p=shibboleth%2Fsp.git diff --git a/shibsp/handler/impl/SAML2Logout.cpp b/shibsp/handler/impl/SAML2Logout.cpp index 9f4108a..be80cb1 100644 --- a/shibsp/handler/impl/SAML2Logout.cpp +++ b/shibsp/handler/impl/SAML2Logout.cpp @@ -29,7 +29,7 @@ #include "util/SPConstants.h" #ifndef SHIBSP_LITE -# include "SessionCache.h" +# include "SessionCacheEx.h" # include "security/SecurityPolicy.h" # include "util/TemplateParameters.h" # include @@ -93,13 +93,9 @@ namespace shibsp { #endif private: - pair doRequest( - const Application& application, const char* session_id, const HTTPRequest& httpRequest, HTTPResponse& httpResponse - ) const; + pair doRequest(const Application& application, const HTTPRequest& httpRequest, HTTPResponse& httpResponse) const; #ifndef SHIBSP_LITE - bool stronglyMatches(const XMLCh* idp, const XMLCh* sp, const saml2::NameID& n1, const saml2::NameID& n2) const; - pair sendResponse( const XMLCh* requestID, const XMLCh* code, @@ -146,7 +142,9 @@ SAML2Logout::SAML2Logout(const DOMElement* e, const char* appId) SAMLConfig& conf = SAMLConfig::getConfig(); // Handle incoming binding. - m_decoder = conf.MessageDecoderManager.newPlugin(getString("Binding").second,make_pair(e,shibspconstants::SHIB2SPCONFIG_NS)); + m_decoder = conf.MessageDecoderManager.newPlugin( + getString("Binding").second, pair(e,shibspconstants::SHIB2SPCONFIG_NS) + ); m_decoder->setArtifactResolver(SPConfig::getConfig().getArtifactResolver()); if (m_decoder->isUserAgentPresent()) { @@ -172,7 +170,9 @@ SAML2Logout::SAML2Logout(const DOMElement* e, const char* appId) m_bindings.push_back(start); try { auto_ptr_char b(start); - MessageEncoder * encoder = conf.MessageEncoderManager.newPlugin(b.get(),make_pair(e,shibspconstants::SHIB2SPCONFIG_NS)); + MessageEncoder * encoder = conf.MessageEncoderManager.newPlugin( + b.get(), pair(e,shibspconstants::SHIB2SPCONFIG_NS) + ); m_encoders[start] = encoder; m_log.debug("supporting outgoing front-channel binding (%s)", b.get()); } @@ -187,7 +187,7 @@ SAML2Logout::SAML2Logout(const DOMElement* e, const char* appId) } else { MessageEncoder* encoder = conf.MessageEncoderManager.newPlugin( - getString("Binding").second,make_pair(e,shibspconstants::SHIB2SPCONFIG_NS) + getString("Binding").second, pair(e,shibspconstants::SHIB2SPCONFIG_NS) ); m_encoders.insert(pair(NULL, encoder)); } @@ -207,21 +207,16 @@ pair SAML2Logout::run(SPRequest& request, bool isHandler) const if (ret.first) return ret; - // Get the session_id in case this is a front-channel LogoutRequest. - pair shib_cookie = request.getApplication().getCookieNameProps("_shibsession_"); - const char* session_id = request.getCookie(shib_cookie.first.c_str()); - SPConfig& conf = SPConfig::getConfig(); if (conf.isEnabled(SPConfig::OutOfProcess)) { // When out of process, we run natively and directly process the message. - return doRequest(request.getApplication(), session_id, request, request); + return doRequest(request.getApplication(), request, request); } else { // When not out of process, we remote all the message processing. - DDF out,in = wrap(request, NULL, true); + vector headers(1,"Cookie"); + DDF out,in = wrap(request, &headers, true); DDFJanitor jin(in), jout(out); - if (session_id) - in.addmember("session_id").string(session_id); out=request.getServiceProvider().getListenerService()->send(in); return unwrap(request, out); } @@ -239,69 +234,64 @@ void SAML2Logout::receive(DDF& in, ostream& out) } // Unpack the request. + auto_ptr req(getRequest(in)); + + // Wrap a response shim. DDF ret(NULL); DDFJanitor jout(ret); - auto_ptr req(getRequest(in)); auto_ptr resp(getResponse(ret)); // Since we're remoted, the result should either be a throw, which we pass on, // a false/0 return, which we just return as an empty structure, or a response/redirect, // which we capture in the facade and send back. - doRequest(*app, in["session_id"].string(), *req.get(), *resp.get()); + doRequest(*app, *req.get(), *resp.get()); out << ret; } -pair SAML2Logout::doRequest( - const Application& application, const char* session_id, const HTTPRequest& request, HTTPResponse& response - ) const +pair SAML2Logout::doRequest(const Application& application, const HTTPRequest& request, HTTPResponse& response) const { #ifndef SHIBSP_LITE + // First capture the active session ID. SessionCache* cache = application.getServiceProvider().getSessionCache(); - if (!strcmp(request.getMethod(),"GET") && request.getParameter("notifying")) { + SessionCacheEx* cacheex = dynamic_cast(cache); + string session_id = cache->active(application, request); + if (!strcmp(request.getMethod(),"GET") && request.getParameter("notifying")) { // This is returning from a front-channel notification, so we have to do the back-channel and then // respond. To do that, we need state from the original request. if (!request.getParameter("entityID")) { - if (session_id) { - cache->remove(session_id, application); - // Clear the cookie. - pair shib_cookie=application.getCookieNameProps("_shibsession_"); - response.setCookie(shib_cookie.first.c_str(), shib_cookie.second); - } + cache->remove(application, request, &response); throw FatalProfileException("Application notification loop did not return entityID for LogoutResponse."); } // Best effort on back channel and to remove the user agent's session. bool worked1 = false,worked2 = false; - if (session_id) { + if (!session_id.empty()) { vector sessions(1,session_id); worked1 = notifyBackChannel(application, request.getRequestURL(), sessions, false); try { - cache->remove(session_id, application); + cache->remove(application, request, &response); worked2 = true; } catch (exception& ex) { m_log.error("error removing session (%s): %s", session_id, ex.what()); } - - // Clear the cookie. - pair shib_cookie=application.getCookieNameProps("_shibsession_"); - response.setCookie(shib_cookie.first.c_str(), shib_cookie.second); } else { worked1 = worked2 = true; } // We need metadata to issue a response. - Locker metadataLocker(application.getMetadataProvider()); - const EntityDescriptor* entity = application.getMetadataProvider()->getEntityDescriptor(request.getParameter("entityID")); - if (!entity) { + MetadataProvider* m = application.getMetadataProvider(); + Locker metadataLocker(m); + MetadataProvider::Criteria mc(request.getParameter("entityID"), &IDPSSODescriptor::ELEMENT_QNAME, samlconstants::SAML20P_NS); + pair entity = m->getEntityDescriptor(mc); + if (!entity.first) { throw MetadataException( "Unable to locate metadata for identity provider ($entityID)", namedparams(1, "entityID", request.getParameter("entityID")) ); } - const IDPSSODescriptor* idp = find_if(entity->getIDPSSODescriptors(), isValidForProtocol(samlconstants::SAML20P_NS)); - if (!idp) { + else if (!entity.second) { throw MetadataException( "Unable to locate SAML 2.0 IdP role for identity provider ($entityID).", namedparams(1, "entityID", request.getParameter("entityID")) @@ -312,7 +302,7 @@ pair SAML2Logout::doRequest( if (worked1 && worked2) { // Successful LogoutResponse. Has to be front-channel or we couldn't be here. return sendResponse( - reqid.get(), StatusCode::SUCCESS, NULL, NULL, request.getParameter("RelayState"), idp, application, response, true + reqid.get(), StatusCode::SUCCESS, NULL, NULL, request.getParameter("RelayState"), entity.second, application, response, true ); } @@ -320,7 +310,7 @@ pair SAML2Logout::doRequest( reqid.get(), StatusCode::RESPONDER, NULL, "Unable to fully destroy principal's session.", request.getParameter("RelayState"), - idp, + entity.second, application, response, true @@ -355,7 +345,7 @@ pair SAML2Logout::doRequest( // Message from IdP to logout one or more sessions. // If this is front-channel, we have to have a session_id to use already. - if (m_decoder->isUserAgentPresent() && !session_id) { + if (m_decoder->isUserAgentPresent() && session_id.empty()) { m_log.error("no active session"); return sendResponse( logoutRequest->getID(), @@ -383,7 +373,13 @@ pair SAML2Logout::doRequest( policy.getIssuerMetadata() ? new MetadataCredentialCriteria(*policy.getIssuerMetadata()) : NULL ); try { - auto_ptr decryptedID(encname->decrypt(*cr,application.getXMLString("entityID").second,mcc.get())); + auto_ptr decryptedID( + encname->decrypt( + *cr, + application.getRelyingParty(policy.getIssuerMetadata() ? dynamic_cast(policy.getIssuerMetadata()->getParent()) : NULL)->getXMLString("entityID").second, + mcc.get() + ) + ); nameid = dynamic_cast(decryptedID.get()); if (nameid) { ownedName = true; @@ -423,11 +419,11 @@ pair SAML2Logout::doRequest( // For a front-channel LogoutRequest, we have to match the information in the request // against the current session. - if (session_id) { - if (!cache->matches(session_id, entity, *nameid, &indexes, application)) { + if (!session_id.empty()) { + if (!cache->matches(application, request, entity, *nameid, &indexes)) { return sendResponse( logoutRequest->getID(), - StatusCode::REQUESTER, StatusCode::REQUEST_DENIED, "Active sessions did not match logout request.", + StatusCode::REQUESTER, StatusCode::REQUEST_DENIED, "Active session did not match logout request.", relayState.c_str(), policy.getIssuerMetadata(), application, @@ -441,14 +437,21 @@ pair SAML2Logout::doRequest( // Now we perform "logout" by finding the matching sessions. vector sessions; try { - time_t expires = logoutRequest->getNotOnOrAfter() ? logoutRequest->getNotOnOrAfterEpoch() : 0; - cache->logout(entity, *nameid, &indexes, expires, application, sessions); - - // Now we actually terminate everything except for the active session, - // if this is front-channel, for notification purposes. - for (vector::const_iterator sit = sessions.begin(); sit != sessions.end(); ++sit) - if (session_id && strcmp(sit->c_str(), session_id)) - cache->remove(sit->c_str(), application); + if (cacheex) { + time_t expires = logoutRequest->getNotOnOrAfter() ? logoutRequest->getNotOnOrAfterEpoch() : 0; + cacheex->logout(application, entity, *nameid, &indexes, expires, sessions); + + // Now we actually terminate everything except for the active session, + // if this is front-channel, for notification purposes. + for (vector::const_iterator sit = sessions.begin(); sit != sessions.end(); ++sit) + if (*sit != session_id) + cacheex->remove(application, sit->c_str()); // using the ID-based removal operation + } + else { + m_log.warn("session cache does not support extended API, can't implement indirect logout of sessions"); + if (!session_id.empty()) + sessions.push_back(session_id); + } } catch (exception& ex) { m_log.error("error while logging out matching sessions: %s", ex.what()); @@ -482,19 +485,15 @@ pair SAML2Logout::doRequest( // For back-channel requests, or if no front-channel notification is needed... bool worked1 = false,worked2 = false; worked1 = notifyBackChannel(application, request.getRequestURL(), sessions, false); - if (session_id) { + if (!session_id.empty()) { // One last session to yoink... try { - cache->remove(session_id, application); + cache->remove(application, request, &response); worked2 = true; } catch (exception& ex) { - m_log.error("error removing active session (%s): %s", session_id, ex.what()); + m_log.error("error removing active session (%s): %s", session_id.c_str(), ex.what()); } - - // Clear the cookie. - pair shib_cookie=application.getCookieNameProps("_shibsession_"); - response.setCookie(shib_cookie.first.c_str(), shib_cookie.second); } return sendResponse( @@ -521,15 +520,21 @@ pair SAML2Logout::doRequest( } checkError(logoutResponse, policy.getIssuerMetadata()); // throws if Status doesn't look good... + // If relay state is set, recover the original return URL. + if (!relayState.empty()) + recoverRelayState(application, request, response, relayState); + if (!relayState.empty()) + return make_pair(true, response.sendRedirect(relayState.c_str())); + // Return template for completion of global logout, or redirect to homeURL. - return sendLogoutPage(application, response, false, "Global logout completed."); + return sendLogoutPage(application, request, response, false, "Global logout completed."); } FatalProfileException ex("Incoming message was not a samlp:LogoutRequest or samlp:LogoutResponse."); if (policy.getIssuerMetadata()) annotateException(&ex, policy.getIssuerMetadata()); // throws it ex.raise(); - return make_pair(false,0); // never happen, satisfies compiler + return make_pair(false,0L); // never happen, satisfies compiler #else throw ConfigurationException("Cannot process logout message using lite version of shibsp library."); #endif @@ -537,41 +542,6 @@ pair SAML2Logout::doRequest( #ifndef SHIBSP_LITE -bool SAML2Logout::stronglyMatches(const XMLCh* idp, const XMLCh* sp, const saml2::NameID& n1, const saml2::NameID& n2) const -{ - if (!XMLString::equals(n1.getName(), n2.getName())) - return false; - - const XMLCh* s1 = n1.getFormat(); - const XMLCh* s2 = n2.getFormat(); - if (!s1 || !*s1) - s1 = saml2::NameID::UNSPECIFIED; - if (!s2 || !*s2) - s2 = saml2::NameID::UNSPECIFIED; - if (!XMLString::equals(s1,s2)) - return false; - - s1 = n1.getNameQualifier(); - s2 = n2.getNameQualifier(); - if (!s1 || !*s1) - s1 = idp; - if (!s2 || !*s2) - s2 = idp; - if (!XMLString::equals(s1,s2)) - return false; - - s1 = n1.getSPNameQualifier(); - s2 = n2.getSPNameQualifier(); - if (!s1 || !*s1) - s1 = sp; - if (!s2 || !*s2) - s2 = sp; - if (!XMLString::equals(s1,s2)) - return false; - - return true; -} - pair SAML2Logout::sendResponse( const XMLCh* requestID, const XMLCh* code, @@ -619,7 +589,7 @@ pair SAML2Logout::sendResponse( } Issuer* issuer = IssuerBuilder::buildIssuer(); logout->setIssuer(issuer); - issuer->setName(application.getXMLString("entityID").second); + issuer->setName(application.getRelyingParty(dynamic_cast(role->getParent()))->getXMLString("entityID").second); fillStatus(*logout.get(), code, subcode, msg); auto_ptr_char dest(logout->getDestination());