e363c058f99b6dde5a9c03c1e84ab223ca76b7a6
[shibboleth/sp.git] / shibsp / handler / impl / LogoutHandler.cpp
1 /*
2  *  Copyright 2001-2007 Internet2
3  * 
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
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 /**
18  * LogoutHandler.cpp
19  * 
20  * Base class for logout-related handlers.
21  */
22
23 #include "internal.h"
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"
30
31 #include <fstream>
32 #include <log4cpp/Category.hh>
33 #include <xmltooling/XMLToolingConfig.h>
34 #include <xmltooling/util/URLEncoder.h>
35
36 #ifndef SHIBSP_LITE
37 #endif
38
39 using namespace shibsp;
40 using namespace xmltooling;
41 using namespace std;
42
43 pair<bool,long> LogoutHandler::run(SPRequest& request, bool isHandler) const
44 {
45     // If we're inside a chain, so do nothing.
46     if (getParent())
47         return make_pair(false,0);
48     
49     // If this isn't a LogoutInitiator, we only "continue" a notification loop, rather than starting one.
50     if (!m_initiator && !request.getParameter("notifying"))
51         return make_pair(false,0);
52
53     // Try another front-channel notification. No extra parameters and the session is implicit.
54     pair<bool,long> ret = notifyFrontChannel(request.getApplication(), request, request);
55     if (ret.first)
56         return ret;
57
58     return make_pair(false,0);
59 }
60
61 void LogoutHandler::receive(DDF& in, ostream& out)
62 {
63     DDF ret(NULL);
64     DDFJanitor jout(ret);
65     if (in["notify"].integer() != 1)
66         throw ListenerException("Unsupported operation.");
67
68     // Find application.
69     const char* aid=in["application_id"].string();
70     const Application* app=aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : NULL;
71     if (!app) {
72         // Something's horribly wrong.
73         log4cpp::Category::getInstance(SHIBSP_LOGCAT".Logout").error("couldn't find application (%s) for logout", aid ? aid : "(missing)");
74         throw ConfigurationException("Unable to locate application for logout, deleted?");
75     }
76
77     vector<string> sessions;
78     DDF s = in["sessions"];
79     DDF temp = s.first();
80     while (temp.isstring()) {
81         sessions.push_back(temp.string());
82         temp = s.next();
83         if (notifyBackChannel(*app, sessions))
84             ret.integer(1);
85     }
86
87     out << ret;
88 }
89
90 pair<bool,long> LogoutHandler::notifyFrontChannel(
91     const Application& application,
92     const HTTPRequest& request,
93     HTTPResponse& response,
94     const map<string,string>* params
95     ) const
96 {
97     // Index of notification point starts at 0.
98     unsigned int index = 0;
99     const char* param = request.getParameter("index");
100     if (param)
101         index = atoi(param);
102
103     // Fetch the next front notification URL and bump the index for the next round trip.
104     string loc = application.getNotificationURL(request, true, index++);
105     if (loc.empty())
106         return make_pair(false,0);
107
108     const URLEncoder* encoder = XMLToolingConfig::getConfig().getURLEncoder();
109
110     // Start with an "action" telling the application what this is about.
111     loc = loc + (strchr(loc.c_str(),'?') ? '&' : '?') + "action=logout";
112
113     // Now we create a second URL representing the return location back to us.
114     const char* start = request.getRequestURL();
115     const char* end = strchr(start,'?');
116     string tempstr(start, end ? end-start : strlen(start));
117     ostringstream locstr(tempstr);
118
119     // Add a signal that we're coming back from notification and the next index.
120     locstr << "?notifying=1&index=" << index;
121
122     // We preserve anything we're instructed to directly.
123     if (params) {
124         for (map<string,string>::const_iterator p = params->begin(); p!=params->end(); ++p)
125             locstr << '&' << p->first << '=' << encoder->encode(p->second.c_str());
126     }
127     else {
128         for (vector<string>::const_iterator q = m_preserve.begin(); q!=m_preserve.end(); ++q) {
129             param = request.getParameter(q->c_str());
130             if (param)
131                 locstr << '&' << *q << '=' << encoder->encode(param);
132         }
133     }
134
135     // Add the return parameter to the destination location and redirect.
136     loc = loc + "&return=" + encoder->encode(locstr.str().c_str());
137     return make_pair(true,response.sendRedirect(loc.c_str()));
138 }
139
140 bool LogoutHandler::notifyBackChannel(const Application& application, const vector<string>& sessions) const
141 {
142     if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
143 #ifndef SHIBSP_LITE
144         return true;
145 #else
146         return false;
147 #endif
148     }
149
150     // When not out of process, we remote the back channel work.
151     DDF out,in(m_address.c_str());
152     DDFJanitor jin(in), jout(out);
153     in.addmember("notify").integer(1);
154     in.addmember("application_id").string(application.getId());
155     DDF s = in.addmember("sessions").list();
156     for (vector<string>::const_iterator i = sessions.begin(); i!=sessions.end(); ++i) {
157         DDF temp = DDF(NULL).string(i->c_str());
158         s.add(temp);
159     }
160     out=application.getServiceProvider().getListenerService()->send(in);
161     return (out.integer() == 1);
162 }
163
164 pair<bool,long> LogoutHandler::sendLogoutPage(const Application& application, HTTPResponse& response, bool local, const char* status) const
165 {
166     pair<bool,const char*> prop = application.getString(local ? "localLogout" : "globalLogout");
167     if (prop.first) {
168         response.setContentType("text/html");
169         response.setResponseHeader("Expires","01-Jan-1997 12:00:00 GMT");
170         response.setResponseHeader("Cache-Control","private,no-store,no-cache");
171         ifstream infile(prop.second);
172         if (!infile)
173             throw ConfigurationException("Unable to access $1 HTML template.", params(1,local ? "localLogout" : "globalLogout"));
174         TemplateParameters tp;
175         tp.setPropertySet(application.getPropertySet("Errors"));
176         if (status)
177             tp.m_map["logoutStatus"] = status;
178         stringstream str;
179         XMLToolingConfig::getConfig().getTemplateEngine()->run(infile, str, tp);
180         return make_pair(true,response.sendResponse(str));
181     }
182     prop = application.getString("homeURL");
183     if (!prop.first)
184         prop.second = "/";
185     return make_pair(true,response.sendRedirect(prop.second));
186 }