VS10 solution files, convert from NULL macro to nullptr.
[shibboleth/sp.git] / shibsp / handler / impl / LogoutHandler.cpp
index e363c05..63e7553 100644 (file)
@@ -1,6 +1,6 @@
 /*
- *  Copyright 2001-2007 Internet2
- * 
+ *  Copyright 2001-2010 Internet2
+ *
  * 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
@@ -16,7 +16,7 @@
 
 /**
  * LogoutHandler.cpp
- * 
+ *
  * Base class for logout-related handlers.
  */
 
 #include "Application.h"
 #include "ServiceProvider.h"
 #include "SessionCache.h"
+#include "SPRequest.h"
 #include "handler/LogoutHandler.h"
 #include "util/TemplateParameters.h"
 
 #include <fstream>
-#include <log4cpp/Category.hh>
 #include <xmltooling/XMLToolingConfig.h>
+#include <xmltooling/util/PathResolver.h>
 #include <xmltooling/util/URLEncoder.h>
 
-#ifndef SHIBSP_LITE
-#endif
-
 using namespace shibsp;
 using namespace xmltooling;
 using namespace std;
 
+LogoutHandler::LogoutHandler() : m_initiator(true)
+{
+}
+
+LogoutHandler::~LogoutHandler()
+{
+}
+
+pair<bool,long> LogoutHandler::sendLogoutPage(
+    const Application& application, const HTTPRequest& request, HTTPResponse& response, bool local, const char* status
+    ) const
+{
+    return sendLogoutPage(application, request, response, local ? "local" : "global");
+}
+
+pair<bool,long> LogoutHandler::sendLogoutPage(
+    const Application& application, const HTTPRequest& request, HTTPResponse& response, const char* type
+    ) const
+{
+    string tname = string(type) + "Logout";
+    const PropertySet* props = application.getPropertySet("Errors");
+    pair<bool,const char*> prop = props ? props->getString(tname.c_str()) : pair<bool,const char*>(false,nullptr);
+    if (!prop.first) {
+        tname += ".html";
+        prop.second = tname.c_str();
+    }
+    response.setContentType("text/html");
+    response.setResponseHeader("Expires","01-Jan-1997 12:00:00 GMT");
+    response.setResponseHeader("Cache-Control","private,no-store,no-cache");
+    string fname(prop.second);
+    ifstream infile(XMLToolingConfig::getConfig().getPathResolver()->resolve(fname, PathResolver::XMLTOOLING_CFG_FILE).c_str());
+    if (!infile)
+        throw ConfigurationException("Unable to access $1 HTML template.", params(1,prop.second));
+    TemplateParameters tp;
+    tp.m_request = &request;
+    tp.setPropertySet(props);
+    tp.m_map["logoutStatus"] = "Logout completed successfully.";  // Backward compatibility.
+    stringstream str;
+    XMLToolingConfig::getConfig().getTemplateEngine()->run(infile, str, tp);
+    return make_pair(true,response.sendResponse(str));
+}
+
 pair<bool,long> LogoutHandler::run(SPRequest& request, bool isHandler) const
 {
-    // If we're inside a chain, so do nothing.
+    // If we're inside a chain, do nothing.
     if (getParent())
-        return make_pair(false,0);
-    
+        return make_pair(false,0L);
+
     // If this isn't a LogoutInitiator, we only "continue" a notification loop, rather than starting one.
     if (!m_initiator && !request.getParameter("notifying"))
-        return make_pair(false,0);
+        return make_pair(false,0L);
 
     // Try another front-channel notification. No extra parameters and the session is implicit.
-    pair<bool,long> ret = notifyFrontChannel(request.getApplication(), request, request);
-    if (ret.first)
-        return ret;
-
-    return make_pair(false,0);
+    return notifyFrontChannel(request.getApplication(), request, request);
 }
 
 void LogoutHandler::receive(DDF& in, ostream& out)
 {
-    DDF ret(NULL);
+    DDF ret(nullptr);
     DDFJanitor jout(ret);
     if (in["notify"].integer() != 1)
         throw ListenerException("Unsupported operation.");
 
     // Find application.
     const char* aid=in["application_id"].string();
-    const Application* app=aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : NULL;
+    const Application* app=aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : nullptr;
     if (!app) {
         // Something's horribly wrong.
-        log4cpp::Category::getInstance(SHIBSP_LOGCAT".Logout").error("couldn't find application (%s) for logout", aid ? aid : "(missing)");
+        Category::getInstance(SHIBSP_LOGCAT".Logout").error("couldn't find application (%s) for logout", aid ? aid : "(missing)");
         throw ConfigurationException("Unable to locate application for logout, deleted?");
     }
 
@@ -80,7 +116,7 @@ void LogoutHandler::receive(DDF& in, ostream& out)
     while (temp.isstring()) {
         sessions.push_back(temp.string());
         temp = s.next();
-        if (notifyBackChannel(*app, sessions))
+        if (notifyBackChannel(*app, in["url"].string(), sessions, in["local"].integer()==1))
             ret.integer(1);
     }
 
@@ -100,10 +136,13 @@ pair<bool,long> LogoutHandler::notifyFrontChannel(
     if (param)
         index = atoi(param);
 
+    // "return" is a backwards-compatible "eventual destination" to go back to after logout completes.
+    param = request.getParameter("return");
+
     // Fetch the next front notification URL and bump the index for the next round trip.
-    string loc = application.getNotificationURL(request, true, index++);
+    string loc = application.getNotificationURL(request.getRequestURL(), true, index++);
     if (loc.empty())
-        return make_pair(false,0);
+        return make_pair(false,0L);
 
     const URLEncoder* encoder = XMLToolingConfig::getConfig().getURLEncoder();
 
@@ -111,13 +150,17 @@ pair<bool,long> LogoutHandler::notifyFrontChannel(
     loc = loc + (strchr(loc.c_str(),'?') ? '&' : '?') + "action=logout";
 
     // Now we create a second URL representing the return location back to us.
+    ostringstream locstr;
     const char* start = request.getRequestURL();
     const char* end = strchr(start,'?');
     string tempstr(start, end ? end-start : strlen(start));
-    ostringstream locstr(tempstr);
 
     // Add a signal that we're coming back from notification and the next index.
-    locstr << "?notifying=1&index=" << index;
+    locstr << tempstr << "?notifying=1&index=" << index;
+
+    // Add return if set.
+    if (param)
+        locstr << "&return=" << encoder->encode(param);
 
     // We preserve anything we're instructed to directly.
     if (params) {
@@ -132,16 +175,89 @@ pair<bool,long> LogoutHandler::notifyFrontChannel(
         }
     }
 
-    // Add the return parameter to the destination location and redirect.
+    // Add the notifier's return parameter to the destination location and redirect.
+    // This is NOT the same as the return parameter that might be embedded inside it ;-)
     loc = loc + "&return=" + encoder->encode(locstr.str().c_str());
     return make_pair(true,response.sendRedirect(loc.c_str()));
 }
 
-bool LogoutHandler::notifyBackChannel(const Application& application, const vector<string>& sessions) const
+#ifndef SHIBSP_LITE
+#include "util/SPConstants.h"
+#include <xmltooling/impl/AnyElement.h>
+#include <xmltooling/soap/SOAP.h>
+#include <xmltooling/soap/SOAPClient.h>
+#include <xmltooling/soap/HTTPSOAPTransport.h>
+using namespace soap11;
+namespace {
+    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);
+    static const XMLCh SessionID[] =            UNICODE_LITERAL_9(S,e,s,s,i,o,n,I,D);
+    static const XMLCh _type[] =                UNICODE_LITERAL_4(t,y,p,e);
+    static const XMLCh _local[] =               UNICODE_LITERAL_5(l,o,c,a,l);
+    static const XMLCh _global[] =              UNICODE_LITERAL_6(g,l,o,b,a,l);
+
+    class SHIBSP_DLLLOCAL SOAPNotifier : public soap11::SOAPClient
+    {
+    public:
+        SOAPNotifier() {}
+        virtual ~SOAPNotifier() {}
+    private:
+        void prepareTransport(SOAPTransport& transport) {
+            transport.setVerifyHost(false);
+            HTTPSOAPTransport* http = dynamic_cast<HTTPSOAPTransport*>(&transport);
+            if (http) {
+                http->useChunkedEncoding(false);
+                http->setRequestHeader("User-Agent", PACKAGE_NAME);
+                http->setRequestHeader(PACKAGE_NAME, PACKAGE_VERSION);
+            }
+        }
+    };
+};
+#endif
+
+bool LogoutHandler::notifyBackChannel(
+    const Application& application, const char* requestURL, const vector<string>& sessions, bool local
+    ) const
 {
+    if (sessions.empty()) {
+        Category::getInstance(SHIBSP_LOGCAT".Logout").error("no sessions supplied to back channel notification method");
+        return false;
+    }
+
+    unsigned int index = 0;
+    string endpoint = application.getNotificationURL(requestURL, false, index++);
+    if (endpoint.empty())
+        return true;
+
     if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
 #ifndef SHIBSP_LITE
-        return true;
+        auto_ptr<Envelope> env(EnvelopeBuilder::buildEnvelope());
+        Body* body = BodyBuilder::buildBody();
+        env->setBody(body);
+        ElementProxy* msg = new AnyElementImpl(shibspconstants::SHIB2SPNOTIFY_NS, LogoutNotification);
+        body->getUnknownXMLObjects().push_back(msg);
+        msg->setAttribute(xmltooling::QName(nullptr, _type), local ? _local : _global);
+        for (vector<string>::const_iterator s = sessions.begin(); s!=sessions.end(); ++s) {
+            auto_ptr_XMLCh temp(s->c_str());
+            ElementProxy* child = new AnyElementImpl(shibspconstants::SHIB2SPNOTIFY_NS, SessionID);
+            child->setTextContent(temp.get());
+            msg->getUnknownXMLObjects().push_back(child);
+        }
+
+        bool result = true;
+        SOAPNotifier soaper;
+        while (!endpoint.empty()) {
+            try {
+                soaper.send(*env.get(), SOAPTransport::Address(application.getId(), application.getId(), endpoint.c_str()));
+                delete soaper.receive();
+            }
+            catch (exception& ex) {
+                Category::getInstance(SHIBSP_LOGCAT".Logout").error("error notifying application of logout event: %s", ex.what());
+                result = false;
+            }
+            soaper.reset();
+            endpoint = application.getNotificationURL(requestURL, false, index++);
+        }
+        return result;
 #else
         return false;
 #endif
@@ -152,35 +268,14 @@ bool LogoutHandler::notifyBackChannel(const Application& application, const vect
     DDFJanitor jin(in), jout(out);
     in.addmember("notify").integer(1);
     in.addmember("application_id").string(application.getId());
+    in.addmember("url").string(requestURL);
+    if (local)
+        in.addmember("local").integer(1);
     DDF s = in.addmember("sessions").list();
     for (vector<string>::const_iterator i = sessions.begin(); i!=sessions.end(); ++i) {
-        DDF temp = DDF(NULL).string(i->c_str());
+        DDF temp = DDF(nullptr).string(i->c_str());
         s.add(temp);
     }
     out=application.getServiceProvider().getListenerService()->send(in);
     return (out.integer() == 1);
 }
-
-pair<bool,long> LogoutHandler::sendLogoutPage(const Application& application, HTTPResponse& response, bool local, const char* status) const
-{
-    pair<bool,const char*> prop = application.getString(local ? "localLogout" : "globalLogout");
-    if (prop.first) {
-        response.setContentType("text/html");
-        response.setResponseHeader("Expires","01-Jan-1997 12:00:00 GMT");
-        response.setResponseHeader("Cache-Control","private,no-store,no-cache");
-        ifstream infile(prop.second);
-        if (!infile)
-            throw ConfigurationException("Unable to access $1 HTML template.", params(1,local ? "localLogout" : "globalLogout"));
-        TemplateParameters tp;
-        tp.setPropertySet(application.getPropertySet("Errors"));
-        if (status)
-            tp.m_map["logoutStatus"] = status;
-        stringstream str;
-        XMLToolingConfig::getConfig().getTemplateEngine()->run(infile, str, tp);
-        return make_pair(true,response.sendResponse(str));
-    }
-    prop = application.getString("homeURL");
-    if (!prop.first)
-        prop.second = "/";
-    return make_pair(true,response.sendRedirect(prop.second));
-}