2 * Licensed to the University Corporation for Advanced Internet
3 * Development, Inc. (UCAID) under one or more contributor license
4 * agreements. See the NOTICE file distributed with this work for
5 * additional information regarding copyright ownership.
7 * UCAID licenses this file to you under the Apache License,
8 * Version 2.0 (the "License"); you may not use this file except
9 * in compliance with the License. You may obtain a copy of the
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
17 * either express or implied. See the License for the specific
18 * language governing permissions and limitations under the License.
24 * Base class for logout-related handlers.
28 #include "exceptions.h"
29 #include "Application.h"
30 #include "ServiceProvider.h"
31 #include "SessionCache.h"
32 #include "SPRequest.h"
33 #include "TransactionLog.h"
34 #include "handler/LogoutHandler.h"
35 #include "util/TemplateParameters.h"
38 #include <boost/lexical_cast.hpp>
39 #include <xmltooling/XMLToolingConfig.h>
40 #include <xmltooling/util/PathResolver.h>
41 #include <xmltooling/util/URLEncoder.h>
43 using namespace shibsp;
44 using namespace xmltooling;
45 using namespace boost;
48 LogoutHandler::LogoutHandler() : m_initiator(true)
52 LogoutHandler::~LogoutHandler()
56 pair<bool,long> LogoutHandler::sendLogoutPage(
57 const Application& application, const HTTPRequest& request, HTTPResponse& response, bool local, const char* status
60 return sendLogoutPage(application, request, response, local ? "local" : "global");
63 pair<bool,long> LogoutHandler::sendLogoutPage(
64 const Application& application, const HTTPRequest& request, HTTPResponse& response, const char* type
67 string tname = string(type) + "Logout";
68 const PropertySet* props = application.getPropertySet("Errors");
69 pair<bool,const char*> prop = props ? props->getString(tname.c_str()) : pair<bool,const char*>(false,nullptr);
72 prop.second = tname.c_str();
74 response.setContentType("text/html");
75 response.setResponseHeader("Expires","Wed, 01 Jan 1997 12:00:00 GMT");
76 response.setResponseHeader("Cache-Control","private,no-store,no-cache,max-age=0");
77 string fname(prop.second);
78 ifstream infile(XMLToolingConfig::getConfig().getPathResolver()->resolve(fname, PathResolver::XMLTOOLING_CFG_FILE).c_str());
80 throw ConfigurationException("Unable to access $1 HTML template.", params(1,prop.second));
81 TemplateParameters tp;
82 tp.m_request = &request;
83 tp.setPropertySet(props);
84 tp.m_map["logoutStatus"] = "Logout completed successfully."; // Backward compatibility.
86 XMLToolingConfig::getConfig().getTemplateEngine()->run(infile, str, tp);
87 return make_pair(true,response.sendResponse(str));
90 pair<bool,long> LogoutHandler::run(SPRequest& request, bool isHandler) const
92 // If we're inside a chain, do nothing.
94 return make_pair(false,0L);
96 // If this isn't a LogoutInitiator, we only "continue" a notification loop, rather than starting one.
97 if (!m_initiator && !request.getParameter("notifying"))
98 return make_pair(false,0L);
100 // Try another front-channel notification. No extra parameters and the session is implicit.
101 return notifyFrontChannel(request.getApplication(), request, request);
104 void LogoutHandler::receive(DDF& in, ostream& out)
107 DDFJanitor jout(ret);
108 if (in["notify"].integer() != 1)
109 throw ListenerException("Unsupported operation.");
112 const char* aid=in["application_id"].string();
113 const Application* app=aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : nullptr;
115 // Something's horribly wrong.
116 Category::getInstance(SHIBSP_LOGCAT".Logout").error("couldn't find application (%s) for logout", aid ? aid : "(missing)");
117 throw ConfigurationException("Unable to locate application for logout, deleted?");
120 vector<string> sessions;
121 DDF s = in["sessions"];
122 DDF temp = s.first();
123 while (temp.isstring()) {
124 sessions.push_back(temp.string());
126 if (notifyBackChannel(*app, in["url"].string(), sessions, in["local"].integer()==1))
133 pair<bool,long> LogoutHandler::notifyFrontChannel(
134 const Application& application,
135 const HTTPRequest& request,
136 HTTPResponse& response,
137 const map<string,string>* params
140 // Index of notification point starts at 0.
141 unsigned int index = 0;
142 const char* param = request.getParameter("index");
146 // "return" is a backwards-compatible "eventual destination" to go back to after logout completes.
147 param = request.getParameter("return");
149 // Fetch the next front notification URL and bump the index for the next round trip.
150 string loc = application.getNotificationURL(request.getRequestURL(), true, index++);
152 return make_pair(false,0L);
154 const URLEncoder* encoder = XMLToolingConfig::getConfig().getURLEncoder();
156 // Start with an "action" telling the application what this is about.
157 loc = loc + (strchr(loc.c_str(),'?') ? '&' : '?') + "action=logout";
159 // Now we create a second URL representing the return location back to us.
160 const char* start = request.getRequestURL();
161 const char* end = strchr(start, '?');
162 string locstr(start, end ? end - start : strlen(start));
164 // Add a signal that we're coming back from notification and the next index.
165 locstr = locstr + "?notifying=1&index=" + lexical_cast<string>(index);
167 // Add return if set.
169 locstr = locstr + "&return=" + encoder->encode(param);
171 // We preserve anything we're instructed to directly.
173 for (map<string,string>::const_iterator p = params->begin(); p!=params->end(); ++p)
174 locstr = locstr + '&' + p->first + '=' + encoder->encode(p->second.c_str());
177 for (vector<string>::const_iterator q = m_preserve.begin(); q!=m_preserve.end(); ++q) {
178 param = request.getParameter(q->c_str());
180 locstr = locstr + '&' + *q + '=' + encoder->encode(param);
184 // Add the notifier's return parameter to the destination location and redirect.
185 // This is NOT the same as the return parameter that might be embedded inside it ;-)
186 loc = loc + "&return=" + encoder->encode(locstr.c_str());
187 return make_pair(true, response.sendRedirect(loc.c_str()));
191 #include "util/SPConstants.h"
192 #include <xmltooling/impl/AnyElement.h>
193 #include <xmltooling/soap/SOAP.h>
194 #include <xmltooling/soap/SOAPClient.h>
195 #include <xmltooling/soap/HTTPSOAPTransport.h>
196 using namespace soap11;
198 static const XMLCh LogoutNotification[] = UNICODE_LITERAL_18(L,o,g,o,u,t,N,o,t,i,f,i,c,a,t,i,o,n);
199 static const XMLCh SessionID[] = UNICODE_LITERAL_9(S,e,s,s,i,o,n,I,D);
200 static const XMLCh _type[] = UNICODE_LITERAL_4(t,y,p,e);
201 static const XMLCh _local[] = UNICODE_LITERAL_5(l,o,c,a,l);
202 static const XMLCh _global[] = UNICODE_LITERAL_6(g,l,o,b,a,l);
204 class SHIBSP_DLLLOCAL SOAPNotifier : public soap11::SOAPClient
208 virtual ~SOAPNotifier() {}
210 void prepareTransport(SOAPTransport& transport) {
211 transport.setVerifyHost(false);
212 HTTPSOAPTransport* http = dynamic_cast<HTTPSOAPTransport*>(&transport);
214 http->useChunkedEncoding(false);
215 http->setRequestHeader(PACKAGE_NAME, PACKAGE_VERSION);
222 bool LogoutHandler::notifyBackChannel(
223 const Application& application, const char* requestURL, const vector<string>& sessions, bool local
226 if (sessions.empty()) {
227 Category::getInstance(SHIBSP_LOGCAT".Logout").error("no sessions supplied to back channel notification method");
231 unsigned int index = 0;
232 string endpoint = application.getNotificationURL(requestURL, false, index++);
233 if (endpoint.empty())
236 if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
238 scoped_ptr<Envelope> env(EnvelopeBuilder::buildEnvelope());
239 Body* body = BodyBuilder::buildBody();
241 ElementProxy* msg = new AnyElementImpl(shibspconstants::SHIB2SPNOTIFY_NS, LogoutNotification);
242 body->getUnknownXMLObjects().push_back(msg);
243 msg->setAttribute(xmltooling::QName(nullptr, _type), local ? _local : _global);
244 for (vector<string>::const_iterator s = sessions.begin(); s != sessions.end(); ++s) {
245 auto_ptr_XMLCh temp(s->c_str());
246 ElementProxy* child = new AnyElementImpl(shibspconstants::SHIB2SPNOTIFY_NS, SessionID);
247 child->setTextContent(temp.get());
248 msg->getUnknownXMLObjects().push_back(child);
253 while (!endpoint.empty()) {
255 soaper.send(*env, SOAPTransport::Address(application.getId(), application.getId(), endpoint.c_str()));
256 delete soaper.receive();
258 catch (std::exception& ex) {
259 Category::getInstance(SHIBSP_LOGCAT".Logout").error("error notifying application of logout event: %s", ex.what());
263 endpoint = application.getNotificationURL(requestURL, false, index++);
271 // When not out of process, we remote the back channel work.
272 DDF out,in(m_address.c_str());
273 DDFJanitor jin(in), jout(out);
274 in.addmember("notify").integer(1);
275 in.addmember("application_id").string(application.getId());
276 in.addmember("url").string(requestURL);
278 in.addmember("local").integer(1);
279 DDF s = in.addmember("sessions").list();
280 for (vector<string>::const_iterator i = sessions.begin(); i!=sessions.end(); ++i) {
281 DDF temp = DDF(nullptr).string(i->c_str());
284 out = application.getServiceProvider().getListenerService()->send(in);
285 return (out.integer() == 1);
290 LogoutEvent* LogoutHandler::newLogoutEvent(
291 const Application& application, const xmltooling::HTTPRequest* request, const Session* session
294 if (!SPConfig::getConfig().isEnabled(SPConfig::Logging))
297 auto_ptr<TransactionLog::Event> event(SPConfig::getConfig().EventManager.newPlugin(LOGOUT_EVENT, nullptr));
298 LogoutEvent* logout_event = dynamic_cast<LogoutEvent*>(event.get());
300 logout_event->m_request = request;
301 logout_event->m_app = &application;
302 logout_event->m_binding = getString("Binding").second;
303 logout_event->m_session = session;
305 logout_event->m_nameID = session->getNameID();
306 logout_event->m_sessions.push_back(session->getID());
312 Category::getInstance(SHIBSP_LOGCAT".Logout").warn("unable to audit event, log event object was of an incorrect type");
315 catch (std::exception& ex) {
316 Category::getInstance(SHIBSP_LOGCAT".Logout").warn("exception auditing event: %s", ex.what());