2 * Copyright 2001-2007 Internet2
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
20 * Base class for logout-related handlers.
24 #include "exceptions.h"
25 #include "Application.h"
26 #include "ServiceProvider.h"
27 #include "SessionCache.h"
28 #include "handler/LogoutHandler.h"
29 #include "util/TemplateParameters.h"
32 #include <xmltooling/XMLToolingConfig.h>
33 #include <xmltooling/util/URLEncoder.h>
35 using namespace shibsp;
36 using namespace xmltooling;
39 pair<bool,long> LogoutHandler::sendLogoutPage(
40 const Application& application, const HTTPRequest& request, HTTPResponse& response, bool local, const char* status
43 pair<bool,const char*> prop = application.getString(local ? "localLogout" : "globalLogout");
45 response.setContentType("text/html");
46 response.setResponseHeader("Expires","01-Jan-1997 12:00:00 GMT");
47 response.setResponseHeader("Cache-Control","private,no-store,no-cache");
48 ifstream infile(prop.second);
50 throw ConfigurationException("Unable to access $1 HTML template.", params(1,local ? "localLogout" : "globalLogout"));
51 TemplateParameters tp;
52 tp.m_request = &request;
53 tp.setPropertySet(application.getPropertySet("Errors"));
55 tp.m_map["logoutStatus"] = status;
57 XMLToolingConfig::getConfig().getTemplateEngine()->run(infile, str, tp);
58 return make_pair(true,response.sendResponse(str));
60 prop = application.getString("homeURL");
63 return make_pair(true,response.sendRedirect(prop.second));
66 pair<bool,long> LogoutHandler::run(SPRequest& request, bool isHandler) const
68 // If we're inside a chain, do nothing.
70 return make_pair(false,0L);
72 // If this isn't a LogoutInitiator, we only "continue" a notification loop, rather than starting one.
73 if (!m_initiator && !request.getParameter("notifying"))
74 return make_pair(false,0L);
76 // Try another front-channel notification. No extra parameters and the session is implicit.
77 return notifyFrontChannel(request.getApplication(), request, request);
80 void LogoutHandler::receive(DDF& in, ostream& out)
84 if (in["notify"].integer() != 1)
85 throw ListenerException("Unsupported operation.");
88 const char* aid=in["application_id"].string();
89 const Application* app=aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : NULL;
91 // Something's horribly wrong.
92 Category::getInstance(SHIBSP_LOGCAT".Logout").error("couldn't find application (%s) for logout", aid ? aid : "(missing)");
93 throw ConfigurationException("Unable to locate application for logout, deleted?");
96 vector<string> sessions;
97 DDF s = in["sessions"];
99 while (temp.isstring()) {
100 sessions.push_back(temp.string());
102 if (notifyBackChannel(*app, in["url"].string(), sessions, in["local"].integer()==1))
109 pair<bool,long> LogoutHandler::notifyFrontChannel(
110 const Application& application,
111 const HTTPRequest& request,
112 HTTPResponse& response,
113 const map<string,string>* params
116 // Index of notification point starts at 0.
117 unsigned int index = 0;
118 const char* param = request.getParameter("index");
122 // "return" is a backwards-compatible "eventual destination" to go back to after logout completes.
123 param = request.getParameter("return");
125 // Fetch the next front notification URL and bump the index for the next round trip.
126 string loc = application.getNotificationURL(request.getRequestURL(), true, index++);
128 return make_pair(false,0L);
130 const URLEncoder* encoder = XMLToolingConfig::getConfig().getURLEncoder();
132 // Start with an "action" telling the application what this is about.
133 loc = loc + (strchr(loc.c_str(),'?') ? '&' : '?') + "action=logout";
135 // Now we create a second URL representing the return location back to us.
136 const char* start = request.getRequestURL();
137 const char* end = strchr(start,'?');
138 string tempstr(start, end ? end-start : strlen(start));
139 ostringstream locstr(tempstr);
141 // Add a signal that we're coming back from notification and the next index.
142 locstr << "?notifying=1&index=" << index;
144 // Add return if set.
146 locstr << "&return=" << encoder->encode(param);
148 // We preserve anything we're instructed to directly.
150 for (map<string,string>::const_iterator p = params->begin(); p!=params->end(); ++p)
151 locstr << '&' << p->first << '=' << encoder->encode(p->second.c_str());
154 for (vector<string>::const_iterator q = m_preserve.begin(); q!=m_preserve.end(); ++q) {
155 param = request.getParameter(q->c_str());
157 locstr << '&' << *q << '=' << encoder->encode(param);
161 // Add the notifier's return parameter to the destination location and redirect.
162 // This is NOT the same as the return parameter that might be embedded inside it ;-)
163 loc = loc + "&return=" + encoder->encode(locstr.str().c_str());
164 return make_pair(true,response.sendRedirect(loc.c_str()));
168 #include "util/SPConstants.h"
169 #include <xmltooling/impl/AnyElement.h>
170 #include <xmltooling/soap/SOAP.h>
171 #include <xmltooling/soap/SOAPClient.h>
172 using namespace soap11;
174 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);
175 static const XMLCh SessionID[] = UNICODE_LITERAL_9(S,e,s,s,i,o,n,I,D);
176 static const XMLCh _type[] = UNICODE_LITERAL_4(t,y,p,e);
177 static const XMLCh _local[] = UNICODE_LITERAL_5(l,o,c,a,l);
178 static const XMLCh _global[] = UNICODE_LITERAL_6(g,l,o,b,a,l);
180 class SHIBSP_DLLLOCAL SOAPNotifier : public soap11::SOAPClient
184 virtual ~SOAPNotifier() {}
186 void prepareTransport(SOAPTransport& transport) {
187 transport.setVerifyHost(false);
193 bool LogoutHandler::notifyBackChannel(
194 const Application& application, const char* requestURL, const vector<string>& sessions, bool local
197 unsigned int index = 0;
198 string endpoint = application.getNotificationURL(requestURL, false, index++);
199 if (endpoint.empty())
202 if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
204 auto_ptr<Envelope> env(EnvelopeBuilder::buildEnvelope());
205 Body* body = BodyBuilder::buildBody();
207 ElementProxy* msg = new AnyElementImpl(shibspconstants::SHIB2SPNOTIFY_NS, LogoutNotification);
208 body->getUnknownXMLObjects().push_back(msg);
209 msg->setAttribute(QName(NULL, _type), local ? _local : _global);
210 for (vector<string>::const_iterator s = sessions.begin(); s!=sessions.end(); ++s) {
211 auto_ptr_XMLCh temp(s->c_str());
212 ElementProxy* child = new AnyElementImpl(shibspconstants::SHIB2SPNOTIFY_NS, SessionID);
213 child->setTextContent(temp.get());
214 msg->getUnknownXMLObjects().push_back(child);
219 while (!endpoint.empty()) {
221 soaper.send(*env.get(), SOAPTransport::Address(application.getId(), application.getId(), endpoint.c_str()));
222 delete soaper.receive();
224 catch (exception& ex) {
225 Category::getInstance(SHIBSP_LOGCAT".Logout").error("error notifying application of logout event: %s", ex.what());
229 endpoint = application.getNotificationURL(requestURL, false, index++);
237 // When not out of process, we remote the back channel work.
238 DDF out,in(m_address.c_str());
239 DDFJanitor jin(in), jout(out);
240 in.addmember("notify").integer(1);
241 in.addmember("application_id").string(application.getId());
242 in.addmember("url").string(requestURL);
244 in.addmember("local").integer(1);
245 DDF s = in.addmember("sessions").list();
246 for (vector<string>::const_iterator i = sessions.begin(); i!=sessions.end(); ++i) {
247 DDF temp = DDF(NULL).string(i->c_str());
250 out=application.getServiceProvider().getListenerService()->send(in);
251 return (out.integer() == 1);