https://bugs.internet2.edu/jira/browse/SSPCPP-254
authorcantor <cantor@cb58f699-b61c-0410-a6fe-9272a202ed29>
Thu, 23 Sep 2010 17:24:57 +0000 (17:24 +0000)
committercantor <cantor@cb58f699-b61c-0410-a6fe-9272a202ed29>
Thu, 23 Sep 2010 17:24:57 +0000 (17:24 +0000)
git-svn-id: https://svn.middleware.georgetown.edu/cpp-sp/branches/REL_2@3330 cb58f699-b61c-0410-a6fe-9272a202ed29

configs/example-shibboleth2.xml
configs/shibboleth2.xml
configs/win-shibboleth2.xml
shibsp/Makefile.am
shibsp/handler/Handler.h
shibsp/handler/impl/AbstractHandler.cpp
shibsp/handler/impl/DiscoveryFeed.cpp [new file with mode: 0644]
shibsp/shibsp-lite.vcxproj
shibsp/shibsp-lite.vcxproj.filters
shibsp/shibsp.vcxproj
shibsp/shibsp.vcxproj.filters

index b3652ce..8ab846d 100644 (file)
             <!-- Session diagnostic service. -->\r
             <Handler type="Session" Location="/Session" showAttributeValues="false"/>\r
 \r
+            <!-- JSON feed of discovery information. -->\r
+            <Handler type="DiscoveryFeed" Location="/DiscoFeed"/>\r
         </Sessions>\r
 \r
         <!--\r
index 8709112..956ca55 100644 (file)
@@ -57,6 +57,8 @@
             <!-- Session diagnostic service. -->
             <Handler type="Session" Location="/Session" showAttributeValues="false"/>
 
+            <!-- JSON feed of discovery information. -->
+            <Handler type="DiscoveryFeed" Location="/DiscoFeed"/>
         </Sessions>
 
         <!--
index e7abe50..bacede7 100644 (file)
             <!-- Session diagnostic service. -->\r
             <Handler type="Session" Location="/Session" showAttributeValues="false"/>\r
 \r
+            <!-- JSON feed of discovery information. -->\r
+            <Handler type="DiscoveryFeed" Location="/DiscoFeed"/>\r
         </Sessions>\r
 \r
         <!--\r
index f6974e1..55072f9 100644 (file)
@@ -121,6 +121,7 @@ common_sources = \
        handler/impl/ChainingLogoutInitiator.cpp \
        handler/impl/ChainingSessionInitiator.cpp \
        handler/impl/CookieSessionInitiator.cpp \
+       handler/impl/DiscoveryFeed.cpp \
        handler/impl/FormSessionInitiator.cpp \
        handler/impl/LocalLogoutInitiator.cpp \
        handler/impl/LogoutHandler.cpp \
index 3c3d4b0..2940dfc 100644 (file)
@@ -156,6 +156,9 @@ namespace shibsp {
     #define SAML20_ARTIFACT_RESOLUTION_SERVICE "SAML2"
 
     /** Handler for metadata generation. */
+    #define DISCOVERY_FEED_HANDLER "DiscoveryFeed"
+
+    /** Handler for metadata generation. */
     #define METADATA_GENERATOR_HANDLER "MetadataGenerator"
 
     /** Handler for status information. */
index da9fc01..07319d3 100644 (file)
@@ -74,6 +74,7 @@ namespace shibsp {
     SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory SAML2LogoutFactory;
     SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory SAML2NameIDMgmtFactory;
     SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory AssertionLookupFactory;
+    SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory DiscoveryFeedFactory;
     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;
@@ -131,6 +132,7 @@ void SHIBSP_API shibsp::registerHandlers()
     conf.ArtifactResolutionServiceManager.registerFactory(SAML20_BINDING_SOAP, SAML2ArtifactResolutionFactory);
 
     conf.HandlerManager.registerFactory(SAML20_BINDING_URI, AssertionLookupFactory);
+    conf.HandlerManager.registerFactory(DISCOVERY_FEED_HANDLER, DiscoveryFeedFactory);
     conf.HandlerManager.registerFactory(METADATA_GENERATOR_HANDLER, MetadataGeneratorFactory);
     conf.HandlerManager.registerFactory(STATUS_HANDLER, StatusHandlerFactory);
     conf.HandlerManager.registerFactory(SESSION_HANDLER, SessionHandlerFactory);
diff --git a/shibsp/handler/impl/DiscoveryFeed.cpp b/shibsp/handler/impl/DiscoveryFeed.cpp
new file mode 100644 (file)
index 0000000..fbad37a
--- /dev/null
@@ -0,0 +1,349 @@
+/*
+ *  Copyright 2010 Internet2
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     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.
+ */
+
+/**
+ * DiscoveryFeed.cpp
+ *
+ * Handler for generating a JSON discovery feed based on metadata.
+ */
+
+#include "internal.h"
+#include "Application.h"
+#include "exceptions.h"
+#include "ServiceProvider.h"
+#include "SPRequest.h"
+#include "handler/AbstractHandler.h"
+#include "handler/RemotedHandler.h"
+
+#include <ctime>
+#include <fstream>
+#include <xmltooling/XMLToolingConfig.h>
+#include <xmltooling/util/Threads.h>
+#include <xmltooling/util/PathResolver.h>
+
+#ifndef SHIBSP_LITE
+# include <queue>
+# include <saml/exceptions.h>
+# include <saml/SAMLConfig.h>
+# include <saml/saml2/metadata/DiscoverableMetadataProvider.h>
+#endif
+
+using namespace shibsp;
+#ifndef SHIBSP_LITE
+using namespace opensaml::saml2md;
+using namespace opensaml;
+#endif
+using namespace xmltooling;
+using namespace std;
+
+namespace shibsp {
+
+#if defined (_MSC_VER)
+    #pragma warning( push )
+    #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 DiscoveryFeed : public AbstractHandler, public RemotedHandler
+    {
+    public:
+        DiscoveryFeed(const DOMElement* e, const char* appId);
+        virtual ~DiscoveryFeed();
+
+        pair<bool,long> run(SPRequest& request, bool isHandler=true) const;
+        void receive(DDF& in, ostream& out);
+
+    private:
+        void feedToFile(const Application& application, string& cacheTag) const;
+        void feedToStream(const Application& application, string& cacheTag, ostream& os) const;
+
+        string m_dir;
+        bool m_cacheToClient;
+#ifndef SHIBSP_LITE
+        // A queue of feed files, linked to the last time of "access".
+        // Each filename is also a cache tag.
+        mutable queue< pair<string,time_t> > m_feedQueue;
+        Mutex* m_feedLock;
+#endif
+    };
+
+#if defined (_MSC_VER)
+    #pragma warning( pop )
+#endif
+
+    Handler* SHIBSP_DLLLOCAL DiscoveryFeedFactory(const pair<const DOMElement*,const char*>& p)
+    {
+        return new DiscoveryFeed(p.first, p.second);
+    }
+
+};
+
+DiscoveryFeed::DiscoveryFeed(const DOMElement* e, const char* appId)
+    : AbstractHandler(e, Category::getInstance(SHIBSP_LOGCAT".DiscoveryFeed"), &g_Blocker), m_cacheToClient(false)
+#ifndef SHIBSP_LITE
+    , m_feedLock(nullptr)
+#endif
+{
+    pair<bool,const char*> prop = getString("Location");
+    if (!prop.first)
+        throw ConfigurationException("DiscoveryFeed handler requires Location property.");
+    string address(appId);
+    address += prop.second;
+    setAddress(address.c_str());
+
+    pair<bool,bool> flag = getBool("cacheToClient");
+    m_cacheToClient = flag.first && flag.second;
+    flag = getBool("cacheToDisk");
+    if (!flag.first || flag.second) {
+        prop = getString("dir");
+        if (prop.first)
+            m_dir = prop.second;
+        XMLToolingConfig::getConfig().getPathResolver()->resolve(m_dir, PathResolver::XMLTOOLING_RUN_FILE);
+        m_log.info("feed files will be cached in %s", m_dir.c_str());
+#ifndef SHIBSP_LITE
+        m_feedLock = Mutex::create();
+#endif
+    }
+}
+
+DiscoveryFeed::~DiscoveryFeed()
+{
+#ifndef SHIBSP_LITE
+    if (m_feedLock) {
+        // Remove any files unused for more than a couple of minutes.
+        // Anything left will be orphaned, but that shouldn't happen too often.
+        time_t now = time(nullptr);
+        while (!m_feedQueue.empty() && now - m_feedQueue.front().second > 120) {
+            string fname = m_dir + '/' + m_feedQueue.front().first;
+            remove(fname.c_str());
+            m_feedQueue.pop();
+        }
+        delete m_feedLock;
+    }
+#endif
+}
+
+pair<bool,long> DiscoveryFeed::run(SPRequest& request, bool isHandler) const
+{
+    try {
+        SPConfig& conf = SPConfig::getConfig();
+
+        string s;
+        if (m_cacheToClient)
+            s = request.getHeader("If-None-Match");
+
+        if (conf.isEnabled(SPConfig::OutOfProcess)) {
+            // When out of process, we run natively and directly process the message.
+            if (m_dir.empty()) {
+                // The feed is directly returned.
+                stringstream buf;
+                feedToStream(request.getApplication(), s, buf);
+                if (!s.empty()) {
+                    if (m_cacheToClient) {
+                        string etag = '"' + s + '"';
+                        request.setResponseHeader("ETag", etag.c_str());
+                    }
+                    request.setContentType("application/json");
+                    return make_pair(true, request.sendResponse(buf));
+                }
+            }
+            else {
+                // Indirect the feed through a file.
+                feedToFile(request.getApplication(), s);
+            }
+        }
+        else {
+            // When not out of process, we remote all the message processing.
+            DDF out,in = DDF(m_address.c_str());
+            in.addmember("application_id").string(request.getApplication().getId());
+            if (!s.empty())
+                in.addmember("cache_tag").string(s.c_str());
+            DDFJanitor jin(in), jout(out);
+            out = request.getServiceProvider().getListenerService()->send(in);
+            s.erase();
+            if (m_dir.empty()) {
+                // The cache tag and feed are in the response struct.
+                if (m_cacheToClient && out["cache_tag"].string()) {
+                    string etag = string("\"") + out["cache_tag"].string() + '"';
+                    request.setResponseHeader("ETag", etag.c_str());
+                }
+                if (out["feed"].string()) {
+                    istringstream buf(out["feed"].string());
+                    request.setContentType("application/json");
+                    return make_pair(true, request.sendResponse(buf));
+                }
+                throw ConfigurationException("Discovery feed was empty.");
+            }
+            else {
+                // The response object is a string containing the cache tag.
+                if (out.isstring() && out.string())
+                    s = out.string();
+            }
+        }
+
+        if (s.empty()) {
+            m_log.debug("client's cache tag matches our feed");
+            istringstream msg("Not Modified");
+            return make_pair(true, request.sendResponse(msg, HTTPResponse::XMLTOOLING_HTTP_STATUS_NOTMODIFIED));
+        }
+
+        string fname = m_dir + '/' + s + ".json";
+        ifstream feed(fname.c_str());
+        if (!feed)
+            throw ConfigurationException("Unable to access cached feed in ($1).", params(1,fname.c_str()));
+        if (m_cacheToClient) {
+            string etag = '"' + s + '"';
+            request.setResponseHeader("ETag", etag.c_str());
+        }
+        request.setContentType("application/json");
+        return make_pair(true, request.sendResponse(feed));
+    }
+    catch (exception& ex) {
+        request.log(SPRequest::SPError, string("error while processing request:") + ex.what());
+        istringstream msg("Discovery Request Failed");
+        return make_pair(true, request.sendResponse(msg, HTTPResponse::XMLTOOLING_HTTP_STATUS_ERROR));
+    }
+}
+
+void DiscoveryFeed::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 discovery feed request", aid ? aid : "(missing)");
+        throw ConfigurationException("Unable to locate application for discovery feed request, deleted?");
+    }
+
+    string cacheTag;
+    if (in["cache_tag"].string())
+        cacheTag = in["cache_tag"].string();
+
+    DDF ret(nullptr);
+    DDFJanitor jout(ret);
+
+    if (!m_dir.empty()) {
+        // We're relaying the feed through a file.
+        feedToFile(*app, cacheTag);
+        if (!cacheTag.empty())
+            ret.string(cacheTag.c_str());
+    }
+    else {
+        // We're relaying the feed directly.
+        ostringstream os;
+        feedToStream(*app, cacheTag, os);
+        if (!cacheTag.empty())
+            ret.addmember("cache_tag").string(cacheTag.c_str());
+        string feed = os.str();
+        if (!feed.empty())
+            ret.addmember("feed").string(feed.c_str());
+    }
+    out << ret;
+}
+
+void DiscoveryFeed::feedToFile(const Application& application, string& cacheTag) const
+{
+#ifndef SHIBSP_LITE
+    m_log.debug("processing discovery feed request");
+
+    DiscoverableMetadataProvider* m=dynamic_cast<DiscoverableMetadataProvider*>(application.getMetadataProvider());
+    if (m) {
+        Locker locker(m);
+        string feedTag = m->getCacheTag();
+        if (cacheTag == ('"' + feedTag + '"')) {
+            // The client already has the same feed we do.
+            m_log.debug("client's cache tag matches our feed (%s)", feedTag.c_str());
+            cacheTag.erase();   // clear the tag to signal no change
+            return;
+        }
+
+        cacheTag = feedTag;
+
+        // The client is out of date or not caching, so we need to see if our copy is good.
+        Lock lock(m_feedLock);
+        time_t now = time(nullptr);
+
+        // Clean up any old files.
+        while (m_feedQueue.size() > 1 && (now - m_feedQueue.front().second > 120)) {
+            string fname = m_dir + '/' + m_feedQueue.front().first;
+            remove(fname.c_str());
+            m_feedQueue.pop();
+        }
+
+        if (m_feedQueue.empty() || m_feedQueue.back().first != feedTag) {
+            // We're out of date.
+            string fname = m_dir + '/' + feedTag + ".json";
+            ofstream ofile(fname.c_str());
+            if (!ofile)
+                throw ConfigurationException("Unable to create feed in ($1).", params(1,fname.c_str()));
+            m->outputFeed(ofile);
+            ofile.close();
+            m_feedQueue.push(make_pair(feedTag, now));
+        }
+        else {
+            // Update the back of the queue.
+            m_feedQueue.back().second = now;
+        }
+    }
+    else {
+        throw MetadataException("MetadataProvider does not support discovery feed.");
+    }
+#else
+    throw ConfigurationException("Build does not support discovery feed.");
+#endif
+}
+
+void DiscoveryFeed::feedToStream(const Application& application, string& cacheTag, ostream& os) const
+{
+#ifndef SHIBSP_LITE
+    m_log.debug("processing discovery feed request");
+
+    DiscoverableMetadataProvider* m=dynamic_cast<DiscoverableMetadataProvider*>(application.getMetadataProvider());
+    if (m) {
+        Locker locker(m);
+        string feedTag = m->getCacheTag();
+        if (cacheTag == ('"' + feedTag + '"')) {
+            // The client already has the same feed we do.
+            m_log.debug("client's cache tag matches our feed (%s)", feedTag.c_str());
+            cacheTag.erase();   // clear the tag to signal no change
+            return;
+        }
+
+        cacheTag = feedTag;
+        m->outputFeed(os);
+    }
+    else {
+        throw MetadataException("MetadataProvider does not support discovery feed.");
+    }
+#else
+    throw ConfigurationException("Build does not support discovery feed.");
+#endif
+}
index 6fbf284..81a331c 100644 (file)
     <ClCompile Include="AbstractSPRequest.cpp" />\r
     <ClCompile Include="Application.cpp" />\r
     <ClCompile Include="binding\impl\XMLProtocolProvider.cpp" />\r
+    <ClCompile Include="handler\impl\DiscoveryFeed.cpp" />\r
     <ClCompile Include="handler\impl\LogoutInitiator.cpp" />\r
     <ClCompile Include="ServiceProvider.cpp" />\r
     <ClCompile Include="SPConfig.cpp" />\r
index 745daf8..2eff898 100644 (file)
     <ClCompile Include="binding\impl\XMLProtocolProvider.cpp">\r
       <Filter>Source Files\binding\impl</Filter>\r
     </ClCompile>\r
+    <ClCompile Include="handler\impl\DiscoveryFeed.cpp">\r
+      <Filter>Source Files\handler\impl</Filter>\r
+    </ClCompile>\r
   </ItemGroup>\r
   <ItemGroup>\r
     <ClInclude Include="remoting\impl\SocketListener.h">\r
index 8fc4d3c..cd56309 100644 (file)
     <ClCompile Include="AbstractSPRequest.cpp" />\r
     <ClCompile Include="Application.cpp" />\r
     <ClCompile Include="binding\impl\XMLProtocolProvider.cpp" />\r
+    <ClCompile Include="handler\impl\DiscoveryFeed.cpp" />\r
     <ClCompile Include="handler\impl\LogoutInitiator.cpp" />\r
     <ClCompile Include="impl\XMLSecurityPolicyProvider.cpp" />\r
     <ClCompile Include="ServiceProvider.cpp" />\r
index c75241f..f1da19e 100644 (file)
     <ClCompile Include="binding\impl\XMLProtocolProvider.cpp">\r
       <Filter>Source Files\binding\impl</Filter>\r
     </ClCompile>\r
+    <ClCompile Include="handler\impl\DiscoveryFeed.cpp">\r
+      <Filter>Source Files\handler\impl</Filter>\r
+    </ClCompile>\r
   </ItemGroup>\r
   <ItemGroup>\r
     <ClInclude Include="remoting\impl\SocketListener.h">\r