Starting to refactor session cache, eliminated IConfig class.
[shibboleth/cpp-sp.git] / shib-target / shib-handlers.cpp
index 4af781e..822b703 100644 (file)
 
 #include "internal.h"
 
+#include <ctime>
+#include <saml/SAMLConfig.h>
+#include <saml/binding/URLEncoder.h>
+#include <saml/saml2/metadata/Metadata.h>
+#include <saml/saml2/metadata/EndpointManager.h>
+#include <saml/util/CommonDomainCookie.h>
+#include <shibsp/AbstractHandler.h>
+#include <shibsp/SPConfig.h>
+
 #ifdef HAVE_UNISTD_H
 # include <unistd.h>
 #endif
 
-#include <shib/shib-threads.h>
-#include <xercesc/util/Base64.hpp>
-
-#ifndef HAVE_STRCASECMP
-# define strcasecmp stricmp
-#endif
-
-using namespace std;
-using namespace saml;
-using namespace shibboleth;
+using namespace shibsp;
 using namespace shibtarget;
+using namespace shibboleth;
+using namespace saml;
+using namespace opensaml::saml2md;
 using namespace log4cpp;
+using namespace std;
 
-namespace {
-  class CgiParse
-  {
-  public:
-    CgiParse(const char* data, size_t len);
-    ~CgiParse();
-    const char* get_value(const char* name) const;
-    
-    static char x2c(char *what);
-    static void url_decode(char *url);
-    static string url_encode(const char* s);
-  private:
-    char * fmakeword(char stop, unsigned int *cl, const char** ppch);
-    char * makeword(char *line, char stop);
-    void plustospace(char *str);
+using opensaml::CommonDomainCookie;
+using opensaml::URLEncoder;
 
-    map<string,char*> kvp_map;
-  };
+#if defined (_MSC_VER)
+    #pragma warning( push )
+    #pragma warning( disable : 4250 )
+#endif
 
-    // Helper class for SAML 2.0 Common Domain Cookie operations
-    class CommonDomainCookie
-    {
-    public:
-        CommonDomainCookie(const char* cookie);
-        ~CommonDomainCookie() {}
-        saml::Iterator<std::string> get() {return m_list;}
-        const char* set(const char* providerId);
-        static const char CDCName[];
-    private:
-        std::string m_encoded;
-        std::vector<std::string> m_list;
-    };
-
-  class SessionInitiator : virtual public IHandler
+namespace {
+  class SessionInitiator : public AbstractHandler
   {
   public:
-    SessionInitiator(const DOMElement* e) {}
+    SessionInitiator(const DOMElement* e) : AbstractHandler(e) {}
     ~SessionInitiator() {}
-    pair<bool,void*> run(ShibTarget* st, const IPropertySet* handler, bool isHandler=true);
-    pair<bool,void*> ShibAuthnRequest(
-        ShibTarget* st,
-        const IPropertySet* shire,
+    pair<bool,long> run(SPRequest& request, bool isHandler=true) const;
+    pair<bool,long> ShibAuthnRequest(
+        SPRequest& request,
+        const Handler* shire,
         const char* dest,
         const char* target,
         const char* providerId
-        );
+        ) const;
   };
 
-  class SAML1Consumer : virtual public IHandler
+  class SAML1Consumer : public AbstractHandler, public virtual Remoted
   {
   public:
-    SAML1Consumer(const DOMElement* e) {}
-    ~SAML1Consumer() {}
-    pair<bool,void*> run(ShibTarget* st, const IPropertySet* handler, bool isHandler=true);
+    SAML1Consumer(const DOMElement* e);
+    ~SAML1Consumer();
+    pair<bool,long> run(SPRequest& request, bool isHandler=true) const;
+    DDF receive(const DDF& in);
+  private:
+    string m_address;
+    static int counter;
   };
 
-  class ShibLogout : virtual public IHandler
+  int SAML1Consumer::counter = 0;
+
+  class ShibLogout : public AbstractHandler
   {
   public:
-    ShibLogout(const DOMElement* e) {}
+    ShibLogout(const DOMElement* e) : AbstractHandler(e) {}
     ~ShibLogout() {}
-    pair<bool,void*> run(ShibTarget* st, const IPropertySet* handler, bool isHandler=true);
+    pair<bool,long> run(SPRequest& request, bool isHandler=true) const;
   };
 }
 
+#if defined (_MSC_VER)
+    #pragma warning( pop )
+#endif
 
-IPlugIn* ShibSessionInitiatorFactory(const DOMElement* e)
+Handler* ShibSessionInitiatorFactory(const DOMElement* const & e)
 {
     return new SessionInitiator(e);
 }
 
-IPlugIn* SAML1POSTFactory(const DOMElement* e)
+Handler* SAML1POSTFactory(const DOMElement* const & e)
 {
     return new SAML1Consumer(e);
 }
 
-IPlugIn* SAML1ArtifactFactory(const DOMElement* e)
+Handler* SAML1ArtifactFactory(const DOMElement* const & e)
 {
     return new SAML1Consumer(e);
 }
 
-IPlugIn* ShibLogoutFactory(const DOMElement* e)
+Handler* ShibLogoutFactory(const DOMElement* const & e)
 {
     return new ShibLogout(e);
 }
 
-pair<bool,void*> SessionInitiator::run(ShibTarget* st, const IPropertySet* handler, bool isHandler)
+pair<bool,long> SessionInitiator::run(SPRequest& request, bool isHandler) const
 {
     string dupresource;
     const char* resource=NULL;
-    const IPropertySet* ACS=NULL;
-    const IApplication* app=st->getApplication();
+    const Handler* ACS=NULL;
+    const IApplication& app=dynamic_cast<const IApplication&>(request.getApplication());
     
     if (isHandler) {
         /* 
@@ -140,21 +129,18 @@ pair<bool,void*> SessionInitiator::run(ShibTarget* st, const IPropertySet* handl
          *  acsIndex    optional index of an ACS to use on the way back in
          *  providerId  optional direct invocation of a specific IdP
          */
-        string query=st->getArgs();
-        CgiParse parser(query.c_str(),query.length());
-
-        const char* option=parser.get_value("acsIndex");
+        const char* option=request.getParameter("acsIndex");
         if (option)
-            ACS=app->getAssertionConsumerServiceByIndex(atoi(option));
-        option=parser.get_value("providerId");
+            ACS=app.getAssertionConsumerServiceByIndex(atoi(option));
+        option=request.getParameter("providerId");
         
-        resource=parser.get_value("target");
+        resource=request.getParameter("target");
         if (!resource || !*resource) {
-            pair<bool,const char*> home=app->getString("homeURL");
+            pair<bool,const char*> home=app.getString("homeURL");
             if (home.first)
                 resource=home.second;
             else
-                throw FatalProfileException("Session initiator requires a target parameter or a homeURL application property.");
+                throw opensaml::FatalProfileException("Session initiator requires a target parameter or a homeURL application property.");
         }
         else if (!option) {
             dupresource=resource;
@@ -164,189 +150,422 @@ pair<bool,void*> SessionInitiator::run(ShibTarget* st, const IPropertySet* handl
         if (option) {
             // Here we actually use metadata to invoke the SSO service directly.
             // The only currently understood binding is the Shibboleth profile.
-            Metadata m(app->getMetadataProviders());
-            const IEntityDescriptor* entity=m.lookup(option);
+            
+            MetadataProvider* m=app.getMetadataProvider();
+            xmltooling::Locker locker(m);
+            const EntityDescriptor* entity=m->getEntityDescriptor(option);
             if (!entity)
-                throw MetadataException("Session initiator unable to locate metadata for provider ($1).", params(1,option));
-            const IIDPSSODescriptor* role=entity->getIDPSSODescriptor(Constants::SHIB_NS);
+                throw MetadataException("Session initiator unable to locate metadata for provider ($1).", xmltooling::params(1,option));
+            const IDPSSODescriptor* role=entity->getIDPSSODescriptor(shibspconstants::SHIB1_PROTOCOL_ENUM);
             if (!role)
                 throw MetadataException(
-                    "Session initiator unable to locate a Shibboleth-aware identity provider role for provider ($1).", params(1,option)
+                    "Session initiator unable to locate a Shibboleth-aware identity provider role for provider ($1).",
+                    xmltooling::params(1,option)
                     );
-            const IEndpointManager* SSO=role->getSingleSignOnServiceManager();
-            const IEndpoint* ep=SSO->getEndpointByBinding(Constants::SHIB_AUTHNREQUEST_PROFILE_URI);
+            const EndpointType* ep=EndpointManager<SingleSignOnService>(role->getSingleSignOnServices()).getByBinding(
+                shibspconstants::SHIB1_AUTHNREQUEST_PROFILE_URI
+                );
             if (!ep)
                 throw MetadataException(
-                    "Session initiator unable to locate compatible SSO service for provider ($1).", params(1,option)
+                    "Session initiator unable to locate compatible SSO service for provider ($1).", xmltooling::params(1,option)
                     );
             auto_ptr_char dest(ep->getLocation());
             return ShibAuthnRequest(
-                st,ACS ? ACS : app->getDefaultAssertionConsumerService(),dest.get(),resource,app->getString("providerId").second
+                request,ACS ? ACS : app.getDefaultAssertionConsumerService(),dest.get(),resource,app.getString("providerId").second
                 );
         }
     }
     else {
         // We're running as a "virtual handler" from within the filter.
         // The target resource is the current one and everything else is defaulted.
-        resource=st->getRequestURL();
+        resource=request.getRequestURL();
     }
     
-    if (!ACS) ACS=app->getDefaultAssertionConsumerService();
+    if (!ACS) ACS=app.getDefaultAssertionConsumerService();
     
     // For now, we only support external session initiation via a wayfURL
-    pair<bool,const char*> wayfURL=handler->getString("wayfURL");
+    pair<bool,const char*> wayfURL=getString("wayfURL");
     if (!wayfURL.first)
         throw ConfigurationException("Session initiator is missing wayfURL property.");
 
-    pair<bool,const XMLCh*> wayfBinding=handler->getXMLString("wayfBinding");
-    if (!wayfBinding.first || !XMLString::compareString(wayfBinding.second,Constants::SHIB_AUTHNREQUEST_PROFILE_URI))
+    pair<bool,const XMLCh*> wayfBinding=getXMLString("wayfBinding");
+    if (!wayfBinding.first || !XMLString::compareString(wayfBinding.second,shibspconstants::SHIB1_AUTHNREQUEST_PROFILE_URI))
         // Standard Shib 1.x
-        return ShibAuthnRequest(st,ACS,wayfURL.second,resource,app->getString("providerId").second);
-    else if (!XMLString::compareString(wayfBinding.second,Constants::SHIB_LEGACY_AUTHNREQUEST_PROFILE_URI))
-        // Shib pre-1.2
-        return ShibAuthnRequest(st,ACS,wayfURL.second,resource,NULL);
-    else if (!strcmp(handler->getString("wayfBinding").second,"urn:mace:shibboleth:1.0:profiles:EAuth")) {
+        return ShibAuthnRequest(request,ACS,wayfURL.second,resource,app.getString("providerId").second);
+    else if (!strcmp(getString("wayfBinding").second,"urn:mace:shibboleth:1.0:profiles:EAuth")) {
         // TODO: Finalize E-Auth profile URI
-        pair<bool,bool> localRelayState=st->getConfig()->getPropertySet("Local")->getBool("localRelayState");
+        pair<bool,bool> localRelayState=request.getServiceProvider().getPropertySet("InProcess")->getBool("localRelayState");
         if (!localRelayState.first || !localRelayState.second)
             throw ConfigurationException("E-Authn requests cannot include relay state, so localRelayState must be enabled.");
 
         // Here we store the state in a cookie.
-        pair<string,const char*> shib_cookie=st->getCookieNameProps("_shibstate_");
-        st->setCookie(shib_cookie.first,CgiParse::url_encode(resource) + shib_cookie.second);
-        return make_pair(true, st->sendRedirect(wayfURL.second));
+        pair<string,const char*> shib_cookie=app.getCookieNameProps("_shibstate_");
+        string stateval = opensaml::SAMLConfig::getConfig().getURLEncoder()->encode(resource) + shib_cookie.second;
+            request.setCookie(shib_cookie.first.c_str(),stateval.c_str());
+        return make_pair(true, request.sendRedirect(wayfURL.second));
     }
    
-    throw UnsupportedProfileException("Unsupported WAYF binding ($1).", params(1,handler->getString("wayfBinding").second));
+    throw opensaml::BindingException("Unsupported WAYF binding ($1).", xmltooling::params(1,getString("wayfBinding").second));
 }
 
 // Handles Shib 1.x AuthnRequest profile.
-pair<bool,void*> SessionInitiator::ShibAuthnRequest(
-    ShibTarget* st,
-    const IPropertySet* shire,
+pair<bool,long> SessionInitiator::ShibAuthnRequest(
+    SPRequest& request,
+    const Handler* shire,
     const char* dest,
     const char* target,
     const char* providerId
-    )
+    ) const
 {
-    // Compute the ACS URL. We add the ACS location to the handler baseURL.
-    // Legacy configs will not have an ACS specified, so no suffix will be added.
-    string ACSloc=st->getHandlerURL(target);
-    if (shire) ACSloc+=shire->getString("Location").second;
+    // Compute the ACS URL. We add the ACS location to the base handlerURL.
+    // Legacy configs will not have the Location property specified, so no suffix will be added.
+    string ACSloc=request.getHandlerURL(target);
+    pair<bool,const char*> loc=shire ? shire->getString("Location") : pair<bool,const char*>(false,NULL);
+    if (loc.first) ACSloc+=loc.second;
     
+    URLEncoder* urlenc = opensaml::SAMLConfig::getConfig().getURLEncoder();
+
     char timebuf[16];
     sprintf(timebuf,"%u",time(NULL));
-    string req=string(dest) + "?shire=" + CgiParse::url_encode(ACSloc.c_str()) + "&time=" + timebuf;
+    string req=string(dest) + "?shire=" + urlenc->encode(ACSloc.c_str()) + "&time=" + timebuf;
 
     // How should the resource value be preserved?
-    pair<bool,bool> localRelayState=st->getConfig()->getPropertySet("Local")->getBool("localRelayState");
+    pair<bool,bool> localRelayState=request.getServiceProvider().getPropertySet("InProcess")->getBool("localRelayState");
     if (!localRelayState.first || !localRelayState.second) {
         // The old way, just send it along.
-        req+="&target=" + CgiParse::url_encode(target);
+        req+="&target=" + urlenc->encode(target);
     }
     else {
         // Here we store the state in a cookie and send a fixed
         // value to the IdP so we can recognize it on the way back.
-        pair<string,const char*> shib_cookie=st->getCookieNameProps("_shibstate_");
-        st->setCookie(shib_cookie.first,CgiParse::url_encode(target) + shib_cookie.second);
+        pair<string,const char*> shib_cookie=request.getApplication().getCookieNameProps("_shibstate_");
+        string stateval = urlenc->encode(target) + shib_cookie.second;
+        request.setCookie(shib_cookie.first.c_str(),stateval.c_str());
         req+="&target=cookie";
     }
     
     // Only omitted for 1.1 style requests.
     if (providerId)
-        req+="&providerId=" + CgiParse::url_encode(providerId);
+        req+="&providerId=" + urlenc->encode(providerId);
+
+    return make_pair(true, request.sendRedirect(req.c_str()));
+}
+
+SAML1Consumer::SAML1Consumer(const DOMElement* e) : AbstractHandler(e)
+{
+    m_address += ('A' + (counter++));
+    m_address += "::SAML1Consumer::run";
+
+    // Register for remoted messages.
+    if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess))
+        SPConfig::getConfig().getServiceProvider()->getListenerService()->regListener(m_address.c_str(),this);
+}
 
-    return make_pair(true, st->sendRedirect(req));
+SAML1Consumer::~SAML1Consumer()
+{
+    ListenerService* listener=SPConfig::getConfig().getServiceProvider()->getListenerService(false);
+    if (listener && SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess))
+        listener->unregListener(m_address.c_str(),this);
+    counter--;
 }
 
-pair<bool,void*> SAML1Consumer::run(ShibTarget* st, const IPropertySet* handler, bool isHandler)
+/*
+ * IPC message definitions:
+ * 
+ *  [A-Z]::SAML1Consumer::run
+ * 
+ *      IN
+ *      application_id
+ *      client_address
+ *      recipient
+ *      SAMLResponse or SAMLart list
+ * 
+ *      OUT
+ *      key
+ *      provider_id
+ */
+DDF SAML1Consumer::receive(const DDF& in)
 {
-    int profile=0;
-    string input,cookie,target,providerId;
-    const IApplication* app=st->getApplication();
+#ifdef _DEBUG
+    xmltooling::NDC ndc("receive");
+#endif
+    Category& log=Category::getInstance(SHIBT_LOGCAT".SAML1Consumer");
+
+    // Find application.
+    const char* aid=in["application_id"].string();
+    const IApplication* app=aid ? dynamic_cast<const IApplication*>(SPConfig::getConfig().getServiceProvider()->getApplication(aid)) : NULL;
+    if (!app) {
+        // Something's horribly wrong.
+        log.error("couldn't find application (%s) for new session", aid ? aid : "(missing)");
+        throw SAMLException("Unable to locate application for new session, deleted?");
+    }
+
+    // Check required parameters.
+    const char* client_address=in["client_address"].string();
+    const char* recipient=in["recipient"].string();
+    if (!client_address || !recipient)
+        throw SAMLException("Required parameters missing in call to SAML1Consumer::run");
     
+    log.debug("processing new assertion for %s", client_address);
+    log.debug("recipient: %s", recipient);
+    log.debug("application: %s", app->getId());
+
+    // Access the application config.
+    ServiceProvider* conf=SPConfig::getConfig().getServiceProvider();
+    xmltooling::Locker confLocker(conf);
+
+    auto_ptr_XMLCh wrecipient(recipient);
+
+    pair<bool,bool> checkAddress=pair<bool,bool>(false,true);
+    pair<bool,bool> checkReplay=pair<bool,bool>(false,true);
+    const PropertySet* props=app->getPropertySet("Sessions");
+    if (props) {
+        checkAddress=props->getBool("checkAddress");
+        if (!checkAddress.first)
+            checkAddress.second=true;
+        checkReplay=props->getBool("checkReplay");
+        if (!checkReplay.first)
+            checkReplay.second=true;
+    }
+
     // Supports either version...
-    pair<bool,unsigned int> version=handler->getUnsignedInt("MinorVersion","urn:oasis:names:tc:SAML:1.0:protocol");
+    pair<bool,unsigned int> version=getUnsignedInt("MinorVersion","urn:oasis:names:tc:SAML:1.0:protocol");
     if (!version.first)
         version.second=1;
 
-    pair<bool,const XMLCh*> binding=handler->getXMLString("Binding");
-    if (!binding.first || !XMLString::compareString(binding.second,SAMLBrowserProfile::BROWSER_POST)) {
-        if (strcasecmp(st->getRequestMethod(), "POST"))
-            throw FatalProfileException(
-                "SAML 1.x Browser/POST handler does not support HTTP method ($1).", params(1,st->getRequestMethod())
+    const EntityDescriptor* provider=NULL;
+    const RoleDescriptor* role=NULL;
+    MetadataProvider* m=app->getMetadataProvider();
+    xmltooling::Locker locker(m);
+    SAMLBrowserProfile::BrowserProfileResponse bpr;
+
+    try {
+        const char* samlResponse=in["SAMLResponse"].string();
+        if (samlResponse) {
+            // POST profile
+            log.debug("executing Browser/POST profile...");
+            bpr=app->getBrowserProfile()->receive(
+                samlResponse,
+                wrecipient.get(),
+                NULL,
+                version.second
                 );
-        
-        if (!st->getContentType() || strcasecmp(st->getContentType(),"application/x-www-form-urlencoded"))
-            throw FatalProfileException(
-                "Blocked invalid content-type ($1) submitted to SAML 1.x Browser/POST handler.", params(1,st->getContentType())
+        }
+        else {
+            // Artifact profile
+            vector<const char*> SAMLart;
+            DDF arts=in["SAMLart"];
+            DDF art=arts.first();
+            while (art.isstring()) {
+                SAMLart.push_back(art.string());
+                art=arts.next();
+            }
+            auto_ptr<SAMLBrowserProfile::ArtifactMapper> artifactMapper(app->getArtifactMapper());
+            log.debug("executing Browser/Artifact profile...");
+            bpr=app->getBrowserProfile()->receive(
+                SAMLart,
+                wrecipient.get(),
+                artifactMapper.get(),
+                NULL,
+                version.second
+                );
+
+            // Blow it away to clear any locks that might be held.
+            delete artifactMapper.release();
+        }
+
+        // Try and map to metadata (again).
+        // Once the metadata layer is in the SAML core, the repetition will be fixed.
+        provider=m->getEntityDescriptor(bpr.assertion->getIssuer());
+        if (!provider && bpr.authnStatement->getSubject()->getNameIdentifier() &&
+                bpr.authnStatement->getSubject()->getNameIdentifier()->getNameQualifier())
+            provider=m->getEntityDescriptor(bpr.authnStatement->getSubject()->getNameIdentifier()->getNameQualifier());
+        if (provider) {
+            role=provider->getIDPSSODescriptor(
+                version.second==1 ? samlconstants::SAML11_PROTOCOL_ENUM : samlconstants::SAML10_PROTOCOL_ENUM
                 );
-        input=st->getPostData();
-        profile|=(version.second==1 ? SAML11_POST : SAML10_POST);
+        }
+        
+        // This isn't likely, since the profile must have found a role.
+        if (!role) {
+            MetadataException ex("Unable to locate role-specific metadata for identity provider.");
+            annotateException(&ex,provider); // throws it
+        }
+    
+        // Maybe verify the client address....
+        if (checkAddress.second) {
+            log.debug("verifying client address");
+            // Verify the client address exists
+            const XMLCh* wip = bpr.authnStatement->getSubjectIP();
+            if (wip && *wip) {
+                // Verify the client address matches authentication
+                auto_ptr_char this_ip(wip);
+                if (strcmp(client_address, this_ip.get())) {
+                    opensaml::FatalProfileException ex(
+                       "Your client's current address ($1) differs from the one used when you authenticated "
+                        "to your identity provider. To correct this problem, you may need to bypass a proxy server. "
+                        "Please contact your local support staff or help desk for assistance.",
+                        xmltooling::params(1,client_address)
+                        );
+                    annotateException(&ex,role); // throws it
+                }
+            }
+        }
+    }
+    catch (exception&) {
+        bpr.clear();
+        throw;
+    }
+    catch (...) {
+        log.error("caught unknown exception");
+        bpr.clear();
+#ifdef _DEBUG
+        throw;
+#else
+        SAMLException e("An unexpected error occurred while creating your session.");
+        shibboleth::annotateException(&e,role);
+#endif
+    }
+
+    // It passes all our tests -- create a new session.
+    log.info("creating new session");
+
+    // Insert into cache.
+    auto_ptr_char authContext(bpr.authnStatement->getAuthMethod());
+    string key=dynamic_cast<ISessionCache*>(conf->getSessionCache())->insert(
+        app,
+        role,
+        client_address,
+        bpr.authnStatement->getSubject(),
+        authContext.get(),
+        bpr.response
+        );
+    // objects owned by cache now
+    log.debug("new session id: %s", key.c_str());
+    auto_ptr_char oname(provider->getEntityID());
+    DDF out=DDF(NULL).structure();
+    out.addmember("key").string(key.c_str());
+    out.addmember("provider_id").string(oname.get());
+
+    return out;
+}
+
+pair<bool,long> SAML1Consumer::run(SPRequest& request, bool isHandler) const
+{
+    DDF in,out;
+    DDFJanitor jin(in),jout(out);
+
+    pair<bool,const XMLCh*> binding=getXMLString("Binding");
+    if (!binding.first || !XMLString::compareString(binding.second,SAMLBrowserProfile::BROWSER_POST)) {
+#ifdef HAVE_STRCASECMP
+        if (strcasecmp(request.getMethod(), "POST")) {
+#else
+        if (_stricmp(request.getMethod(), "POST")) {
+#endif
+            request.log(SPRequest::SPInfo, "SAML 1.x Browser/POST handler ignoring non-POST request");
+            return pair<bool,long>(false,NULL);
+        }
+#ifdef HAVE_STRCASECMP
+        if (strcasecmp(request.getContentType().c_str(),"application/x-www-form-urlencoded")) {
+#else
+        if (_stricmp(request.getContentType().c_str(),"application/x-www-form-urlencoded")) {
+#endif
+            request.log(SPRequest::SPInfo, "SAML 1.x Browser/POST handler ignoring submission with unknown content-type.");
+            return pair<bool,long>(false,0);
+        }
+
+        const char* samlResponse = request.getParameter("SAMLResponse");
+        if (!samlResponse) {
+            request.log(SPRequest::SPInfo, "SAML 1.x Browser/POST handler ignoring request with no SAMLResponse parameter.");
+            return pair<bool,long>(false,0);
+        }
+
+        in=DDF(m_address.c_str()).structure();
+        in.addmember("SAMLResponse").string(samlResponse);
     }
     else if (!XMLString::compareString(binding.second,SAMLBrowserProfile::BROWSER_ARTIFACT)) {
-        if (strcasecmp(st->getRequestMethod(), "GET"))
-            throw FatalProfileException(
-                "SAML 1.x Browser/Artifact handler does not support HTTP method ($1).", params(1,st->getRequestMethod())
-                );
-        input=st->getArgs();
-        profile|=(version.second==1 ? SAML11_ARTIFACT : SAML10_ARTIFACT);
+#ifdef HAVE_STRCASECMP
+        if (strcasecmp(request.getMethod(), "GET")) {
+#else
+        if (_stricmp(request.getMethod(), "GET")) {
+#endif
+            request.log(SPRequest::SPInfo, "SAML 1.x Browser/Artifact handler ignoring non-GET request");
+            return pair<bool,long>(false,0);
+        }
+
+        vector<const char*> arts;
+        if (request.getParameters("SAMLart",arts)==0) {
+            request.log(SPRequest::SPInfo, "SAML 1.x Browser/Artifact handler ignoring request with no SAMLart parameter.");
+            return pair<bool,long>(false,0);
+        }
+
+        in=DDF(m_address.c_str()).structure();
+        DDF artlist=in.addmember("SAMLart").list();
+
+        for (vector<const char*>::const_iterator a=arts.begin(); a!=arts.end(); ++a)
+            artlist.add(DDF(NULL).string(*a));
     }
     
-    if (input.empty())
-        throw FatalProfileException("SAML 1.x Browser Profile handler received no data from browser.");
-    
-    string hURL=st->getHandlerURL(st->getRequestURL());
-    pair<bool,const char*> loc=handler->getString("Location");
+    // Compute the endpoint location.
+    string hURL=request.getHandlerURL(request.getRequestURL());
+    pair<bool,const char*> loc=getString("Location");
     string recipient=loc.first ? hURL + loc.second : hURL;
-    st->getConfig()->getListener()->sessionNew(
-        app,
-        profile,
-        recipient.c_str(),
-        input.c_str(),
-        st->getRemoteAddr(),
-        target,
-        cookie,
-        providerId
-        );
+    in.addmember("recipient").string(recipient.c_str());
 
-    st->log(ShibTarget::LogLevelDebug, string("profile processing succeeded, new session created (") + cookie + ")");
+    // Add remaining parameters.
+    in.addmember("application_id").string(request.getApplication().getId());
+    in.addmember("client_address").string(request.getRemoteAddr().c_str());
 
-    if (target=="default") {
-        pair<bool,const char*> homeURL=app->getString("homeURL");
+    out=request.getServiceProvider().getListenerService()->send(in);
+    if (!out["key"].isstring())
+        throw opensaml::FatalProfileException("Remote processing of SAML 1.x Browser profile did not return a usable session key.");
+    string key=out["key"].string();
+
+    request.log(SPRequest::SPDebug, string("profile processing succeeded, new session created (") + key + ")");
+
+    const char* target=request.getParameter("TARGET");
+    if (target && !strcmp(target,"default")) {
+        pair<bool,const char*> homeURL=request.getApplication().getString("homeURL");
         target=homeURL.first ? homeURL.second : "/";
     }
-    else if (target=="cookie" || target.empty()) {
+    else if (!target || !strcmp(target,"cookie")) {
         // Pull the target value from the "relay state" cookie.
-        pair<string,const char*> relay_cookie = st->getCookieNameProps("_shibstate_");
-        const char* relay_state = st->getCookie(relay_cookie.first);
+        pair<string,const char*> relay_cookie = request.getApplication().getCookieNameProps("_shibstate_");
+        const char* relay_state = request.getCookie(relay_cookie.first.c_str());
         if (!relay_state || !*relay_state) {
             // No apparent relay state value to use, so fall back on the default.
-            pair<bool,const char*> homeURL=app->getString("homeURL");
+            pair<bool,const char*> homeURL=request.getApplication().getString("homeURL");
             target=homeURL.first ? homeURL.second : "/";
         }
         else {
             char* rscopy=strdup(relay_state);
-            CgiParse::url_decode(rscopy);
-            target=rscopy;
+            opensaml::SAMLConfig::getConfig().getURLEncoder()->decode(rscopy);
+            hURL=rscopy;
             free(rscopy);
+            target=hURL.c_str();
         }
+        request.setCookie(relay_cookie.first.c_str(),relay_cookie.second);
     }
 
     // We've got a good session, set the session cookie.
-    pair<string,const char*> shib_cookie=st->getCookieNameProps("_shibsession_");
-    st->setCookie(shib_cookie.first, cookie + shib_cookie.second);
-
-    const IPropertySet* sessionProps=app->getPropertySet("Sessions");
-    pair<bool,bool> idpHistory=sessionProps->getBool("idpHistory");
-    if (!idpHistory.first || idpHistory.second) {
-        // Set an IdP history cookie locally (essentially just a CDC).
-        CommonDomainCookie cdc(st->getCookie(CommonDomainCookie::CDCName));
-
-        // Either leave in memory or set an expiration.
-        pair<bool,unsigned int> days=sessionProps->getUnsignedInt("idpHistoryDays");
-            if (!days.first || days.second==0)
-                st->setCookie(CommonDomainCookie::CDCName,string(cdc.set(providerId.c_str())) + shib_cookie.second);
+    pair<string,const char*> shib_cookie=request.getApplication().getCookieNameProps("_shibsession_");
+    key += shib_cookie.second;
+    request.setCookie(shib_cookie.first.c_str(), key.c_str());
+
+    const char* providerId=out["provider_id"].string();
+    if (providerId) {
+        const PropertySet* sessionProps=request.getApplication().getPropertySet("Sessions");
+        pair<bool,bool> idpHistory=sessionProps->getBool("idpHistory");
+        if (!idpHistory.first || idpHistory.second) {
+            // Set an IdP history cookie locally (essentially just a CDC).
+            CommonDomainCookie cdc(request.getCookie(CommonDomainCookie::CDCName));
+
+            // Either leave in memory or set an expiration.
+            pair<bool,unsigned int> days=sessionProps->getUnsignedInt("idpHistoryDays");
+            if (!days.first || days.second==0) {
+                key = string(cdc.set(providerId)) + shib_cookie.second;
+                request.setCookie(CommonDomainCookie::CDCName, key.c_str());
+            }
             else {
                 time_t now=time(NULL) + (days.second * 24 * 60 * 60);
 #ifdef HAVE_GMTIME_R
@@ -357,270 +576,46 @@ pair<bool,void*> SAML1Consumer::run(ShibTarget* st, const IPropertySet* handler,
 #endif
                 char timebuf[64];
                 strftime(timebuf,64,"%a, %d %b %Y %H:%M:%S GMT",ptime);
-                st->setCookie(
-                    CommonDomainCookie::CDCName,
-                    string(cdc.set(providerId.c_str())) + shib_cookie.second + "; expires=" + timebuf
-                    );
+                key = string(cdc.set(providerId)) + shib_cookie.second + "; expires=" + timebuf;
+                request.setCookie(CommonDomainCookie::CDCName, key.c_str());
+            }
         }
     }
 
     // Now redirect to the target.
-    return make_pair(true, st->sendRedirect(target));
+    return make_pair(true, request.sendRedirect(target));
 }
 
-pair<bool,void*> ShibLogout::run(ShibTarget* st, const IPropertySet* handler, bool isHandler)
+pair<bool,long> ShibLogout::run(SPRequest& request, bool isHandler) const
 {
     // Recover the session key.
-    pair<string,const char*> shib_cookie = st->getCookieNameProps("_shibsession_");
-    const char* session_id = st->getCookie(shib_cookie.first);
+    pair<string,const char*> shib_cookie = request.getApplication().getCookieNameProps("_shibsession_");
+    const char* session_id = request.getCookie(shib_cookie.first.c_str());
     
     // Logout is best effort.
     if (session_id && *session_id) {
         try {
-            st->getConfig()->getListener()->sessionEnd(st->getApplication(),session_id);
+            // TODO: port to new cache API
+            //request.getServiceProvider().getSessionCache()->remove(session_id,request.getApplication(),request.getRemoteAddr().c_str());
         }
-        catch (SAMLException& e) {
-            st->log(ShibTarget::LogLevelError, string("logout processing failed with exception: ") + e.what());
+        catch (exception& e) {
+            request.log(SPRequest::SPError, string("logout processing failed with exception: ") + e.what());
         }
 #ifndef _DEBUG
         catch (...) {
-            st->log(ShibTarget::LogLevelError, "logout processing failed with unknown exception");
+            request.log(SPRequest::SPError, "logout processing failed with unknown exception");
         }
 #endif
         // We send the cookie property alone, which acts as an empty value.
-        st->setCookie(shib_cookie.first,shib_cookie.second);
+        request.setCookie(shib_cookie.first.c_str(),shib_cookie.second);
     }
     
-    string query=st->getArgs();
-    CgiParse parser(query.c_str(),query.length());
-
-    const char* ret=parser.get_value("return");
+    const char* ret=request.getParameter("return");
     if (!ret)
-        ret=handler->getString("ResponseLocation").second;
+        ret=getString("ResponseLocation").second;
     if (!ret)
-        ret=st->getApplication()->getString("homeURL").second;
+        ret=request.getApplication().getString("homeURL").second;
     if (!ret)
         ret="/";
-    return make_pair(true, st->sendRedirect(ret));
-}
-
-/*************************************************************************
- * CGI Parser implementation
- */
-
-CgiParse::CgiParse(const char* data, unsigned int len)
-{
-    const char* pch = data;
-    unsigned int cl = len;
-        
-    while (cl && pch) {
-        char *name;
-        char *value;
-        value=fmakeword('&',&cl,&pch);
-        plustospace(value);
-        url_decode(value);
-        name=makeword(value,'=');
-        kvp_map[name]=value;
-        free(name);
-    }
-}
-
-CgiParse::~CgiParse()
-{
-    for (map<string,char*>::iterator i=kvp_map.begin(); i!=kvp_map.end(); i++)
-        free(i->second);
-}
-
-const char*
-CgiParse::get_value(const char* name) const
-{
-    map<string,char*>::const_iterator i=kvp_map.find(name);
-    if (i==kvp_map.end())
-        return NULL;
-    return i->second;
-}
-
-/* Parsing routines modified from NCSA source. */
-char *
-CgiParse::makeword(char *line, char stop)
-{
-    int x = 0,y;
-    char *word = (char *) malloc(sizeof(char) * (strlen(line) + 1));
-
-    for(x=0;((line[x]) && (line[x] != stop));x++)
-        word[x] = line[x];
-
-    word[x] = '\0';
-    if(line[x])
-        ++x;
-    y=0;
-
-    while(line[x])
-      line[y++] = line[x++];
-    line[y] = '\0';
-    return word;
-}
-
-char *
-CgiParse::fmakeword(char stop, unsigned int *cl, const char** ppch)
-{
-    int wsize;
-    char *word;
-    int ll;
-
-    wsize = 1024;
-    ll=0;
-    word = (char *) malloc(sizeof(char) * (wsize + 1));
-
-    while(1)
-    {
-        word[ll] = *((*ppch)++);
-        if(ll==wsize-1)
-        {
-            word[ll+1] = '\0';
-            wsize+=1024;
-            word = (char *)realloc(word,sizeof(char)*(wsize+1));
-        }
-        --(*cl);
-        if((word[ll] == stop) || word[ll] == EOF || (!(*cl)))
-        {
-            if(word[ll] != stop)
-                ll++;
-            word[ll] = '\0';
-            return word;
-        }
-        ++ll;
-    }
-}
-
-void
-CgiParse::plustospace(char *str)
-{
-    register int x;
-
-    for(x=0;str[x];x++)
-        if(str[x] == '+') str[x] = ' ';
-}
-
-char
-CgiParse::x2c(char *what)
-{
-    register char digit;
-
-    digit = (what[0] >= 'A' ? ((what[0] & 0xdf) - 'A')+10 : (what[0] - '0'));
-    digit *= 16;
-    digit += (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A')+10 : (what[1] - '0'));
-    return(digit);
-}
-
-void
-CgiParse::url_decode(char *url)
-{
-    register int x,y;
-
-    for(x=0,y=0;url[y];++x,++y)
-    {
-        if((url[x] = url[y]) == '%')
-        {
-            url[x] = x2c(&url[y+1]);
-            y+=2;
-        }
-    }
-    url[x] = '\0';
-}
-
-static inline char hexchar(unsigned short s)
-{
-    return (s<=9) ? ('0' + s) : ('A' + s - 10);
-}
-
-string CgiParse::url_encode(const char* s)
-{
-    static char badchars[]="\"\\+<>#%{}|^~[]`;/?:@=&";
-
-    string ret;
-    for (; *s; s++) {
-        if (strchr(badchars,*s) || *s<=0x20 || *s>=0x7F) {
-            ret+='%';
-        ret+=hexchar(*s >> 4);
-        ret+=hexchar(*s & 0x0F);
-        }
-        else
-            ret+=*s;
-    }
-    return ret;
-}
-
-// CDC implementation
-
-const char CommonDomainCookie::CDCName[] = "_saml_idp";
-
-CommonDomainCookie::CommonDomainCookie(const char* cookie)
-{
-    if (!cookie)
-        return;
-
-    Category& log=Category::getInstance(SHIBT_LOGCAT".CommonDomainCookie");
-
-    // Copy it so we can URL-decode it.
-    char* b64=strdup(cookie);
-    CgiParse::url_decode(b64);
-
-    // Chop it up and save off elements.
-    vector<string> templist;
-    char* ptr=b64;
-    while (*ptr) {
-        while (*ptr && isspace(*ptr)) ptr++;
-        char* end=ptr;
-        while (*end && !isspace(*end)) end++;
-        templist.push_back(string(ptr,end-ptr));
-        ptr=end;
-    }
-    free(b64);
-
-    // Now Base64 decode the list.
-    for (vector<string>::iterator i=templist.begin(); i!=templist.end(); i++) {
-        unsigned int len;
-        XMLByte* decoded=Base64::decode(reinterpret_cast<const XMLByte*>(i->c_str()),&len);
-        if (decoded && *decoded) {
-            m_list.push_back(reinterpret_cast<char*>(decoded));
-            XMLString::release(&decoded);
-        }
-        else
-            log.warn("cookie element does not appear to be base64-encoded");
-    }
-}
-
-const char* CommonDomainCookie::set(const char* providerId)
-{
-    // First scan the list for this IdP.
-    for (vector<string>::iterator i=m_list.begin(); i!=m_list.end(); i++) {
-        if (*i == providerId) {
-            m_list.erase(i);
-            break;
-        }
-    }
-    
-    // Append it to the end.
-    m_list.push_back(providerId);
-    
-    // Now rebuild the delimited list.
-    string delimited;
-    for (vector<string>::const_iterator j=m_list.begin(); j!=m_list.end(); j++) {
-        if (!delimited.empty()) delimited += ' ';
-        
-        unsigned int len;
-        XMLByte* b64=Base64::encode(reinterpret_cast<const XMLByte*>(j->c_str()),j->length(),&len);
-        XMLByte *pos, *pos2;
-        for (pos=b64, pos2=b64; *pos2; pos2++)
-            if (isgraph(*pos2))
-                *pos++=*pos2;
-        *pos=0;
-        
-        delimited += reinterpret_cast<char*>(b64);
-        XMLString::release(&b64);
-    }
-    
-    m_encoded=CgiParse::url_encode(delimited.c_str());
-    return m_encoded.c_str();
+    return make_pair(true, request.sendRedirect(ret));
 }