[cpp-sp] branch main updated: Remove old SAML logout code.
Codeberg
noreply at shibboleth.net
Tue May 19 13:47:07 UTC 2026
This is an automated email from the git hooks/post-receive script.
codeberg pushed a commit to branch main
in repository cpp-sp.
View the commit online:
https://codeberg.org/Shibboleth/cpp-sp/commit/7f2d57b946b8e25526ce0fb488f23e1cc7befde7
The following commit(s) were added to refs/heads/main by this push:
new 7f2d57b9 Remove old SAML logout code.
7f2d57b9 is described below
commit 7f2d57b946b8e25526ce0fb488f23e1cc7befde7
Author: Scott Cantor <scott at restingparrotsoftware.com>
AuthorDate: Tue May 19 09:46:51 2026 -0400
Remove old SAML logout code.
---
shibsp/handler/impl/SAML2Logout.cpp | 546 ---------------------------
shibsp/handler/impl/SAML2LogoutInitiator.cpp | 476 -----------------------
2 files changed, 1022 deletions(-)
diff --git a/shibsp/handler/impl/SAML2Logout.cpp b/shibsp/handler/impl/SAML2Logout.cpp
deleted file mode 100644
index 485c0474..00000000
--- a/shibsp/handler/impl/SAML2Logout.cpp
+++ /dev/null
@@ -1,546 +0,0 @@
-/**
- * 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.
- */
-
-/**
- * handler/impl/SAML2Logout.cpp
- *
- * Handles SAML 2.0 single logout protocol messages.
- */
-
-#include "internal.h"
-#include "exceptions.h"
-#include "SPRequest.h"
-#include "handler/AbstractHandler.h"
-#include "handler/LogoutHandler.h"
-#include "util/SPConstants.h"
-
-#include <boost/scoped_ptr.hpp>
-
-using namespace shibsp;
-using namespace boost;
-using namespace xercesc;
-using namespace std;
-
-namespace shibsp {
-
-#if defined (_MSC_VER)
- #pragma warning( push )
- #pragma warning( disable : 4250 )
-#endif
-
- class SHIBSP_DLLLOCAL SAML2Logout : public AbstractHandler, public LogoutHandler
- {
- public:
- SAML2Logout(const DOMElement* e, const char* appId, bool deprecationSupport=true);
- virtual ~SAML2Logout() {}
-
- void receive(DDF& in, ostream& out);
- pair<bool,long> run(SPRequest& request, bool isHandler=true) const;
-
- private:
- pair<bool,long> doRequest(SPRequest& request) const;
- };
-
-#if defined (_MSC_VER)
- #pragma warning( pop )
-#endif
-
- Handler* SHIBSP_DLLLOCAL SAML2LogoutFactory(const pair<const DOMElement*,const char*>& p, bool deprecationSupport)
- {
- return new SAML2Logout(p.first, p.second, deprecationSupport);
- }
-};
-
-SAML2Logout::SAML2Logout(const DOMElement* e, const char* appId, bool deprecationSupport)
- : AbstractHandler(e, Category::getInstance(SHIBSP_LOGCAT ".Logout.SAML2"))
-{
- m_initiator = false;
-#ifndef SHIBSP_LITE
- m_preserve.push_back("ID");
- m_preserve.push_back("entityID");
- m_preserve.push_back("RelayState");
-
- pair<bool, bool> flag = getBool("notifyWithoutSession", shibspconstants::ASCII_SHIBSPCONFIG_NS);
- if (!flag.first)
- flag = getBool("notifyWithoutSession");
- m_notifyWithoutSession = flag.first && flag.second;
-
- if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
- SAMLConfig& conf = SAMLConfig::getConfig();
-
- // Handle incoming binding.
- m_decoder.reset(conf.MessageDecoderManager.newPlugin(getString("Binding").second, e, deprecationSupport));
- m_decoder->setArtifactResolver(SPConfig::getConfig().getArtifactResolver());
-
- if (m_decoder->isUserAgentPresent()) {
- // Handle front-channel binding setup.
- string dupBindings;
- pair<bool,const char*> outgoing = getString("outgoingBindings", shibspconstants::ASCII_SHIBSPCONFIG_NS);
- if (outgoing.first) {
- dupBindings = outgoing.second;
- trim(dupBindings);
- }
- else {
- // No override, so we'll install a default binding precedence.
- dupBindings = string(samlconstants::SAML20_BINDING_HTTP_REDIRECT) + ' ' + samlconstants::SAML20_BINDING_HTTP_POST + ' ' +
- samlconstants::SAML20_BINDING_HTTP_POST_SIMPLESIGN + ' ' + samlconstants::SAML20_BINDING_HTTP_ARTIFACT;
- }
-
- split(m_bindings, dupBindings, is_space(), algorithm::token_compress_on);
- for (vector<string>::const_iterator b = m_bindings.begin(); b != m_bindings.end(); ++b) {
- try {
- boost::shared_ptr<MessageEncoder> encoder(conf.MessageEncoderManager.newPlugin(*b, e, deprecationSupport));
- if (encoder->isUserAgentPresent() && XMLString::equals(getProtocolFamily(), encoder->getProtocolFamily())) {
- m_encoders[*b] = encoder;
- m_log.debug("supporting outgoing binding (%s)", b->c_str());
- }
- else {
- m_log.warn("skipping outgoing binding (%s), not a SAML 2.0 front-channel mechanism", b->c_str());
- }
- }
- catch (const std::exception& ex) {
- m_log.error("error building MessageEncoder: %s", ex.what());
- }
- }
- }
- else {
- pair<bool,const char*> b = getString("Binding");
- boost::shared_ptr<MessageEncoder> encoder(conf.MessageEncoderManager.newPlugin(b.second, e, deprecationSupport));
- m_encoders[b.second] = encoder;
- }
- }
-#endif
-
- string address(appId);
- address += getString("Location").second;
- setAddress(address.c_str());
-}
-
-pair<bool,long> SAML2Logout::run(SPRequest& request, bool isHandler) const
-{
- // Defer to base class for front-channel loop first.
- // This won't initiate the loop, only continue/end it.
- pair<bool,long> ret = LogoutHandler::run(request, isHandler);
- if (ret.first)
- return ret;
-
- if (false) {
- // When out of process, we run natively and directly process the message.
- return doRequest(request);
- }
- else {
- // When not out of process, we remote all the message processing.
- vector<string> headers(1,"Cookie");
- headers.push_back("User-Agent");
- DDF out,in = wrap(request, &headers, true);
- DDFJanitor jin(in), jout(out);
- out = send(request, in);
- return unwrap(request, out);
- }
-}
-
-void SAML2Logout::receive(DDF& in, ostream& out)
-{
- /*
- // Find application.
- const char* aid = in["application_id"].string();
- const Application* app = aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : nullptr;
- if (!app) {
- // Something's horribly wrong.
- m_log.error("couldn't find application (%s) for logout", aid ? aid : "(missing)");
- throw ConfigurationException("Unable to locate application for logout, deleted?");
- }
-
- // Unpack the request.
- scoped_ptr<HTTPRequest> req(getRequest(*app, in));
-
- // Wrap a response shim.
- DDF ret(nullptr);
- DDFJanitor jout(ret);
- scoped_ptr<HTTPResponse> resp(getResponse(*app, ret));
-
- // Since we're remoted, the result should either be a throw, which we pass on,
- // a false/0 return, which we just return as an empty structure, or a response/redirect,
- // which we capture in the facade and send back.
- doRequest(*app, *req, *resp);
- out << ret;
- */
-}
-
-pair<bool,long> SAML2Logout::doRequest(SPRequest& request) const
-{
-#ifndef SHIBSP_LITE
- // First capture the active session ID, if any.
- SessionCache* cache = application.getServiceProvider().getSessionCache();
- 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")) {
- 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.empty()) {
- vector<string> sessions(1,session_id);
- worked1 = notifyBackChannel(application, request.getRequestURL(), sessions, false);
- try {
- cache->remove(application, request, &response);
- worked2 = true;
- }
- catch (const std::exception& ex) {
- m_log.error("error removing session (%s): %s", session_id.c_str(), ex.what());
- }
- }
- else {
- worked1 = worked2 = true;
- }
-
- // We need metadata to issue a response.
- MetadataProvider* m = application.getMetadataProvider();
- Locker metadataLocker(m);
- MetadataProviderCriteria mc(
- application, request.getParameter("entityID"), &IDPSSODescriptor::ELEMENT_QNAME, samlconstants::SAML20P_NS
- );
- pair<const EntityDescriptor*,const RoleDescriptor*> entity = m->getEntityDescriptor(mc);
- if (!entity.first) {
- throw MetadataException(
- "Unable to locate metadata for identity provider ($entityID)",
- namedparams(1, "entityID", request.getParameter("entityID"))
- );
- }
- else if (!entity.second) {
- throw MetadataException(
- "Unable to locate SAML 2.0 IdP role for identity provider ($entityID).",
- namedparams(1, "entityID", request.getParameter("entityID"))
- );
- }
-
- auto_ptr_XMLCh reqid(request.getParameter("ID"));
- if (worked1 && worked2) {
- // Successful LogoutResponse. Has to be front-channel or we couldn't be here.
- return sendResponse(
- reqid.get(),
- StatusCode::SUCCESS, nullptr, nullptr,
- request.getParameter("RelayState"),
- entity.second,
- application,
- response,
- true
- );
- }
-
- return sendResponse(
- reqid.get(),
- StatusCode::RESPONDER, nullptr, "Unable to fully destroy principal's session.",
- request.getParameter("RelayState"),
- entity.second,
- application,
- response,
- true
- );
- }
-
- // If we get here, it's an external protocol message to decode.
-
- // Locate policy key.
- pair<bool,const char*> policyId = getString("policyId", shibspconstants::ASCII_SHIBSPCONFIG_NS); // may be namespace-qualified inside handler element
- if (!policyId.first)
- policyId = getString("policyId"); // try unqualified
- if (!policyId.first)
- policyId = application.getString("policyId"); // unqualified in Application(s) element
-
- // Lock metadata for use by policy.
- Locker metadataLocker(application.getMetadataProvider());
-
- // Create the policy.
- scoped_ptr<SecurityPolicy> policy(
- application.getServiceProvider().getSecurityPolicyProvider()->createSecurityPolicy(
- samlconstants::SAML20_PROFILE_SSO_LOGOUT, application, &IDPSSODescriptor::ELEMENT_QNAME, policyId.second
- )
- );
-
- // Decode the message.
- string relayState;
- scoped_ptr<XMLObject> msg(m_decoder->decode(relayState, request, &response, *policy));
- const LogoutRequest* logoutRequest = dynamic_cast<LogoutRequest*>(msg.get());
- if (logoutRequest) {
- if (!policy->isAuthenticated())
- throw SecurityPolicyException("Security of LogoutRequest not established.");
-
- // Message from IdP to logout one or more sessions.
- // Extract the NameID from the request, decrypting it if needed.
-
- scoped_ptr<XMLObject> decryptedID;
- NameID* nameid = logoutRequest->getNameID();
- if (!nameid) {
- // Check for EncryptedID.
- EncryptedID* encname = logoutRequest->getEncryptedID();
- if (encname) {
- CredentialResolver* cr=application.getCredentialResolver();
- if (!cr)
- m_log.warn("found encrypted NameID, but no decryption credential was available");
- else {
- Locker credlocker(cr);
- scoped_ptr<MetadataCredentialCriteria> mcc(
- policy->getIssuerMetadata() ? new MetadataCredentialCriteria(*policy->getIssuerMetadata()) : nullptr
- );
- try {
- decryptedID.reset(
- encname->decrypt(
- *cr,
- application.getRelyingParty(
- policy->getIssuerMetadata() ?
- dynamic_cast<EntityDescriptor*>(policy->getIssuerMetadata()->getParent()) :
- nullptr)->getXMLString("entityID").second,
- mcc.get()
- )
- );
- nameid = dynamic_cast<NameID*>(decryptedID.get());
- }
- catch (const std::exception& ex) {
- m_log.error(ex.what());
- }
- }
- }
- }
- if (!nameid) {
- // No NameID, so must respond with an error.
- m_log.error("NameID not found in request");
- return sendResponse(
- logoutRequest->getID(),
- StatusCode::REQUESTER, StatusCode::UNKNOWN_PRINCIPAL, "NameID not found in request.",
- relayState.c_str(),
- policy->getIssuerMetadata(),
- application,
- response,
- m_decoder->isUserAgentPresent()
- );
- }
-
- // Suck indexes out of the request for next steps.
- set<string> indexes;
- EntityDescriptor* entity =
- policy->getIssuerMetadata() ? dynamic_cast<EntityDescriptor*>(policy->getIssuerMetadata()->getParent()) : nullptr;
- const vector<SessionIndex*> sindexes = logoutRequest->getSessionIndexs();
- for (indirect_iterator<vector<SessionIndex*>::const_iterator> i = make_indirect_iterator(sindexes.begin());
- i != make_indirect_iterator(sindexes.end()); ++i) {
- auto_ptr_char sindex(i->getSessionIndex());
- indexes.insert(sindex.get());
- }
-
- // For a front-channel non-admin LogoutRequest, we have to match the information in the request
- // against the current session, if one is known/available.
- if (!session_id.empty()) {
- if (!XMLString::equals(logoutRequest->getReason(),LogoutRequest::REASON_ADMIN)
- && !cache->matches(application, request, entity, *nameid, &indexes)) {
- return sendResponse(
- logoutRequest->getID(),
- StatusCode::REQUESTER, StatusCode::REQUEST_DENIED, "Active session did not match logout request.",
- relayState.c_str(),
- policy->getIssuerMetadata(),
- application,
- response,
- true
- );
- }
- }
- else if (m_decoder->isUserAgentPresent()) {
- m_log.info("processing front channel logout request with no active session");
- }
-
- // Now we perform "logout" by finding the matching sessions.
- vector<string> sessions;
- try {
- time_t expires = logoutRequest->getNotOnOrAfter() ? logoutRequest->getNotOnOrAfterEpoch() : 0;
- cache->logout(application, entity, *nameid, &indexes, expires, sessions);
- m_log.debug("session cache returned %d sessions bound to NameID in logout request", sessions.size());
-
- // Now we actually terminate everything except for the active session,
- // if this is front-channel, for notification purposes.
- for (vector<string>::const_iterator sit = sessions.begin(); sit != sessions.end(); ++sit)
- if (*sit != session_id)
- cache->remove(application, sit->c_str()); // using the ID-based removal operation
- }
- catch (const std::exception& ex) {
- m_log.error("error while logging out matching sessions: %s", ex.what());
- return sendResponse(
- logoutRequest->getID(),
- StatusCode::RESPONDER, nullptr, ex.what(),
- relayState.c_str(),
- policy->getIssuerMetadata(),
- application,
- response,
- m_decoder->isUserAgentPresent()
- );
- }
-
- if (m_decoder->isUserAgentPresent()) {
- if (!session_id.empty() || m_notifyWithoutSession) {
- // Pass control to the first front channel notification point, if any.
- map<string,string> parammap;
- if (!relayState.empty())
- parammap["RelayState"] = relayState;
- auto_ptr_char entityID(entity ? entity->getEntityID() : nullptr);
- if (entityID.get())
- parammap["entityID"] = entityID.get();
- auto_ptr_char reqID(logoutRequest->getID());
- if (reqID.get())
- parammap["ID"] = reqID.get();
- pair<bool,long> result = notifyFrontChannel(application, request, response, ¶mmap);
- if (result.first)
- return result;
- }
- else {
- m_log.info("client's session isn't available, skipping front-channel notifications");
- }
- }
-
- // For back-channel requests, or if no front-channel notification is needed or possible...
- bool worked1 = notifyBackChannel(application, request.getRequestURL(), sessions, false);
- bool worked2 = true;
- if (!session_id.empty()) {
- // One last session to yoink...
- try {
- cache->remove(application, request, &response);
- }
- catch (std::exception& ex) {
- worked2 = false;
- m_log.error("error removing active session (%s): %s", session_id.c_str(), ex.what());
- }
- }
-
- return sendResponse(
- logoutRequest->getID(),
- (worked1 && worked2) ? StatusCode::SUCCESS : StatusCode::RESPONDER,
- (worked1 && worked2) ? nullptr : StatusCode::PARTIAL_LOGOUT,
- nullptr,
- relayState.c_str(),
- policy->getIssuerMetadata(),
- application,
- response,
- m_decoder->isUserAgentPresent()
- );
- }
-
- // A LogoutResponse completes an SP-initiated logout sequence.
- const LogoutResponse* logoutResponse = dynamic_cast<LogoutResponse*>(msg.get());
- if (logoutResponse) {
- if (!policy->isAuthenticated()) {
- SecurityPolicyException ex("Security of LogoutResponse not established.");
- annotateException(&ex, policy->getIssuerMetadata()); // throws it
- }
-
- 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);
- }
-
- // Check for partial logout.
- bool wasPartial = false;
- const StatusCode* sc = logoutResponse->getStatus() ? logoutResponse->getStatus()->getStatusCode() : nullptr;
- sc = sc ? sc->getStatusCode() : nullptr;
- if (sc && XMLString::equals(sc->getValue(), StatusCode::PARTIAL_LOGOUT)) {
- wasPartial = true;
- }
-
- if (!relayState.empty()) {
- application.limitRedirect(request, relayState.c_str());
- return make_pair(true, response.sendRedirect(relayState.c_str()));
- }
-
- // Return template for completion of logout.
- return sendLogoutPage(application, request, response, wasPartial ? "partial" : "global");
- }
-
- 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,0L); // never happen, satisfies compiler
-#else
- throw ConfigurationException("Cannot process logout message using lite version of shibsp library.");
-#endif
-}
-
-#ifndef SHIBSP_LITE
-
-pair<bool,long> SAML2Logout::sendResponse(
- const XMLCh* requestID,
- const XMLCh* code,
- const XMLCh* subcode,
- const char* msg,
- const char* relayState,
- const RoleDescriptor* role,
- const Application& application,
- HTTPResponse& httpResponse,
- bool front
- ) const
-{
- // Get endpoint and encoder to use.
- const EndpointType* ep = nullptr;
- const MessageEncoder* encoder = nullptr;
- if (front) {
- const IDPSSODescriptor* idp = dynamic_cast<const IDPSSODescriptor*>(role);
- for (vector<string>::const_iterator b = m_bindings.begin(); idp && b != m_bindings.end(); ++b) {
- auto_ptr_XMLCh wideb(b->c_str());
- if ((ep = EndpointManager<SingleLogoutService>(idp->getSingleLogoutServices()).getByBinding(wideb.get()))) {
- map< string,boost::shared_ptr<MessageEncoder> >::const_iterator enc = m_encoders.find(*b);
- if (enc != m_encoders.end())
- encoder = enc->second.get();
- break;
- }
- }
- if (!ep || !encoder) {
- auto_ptr_char id(role ? dynamic_cast<EntityDescriptor*>(role->getParent())->getEntityID() : nullptr);
- m_log.error("unable to locate compatible SLO service for provider (%s)", id.get() ? id.get() : "unknown");
- MetadataException ex("Unable to locate endpoint at IdP ($entityID) to send LogoutResponse.");
- annotateException(&ex, role); // throws it
- }
- }
- else {
- encoder = m_encoders.begin()->second.get();
- }
-
- // Prepare response.
- auto_ptr<LogoutResponse> logout(LogoutResponseBuilder::buildLogoutResponse());
- logout->setInResponseTo(requestID);
- if (ep) {
- const XMLCh* loc = ep->getResponseLocation();
- if (!loc || !*loc)
- loc = ep->getLocation();
- logout->setDestination(loc);
- }
- Issuer* issuer = IssuerBuilder::buildIssuer();
- logout->setIssuer(issuer);
- issuer->setName(application.getRelyingParty(role ? dynamic_cast<EntityDescriptor*>(role->getParent()) :
- nullptr)->getXMLString("entityID").second);
- fillStatus(*logout, code, subcode, msg);
- XMLCh* msgid = SAMLConfig::getConfig().generateIdentifier();
- logout->setID(msgid);
- XMLString::release(&msgid);
- logout->setIssueInstant(time(nullptr));
-
- auto_ptr_char dest(logout->getDestination());
- long ret = sendMessage(*encoder, logout.get(), relayState, dest.get(), role, application, httpResponse, "conditional");
- logout.release(); // freed by encoder
- return make_pair(true, ret);
-}
-
-#endif
diff --git a/shibsp/handler/impl/SAML2LogoutInitiator.cpp b/shibsp/handler/impl/SAML2LogoutInitiator.cpp
deleted file mode 100644
index dc7bc388..00000000
--- a/shibsp/handler/impl/SAML2LogoutInitiator.cpp
+++ /dev/null
@@ -1,476 +0,0 @@
-/**
- * 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.
- */
-
-/**
- * handler/impl/SAML2LogoutInitiator.cpp
- *
- * Triggers SP-initiated logout for SAML 2.0 sessions.
- */
-
-#include "internal.h"
-#include "exceptions.h"
-#include "Agent.h"
-#include "handler/AbstractHandler.h"
-#include "handler/LogoutInitiator.h"
-#include "session/SessionCache.h"
-
-#include <mutex>
-
-using namespace shibsp;
-using namespace xercesc;
-using namespace std;
-
-namespace shibsp {
-
-#if defined (_MSC_VER)
- #pragma warning( push )
- #pragma warning( disable : 4250 )
-#endif
-
- class SHIBSP_DLLLOCAL SAML2LogoutInitiator : public AbstractHandler, public LogoutInitiator
- {
- public:
- SAML2LogoutInitiator(const DOMElement* e, const char* appId, bool deprecationSupport=true);
- virtual ~SAML2LogoutInitiator() {}
-
- void init(const char* location); // encapsulates actions that need to run either in the c'tor or setParent
-
- void setParent(const PropertySet* parent);
- void receive(DDF& in, ostream& out);
- pair<bool,long> run(SPRequest& request, bool isHandler=true) const;
-
- private:
- pair<bool,long> doRequest(SPRequest& request, Session* session) const;
-
- string m_appId;
- bool m_deprecationSupport;
- };
-
-#if defined (_MSC_VER)
- #pragma warning( pop )
-#endif
-
- Handler* SHIBSP_DLLLOCAL SAML2LogoutInitiatorFactory(const pair<const DOMElement*,const char*>& p, bool deprecationSupport)
- {
- return new SAML2LogoutInitiator(p.first, p.second, deprecationSupport);
- }
-};
-
-SAML2LogoutInitiator::SAML2LogoutInitiator(const DOMElement* e, const char* appId, bool deprecationSupport)
- : AbstractHandler(e, Category::getInstance(SHIBSP_LOGCAT ".LogoutInitiator.SAML2")),
- m_appId(appId), m_deprecationSupport(deprecationSupport)
-{
- // If Location isn't set, defer initialization until the setParent call.
- pair<bool,const char*> loc = getString("Location");
- if (loc.first) {
- init(loc.second);
- }
-}
-
-void SAML2LogoutInitiator::setParent(const PropertySet* parent)
-{
- DOMPropertySet::setParent(parent);
- pair<bool,const char*> loc = getString("Location");
- init(loc.second);
-}
-
-void SAML2LogoutInitiator::init(const char* location)
-{
- if (location) {
- string address = m_appId + location + "::run::SAML2LI";
- setAddress(address.c_str());
- }
- else {
- m_log.warn("no Location property in SAML2 LogoutInitiator (or parent), can't register as remoted handler");
- }
-
-#ifndef SHIBSP_LITE
- if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
- pair<bool,bool> async = getBool("asynchronous");
- m_async = !async.first || async.second;
-
- string dupBindings;
- pair<bool,const char*> outgoing = getString("outgoingBindings");
- if (outgoing.first) {
- dupBindings = outgoing.second;
- trim(dupBindings);
- }
- else {
- // No override, so we'll install a default binding precedence.
- dupBindings = string(samlconstants::SAML20_BINDING_HTTP_REDIRECT) + ' ' + samlconstants::SAML20_BINDING_HTTP_POST + ' ' +
- samlconstants::SAML20_BINDING_HTTP_POST_SIMPLESIGN + ' ' + samlconstants::SAML20_BINDING_HTTP_ARTIFACT;
- }
- split(m_bindings, dupBindings, is_space(), algorithm::token_compress_on);
- for (vector<string>::const_iterator b = m_bindings.begin(); b != m_bindings.end(); ++b) {
- try {
- boost::shared_ptr<MessageEncoder> encoder(SAMLConfig::getConfig().MessageEncoderManager.newPlugin(*b, getElement(), m_deprecationSupport));
- if (encoder->isUserAgentPresent() && XMLString::equals(getProtocolFamily(), encoder->getProtocolFamily())) {
- m_encoders[*b] = encoder;
- m_log.debug("supporting outgoing binding (%s)", b->c_str());
- }
- else {
- m_log.warn("skipping outgoing binding (%s), not a SAML 2.0 front-channel mechanism", b->c_str());
- }
- }
- catch (const std::exception& ex) {
- m_log.error("error building MessageEncoder: %s", ex.what());
- }
- }
- }
-#endif
-}
-
-
-pair<bool,long> SAML2LogoutInitiator::run(SPRequest& request, bool isHandler) const
-{
- // Defer to base class for front-channel loop first.
- pair<bool,long> ret = LogoutHandler::run(request, isHandler);
- if (ret.first)
- return ret;
-
- // At this point we know the front-channel is handled.
- // We need the session to do any other work.
-
- Session* session = nullptr;
- try {
- session = request.getSession(false, true, false); // don't cache it and ignore all checks
- if (!session)
- return make_pair(false, 0L);
-
- // We only handle SAML 2.0 sessions.
- //if (!XMLString::equals(session->getProtocol(), m_protocol.get())) {
- // session->unlock();
- // return make_pair(false, 0L);
- //}
- }
- catch (const std::exception& ex) {
- m_log.error("error accessing current session: %s", ex.what());
- return make_pair(false, 0L);
- }
-
- if (false) {
- // When out of process, we run natively.
- return doRequest(request, session);
- }
- else {
- // When not out of process, we remote the request.
- session->unlock();
- vector<string> headers(1,"Cookie");
- DDF out,in = wrap(request,&headers);
- DDFJanitor jin(in), jout(out);
- out = send(request, in);
- return unwrap(request, out);
- }
-}
-
-void SAML2LogoutInitiator::receive(DDF& in, ostream& out)
-{
-#ifndef SHIBSP_LITE
- // Defer to base class for notifications
- if (in["notify"].integer() == 1)
- return LogoutHandler::receive(in, out);
-
- // Find application.
- const char* aid=in["application_id"].string();
- const Application* app=aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : nullptr;
- if (!app) {
- // Something's horribly wrong.
- m_log.error("couldn't find application (%s) for logout", aid ? aid : "(missing)");
- throw ConfigurationException("Unable to locate application for logout, deleted?");
- }
-
- // Unpack the request.
- scoped_ptr<HTTPRequest> req(getRequest(*app, in));
-
- // Set up a response shim.
- DDF ret(nullptr);
- DDFJanitor jout(ret);
- scoped_ptr<HTTPResponse> resp(getResponse(*app, ret));
-
- Session* session = nullptr;
- try {
- session = app->getServiceProvider().getSessionCache()->find(*app, *req, nullptr, nullptr);
- }
- catch (std::exception& ex) {
- m_log.error("error accessing current session: %s", ex.what());
- }
-
- // With no session, we just skip the request and let it fall through to an empty struct return.
- if (session) {
- if (session->getNameID() && session->getEntityID()) {
- // Since we're remoted, the result should either be a throw, which we pass on,
- // a false/0 return, which we just return as an empty structure, or a response/redirect,
- // which we capture in the facade and send back.
- doRequest(*app, *req, *resp, session);
- }
- else {
- time_t revocationExp = session->getExpiration();
- session->unlock();
- m_log.log(getParent() ? Priority::WARN : Priority::ERROR, "bypassing SAML 2.0 logout, no NameID or issuing entityID found in session");
- app->getServiceProvider().getSessionCache()->remove(*app, *req, resp.get(), revocationExp);
- }
- }
- out << ret;
-#else
- throw ConfigurationException("Cannot perform logout using lite version of shibsp library.");
-#endif
-}
-
-pair<bool,long> SAML2LogoutInitiator::doRequest(SPRequest& request, Session* session) const
-{
- unique_lock<Session> sessionLocker(*session, adopt_lock);
-
- // Do back channel notification.
- vector<string> sessions(1, session->getID());
- if (!notifyBackChannel(request, sessions, false)) {
- time_t revocationExp = session->getExpiration();
- sessionLocker.unlock();
- session = nullptr;
- request.getAgent().getSessionCache()->remove(request, revocationExp);
- //return sendLogoutPage(application, httpRequest, httpResponse, "partial");
- }
-
-#ifndef SHIBSP_LITE
- pair<bool,long> ret = make_pair(false, 0L);
- try {
- // With a session in hand, we can create a LogoutRequest message, if we can find a compatible endpoint.
- MetadataProvider* m = application.getMetadataProvider();
- Locker metadataLocker(m);
- MetadataProviderCriteria mc(application, session->getEntityID(), &IDPSSODescriptor::ELEMENT_QNAME, samlconstants::SAML20P_NS);
- pair<const EntityDescriptor*,const RoleDescriptor*> entity = m->getEntityDescriptor(mc);
- if (!entity.first) {
- throw MetadataException(
- "Unable to locate metadata for identity provider ($entityID)", namedparams(1, "entityID", session->getEntityID())
- );
- }
- else if (!entity.second) {
- throw MetadataException(
- "Unable to locate SAML 2.0 IdP role for identity provider ($entityID).", namedparams(1, "entityID", session->getEntityID())
- );
- }
-
- const IDPSSODescriptor* role = dynamic_cast<const IDPSSODescriptor*>(entity.second);
- if (role->getSingleLogoutServices().empty()) {
- throw MetadataException(
- "No SingleLogoutService endpoints in metadata for identity provider ($entityID).", namedparams(1, "entityID", session->getEntityID())
- );
- }
-
- const EndpointType* ep = nullptr;
- const MessageEncoder* encoder = nullptr;
- for (vector<string>::const_iterator b = m_bindings.begin(); b != m_bindings.end(); ++b) {
- auto_ptr_XMLCh wideb(b->c_str());
- ep = EndpointManager<SingleLogoutService>(role->getSingleLogoutServices()).getByBinding(wideb.get());
- if (ep) {
- map< string,boost::shared_ptr<MessageEncoder> >::const_iterator enc = m_encoders.find(*b);
- if (enc != m_encoders.end())
- encoder = enc->second.get();
- break;
- }
- }
- if (!ep || !encoder) {
- m_log.debug("no compatible front channel SingleLogoutService, trying back channel...");
- shibsp::SecurityPolicy policy(application);
- shibsp::SOAPClient soaper(policy);
- MetadataCredentialCriteria mcc(*role);
-
- LogoutResponse* logoutResponse = nullptr;
- scoped_ptr<StatusResponseType> srt;
- auto_ptr_XMLCh binding(samlconstants::SAML20_BINDING_SOAP);
- const vector<SingleLogoutService*>& endpoints = role->getSingleLogoutServices();
- for (indirect_iterator<vector<SingleLogoutService*>::const_iterator> epit = make_indirect_iterator(endpoints.begin());
- !logoutResponse && epit != make_indirect_iterator(endpoints.end()); ++epit) {
- try {
- if (!XMLString::equals(epit->getBinding(), binding.get()))
- continue;
- auto_ptr<LogoutRequest> msg(buildRequest(application, *session, *role, epit->getLocation()));
-
- SAML2SOAPClient client(soaper, false);
- auto_ptr_char dest(epit->getLocation());
- client.sendSAML(msg.release(), application.getId(), mcc, dest.get());
- srt.reset(client.receiveSAML());
- if (!(logoutResponse = dynamic_cast<LogoutResponse*>(srt.get()))) {
- break;
- }
- }
- catch (const std::exception& ex) {
- m_log.error("error sending LogoutRequest message: %s", ex.what());
- soaper.reset();
- }
- }
-
- // No answer at all?
- if (!logoutResponse) {
- if (endpoints.empty())
- m_log.info("IdP doesn't support single logout protocol over a compatible binding");
- else
- m_log.warn("IdP didn't respond to logout request");
-
- ret = sendLogoutPage(application, httpRequest, httpResponse, "partial");
- }
- else {
- // Check the status, looking for non-success or a partial logout code.
- const StatusCode* sc = logoutResponse->getStatus() ? logoutResponse->getStatus()->getStatusCode() : nullptr;
- bool partial = (!sc || !XMLString::equals(sc->getValue(), StatusCode::SUCCESS));
- if (!partial && sc->getStatusCode()) {
- // Success, but still need to check for partial.
- partial = XMLString::equals(sc->getStatusCode()->getValue(), StatusCode::PARTIAL_LOGOUT);
- }
-
- if (partial)
- ret = sendLogoutPage(application, httpRequest, httpResponse, "partial");
- else {
- const char* returnloc = httpRequest.getParameter("return");
- if (returnloc) {
- // Relative URLs get promoted, absolutes get validated.
- if (*returnloc == '/') {
- string loc(returnloc);
- httpRequest.absolutize(loc);
- ret.second = httpResponse.sendRedirect(loc.c_str());
- }
- else {
- application.limitRedirect(httpRequest, returnloc);
- ret.second = httpResponse.sendRedirect(returnloc);
- }
- ret.first = true;
- }
- else {
- ret = sendLogoutPage(application, httpRequest, httpResponse, "global");
- }
- }
- }
-
- if (session) {
- time_t revocationExp = session->getExpiration();
- sessionLocker.assign();
- session = nullptr;
- application.getServiceProvider().getSessionCache()->remove(application, httpRequest, &httpResponse, revocationExp);
- }
-
- return ret;
- }
-
- // Save off return location as RelayState.
- string relayState;
- const char* returnloc = httpRequest.getParameter("return");
- if (returnloc) {
- application.limitRedirect(httpRequest, returnloc);
- relayState = returnloc;
- httpRequest.absolutize(relayState);
- }
- cleanRelayState(application, httpRequest, httpResponse);
- preserveRelayState(application, httpResponse, relayState);
-
- auto_ptr<LogoutRequest> msg(buildRequest(application, *session, *role, ep->getLocation(), encoder));
- msg->setDestination(ep->getLocation());
-
- auto_ptr_char dest(ep->getLocation());
- ret.second = sendMessage(*encoder, msg.get(), relayState.c_str(), dest.get(), role, application, httpResponse, "true");
- ret.first = true;
- msg.release(); // freed by encoder
-
- if (session) {
- time_t revocationExp = session->getExpiration();
- sessionLocker.assign();
- session = nullptr;
- application.getServiceProvider().getSessionCache()->remove(application, httpRequest, &httpResponse, revocationExp);
- }
- }
- catch (const MetadataException& mex) {
- // Less noise for IdPs that don't support logout (i.e. most)
- m_log.info("unable to issue SAML 2.0 logout request: %s", mex.what());
- }
- catch (const std::exception& ex) {
- m_log.error("error issuing SAML 2.0 logout request: %s", ex.what());
- }
-
- return ret;
-#else
- throw ConfigurationException("Cannot perform logout using lite version of shibsp library.");
-#endif
-}
-
-#ifndef SHIBSP_LITE
-
-auto_ptr<LogoutRequest> SAML2LogoutInitiator::buildRequest(
- const Application& application,
- const Session& session,
- const RoleDescriptor& role,
- const XMLCh* endpoint,
- const MessageEncoder* encoder) const
-{
- const PropertySet* relyingParty = application.getRelyingParty(dynamic_cast<EntityDescriptor*>(role.getParent()));
-
- auto_ptr<LogoutRequest> msg(LogoutRequestBuilder::buildLogoutRequest());
- Issuer* issuer = IssuerBuilder::buildIssuer();
- msg->setIssuer(issuer);
- issuer->setName(relyingParty->getXMLString("entityID").second);
- auto_ptr_XMLCh index(session.getSessionIndex());
- if (index.get() && *index.get()) {
- SessionIndex* si = SessionIndexBuilder::buildSessionIndex();
- msg->getSessionIndexs().push_back(si);
- si->setSessionIndex(index.get());
- }
-
- const NameID* nameid = session.getNameID();
- pair<bool, const char*> flag = getString("encryption");
- if (!flag.first)
- flag = relyingParty->getString("encryption");
- auto_ptr_char dest(endpoint);
- if (SPConfig::shouldSignOrEncrypt(flag.first ? flag.second : "conditional", dest.get(), encoder != nullptr)) {
- try {
- auto_ptr<EncryptedID> encrypted(EncryptedIDBuilder::buildEncryptedID());
- MetadataCredentialCriteria mcc(role);
- encrypted->encrypt(
- *nameid,
- *(application.getMetadataProvider()),
- mcc,
- encoder ? encoder->isCompact() : false,
- relyingParty->getXMLString("encryptionAlg").second
- );
- msg->setEncryptedID(encrypted.get());
- encrypted.release();
- }
- catch (std::exception& ex) {
- // If we're encrypting deliberately, failure should be fatal.
- if (flag.first && strcmp(flag.second, "conditional")) {
- throw;
- }
- // If opportunistically, just log and move on.
- m_log.info("Conditional encryption of NameID in LogoutRequest failed: %s", ex.what());
- auto_ptr<NameID> namewrapper(nameid->cloneNameID());
- msg->setNameID(namewrapper.get());
- namewrapper.release();
- }
- }
- else {
- auto_ptr<NameID> namewrapper(nameid->cloneNameID());
- msg->setNameID(namewrapper.get());
- namewrapper.release();
- }
-
- XMLCh* msgid = SAMLConfig::getConfig().generateIdentifier();
- msg->setID(msgid);
- XMLString::release(&msgid);
- msg->setIssueInstant(time(nullptr));
-
- if (m_async && encoder) {
- msg->setExtensions(saml2p::ExtensionsBuilder::buildExtensions());
- msg->getExtensions()->getUnknownXMLObjects().push_back(AsynchronousBuilder::buildAsynchronous());
- }
-
- return msg;
-}
-
-#endif
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list