https://issues.shibboleth.net/jira/browse/SSPCPP-440
authorScott Cantor <cantor.2@osu.edu>
Sun, 29 Apr 2012 21:00:28 +0000 (21:00 +0000)
committerScott Cantor <cantor.2@osu.edu>
Sun, 29 Apr 2012 21:00:28 +0000 (21:00 +0000)
18 files changed:
shibsp/Makefile.am
shibsp/handler/Handler.h
shibsp/handler/SecuredHandler.h [new file with mode: 0644]
shibsp/handler/impl/AbstractHandler.cpp
shibsp/handler/impl/AssertionConsumerService.cpp
shibsp/handler/impl/AssertionLookup.cpp
shibsp/handler/impl/ExternalAuthHandler.cpp [new file with mode: 0644]
shibsp/handler/impl/LocalLogoutInitiator.cpp
shibsp/handler/impl/MetadataGenerator.cpp
shibsp/handler/impl/RemotedHandler.cpp
shibsp/handler/impl/SecuredHandler.cpp [new file with mode: 0644]
shibsp/handler/impl/SessionHandler.cpp
shibsp/handler/impl/StatusHandler.cpp
shibsp/impl/TransactionLog.cpp
shibsp/shibsp-lite.vcxproj
shibsp/shibsp-lite.vcxproj.filters
shibsp/shibsp.vcxproj
shibsp/shibsp.vcxproj.filters

index 579c938..41a51fc 100644 (file)
@@ -76,6 +76,7 @@ handinclude_HEADERS = \
        handler/LogoutHandler.h \
        handler/LogoutInitiator.h \
        handler/RemotedHandler.h \
+    handler/SecuredHandler.h \
        handler/SessionInitiator.h
 
 liteinclude_HEADERS = \
@@ -129,14 +130,13 @@ common_sources = \
        handler/impl/ChainingSessionInitiator.cpp \
        handler/impl/CookieSessionInitiator.cpp \
        handler/impl/DiscoveryFeed.cpp \
+    handler/impl/ExternalAuthHandler.cpp \
        handler/impl/FormSessionInitiator.cpp \
        handler/impl/LocalLogoutInitiator.cpp \
        handler/impl/LogoutHandler.cpp \
        handler/impl/LogoutInitiator.cpp \
        handler/impl/MetadataGenerator.cpp \
        handler/impl/RemotedHandler.cpp \
-       handler/impl/StatusHandler.cpp \
-       handler/impl/SessionHandler.cpp \
        handler/impl/SAML1Consumer.cpp \
        handler/impl/SAML2Consumer.cpp \
        handler/impl/SAML2ArtifactResolution.cpp \
@@ -145,8 +145,11 @@ common_sources = \
        handler/impl/SAML2NameIDMgmt.cpp \
        handler/impl/SAML2SessionInitiator.cpp \
        handler/impl/SAMLDSSessionInitiator.cpp \
+    handler/impl/SecuredHandler.cpp \
+       handler/impl/SessionHandler.cpp \
        handler/impl/SessionInitiator.cpp \
        handler/impl/Shib1SessionInitiator.cpp \
+       handler/impl/StatusHandler.cpp \
        handler/impl/TransformSessionInitiator.cpp \
        handler/impl/WAYFSessionInitiator.cpp \
        impl/ChainingAccessControl.cpp \
index f64cfed..3a2e623 100644 (file)
@@ -164,6 +164,9 @@ namespace shibsp {
     /** Handler for metadata generation. */
     #define DISCOVERY_FEED_HANDLER "DiscoveryFeed"
 
+    /** Handler for external authentication integration. */
+    #define EXTERNAL_AUTH_HANDLER "ExternalAuth"
+
     /** Handler for metadata generation. */
     #define METADATA_GENERATOR_HANDLER "MetadataGenerator"
 
diff --git a/shibsp/handler/SecuredHandler.h b/shibsp/handler/SecuredHandler.h
new file mode 100644 (file)
index 0000000..df31561
--- /dev/null
@@ -0,0 +1,83 @@
+/**
+ * Licensed to the University Corporation for Advanced Internet
+ * Development, Inc. (UCAID) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ *
+ * UCAID licenses this file to you 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
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific
+ * language governing permissions and limitations under the License.
+ */
+
+/**
+ * @file shibsp/handler/SecuredHandler.h
+ * 
+ * Pluggable runtime functionality that is protected by simple access control.
+ */
+
+#ifndef __shibsp_securedhandler_h__
+#define __shibsp_securedhandler_h__
+
+#include <shibsp/handler/AbstractHandler.h>
+#include <shibsp/util/IPRange.h>
+
+#include <vector>
+
+namespace shibsp {
+
+#if defined (_MSC_VER)
+    #pragma warning( push )
+    #pragma warning( disable : 4250 )
+#endif
+
+    /**
+     * Pluggable runtime functionality that is protected by simple access control.
+     */
+    class SHIBSP_API SecuredHandler : public AbstractHandler
+    {
+    protected:
+        /**
+         * Constructor
+         * 
+         * @param e             DOM element to load as property set
+         * @param log           logging category to use
+         * @param aclProperty   name of IP/CIDR ACL property
+         * @param defaultACL    IP/CIDR ACL to apply if no acl property is set
+         * @param filter        optional filter controls what child elements to include as nested PropertySets
+         * @param remapper      optional map of property rename rules for legacy property support
+         */
+        SecuredHandler(
+            const xercesc::DOMElement* e,
+            xmltooling::logging::Category& log,
+            const char* aclProperty="acl",
+            const char* defaultACL=nullptr,
+            xercesc::DOMNodeFilter* filter=nullptr,
+            const std::map<std::string,std::string>* remapper=nullptr
+            );
+
+    public:
+        virtual ~SecuredHandler();
+
+        std::pair<bool,long> run(SPRequest& request, bool isHandler=true) const;
+
+    private:
+        void parseACL(const std::string& acl);
+        std::vector<IPRange> m_acl;
+    };
+
+#if defined (_MSC_VER)
+    #pragma warning( pop )
+#endif
+
+};
+
+#endif /* __shibsp_securedhandler_h__ */
index c1b0f25..b1ac8cd 100644 (file)
@@ -83,6 +83,7 @@ namespace shibsp {
     SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory AssertionLookupFactory;
     SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory AttributeCheckerFactory;
     SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory DiscoveryFeedFactory;
+    SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory ExternalAuthFactory;
     SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory MetadataGeneratorFactory;
     SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory StatusHandlerFactory;
     SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory SessionHandlerFactory;
@@ -124,6 +125,7 @@ void SHIBSP_API shibsp::registerHandlers()
     conf.HandlerManager.registerFactory(SAML20_BINDING_URI, AssertionLookupFactory);
     conf.HandlerManager.registerFactory(ATTR_CHECKER_HANDLER, AttributeCheckerFactory);
     conf.HandlerManager.registerFactory(DISCOVERY_FEED_HANDLER, DiscoveryFeedFactory);
+    conf.HandlerManager.registerFactory(EXTERNAL_AUTH_HANDLER, ExternalAuthFactory);
     conf.HandlerManager.registerFactory(METADATA_GENERATOR_HANDLER, MetadataGeneratorFactory);
     conf.HandlerManager.registerFactory(STATUS_HANDLER, StatusHandlerFactory);
     conf.HandlerManager.registerFactory(SESSION_HANDLER, SessionHandlerFactory);
index 8a152ed..c42abd9 100644 (file)
@@ -376,26 +376,28 @@ opensaml::SecurityPolicy* AssertionConsumerService::createSecurityPolicy(
     return new SecurityPolicy(application, role, validate, policyId);
 }
 
-class SHIBSP_DLLLOCAL DummyContext : public ResolutionContext
-{
-public:
-    DummyContext(const vector<Attribute*>& attributes) : m_attributes(attributes) {
-    }
+namespace {
+    class SHIBSP_DLLLOCAL DummyContext : public ResolutionContext
+    {
+    public:
+        DummyContext(const vector<Attribute*>& attributes) : m_attributes(attributes) {
+        }
 
-    virtual ~DummyContext() {
-        for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<Attribute>());
-    }
+        virtual ~DummyContext() {
+            for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<Attribute>());
+        }
 
-    vector<Attribute*>& getResolvedAttributes() {
-        return m_attributes;
-    }
-    vector<Assertion*>& getResolvedAssertions() {
-        return m_tokens;
-    }
+        vector<Attribute*>& getResolvedAttributes() {
+            return m_attributes;
+        }
+        vector<Assertion*>& getResolvedAssertions() {
+            return m_tokens;
+        }
 
-private:
-    vector<Attribute*> m_attributes;
-    static vector<Assertion*> m_tokens; // never any tokens, so just share an empty vector
+    private:
+        vector<Attribute*> m_attributes;
+        static vector<Assertion*> m_tokens; // never any tokens, so just share an empty vector
+    };
 };
 
 vector<Assertion*> DummyContext::m_tokens;
@@ -430,7 +432,7 @@ ResolutionContext* AssertionConsumerService::resolveAttributes(
 ResolutionContext* AssertionConsumerService::resolveAttributes(
     const Application& application,
     const GenericRequest* request,
-    const saml2md::RoleDescriptor* issuer,
+    const RoleDescriptor* issuer,
     const XMLCh* protocol,
     const xmltooling::XMLObject* protmsg,
     const saml1::NameIdentifier* v1nameid,
@@ -516,7 +518,7 @@ ResolutionContext* AssertionConsumerService::resolveAttributes(
 
         AttributeFilter* filter = application.getAttributeFilter();
         if (filter && !resolvedAttributes.empty()) {
-            BasicFilteringContext fc(application, resolvedAttributes, issuer, authncontext_class);
+            BasicFilteringContext fc(application, resolvedAttributes, issuer, authncontext_class, authncontext_decl);
             Locker filtlocker(filter);
             try {
                 filter->filterAttributes(fc, resolvedAttributes);
@@ -621,7 +623,7 @@ void AssertionConsumerService::extractMessageDetails(const Assertion& assertion,
     }
 }
 
-LoginEvent* AssertionConsumerService::newLoginEvent(const Application& application, const xmltooling::HTTPRequest& request) const
+LoginEvent* AssertionConsumerService::newLoginEvent(const Application& application, const HTTPRequest& request) const
 {
     if (!SPConfig::getConfig().isEnabled(SPConfig::Logging))
         return nullptr;
index 490e554..7063049 100644 (file)
 #include "ServiceProvider.h"
 #include "SessionCacheEx.h"
 #include "SPRequest.h"
-#include "handler/AbstractHandler.h"
 #include "handler/RemotedHandler.h"
-#include "util/IPRange.h"
-#include "util/SPConstants.h"
+#include "handler/SecuredHandler.h"
 
-#include <boost/bind.hpp>
 #include <boost/scoped_ptr.hpp>
-#include <boost/algorithm/string.hpp>
 
 #ifndef SHIBSP_LITE
 # include <saml/exceptions.h>
@@ -59,22 +55,7 @@ namespace shibsp {
     #pragma warning( disable : 4250 )
 #endif
 
-    class SHIBSP_DLLLOCAL Blocker : public DOMNodeFilter
-    {
-    public:
-#ifdef SHIBSP_XERCESC_SHORT_ACCEPTNODE
-        short
-#else
-        FilterAction
-#endif
-        acceptNode(const DOMNode* node) const {
-            return FILTER_REJECT;
-        }
-    };
-
-    static SHIBSP_DLLLOCAL Blocker g_Blocker;
-
-    class SHIBSP_API AssertionLookup : public AbstractHandler, public RemotedHandler
+    class SHIBSP_API AssertionLookup : public SecuredHandler, public RemotedHandler
     {
     public:
         AssertionLookup(const DOMElement* e, const char* appId);
@@ -89,16 +70,6 @@ namespace shibsp {
 
     private:
         pair<bool,long> processMessage(const Application& application, HTTPRequest& httpRequest, HTTPResponse& httpResponse) const;
-        void parseACL(const string& acl) {
-            try {
-                m_acl.push_back(IPRange::parseCIDRBlock(acl.c_str()));
-            }
-            catch (std::exception& ex) {
-                m_log.error("invalid CIDR block (%s): %s", acl.c_str(), ex.what());
-            }
-        }
-
-        vector<IPRange> m_acl;
     };
 
 #if defined (_MSC_VER)
@@ -113,42 +84,20 @@ namespace shibsp {
 };
 
 AssertionLookup::AssertionLookup(const DOMElement* e, const char* appId)
-    : AbstractHandler(e, Category::getInstance(SHIBSP_LOGCAT".AssertionLookup"), &g_Blocker)
+    : SecuredHandler(e, Category::getInstance(SHIBSP_LOGCAT".AssertionLookup"), "exportACL", "127.0.0.1 ::1")
 {
-    if (SPConfig::getConfig().isEnabled(SPConfig::InProcess)) {
-        pair<bool,const char*> acl = getString("exportACL");
-        if (acl.first) {
-            string aclbuf=acl.second;
-            vector<string> aclarray;
-            split(aclarray, aclbuf, is_space(), algorithm::token_compress_on);
-            for_each(aclarray.begin(), aclarray.end(), boost::bind(&AssertionLookup::parseACL, this, _1));
-            if (m_acl.empty()) {
-                m_log.warn("invalid CIDR range(s) in acl property, allowing 127.0.0.1 as a fall back");
-                m_acl.push_back(IPRange::parseCIDRBlock("127.0.0.1"));
-            }
-        }
-        else {
-            m_acl.push_back(IPRange::parseCIDRBlock("127.0.0.1"));
-        }
-    }
-
     setAddress("run::AssertionLookup");
 }
 
 pair<bool,long> AssertionLookup::run(SPRequest& request, bool isHandler) const
 {
-    SPConfig& conf = SPConfig::getConfig();
-    if (conf.isEnabled(SPConfig::InProcess) && !m_acl.empty()) {
-        static bool (IPRange::* contains)(const char*) const = &IPRange::contains;
-        if (find_if(m_acl.begin(), m_acl.end(), boost::bind(contains, _1, request.getRemoteAddr().c_str())) == m_acl.end()) {
-            m_log.error("request for assertion lookup blocked from invalid address (%s)", request.getRemoteAddr().c_str());
-            istringstream msg("Assertion Lookup Blocked");
-            return make_pair(true,request.sendResponse(msg, HTTPResponse::XMLTOOLING_HTTP_STATUS_FORBIDDEN));
-        }
-    }
+    // Check ACL in base class.
+    pair<bool,long> ret = SecuredHandler::run(request, isHandler);
+    if (ret.first)
+        return ret;
 
     try {
-        if (conf.isEnabled(SPConfig::OutOfProcess)) {
+        if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
             // When out of process, we run natively and directly process the message.
             return processMessage(request.getApplication(), request, request);
         }
@@ -171,8 +120,8 @@ pair<bool,long> AssertionLookup::run(SPRequest& request, bool isHandler) const
 void AssertionLookup::receive(DDF& in, ostream& out)
 {
     // Find application.
-    const char* aid=in["application_id"].string();
-    const Application* app=aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : nullptr;
+    const char* aid = in["application_id"].string();
+    const Application* app = aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : nullptr;
     if (!app) {
         // Something's horribly wrong.
         m_log.error("couldn't find application (%s) for assertion lookup", aid ? aid : "(missing)");
@@ -233,6 +182,6 @@ pair<bool,long> AssertionLookup::processMessage(const Application& application,
     httpResponse.setContentType("application/samlassertion+xml");
     return make_pair(true, httpResponse.sendResponse(s));
 #else
-    return make_pair(false,0L);
+    return make_pair(false, 0L);
 #endif
 }
diff --git a/shibsp/handler/impl/ExternalAuthHandler.cpp b/shibsp/handler/impl/ExternalAuthHandler.cpp
new file mode 100644 (file)
index 0000000..5c12068
--- /dev/null
@@ -0,0 +1,754 @@
+/**
+ * Licensed to the University Corporation for Advanced Internet
+ * Development, Inc. (UCAID) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ *
+ * UCAID licenses this file to you 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
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific
+ * language governing permissions and limitations under the License.
+ */
+
+/**
+ * ExternalAuthHandler.cpp
+ *
+ * Handler for integrating with external authentication mechanisms.
+ */
+
+#include "internal.h"
+#include "exceptions.h"
+#include "Application.h"
+#include "ServiceProvider.h"
+#include "SPRequest.h"
+#include "handler/RemotedHandler.h"
+#include "handler/SecuredHandler.h"
+
+#include <sstream>
+#include <boost/scoped_ptr.hpp>
+
+#ifndef SHIBSP_LITE
+# include "SessionCache.h"
+# include "TransactionLog.h"
+# include "attribute/SimpleAttribute.h"
+# include "attribute/filtering/AttributeFilter.h"
+# include "attribute/filtering/BasicFilteringContext.h"
+# include "attribute/resolver/AttributeExtractor.h"
+# include "attribute/resolver/AttributeResolver.h"
+# include "attribute/resolver/ResolutionContext.h"
+# include <boost/tokenizer.hpp>
+# include <boost/iterator/indirect_iterator.hpp>
+# include <saml/exceptions.h>
+# include <saml/saml2/core/Assertions.h>
+# include <saml/saml2/metadata/Metadata.h>
+# include <saml/saml2/metadata/MetadataProvider.h>
+# include <xmltooling/XMLToolingConfig.h>
+# include <xmltooling/util/DateTime.h>
+# include <xmltooling/util/ParserPool.h>
+# include <xmltooling/util/XMLHelper.h>
+# include <xercesc/framework/MemBufInputSource.hpp>
+# include <xercesc/framework/Wrapper4InputSource.hpp>
+using namespace opensaml::saml2md;
+using namespace opensaml;
+using saml2::NameID;
+using saml2::AuthnStatement;
+using saml2::AuthnContext;
+#endif
+
+using namespace shibspconstants;
+using namespace shibsp;
+using namespace xmltooling;
+using namespace boost;
+using namespace std;
+
+namespace shibsp {
+
+#if defined (_MSC_VER)
+    #pragma warning( push )
+    #pragma warning( disable : 4250 )
+#endif
+
+    class SHIBSP_API ExternalAuth : public SecuredHandler, public RemotedHandler
+    {
+    public:
+        ExternalAuth(const DOMElement* e, const char* appId);
+        virtual ~ExternalAuth() {}
+
+        pair<bool,long> run(SPRequest& request, bool isHandler=true) const;
+        void receive(DDF& in, ostream& out);
+
+        const char* getType() const {
+            return "ExternalAuth";
+        }
+
+    private:
+        pair<bool,long> processMessage(
+            const Application& application, HTTPRequest& httpRequest, HTTPResponse& httpResponse, const DDF* respDDF=nullptr
+            ) const;
+#ifndef SHIBSP_LITE
+        LoginEvent* newLoginEvent(const Application& application, const HTTPRequest& request) const;
+        ResolutionContext* resolveAttributes(
+            const Application& application,
+            const GenericRequest* request,
+            const saml2md::RoleDescriptor* issuer,
+            const XMLCh* protocol,
+            const saml2::NameID* nameid,
+            const saml2::AuthnStatement* statement,
+            const XMLCh* authncontext_class,
+            const XMLCh* authncontext_decl,
+            const vector<const Assertion*>* tokens=nullptr,
+            const vector<Attribute*>* inputAttributes=nullptr
+            ) const;
+#endif
+    };
+
+#if defined (_MSC_VER)
+    #pragma warning( pop )
+#endif
+
+    Handler* SHIBSP_DLLLOCAL ExternalAuthFactory(const pair<const DOMElement*,const char*>& p)
+    {
+        return new ExternalAuth(p.first, p.second);
+    }
+
+};
+
+static ostream& json_safe(ostream& os, const char* buf)
+{
+    os << '"';
+    for (; *buf; ++buf) {
+        switch (*buf) {
+            case '\\':
+            case '"':
+                os << '\\';
+                os << *buf;
+                break;
+            case '\b':
+                os << "\\b";
+                break;
+            case '\t':
+                os << "\\t";
+                break;
+            case '\n':
+                os << "\\n";
+                break;
+            case '\f':
+                os << "\\f";
+                break;
+            case '\r':
+                os << "\\r";
+                break;
+            default:
+                os << *buf;
+        }
+    }
+    os << '"';
+    return os;
+}
+
+ExternalAuth::ExternalAuth(const DOMElement* e, const char* appId)
+    : SecuredHandler(e, Category::getInstance(SHIBSP_LOGCAT".ExternalAuth"), "acl", "127.0.0.1 ::1")
+{
+    setAddress("run::ExternalAuth");
+}
+
+pair<bool,long> ExternalAuth::run(SPRequest& request, bool isHandler) const
+{
+    // Check ACL in base class.
+    pair<bool,long> ret = SecuredHandler::run(request, isHandler);
+    if (ret.first)
+        return ret;
+
+    try {
+        if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
+            // When out of process, we run natively and directly process the message.
+            return processMessage(request.getApplication(), request, request);
+        }
+        else {
+            // When not out of process, we remote all the message processing.
+            vector<string> headers(1, "User-Agent");
+            headers.push_back("Accept");
+            headers.push_back("Accept-Language");
+            headers.push_back("Cookie");
+            DDF out,in = wrap(request, &headers);
+            DDFJanitor jin(in), jout(out);
+            out=request.getServiceProvider().getListenerService()->send(in);
+            return unwrap(request, out);
+        }
+    }
+    catch (std::exception& ex) {
+        m_log.error("error while processing request: %s", ex.what());
+        istringstream msg("External Authentication Failed");
+        return make_pair(true, request.sendResponse(msg, HTTPResponse::XMLTOOLING_HTTP_STATUS_ERROR));
+    }
+}
+
+void ExternalAuth::receive(DDF& in, ostream& out)
+{
+    // Find application.
+    const char* aid = in["application_id"].string();
+    const Application* app = aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : nullptr;
+    if (!app) {
+        // Something's horribly wrong.
+        m_log.error("couldn't find application (%s) for external authentication", aid ? aid : "(missing)");
+        throw ConfigurationException("Unable to locate application for external authentication, deleted?");
+    }
+
+    // Unpack the request.
+    scoped_ptr<HTTPRequest> req(getRequest(in));
+
+    // Wrap a response shim.
+    DDF ret(nullptr);
+    DDFJanitor jout(ret);
+    scoped_ptr<HTTPResponse> resp(getResponse(ret));
+
+    // Since we're remoted, the result should either be a throw, a false/0 return,
+    // which we just return as an empty structure, or a response/redirect,
+    // which we capture in the facade and send back.
+    try {
+        processMessage(*app, *req, *resp, &ret);
+    }
+    catch (std::exception& ex) {
+        m_log.error("raising exception: %s", ex.what());
+        throw;
+    }
+    out << ret;
+}
+
+pair<bool,long> ExternalAuth::processMessage(
+    const Application& application, HTTPRequest& httpRequest, HTTPResponse& httpResponse, const DDF* respDDF
+    ) const
+{
+#ifndef SHIBSP_LITE
+    string session_id;
+    SessionCache* cache = application.getServiceProvider().getSessionCache();
+    MetadataProvider* m = application.getMetadataProvider(false);
+    Locker mocker(m);
+
+    string ctype(httpRequest.getContentType());
+    if (ctype == "text/xml" || ctype == "application/samlassertion+xml") {
+        // Body should contain an assertion.
+        const char* body = httpRequest.getRequestBody();
+        if (!body)
+            throw FatalProfileException("Request body was empty.");
+
+        // Parse and bind the document into an XMLObject.
+        MemBufInputSource src(reinterpret_cast<const XMLByte*>(body), httpRequest.getContentLength(), "SAMLAssertion");
+        Wrapper4InputSource dsrc(&src, false);
+        DOMDocument* doc = XMLToolingConfig::getConfig().getParser().parse(dsrc);
+        XercesJanitor<DOMDocument> janitor(doc);
+        auto_ptr<XMLObject> xmlObject(XMLObjectBuilder::buildOneFromElement(doc->getDocumentElement(), true));
+        janitor.release();
+
+        saml2::Assertion* token = dynamic_cast<saml2::Assertion*>(xmlObject.get());
+        if (!token)
+            throw FatalProfileException("Request body did not contain a SAML 2.0 assertion.");
+        else if (token->getAuthnStatements().empty())
+            throw FatalProfileException("Assertion in request did not contain an AuthnStatement.");
+
+        // We're not implementing a full SAML profile here, only a minimal one that ignores most
+        // security checking, conditions, etc. The caller is in full control here and we just consume
+        // what we're given. The only thing we're honoring is the authentication information we find
+        // and processing any attributes.
+
+        const XMLCh* protocol = nullptr;
+        pair<const EntityDescriptor*, const RoleDescriptor*> issuer = pair<const EntityDescriptor*, const RoleDescriptor*>(nullptr,nullptr);
+        if (m && token->getIssuer() && token->getIssuer()->getName()) {
+            MetadataProvider::Criteria mc;
+            mc.entityID_unicode = token->getIssuer()->getName();
+            mc.role = &IDPSSODescriptor::ELEMENT_QNAME;
+            mc.protocol = samlconstants::SAML20P_NS;
+            issuer = m->getEntityDescriptor(mc);
+            if (!issuer.first) {
+                auto_ptr_char iname(token->getIssuer()->getName());
+                m_log.warn("no metadata found for issuer (%s)", iname.get());
+            }
+            else if (!issuer.second) {
+                auto_ptr_char iname(token->getIssuer()->getName());
+                m_log.warn("no IdP role found in metadata for issuer (%s)", iname.get());
+            }
+            protocol = mc.protocol;
+        }
+
+        const saml2::NameID* nameid = nullptr;
+        if (token->getSubject())
+            nameid = token->getSubject()->getNameID();
+        const AuthnStatement* ssoStatement = token->getAuthnStatements().front();
+
+        // authnskew allows rejection of SSO if AuthnInstant is too old.
+        const PropertySet* sessionProps = application.getPropertySet("Sessions");
+        pair<bool,unsigned int> authnskew = sessionProps ? sessionProps->getUnsignedInt("maxTimeSinceAuthn") : pair<bool,unsigned int>(false,0);
+
+        time_t now(time(nullptr));
+        if (ssoStatement->getAuthnInstant() &&
+                ssoStatement->getAuthnInstantEpoch() - XMLToolingConfig::getConfig().clock_skew_secs > now) {
+            throw FatalProfileException("The AuthnInstant was future-dated.");
+        }
+        else if (authnskew.first && authnskew.second && ssoStatement->getAuthnInstant() &&
+                ssoStatement->getAuthnInstantEpoch() <= now && (now - ssoStatement->getAuthnInstantEpoch() > authnskew.second)) {
+            throw FatalProfileException("The gap between now and the AuthnInstant exceeds the allowed limit.");
+        }
+        else if (authnskew.first && authnskew.second && ssoStatement->getAuthnInstant() == nullptr) {
+            throw FatalProfileException("No AuthnInstant was supplied, violating local policy.");
+        }
+
+        // Session expiration for SAML 2.0 is jointly IdP- and SP-driven.
+        time_t sessionExp = ssoStatement->getSessionNotOnOrAfter() ?
+            (ssoStatement->getSessionNotOnOrAfterEpoch() + XMLToolingConfig::getConfig().clock_skew_secs) : 0;
+        pair<bool,unsigned int> lifetime = sessionProps ? sessionProps->getUnsignedInt("lifetime") : pair<bool,unsigned int>(true,28800);
+        if (!lifetime.first || lifetime.second == 0)
+            lifetime.second = 28800;
+        if (sessionExp == 0)
+            sessionExp = now + lifetime.second;     // IdP says nothing, calulate based on SP.
+        else
+            sessionExp = min(sessionExp, now + lifetime.second);    // Use the lowest.
+
+        const XMLCh* authncontext_class = nullptr;
+        const XMLCh* authncontext_decl = nullptr;
+        const AuthnContext* authnContext = ssoStatement->getAuthnContext();
+        if (authnContext) {
+            authncontext_class = authnContext->getAuthnContextClassRef() ? authnContext->getAuthnContextClassRef()->getReference() : nullptr;
+            authncontext_decl = authnContext->getAuthnContextDeclRef() ? authnContext->getAuthnContextDeclRef()->getReference() : nullptr;
+        }
+
+        // The context will handle deleting attributes and tokens.
+        vector<const Assertion*> tokens(1, token);
+        scoped_ptr<ResolutionContext> ctx(
+            resolveAttributes(
+                application,
+                &httpRequest,
+                issuer.second,
+                protocol,
+                nameid,
+                ssoStatement,
+                authncontext_class,
+                authncontext_decl,
+                &tokens
+                )
+            );
+        tokens.clear(); // don't store the original token in the session, since it was contrived
+
+        if (ctx) {
+            // Copy over any new tokens, but leave them in the context for cleanup.
+            tokens.insert(tokens.end(), ctx->getResolvedAssertions().begin(), ctx->getResolvedAssertions().end());
+        }
+
+        cache->insert(
+            session_id,
+            application,
+            httpRequest,
+            httpResponse,
+            sessionExp,
+            issuer.first,
+            protocol,
+            nameid,
+            ssoStatement->getAuthnInstant() ? ssoStatement->getAuthnInstant()->getRawData() : nullptr,
+            ssoStatement->getSessionIndex(),
+            authncontext_class,
+            authncontext_decl,
+            &tokens,
+            &ctx->getResolvedAttributes()
+            );
+    }
+    else if (ctype == "application/x-www-form-urlencoded") {
+        auto_ptr_XMLCh protocol(httpRequest.getParameter("protocol"));
+        const char* param = httpRequest.getParameter("issuer");
+        pair<const EntityDescriptor*, const RoleDescriptor*> issuer = pair<const EntityDescriptor*, const RoleDescriptor*>(nullptr,nullptr);
+        if (m && param && *param) {
+            MetadataProvider::Criteria mc;
+            mc.entityID_ascii = param;
+            mc.role = &IDPSSODescriptor::ELEMENT_QNAME;
+            mc.protocol = protocol.get();
+            issuer = m->getEntityDescriptor(mc);
+            if (!issuer.first)
+                m_log.warn("no metadata found for issuer (%s)", param);
+            else if (!issuer.second)
+                m_log.warn("no IdP role found in metadata for issuer (%s)", param);
+        }
+
+        scoped_ptr<saml2::NameID> nameid;
+        param = httpRequest.getParameter("NameID");
+        if (param && *param) {
+            nameid.reset(saml2::NameIDBuilder::buildNameID());
+            auto_arrayptr<XMLCh> n(fromUTF8(param));
+            nameid->setName(n.get());
+            param = httpRequest.getParameter("Format");
+            if (param && param) {
+                auto_ptr_XMLCh f(param);
+                nameid->setFormat(f.get());
+            }
+        }
+
+        scoped_ptr<DateTime> authn_instant;
+        param = httpRequest.getParameter("AuthnInstant");
+        if (param && *param) {
+            auto_ptr_XMLCh d(param);
+            authn_instant.reset(new DateTime(d.get()));
+            authn_instant->parseDateTime();
+        }
+
+        auto_ptr_XMLCh session_index(httpRequest.getParameter("SessionIndex"));
+        auto_ptr_XMLCh authncontext_class(httpRequest.getParameter("AuthnContextClassRef"));
+        auto_ptr_XMLCh authncontext_decl(httpRequest.getParameter("AuthnContextDeclRef"));
+
+        time_t sessionExp = 0;
+        param = httpRequest.getParameter("lifetime");
+        if (param && param)
+            sessionExp = atol(param);
+        if (sessionExp) {
+            sessionExp += time(nullptr);
+        }
+        else {
+            const PropertySet* sessionProps = application.getPropertySet("Sessions");
+            pair<bool,unsigned int> lifetime = sessionProps ? sessionProps->getUnsignedInt("lifetime") : pair<bool,unsigned int>(true,28800);
+            if (!lifetime.first || lifetime.second == 0)
+                lifetime.second = 28800;
+            sessionExp = time(nullptr) + lifetime.second;
+        }
+
+        // Create simple attributes around whatever parameters are specified.
+        vector<Attribute*> resolvedAttributes;
+        param = httpRequest.getParameter("attributes");
+        if (param && *param) {
+            char_separator<char> sep(", ");
+            string dup(param);
+            tokenizer< char_separator<char> > tokens(dup, sep);
+            try {
+                for (tokenizer< char_separator<char> >::iterator t = tokens.begin(); t != tokens.end(); ++t) {
+                    vector<const char*> vals;
+                    if (httpRequest.getParameters(t->c_str(), vals)) {
+                        vector<string> ids(1, *t);
+                        auto_ptr<SimpleAttribute> attr(new SimpleAttribute(ids));
+                        vector<string>& dest = attr->getValues();
+                        for (vector<const char*>::const_iterator v = vals.begin(); v != vals.end(); ++v)
+                            dest.push_back(*v);
+                        resolvedAttributes.push_back(attr.get());
+                        attr.release();
+                    }
+                }
+            }
+            catch (std::exception&) {
+                for_each(resolvedAttributes.begin(), resolvedAttributes.end(), xmltooling::cleanup<shibsp::Attribute>());
+                throw;
+            }
+        }
+
+        scoped_ptr<ResolutionContext> ctx(
+            resolveAttributes(
+                application,
+                &httpRequest,
+                issuer.second,
+                protocol.get(),
+                nameid.get(),
+                nullptr,
+                authncontext_class.get(),
+                authncontext_decl.get(),
+                nullptr,
+                &resolvedAttributes
+                )
+            );
+
+        vector<const Assertion*> tokens;
+        if (ctx) {
+            // Copy over any new tokens, but leave them in the context for cleanup.
+            tokens.insert(tokens.end(), ctx->getResolvedAssertions().begin(), ctx->getResolvedAssertions().end());
+        }
+
+        cache->insert(
+            session_id,
+            application,
+            httpRequest,
+            httpResponse,
+            sessionExp,
+            issuer.first,
+            protocol.get(),
+            nameid.get(),
+            authn_instant ? authn_instant->getRawData() : nullptr,
+            session_index.get(),
+            authncontext_class.get(),
+            authncontext_decl.get(),
+            &tokens,
+            &ctx->getResolvedAttributes()
+            );
+    }
+    else {
+        throw FatalProfileException("Submission was not in a recognized SAML assertion or form-encoded format.");
+    }
+
+    const char* param = httpRequest.getParameter("RelayState");
+    string target(param ? param : "");
+    try {
+        recoverRelayState(application, httpRequest, httpResponse, target);
+    }
+    catch (std::exception& ex) {
+        m_log.error("error recovering relay state: %s", ex.what());
+        target.erase();
+    }
+
+    stringstream os;
+    string accept = httpRequest.getHeader("Accept");
+    if (accept.find("application/json") != string::npos) {
+        httpResponse.setContentType("application/json");
+        os << "{ \"SessionID\": "; json_safe(os, session_id.c_str());
+        bool firstCookie = true;
+        if (respDDF) {
+            DDF hdr;
+            DDF hdrs = respDDF->getmember("headers");
+            hdr = hdrs.first();
+            while (hdr.isstring()) {
+                if (!strcmp(hdr.name(), "Set-Cookie")) {
+                    if (firstCookie) {
+                        os << ", \"Cookies\": [ ";
+                        firstCookie = false;
+                    }
+                    else {
+                        os << ", ";
+                    }
+                    json_safe(os, hdr.string());
+                }
+                hdr = hdrs.next();
+            }
+        }
+        os << " ]";
+        if (!target.empty())
+            os << ", \"RelayState\": "; json_safe(os, target.c_str());
+        os << " }";
+    }
+    else {
+        httpResponse.setContentType("text/xml");
+        static const XMLCh _ExternalAuth[] = UNICODE_LITERAL_12(E,x,t,e,r,n,a,l,A,u,t,h);
+        static const XMLCh _SessionID[] = UNICODE_LITERAL_9(S,e,s,s,i,o,n,I,D);
+        static const XMLCh _RelayState[] = UNICODE_LITERAL_10(R,e,l,a,y,S,t,a,t,e);
+        static const XMLCh _Cookie[] = UNICODE_LITERAL_6(C,o,o,k,i,e);
+        static const XMLCh _name[] = UNICODE_LITERAL_4(n,a,m,e);
+        DOMDocument* retdoc = XMLToolingConfig::getConfig().getParser().newDocument();
+        XercesJanitor<DOMDocument> retjanitor(retdoc);
+        retdoc->appendChild(retdoc->createElement(_ExternalAuth));
+        auto_ptr_XMLCh wideid(session_id.c_str());
+        DOMElement* child = retdoc->createElement(_SessionID);
+        child->appendChild(retdoc->createTextNode(wideid.get()));
+        retdoc->getDocumentElement()->appendChild(child);
+        if (respDDF) {
+            DDF hdr;
+            DDF hdrs = respDDF->getmember("headers");
+            hdr = hdrs.first();
+            while (hdr.isstring()) {
+                if (!strcmp(hdr.name(), "Set-Cookie")) {
+                    child = retdoc->createElement(_Cookie);
+                    auto_ptr_XMLCh wideval(hdr.string());
+                    child->appendChild(retdoc->createTextNode(wideval.get()));
+                    retdoc->getDocumentElement()->appendChild(child);
+                }
+                hdr = hdrs.next();
+            }
+        }
+        if (!target.empty()) {
+            auto_ptr_XMLCh widetar(target.c_str());
+            child = retdoc->createElement(_RelayState);
+            child->appendChild(retdoc->createTextNode(widetar.get()));
+            retdoc->getDocumentElement()->appendChild(child);
+        }
+        XMLHelper::serialize(retdoc->getDocumentElement(), os, true);
+    }
+    return make_pair(true, httpResponse.sendResponse(os));
+#else
+    return make_pair(false, 0L);
+#endif
+}
+
+#ifndef SHIBSP_LITE
+
+namespace {
+    class SHIBSP_DLLLOCAL DummyContext : public ResolutionContext
+    {
+    public:
+        DummyContext(const vector<Attribute*>& attributes) : m_attributes(attributes) {
+        }
+
+        virtual ~DummyContext() {
+            for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<Attribute>());
+        }
+
+        vector<Attribute*>& getResolvedAttributes() {
+            return m_attributes;
+        }
+        vector<Assertion*>& getResolvedAssertions() {
+            return m_tokens;
+        }
+
+    private:
+        vector<Attribute*> m_attributes;
+        static vector<Assertion*> m_tokens; // never any tokens, so just share an empty vector
+    };
+};
+
+vector<Assertion*> DummyContext::m_tokens;
+
+ResolutionContext* ExternalAuth::resolveAttributes(
+    const Application& application,
+    const GenericRequest* request,
+    const RoleDescriptor* issuer,
+    const XMLCh* protocol,
+    const saml2::NameID* nameid,
+    const saml2::AuthnStatement* statement,
+    const XMLCh* authncontext_class,
+    const XMLCh* authncontext_decl,
+    const vector<const Assertion*>* tokens,
+    const vector<Attribute*>* inputAttributes
+    ) const
+{
+    vector<Attribute*> resolvedAttributes;
+    if (inputAttributes)
+        resolvedAttributes = *inputAttributes;
+
+    // First we do the extraction of any pushed information, including from metadata.
+    AttributeExtractor* extractor = application.getAttributeExtractor();
+    if (extractor) {
+        Locker extlocker(extractor);
+        if (issuer) {
+            pair<bool,const char*> mprefix = application.getString("metadataAttributePrefix");
+            if (mprefix.first) {
+                m_log.debug("extracting metadata-derived attributes...");
+                try {
+                    // We pass nullptr for "issuer" because the IdP isn't the one asserting metadata-based attributes.
+                    extractor->extractAttributes(application, request, nullptr, *issuer, resolvedAttributes);
+                    for (indirect_iterator<vector<Attribute*>::iterator> a = make_indirect_iterator(resolvedAttributes.begin());
+                            a != make_indirect_iterator(resolvedAttributes.end()); ++a) {
+                        vector<string>& ids = a->getAliases();
+                        for (vector<string>::iterator id = ids.begin(); id != ids.end(); ++id)
+                            *id = mprefix.second + *id;
+                    }
+                }
+                catch (std::exception& ex) {
+                    m_log.error("caught exception extracting attributes: %s", ex.what());
+                }
+            }
+        }
+
+        m_log.debug("extracting pushed attributes...");
+
+        if (nameid) {
+            try {
+                extractor->extractAttributes(application, request, issuer, *nameid, resolvedAttributes);
+            }
+            catch (std::exception& ex) {
+                m_log.error("caught exception extracting attributes: %s", ex.what());
+            }
+        }
+
+        if (statement) {
+            try {
+                extractor->extractAttributes(application, request, issuer, *statement, resolvedAttributes);
+            }
+            catch (std::exception& ex) {
+                m_log.error("caught exception extracting attributes: %s", ex.what());
+            }
+        }
+
+        if (tokens) {
+            for (indirect_iterator<vector<const Assertion*>::const_iterator> t = make_indirect_iterator(tokens->begin());
+                    t != make_indirect_iterator(tokens->end()); ++t) {
+                try {
+                    extractor->extractAttributes(application, request, issuer, *t, resolvedAttributes);
+                }
+                catch (std::exception& ex) {
+                    m_log.error("caught exception extracting attributes: %s", ex.what());
+                }
+            }
+        }
+
+        AttributeFilter* filter = application.getAttributeFilter();
+        if (filter && !resolvedAttributes.empty()) {
+            BasicFilteringContext fc(application, resolvedAttributes, issuer, authncontext_class, authncontext_decl);
+            Locker filtlocker(filter);
+            try {
+                filter->filterAttributes(fc, resolvedAttributes);
+            }
+            catch (std::exception& ex) {
+                m_log.error("caught exception filtering attributes: %s", ex.what());
+                m_log.error("dumping extracted attributes due to filtering exception");
+                for_each(resolvedAttributes.begin(), resolvedAttributes.end(), xmltooling::cleanup<shibsp::Attribute>());
+                resolvedAttributes.clear();
+            }
+        }
+    }
+    else {
+        m_log.warn("no AttributeExtractor plugin installed, check log during startup");
+    }
+
+    try {
+        AttributeResolver* resolver = application.getAttributeResolver();
+        if (resolver) {
+            m_log.debug("resolving attributes...");
+
+            Locker locker(resolver);
+            auto_ptr<ResolutionContext> ctx(
+                resolver->createResolutionContext(
+                    application,
+                    request,
+                    issuer ? dynamic_cast<const EntityDescriptor*>(issuer->getParent()) : nullptr,
+                    protocol,
+                    nameid,
+                    authncontext_class,
+                    authncontext_decl,
+                    tokens,
+                    &resolvedAttributes
+                    )
+                );
+            resolver->resolveAttributes(*ctx);
+            // Copy over any pushed attributes.
+            while (!resolvedAttributes.empty()) {
+                ctx->getResolvedAttributes().push_back(resolvedAttributes.back());
+                resolvedAttributes.pop_back();
+            }
+            return ctx.release();
+        }
+    }
+    catch (std::exception& ex) {
+        m_log.error("attribute resolution failed: %s", ex.what());
+    }
+
+    if (!resolvedAttributes.empty()) {
+        try {
+            return new DummyContext(resolvedAttributes);
+        }
+        catch (bad_alloc&) {
+            for_each(resolvedAttributes.begin(), resolvedAttributes.end(), xmltooling::cleanup<shibsp::Attribute>());
+        }
+    }
+    return nullptr;
+}
+
+LoginEvent* ExternalAuth::newLoginEvent(const Application& application, const HTTPRequest& request) const
+{
+    if (!SPConfig::getConfig().isEnabled(SPConfig::Logging))
+        return nullptr;
+    try {
+        auto_ptr<TransactionLog::Event> event(SPConfig::getConfig().EventManager.newPlugin(LOGIN_EVENT, nullptr));
+        LoginEvent* login_event = dynamic_cast<LoginEvent*>(event.get());
+        if (login_event) {
+            login_event->m_request = &request;
+            login_event->m_app = &application;
+            login_event->m_binding = "ExternalAuth";
+            event.release();
+            return login_event;
+        }
+        else {
+            m_log.warn("unable to audit event, log event object was of an incorrect type");
+        }
+    }
+    catch (std::exception& ex) {
+        m_log.warn("exception auditing event: %s", ex.what());
+    }
+    return nullptr;
+}
+
+#endif
\ No newline at end of file
index 2b0fd07..5531fd2 100644 (file)
@@ -120,6 +120,7 @@ pair<bool,long> LocalLogoutInitiator::run(SPRequest& request, bool isHandler) co
     else {
         // When not out of process, we remote the request.
         vector<string> headers(1,"Cookie");
+        headers.push_back("User-Agent");
         DDF out,in = wrap(request,&headers);
         DDFJanitor jin(in), jout(out);
         out=request.getServiceProvider().getListenerService()->send(in);
index 48663b5..5c912aa 100644 (file)
 #include "exceptions.h"
 #include "ServiceProvider.h"
 #include "SPRequest.h"
-#include "handler/AbstractHandler.h"
 #include "handler/RemotedHandler.h"
-#include "util/IPRange.h"
+#include "handler/SecuredHandler.h"
 
-#include <boost/bind.hpp>
 #include <boost/scoped_ptr.hpp>
 #include <boost/iterator/indirect_iterator.hpp>
-#include <boost/algorithm/string.hpp>
 
 #ifndef SHIBSP_LITE
 # include "attribute/resolver/AttributeExtractor.h"
@@ -76,22 +73,7 @@ namespace shibsp {
     #pragma warning( disable : 4250 )
 #endif
 
-    class SHIBSP_DLLLOCAL Blocker : public DOMNodeFilter
-    {
-    public:
-#ifdef SHIBSP_XERCESC_SHORT_ACCEPTNODE
-        short
-#else
-        FilterAction
-#endif
-        acceptNode(const DOMNode* node) const {
-            return FILTER_REJECT;
-        }
-    };
-
-    static SHIBSP_DLLLOCAL Blocker g_Blocker;
-
-    class SHIBSP_API MetadataGenerator : public AbstractHandler, public RemotedHandler
+    class SHIBSP_API MetadataGenerator : public SecuredHandler, public RemotedHandler
     {
     public:
         MetadataGenerator(const DOMElement* e, const char* appId);
@@ -108,16 +90,6 @@ namespace shibsp {
             HTTPResponse& httpResponse
             ) const;
 
-        void parseACL(const string& acl) {
-            try {
-                m_acl.push_back(IPRange::parseCIDRBlock(acl.c_str()));
-            }
-            catch (std::exception& ex) {
-                m_log.error("invalid CIDR block (%s): %s", acl.c_str(), ex.what());
-            }
-        }
-
-        vector<IPRange> m_acl;
 #ifndef SHIBSP_LITE
         string m_salt;
         short m_http,m_https;
@@ -144,7 +116,7 @@ namespace shibsp {
 };
 
 MetadataGenerator::MetadataGenerator(const DOMElement* e, const char* appId)
-    : AbstractHandler(e, Category::getInstance(SHIBSP_LOGCAT".MetadataGenerator"), &g_Blocker)
+    : SecuredHandler(e, Category::getInstance(SHIBSP_LOGCAT".MetadataGenerator"))
 #ifndef SHIBSP_LITE
         ,m_http(0), m_https(0)
 #endif
@@ -153,20 +125,6 @@ MetadataGenerator::MetadataGenerator(const DOMElement* e, const char* appId)
     address += getString("Location").second;
     setAddress(address.c_str());
 
-    if (SPConfig::getConfig().isEnabled(SPConfig::InProcess)) {
-        pair<bool,const char*> acl = getString("acl");
-        if (acl.first) {
-            string aclbuf=acl.second;
-            vector<string> aclarray;
-            split(aclarray, aclbuf, is_space(), algorithm::token_compress_on);
-            for_each(aclarray.begin(), aclarray.end(), boost::bind(&MetadataGenerator::parseACL, this, _1));
-            if (m_acl.empty()) {
-                m_log.warn("invalid CIDR range(s) in Metadata Generator acl property, allowing 127.0.0.1 as a fall back");
-                m_acl.push_back(IPRange::parseCIDRBlock("127.0.0.1"));
-            }
-        }
-    }
-
 #ifndef SHIBSP_LITE
     static XMLCh EndpointBase[] = UNICODE_LITERAL_12(E,n,d,p,o,i,n,t,B,a,s,e);
 
@@ -261,18 +219,13 @@ MetadataGenerator::MetadataGenerator(const DOMElement* e, const char* appId)
 
 pair<bool,long> MetadataGenerator::run(SPRequest& request, bool isHandler) const
 {
-    SPConfig& conf = SPConfig::getConfig();
-    if (conf.isEnabled(SPConfig::InProcess) && !m_acl.empty()) {
-        static bool (IPRange::* contains)(const char*) const = &IPRange::contains;
-        if (find_if(m_acl.begin(), m_acl.end(), boost::bind(contains, _1, request.getRemoteAddr().c_str())) == m_acl.end()) {
-            m_log.error("request for metadata blocked from invalid address (%s)", request.getRemoteAddr().c_str());
-            istringstream msg("Metadata Request Blocked");
-            return make_pair(true, request.sendResponse(msg, HTTPResponse::XMLTOOLING_HTTP_STATUS_FORBIDDEN));
-        }
-    }
+    // Check ACL in base class.
+    pair<bool,long> ret = SecuredHandler::run(request, isHandler);
+    if (ret.first)
+        return ret;
 
     try {
-        if (conf.isEnabled(SPConfig::OutOfProcess)) {
+        if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
             // When out of process, we run natively and directly process the message.
             return processMessage(request.getApplication(), request.getHandlerURL(), request.getParameter("entityID"), request);
         }
@@ -285,7 +238,7 @@ pair<bool,long> MetadataGenerator::run(SPRequest& request, bool isHandler) const
                 in.addmember("entity_id").string(request.getParameter("entityID"));
             DDFJanitor jin(in), jout(out);
 
-            out=request.getServiceProvider().getListenerService()->send(in);
+            out = request.getServiceProvider().getListenerService()->send(in);
             return unwrap(request, out);
         }
     }
@@ -299,9 +252,9 @@ pair<bool,long> MetadataGenerator::run(SPRequest& request, bool isHandler) const
 void MetadataGenerator::receive(DDF& in, ostream& out)
 {
     // Find application.
-    const char* aid=in["application_id"].string();
-    const char* hurl=in["handler_url"].string();
-    const Application* app=aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : nullptr;
+    const char* aid = in["application_id"].string();
+    const char* hurl = in["handler_url"].string();
+    const Application* app = aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : nullptr;
     if (!app) {
         // Something's horribly wrong.
         m_log.error("couldn't find application (%s) for metadata request", aid ? aid : "(missing)");
@@ -478,7 +431,7 @@ pair<bool,long> MetadataGenerator::processMessage(
         extractor->generateMetadata(*role);
     }
 
-    CredentialResolver* credResolver=application.getCredentialResolver();
+    CredentialResolver* credResolver = application.getCredentialResolver();
     if (credResolver) {
         Locker credLocker(credResolver);
         CredentialCriteria cc;
@@ -576,6 +529,6 @@ pair<bool,long> MetadataGenerator::processMessage(
     httpResponse.setContentType(prop.first ? prop.second : "application/samlmetadata+xml");
     return make_pair(true, httpResponse.sendResponse(s));
 #else
-    return make_pair(false,0L);
+    return make_pair(false, 0L);
 #endif
 }
index d92a4c1..668ed9d 100644 (file)
@@ -365,8 +365,8 @@ DDF RemotedHandler::wrap(const SPRequest& request, const vector<string>* headers
     in.addmember("hostname").unsafe_string(request.getHostname());
     in.addmember("port").integer(request.getPort());
     in.addmember("content_type").string(request.getContentType().c_str());
-    in.addmember("content_length").integer(request.getContentLength());
     in.addmember("body").string(request.getRequestBody());
+    in.addmember("content_length").integer(request.getContentLength());
     in.addmember("remote_user").string(request.getRemoteUser().c_str());
     in.addmember("client_addr").string(request.getRemoteAddr().c_str());
     in.addmember("method").string(request.getMethod());
diff --git a/shibsp/handler/impl/SecuredHandler.cpp b/shibsp/handler/impl/SecuredHandler.cpp
new file mode 100644 (file)
index 0000000..17074dc
--- /dev/null
@@ -0,0 +1,112 @@
+/**
+ * Licensed to the University Corporation for Advanced Internet
+ * Development, Inc. (UCAID) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ *
+ * UCAID licenses this file to you 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
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific
+ * language governing permissions and limitations under the License.
+ */
+
+/**
+ * SecuredHandler.cpp
+ *
+ * Pluggable runtime functionality that is protected by simple access control.
+ */
+
+#include "internal.h"
+#include "SPRequest.h"
+#include "handler/SecuredHandler.h"
+
+#include <boost/bind.hpp>
+#include <boost/algorithm/string.hpp>
+
+using namespace shibsp;
+using namespace xmltooling;
+using namespace boost;
+using namespace std;
+
+namespace {
+    class SHIBSP_DLLLOCAL Blocker : public DOMNodeFilter
+    {
+    public:
+#ifdef SHIBSP_XERCESC_SHORT_ACCEPTNODE
+        short
+#else
+        FilterAction
+#endif
+        acceptNode(const DOMNode* node) const {
+            return FILTER_REJECT;
+        }
+    };
+
+    static Blocker g_Blocker;
+};
+
+SecuredHandler::SecuredHandler(
+    const DOMElement* e,
+    Category& log,
+    const char* aclProperty,
+    const char* defaultACL,
+    DOMNodeFilter* filter,
+    const map<string,string>* remapper
+    ) : AbstractHandler(e, log, filter ? filter : &g_Blocker, remapper)
+{
+    if (SPConfig::getConfig().isEnabled(SPConfig::InProcess)) {
+        pair<bool,const char*> acl = getString(aclProperty);
+        if (!acl.first && defaultACL) {
+            m_log.info("installing default ACL (%s)", defaultACL);
+            acl.first = true;
+            acl.second = defaultACL;
+        }
+        if (acl.first) {
+            string aclbuf(acl.second);
+            vector<string> aclarray;
+            split(aclarray, aclbuf, is_space(), algorithm::token_compress_on);
+            for_each(aclarray.begin(), aclarray.end(), boost::bind(&SecuredHandler::parseACL, this, _1));
+            if (m_acl.empty()) {
+                m_log.warn("invalid CIDR range(s) in handler's acl property, allowing 127.0.0.1 and ::1 as a fall back");
+                m_acl.push_back(IPRange::parseCIDRBlock("127.0.0.1"));
+                m_acl.push_back(IPRange::parseCIDRBlock("::1"));
+            }
+        }
+    }
+}
+
+SecuredHandler::~SecuredHandler()
+{
+}
+
+void SecuredHandler::parseACL(const string& acl)
+{
+    try {
+        m_acl.push_back(IPRange::parseCIDRBlock(acl.c_str()));
+    }
+    catch (std::exception& ex) {
+        m_log.error("invalid CIDR block (%s): %s", acl.c_str(), ex.what());
+    }
+}
+
+pair<bool,long> SecuredHandler::run(SPRequest& request, bool isHandler) const
+{
+    SPConfig& conf = SPConfig::getConfig();
+    if (conf.isEnabled(SPConfig::InProcess) && !m_acl.empty()) {
+        static bool (IPRange::* contains)(const char*) const = &IPRange::contains;
+        if (find_if(m_acl.begin(), m_acl.end(), boost::bind(contains, _1, request.getRemoteAddr().c_str())) == m_acl.end()) {
+            m_log.error("handler request blocked from invalid address (%s)", request.getRemoteAddr().c_str());
+            istringstream msg("Access Denied");
+            return make_pair(true, request.sendResponse(msg, HTTPResponse::XMLTOOLING_HTTP_STATUS_FORBIDDEN));
+        }
+    }
+    return make_pair(false, 0L);
+}
index 721fcb7..1e199f6 100644 (file)
 #include "SessionCache.h"
 #include "SPRequest.h"
 #include "attribute/Attribute.h"
-#include "handler/AbstractHandler.h"
-#include "util/IPRange.h"
+#include "handler/SecuredHandler.h"
 
 #include <ctime>
-#include <boost/bind.hpp>
-#include <boost/algorithm/string.hpp>
 
 using namespace shibsp;
 using namespace xmltooling;
-using namespace boost;
 using namespace std;
 
 namespace shibsp {
@@ -50,22 +46,7 @@ namespace shibsp {
     #pragma warning( disable : 4250 )
 #endif
 
-    class SHIBSP_DLLLOCAL Blocker : public DOMNodeFilter
-    {
-    public:
-#ifdef SHIBSP_XERCESC_SHORT_ACCEPTNODE
-        short
-#else
-        FilterAction
-#endif
-        acceptNode(const DOMNode* node) const {
-            return FILTER_REJECT;
-        }
-    };
-
-    static SHIBSP_DLLLOCAL Blocker g_Blocker;
-
-    class SHIBSP_API SessionHandler : public AbstractHandler
+    class SHIBSP_API SessionHandler : public SecuredHandler
     {
     public:
         SessionHandler(const DOMElement* e, const char* appId);
@@ -74,17 +55,7 @@ namespace shibsp {
         pair<bool,long> run(SPRequest& request, bool isHandler=true) const;
 
     private:
-        void parseACL(const string& acl) {
-            try {
-                m_acl.push_back(IPRange::parseCIDRBlock(acl.c_str()));
-            }
-            catch (std::exception& ex) {
-                m_log.error("invalid CIDR block (%s): %s", acl.c_str(), ex.what());
-            }
-        }
-
         bool m_values;
-        vector<IPRange> m_acl;
     };
 
 #if defined (_MSC_VER)
@@ -99,20 +70,8 @@ namespace shibsp {
 };
 
 SessionHandler::SessionHandler(const DOMElement* e, const char* appId)
-    : AbstractHandler(e, Category::getInstance(SHIBSP_LOGCAT".SessionHandler"), &g_Blocker), m_values(false)
+    : SecuredHandler(e, Category::getInstance(SHIBSP_LOGCAT".SessionHandler")), m_values(false)
 {
-    pair<bool,const char*> acl = getString("acl");
-    if (acl.first) {
-        string aclbuf=acl.second;
-        vector<string> aclarray;
-        split(aclarray, aclbuf, is_space(), algorithm::token_compress_on);
-        for_each(aclarray.begin(), aclarray.end(), boost::bind(&SessionHandler::parseACL, this, _1));
-        if (m_acl.empty()) {
-            m_log.warn("invalid CIDR range(s) in Session handler acl property, allowing 127.0.0.1 as a fall back");
-            m_acl.push_back(IPRange::parseCIDRBlock("127.0.0.1"));
-        }
-    }
-
     pair<bool,bool> flag = getBool("showAttributeValues");
     if (flag.first)
         m_values = flag.second;
@@ -120,14 +79,10 @@ SessionHandler::SessionHandler(const DOMElement* e, const char* appId)
 
 pair<bool,long> SessionHandler::run(SPRequest& request, bool isHandler) const
 {
-    if (!m_acl.empty()) {
-        static bool (IPRange::* contains)(const char*) const = &IPRange::contains;
-        if (find_if(m_acl.begin(), m_acl.end(), boost::bind(contains, _1, request.getRemoteAddr().c_str())) == m_acl.end()) {
-            m_log.error("session handler request blocked from invalid address (%s)", request.getRemoteAddr().c_str());
-            istringstream msg("Session Handler Blocked");
-            return make_pair(true,request.sendResponse(msg, HTTPResponse::XMLTOOLING_HTTP_STATUS_FORBIDDEN));
-        }
-    }
+    // Check ACL in base class.
+    pair<bool,long> ret = SecuredHandler::run(request, isHandler);
+    if (ret.first)
+        return ret;
 
     stringstream s;
     s << "<html><head><title>Session Summary</title></head><body><pre>" << endl;
index 4f549c2..a6a4c06 100644 (file)
 #include "exceptions.h"
 #include "ServiceProvider.h"
 #include "SPRequest.h"
-#include "handler/AbstractHandler.h"
 #include "handler/RemotedHandler.h"
-#include "util/IPRange.h"
+#include "handler/SecuredHandler.h"
 #include "util/CGIParser.h"
 
-#include <boost/bind.hpp>
 #include <boost/iterator/indirect_iterator.hpp>
 #include <boost/scoped_ptr.hpp>
-#include <boost/algorithm/string.hpp>
 #include <xmltooling/version.h>
 #include <xmltooling/util/DateTime.h>
 
@@ -68,22 +65,7 @@ namespace shibsp {
     #pragma warning( disable : 4250 )
 #endif
 
-    class SHIBSP_DLLLOCAL Blocker : public DOMNodeFilter
-    {
-    public:
-#ifdef SHIBSP_XERCESC_SHORT_ACCEPTNODE
-        short
-#else
-        FilterAction
-#endif
-        acceptNode(const DOMNode* node) const {
-            return FILTER_REJECT;
-        }
-    };
-
-    static SHIBSP_DLLLOCAL Blocker g_Blocker;
-
-    class SHIBSP_API StatusHandler : public AbstractHandler, public RemotedHandler
+    class SHIBSP_API StatusHandler : public SecuredHandler, public RemotedHandler
     {
     public:
         StatusHandler(const DOMElement* e, const char* appId);
@@ -95,16 +77,6 @@ namespace shibsp {
     private:
         pair<bool,long> processMessage(const Application& application, const HTTPRequest& httpRequest, HTTPResponse& httpResponse) const;
         ostream& systemInfo(ostream& os) const;
-        void parseACL(const string& acl) {
-            try {
-                m_acl.push_back(IPRange::parseCIDRBlock(acl.c_str()));
-            }
-            catch (std::exception& ex) {
-                m_log.error("invalid CIDR block (%s): %s", acl.c_str(), ex.what());
-            }
-        }
-
-        vector<IPRange> m_acl;
     };
 
 #if defined (_MSC_VER)
@@ -279,22 +251,8 @@ namespace shibsp {
 };
 
 StatusHandler::StatusHandler(const DOMElement* e, const char* appId)
-    : AbstractHandler(e, Category::getInstance(SHIBSP_LOGCAT".StatusHandler"), &g_Blocker)
+    : SecuredHandler(e, Category::getInstance(SHIBSP_LOGCAT".StatusHandler"))
 {
-    if (SPConfig::getConfig().isEnabled(SPConfig::InProcess)) {
-        pair<bool,const char*> acl = getString("acl");
-        if (acl.first) {
-            string aclbuf=acl.second;
-            vector<string> aclarray;
-            split(aclarray, aclbuf, is_space(), algorithm::token_compress_on);
-            for_each(aclarray.begin(), aclarray.end(), boost::bind(&StatusHandler::parseACL, this, _1));
-            if (m_acl.empty()) {
-                m_log.warn("invalid CIDR range(s) in Status handler acl property, allowing 127.0.0.1 as a fall back");
-                m_acl.push_back(IPRange::parseCIDRBlock("127.0.0.1"));
-            }
-        }
-    }
-
     string address(appId);
     address += getString("Location").second;
     setAddress(address.c_str());
@@ -302,15 +260,10 @@ StatusHandler::StatusHandler(const DOMElement* e, const char* appId)
 
 pair<bool,long> StatusHandler::run(SPRequest& request, bool isHandler) const
 {
-    SPConfig& conf = SPConfig::getConfig();
-    if (conf.isEnabled(SPConfig::InProcess) && !m_acl.empty()) {
-        static bool (IPRange::* contains)(const char*) const = &IPRange::contains;
-        if (find_if(m_acl.begin(), m_acl.end(), boost::bind(contains, _1, request.getRemoteAddr().c_str())) == m_acl.end()) {
-            m_log.error("status handler request blocked from invalid address (%s)", request.getRemoteAddr().c_str());
-            istringstream msg("Status Handler Blocked");
-            return make_pair(true,request.sendResponse(msg, HTTPResponse::XMLTOOLING_HTTP_STATUS_FORBIDDEN));
-        }
-    }
+    // Check ACL in base class.
+    pair<bool,long> ret = SecuredHandler::run(request, isHandler);
+    if (ret.first)
+        return ret;
 
     const char* target = request.getParameter("target");
     if (target) {
@@ -343,7 +296,7 @@ pair<bool,long> StatusHandler::run(SPRequest& request, bool isHandler) const
     }
 
     try {
-        if (conf.isEnabled(SPConfig::OutOfProcess)) {
+        if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
             // When out of process, we run natively and directly process the message.
             return processMessage(request.getApplication(), request, request);
         }
index 1f6712d..1207c59 100644 (file)
@@ -688,7 +688,7 @@ namespace {
         }
         
         const LogoutEvent* logout = dynamic_cast<const LogoutEvent*>(&e);
-        if (logout && logout->m_session) {
+        if (logout && logout->m_session && logout->m_session->getEntityID()) {
             os << logout->m_session->getEntityID();
             return true;
         }
index 6f4f036..5485694 100644 (file)
     <ClCompile Include="binding\impl\XMLProtocolProvider.cpp" />\r
     <ClCompile Include="handler\impl\AttributeCheckerHandler.cpp" />\r
     <ClCompile Include="handler\impl\DiscoveryFeed.cpp" />\r
+    <ClCompile Include="handler\impl\ExternalAuthHandler.cpp" />\r
     <ClCompile Include="handler\impl\LogoutInitiator.cpp" />\r
+    <ClCompile Include="handler\impl\SecuredHandler.cpp" />\r
     <ClCompile Include="ServiceProvider.cpp" />\r
     <ClCompile Include="SPConfig.cpp" />\r
     <ClCompile Include="util\CGIParser.cpp" />\r
     <ClInclude Include="binding\ProtocolProvider.h" />\r
     <ClInclude Include="GSSRequest.h" />\r
     <ClInclude Include="handler\LogoutInitiator.h" />\r
+    <ClInclude Include="handler\SecuredHandler.h" />\r
     <ClInclude Include="remoting\impl\SocketListener.h" />\r
     <ClInclude Include="AbstractSPRequest.h" />\r
     <ClInclude Include="AccessControl.h" />\r
index a3c5028..210e6b2 100644 (file)
     <ClCompile Include="handler\impl\AttributeCheckerHandler.cpp">\r
       <Filter>Source Files\handler\impl</Filter>\r
     </ClCompile>\r
+    <ClCompile Include="handler\impl\SecuredHandler.cpp">\r
+      <Filter>Source Files\handler\impl</Filter>\r
+    </ClCompile>\r
+    <ClCompile Include="handler\impl\ExternalAuthHandler.cpp">\r
+      <Filter>Source Files\handler\impl</Filter>\r
+    </ClCompile>\r
   </ItemGroup>\r
   <ItemGroup>\r
     <ClInclude Include="remoting\impl\SocketListener.h">\r
     <ClInclude Include="attribute\BinaryAttribute.h">\r
       <Filter>Header Files\attribute</Filter>\r
     </ClInclude>\r
+    <ClInclude Include="handler\SecuredHandler.h">\r
+      <Filter>Header Files\handler</Filter>\r
+    </ClInclude>\r
   </ItemGroup>\r
   <ItemGroup>\r
     <ResourceCompile Include="shibsp.rc">\r
index 3513dcf..75d0215 100644 (file)
     <ClCompile Include="binding\impl\XMLProtocolProvider.cpp" />\r
     <ClCompile Include="handler\impl\AttributeCheckerHandler.cpp" />\r
     <ClCompile Include="handler\impl\DiscoveryFeed.cpp" />\r
+    <ClCompile Include="handler\impl\ExternalAuthHandler.cpp" />\r
     <ClCompile Include="handler\impl\LogoutInitiator.cpp" />\r
+    <ClCompile Include="handler\impl\SecuredHandler.cpp" />\r
     <ClCompile Include="impl\XMLSecurityPolicyProvider.cpp" />\r
     <ClCompile Include="ServiceProvider.cpp" />\r
     <ClCompile Include="SPConfig.cpp" />\r
     <ClInclude Include="binding\ProtocolProvider.h" />\r
     <ClInclude Include="GSSRequest.h" />\r
     <ClInclude Include="handler\LogoutInitiator.h" />\r
+    <ClInclude Include="handler\SecuredHandler.h" />\r
     <ClInclude Include="remoting\impl\SocketListener.h" />\r
     <ClInclude Include="AbstractSPRequest.h" />\r
     <ClInclude Include="AccessControl.h" />\r
index f205e38..471b777 100644 (file)
     <ClCompile Include="attribute\filtering\impl\AttributeRequesterEntityMatcherFunctor.cpp">\r
       <Filter>Source Files\attribute\filtering\impl</Filter>\r
     </ClCompile>\r
+    <ClCompile Include="handler\impl\SecuredHandler.cpp">\r
+      <Filter>Source Files\handler\impl</Filter>\r
+    </ClCompile>\r
+    <ClCompile Include="handler\impl\ExternalAuthHandler.cpp">\r
+      <Filter>Source Files\handler\impl</Filter>\r
+    </ClCompile>\r
   </ItemGroup>\r
   <ItemGroup>\r
     <ClInclude Include="remoting\impl\SocketListener.h">\r
     <ClInclude Include="attribute\BinaryAttribute.h">\r
       <Filter>Header Files\attribute</Filter>\r
     </ClInclude>\r
+    <ClInclude Include="handler\SecuredHandler.h">\r
+      <Filter>Header Files\handler</Filter>\r
+    </ClInclude>\r
   </ItemGroup>\r
   <ItemGroup>\r
     <ResourceCompile Include="shibsp.rc">\r