1 /**
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.
6  *
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
10  * License at
11  *
12  * http://www.apache.org/licenses/LICENSE-2.0
13  *
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.
19  */
20 
21 /**
22  * LogoutHandler.cpp
23  *
24  * Base class for logout-related handlers.
25  */
26 
27 #include "internal.h"
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"
36 
37 #include <fstream>
38 #include <boost/lexical_cast.hpp>
39 #include <xmltooling/XMLToolingConfig.h>
40 #include <xmltooling/util/PathResolver.h>
41 #include <xmltooling/util/URLEncoder.h>
42 
43 using namespace shibsp;
44 using namespace xmltooling;
45 using namespace boost;
46 using namespace std;
47 
LogoutHandler()48 LogoutHandler::LogoutHandler() : m_initiator(true)
49 {
50 }
51 
~LogoutHandler()52 LogoutHandler::~LogoutHandler()
53 {
54 }
55 
getEventType() const56 const char* LogoutHandler::getEventType() const {
57     return LOGOUT_EVENT;
58 }
59 
sendLogoutPage(const Application & application,const HTTPRequest & request,HTTPResponse & response,const char * type) const60 pair<bool,long> LogoutHandler::sendLogoutPage(
61     const Application& application, const HTTPRequest& request, HTTPResponse& response, const char* type
62     ) const
63 {
64     string tname = string(type) + "Logout";
65     const PropertySet* props = application.getPropertySet("Errors");
66 
67     pair<bool,const char*> prop = props ? props->getString(tname.c_str()) : pair<bool,const char*>(false,nullptr);
68     if (!prop.first) {
69         tname += ".html";
70         prop.second = tname.c_str();
71     }
72     response.setContentType("text/html");
73     response.setResponseHeader("Expires","Wed, 01 Jan 1997 12:00:00 GMT");
74     response.setResponseHeader("Cache-Control","private,no-store,no-cache,max-age=0");
75     string fname(prop.second);
76     ifstream infile(XMLToolingConfig::getConfig().getPathResolver()->resolve(fname, PathResolver::XMLTOOLING_CFG_FILE).c_str());
77     if (!infile)
78         throw ConfigurationException("Unable to access $1 HTML template.", params(1,prop.second));
79     TemplateParameters tp;
80 
81     // If the externalParameters option isn't set, don't populate the request field.
82     pair<bool,bool> externalParameters =
83             props ? props->getBool("externalParameters") : pair<bool,bool>(false,false);
84     if (externalParameters.first && externalParameters.second) {
85         tp.m_request = &request;
86     }
87 
88     tp.setPropertySet(props);
89     tp.m_map["logoutStatus"] = "Logout completed successfully.";  // Backward compatibility.
90     stringstream str;
91     XMLToolingConfig::getConfig().getTemplateEngine()->run(infile, str, tp);
92     return make_pair(true,response.sendResponse(str));
93 }
94 
run(SPRequest & request,bool isHandler) const95 pair<bool,long> LogoutHandler::run(SPRequest& request, bool isHandler) const
96 {
97     // If we're inside a chain, do nothing.
98     if (getParent())
99         return make_pair(false,0L);
100 
101     // If this isn't a LogoutInitiator, we only "continue" a notification loop, rather than starting one.
102     if (!m_initiator && !request.getParameter("notifying"))
103         return make_pair(false,0L);
104 
105     // Try another front-channel notification. No extra parameters and the session is implicit.
106     return notifyFrontChannel(request.getApplication(), request, request);
107 }
108 
receive(DDF & in,ostream & out)109 void LogoutHandler::receive(DDF& in, ostream& out)
110 {
111     DDF ret(nullptr);
112     DDFJanitor jout(ret);
113     if (in["notify"].integer() != 1)
114         throw ListenerException("Unsupported operation.");
115 
116     // Find application.
117     const char* aid=in["application_id"].string();
118     const Application* app=aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : nullptr;
119     if (!app) {
120         // Something's horribly wrong.
121         Category::getInstance(SHIBSP_LOGCAT ".Logout").error("couldn't find application (%s) for logout", aid ? aid : "(missing)");
122         throw ConfigurationException("Unable to locate application for logout, deleted?");
123     }
124 
125     vector<string> sessions;
126     DDF s = in["sessions"];
127     DDF temp = s.first();
128     while (temp.isstring()) {
129         sessions.push_back(temp.string());
130         temp = s.next();
131         if (notifyBackChannel(*app, in["url"].string(), sessions, in["local"].integer()==1))
132             ret.integer(1);
133     }
134 
135     out << ret;
136 }
137 
notifyFrontChannel(const Application & application,const HTTPRequest & request,HTTPResponse & response,const map<string,string> * params) const138 pair<bool,long> LogoutHandler::notifyFrontChannel(
139     const Application& application,
140     const HTTPRequest& request,
141     HTTPResponse& response,
142     const map<string,string>* params
143     ) const
144 {
145     // Index of notification point starts at 0.
146     unsigned int index = 0;
147     const char* param = request.getParameter("index");
148     if (param)
149         index = atoi(param);
150 
151     // "return" is a backwards-compatible "eventual destination" to go back to after logout completes.
152     param = request.getParameter("return");
153 
154     // Fetch the next front notification URL and bump the index for the next round trip.
155     string loc = application.getNotificationURL(request.getRequestURL(), true, index++);
156     if (loc.empty())
157         return make_pair(false,0L);
158 
159     const URLEncoder* encoder = XMLToolingConfig::getConfig().getURLEncoder();
160 
161     // Start with an "action" telling the application what this is about.
162     loc = loc + (strchr(loc.c_str(),'?') ? '&' : '?') + "action=logout";
163 
164     // Now we create a second URL representing the return location back to us.
165     const char* start = request.getRequestURL();
166     const char* end = strchr(start, '?');
167     string locstr(start, end ? end - start : strlen(start));
168 
169     // Add a signal that we're coming back from notification and the next index.
170     locstr = locstr + "?notifying=1&index=" + lexical_cast<string>(index);
171 
172     // Add return if set.
173     if (param)
174         locstr = locstr + "&return=" + encoder->encode(param);
175 
176     // We preserve anything we're instructed to directly.
177     if (params) {
178         for (map<string,string>::const_iterator p = params->begin(); p!=params->end(); ++p)
179             locstr = locstr + '&' + p->first + '=' + encoder->encode(p->second.c_str());
180     }
181     else {
182         for (vector<string>::const_iterator q = m_preserve.begin(); q!=m_preserve.end(); ++q) {
183             param = request.getParameter(q->c_str());
184             if (param)
185                 locstr = locstr + '&' + *q + '=' + encoder->encode(param);
186         }
187     }
188 
189     // Add the notifier's return parameter to the destination location and redirect.
190     // This is NOT the same as the return parameter that might be embedded inside it ;-)
191     loc = loc + "&return=" + encoder->encode(locstr.c_str());
192     return make_pair(true, response.sendRedirect(loc.c_str()));
193 }
194 
195 #ifndef SHIBSP_LITE
196 #include "util/SPConstants.h"
197 #include <xmltooling/impl/AnyElement.h>
198 #include <xmltooling/soap/SOAP.h>
199 #include <xmltooling/soap/SOAPClient.h>
200 #include <xmltooling/soap/HTTPSOAPTransport.h>
201 using namespace soap11;
202 namespace {
203     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);
204     static const XMLCh SessionID[] =            UNICODE_LITERAL_9(S,e,s,s,i,o,n,I,D);
205     static const XMLCh _type[] =                UNICODE_LITERAL_4(t,y,p,e);
206     static const XMLCh _local[] =               UNICODE_LITERAL_5(l,o,c,a,l);
207     static const XMLCh _global[] =              UNICODE_LITERAL_6(g,l,o,b,a,l);
208 
209     class SHIBSP_DLLLOCAL SOAPNotifier : public soap11::SOAPClient
210     {
211     public:
SOAPNotifier()212         SOAPNotifier() {}
~SOAPNotifier()213         virtual ~SOAPNotifier() {}
214     private:
prepareTransport(SOAPTransport & transport)215         void prepareTransport(SOAPTransport& transport) {
216             transport.setVerifyHost(false);
217             HTTPSOAPTransport* http = dynamic_cast<HTTPSOAPTransport*>(&transport);
218             if (http) {
219                 http->useChunkedEncoding(false);
220                 http->setRequestHeader(PACKAGE_NAME, PACKAGE_VERSION);
221             }
222         }
223     };
224 };
225 #endif
226 
notifyBackChannel(const Application & application,const char * requestURL,const vector<string> & sessions,bool local) const227 bool LogoutHandler::notifyBackChannel(
228     const Application& application, const char* requestURL, const vector<string>& sessions, bool local
229     ) const
230 {
231     if (sessions.empty()) {
232         Category::getInstance(SHIBSP_LOGCAT ".Logout").error("no sessions supplied to back channel notification method");
233         return false;
234     }
235 
236     unsigned int index = 0;
237     string endpoint = application.getNotificationURL(requestURL, false, index++);
238     if (endpoint.empty())
239         return true;
240 
241     if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
242 #ifndef SHIBSP_LITE
243         scoped_ptr<Envelope> env(EnvelopeBuilder::buildEnvelope());
244         Body* body = BodyBuilder::buildBody();
245         env->setBody(body);
246         ElementProxy* msg = new AnyElementImpl(shibspconstants::SHIB2SPNOTIFY_NS, LogoutNotification);
247         body->getUnknownXMLObjects().push_back(msg);
248         msg->setAttribute(xmltooling::QName(nullptr, _type), local ? _local : _global);
249         for (vector<string>::const_iterator s = sessions.begin(); s != sessions.end(); ++s) {
250             auto_ptr_XMLCh temp(s->c_str());
251             ElementProxy* child = new AnyElementImpl(shibspconstants::SHIB2SPNOTIFY_NS, SessionID);
252             child->setTextContent(temp.get());
253             msg->getUnknownXMLObjects().push_back(child);
254         }
255 
256         bool result = true;
257         SOAPNotifier soaper;
258         while (!endpoint.empty()) {
259             try {
260                 soaper.send(*env, SOAPTransport::Address(application.getId(), application.getId(), endpoint.c_str()));
261                 delete soaper.receive();
262             }
263             catch (std::exception& ex) {
264                 Category::getInstance(SHIBSP_LOGCAT ".Logout").error("error notifying application of logout event: %s", ex.what());
265                 result = false;
266             }
267             soaper.reset();
268             endpoint = application.getNotificationURL(requestURL, false, index++);
269         }
270         return result;
271 #else
272         return false;
273 #endif
274     }
275 
276     // When not out of process, we remote the back channel work.
277     DDF out,in(m_address.c_str());
278     DDFJanitor jin(in), jout(out);
279     in.addmember("notify").integer(1);
280     in.addmember("application_id").string(application.getId());
281     in.addmember("url").string(requestURL);
282     if (local)
283         in.addmember("local").integer(1);
284     DDF s = in.addmember("sessions").list();
285     for (vector<string>::const_iterator i = sessions.begin(); i!=sessions.end(); ++i) {
286         DDF temp = DDF(nullptr).string(i->c_str());
287         s.add(temp);
288     }
289     out = application.getServiceProvider().getListenerService()->send(in);
290     return (out.integer() == 1);
291 }
292 
293 #ifndef SHIBSP_LITE
294 
newLogoutEvent(const Application & application,const xmltooling::HTTPRequest * request,const Session * session) const295 LogoutEvent* LogoutHandler::newLogoutEvent(
296     const Application& application, const xmltooling::HTTPRequest* request, const Session* session
297     ) const
298 {
299     if (!SPConfig::getConfig().isEnabled(SPConfig::Logging))
300         return nullptr;
301     try {
302         auto_ptr<TransactionLog::Event> event(SPConfig::getConfig().EventManager.newPlugin(LOGOUT_EVENT, nullptr, false));
303         LogoutEvent* logout_event = dynamic_cast<LogoutEvent*>(event.get());
304         if (logout_event) {
305             logout_event->m_request = request;
306             logout_event->m_app = &application;
307             logout_event->m_binding = getString("Binding").second;
308             logout_event->m_session = session;
309             if (session) {
310                 logout_event->m_nameID = session->getNameID();
311                 logout_event->m_sessions.push_back(session->getID());
312             }
313             event.release();
314             return logout_event;
315         }
316         else {
317             Category::getInstance(SHIBSP_LOGCAT ".Logout").warn("unable to audit event, log event object was of an incorrect type");
318         }
319     }
320     catch (std::exception& ex) {
321         Category::getInstance(SHIBSP_LOGCAT ".Logout").warn("exception auditing event: %s", ex.what());
322     }
323     return nullptr;
324 }
325 
326 #endif
327