Adjust logging/error-handling, schema fixes, failed message handling.
[shibboleth/sp.git] / shibsp / impl / RemotedSessionCache.cpp
index 08837ae..a64222d 100644 (file)
@@ -1,5 +1,5 @@
 /*\r
- *  Copyright 2001-2005 Internet2\r
+ *  Copyright 2001-2007 Internet2\r
  * \r
  * Licensed under the Apache License, Version 2.0 (the "License");\r
  * you may not use this file except in compliance with the License.\r
@@ -49,14 +49,13 @@ namespace shibsp {
     class RemotedSession : public virtual Session\r
     {\r
     public:\r
-        RemotedSession(RemotedCache* cache, const Application& app, DDF& obj)\r
-                : m_appId(app.getId()), m_version(obj["version"].integer()), m_obj(obj),\r
-                    m_nameid(NULL), m_expires(0), m_lastAccess(time(NULL)), m_cache(cache), m_lock(NULL) {\r
+        RemotedSession(RemotedCache* cache, DDF& obj) : m_version(obj["version"].integer()), m_obj(obj),\r
+                m_nameid(NULL), m_expires(0), m_lastAccess(time(NULL)), m_cache(cache), m_lock(NULL) {\r
             const char* nameid = obj["nameid"].string();\r
             if (!nameid)\r
                 throw FatalProfileException("NameID missing from remotely cached session.");\r
             \r
-            // Parse and bind the document into an XMLObject.\r
+            // Parse and bind the NameID into an XMLObject.\r
             istringstream instr(nameid);\r
             DOMDocument* doc = XMLToolingConfig::getConfig().getParser().parse(instr); \r
             XercesJanitor<DOMDocument> janitor(doc);\r
@@ -64,8 +63,6 @@ namespace shibsp {
             n->unmarshall(doc->getDocumentElement(), true);\r
             janitor.release();\r
             \r
-            // TODO: Process attributes...\r
-\r
             auto_ptr_XMLCh exp(m_obj["expires"].string());\r
             if (exp.get()) {\r
                 DateTime iso(exp.get());\r
@@ -73,16 +70,16 @@ namespace shibsp {
                 m_expires = iso.getEpoch();\r
             }\r
 \r
-            m_nameid = n.release();\r
             m_lock = Mutex::create();\r
+            m_nameid = n.release();\r
         }\r
         \r
         ~RemotedSession() {\r
             delete m_lock;\r
             m_obj.destroy();\r
             delete m_nameid;\r
-            for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<Attribute>());\r
-            for_each(m_tokens.begin(), m_tokens.end(), xmltooling::cleanup_pair<string,RootObject>());\r
+            for_each(m_attributes.begin(), m_attributes.end(), cleanup_const_pair<string,Attribute>());\r
+            for_each(m_tokens.begin(), m_tokens.end(), cleanup_pair<string,Assertion>());\r
         }\r
         \r
         Lockable* lock() {\r
@@ -92,7 +89,10 @@ namespace shibsp {
         void unlock() {\r
             m_lock->unlock();\r
         }\r
-        \r
+\r
+        const char* getApplicationID() const {\r
+            return m_obj["application_id"].string();\r
+        }\r
         const char* getClientAddress() const {\r
             return m_obj["client_addr"].string();\r
         }\r
@@ -114,27 +114,29 @@ namespace shibsp {
         const char* getAuthnContextDeclRef() const {\r
             return m_obj["authncontext_decl"].string();\r
         }\r
-        const vector<const Attribute*>& getAttributes() const {\r
+        const map<string,const Attribute*>& getAttributes() const {\r
+            if (m_attributes.empty())\r
+                unmarshallAttributes();\r
             return m_attributes;\r
         }\r
         const vector<const char*>& getAssertionIDs() const {\r
             if (m_ids.empty()) {\r
                 DDF id = m_obj["assertions"].first();\r
                 while (id.isstring()) {\r
-                    m_ids.push_back(id.name());\r
+                    m_ids.push_back(id.string());\r
                     id = id.next();\r
                 }\r
             }\r
             return m_ids;\r
         }\r
         \r
-        const RootObject* getAssertion(const char* id) const;\r
+        const Assertion* getAssertion(const char* id) const;\r
 \r
         void addAttributes(const vector<Attribute*>& attributes) {\r
             throw ConfigurationException("addAttributes method not implemented by this session cache plugin.");\r
         }\r
-        void addAssertion(RootObject* assertion) {\r
-            throw ConfigurationException("addAttributes method not implemented by this session cache plugin.");\r
+        void addAssertion(Assertion* assertion) {\r
+            throw ConfigurationException("addAssertion method not implemented by this session cache plugin.");\r
         }\r
 \r
         time_t expires() const { return m_expires; }\r
@@ -142,13 +144,14 @@ namespace shibsp {
         void validate(const Application& application, const char* client_addr, time_t timeout, bool local=true);\r
 \r
     private:\r
-        string m_appId;\r
+        void unmarshallAttributes() const;\r
+\r
         int m_version;\r
         mutable DDF m_obj;\r
         saml2::NameID* m_nameid;\r
-        vector<const Attribute*> m_attributes;\r
+        mutable map<string,const Attribute*> m_attributes;\r
         mutable vector<const char*> m_ids;\r
-        mutable map<string,RootObject*> m_tokens;\r
+        mutable map<string,Assertion*> m_tokens;\r
         time_t m_expires,m_lastAccess;\r
         RemotedCache* m_cache;\r
         Mutex* m_lock;\r
@@ -170,7 +173,7 @@ namespace shibsp {
             const char* session_index=NULL,\r
             const char* authncontext_class=NULL,\r
             const char* authncontext_decl=NULL,\r
-            const RootObject* ssoToken=NULL,\r
+            const vector<const Assertion*>* tokens=NULL,\r
             const vector<Attribute*>* attributes=NULL\r
             );\r
         Session* find(const char* key, const Application& application, const char* client_addr=NULL, time_t timeout=0);\r
@@ -197,25 +200,51 @@ namespace shibsp {
     }\r
 }\r
 \r
-const RootObject* RemotedSession::getAssertion(const char* id) const\r
+void RemotedSession::unmarshallAttributes() const\r
 {\r
-    map<string,RootObject*>::const_iterator i = m_tokens.find(id);\r
+    Attribute* attribute;\r
+    DDF attr = m_obj["attributes"].first();\r
+    while (!attr.isnull()) {\r
+        try {\r
+            attribute = Attribute::unmarshall(attr);\r
+            m_attributes[attribute->getId()] = attribute;\r
+            if (m_cache->m_log.isDebugEnabled())\r
+                m_cache->m_log.debug("unmarshalled attribute (ID: %s) with %d value%s",\r
+                    attribute->getId(), attr.first().integer(), attr.first().integer()!=1 ? "s" : "");\r
+        }\r
+        catch (AttributeException& ex) {\r
+            const char* id = attr.first().name();\r
+            m_cache->m_log.error("error unmarshalling attribute (ID: %s): %s", id ? id : "none", ex.what());\r
+        }\r
+        attr = attr.next();\r
+    }\r
+}\r
+\r
+const Assertion* RemotedSession::getAssertion(const char* id) const\r
+{\r
+    map<string,Assertion*>::const_iterator i = m_tokens.find(id);\r
     if (i!=m_tokens.end())\r
         return i->second;\r
 \r
-    const char* tokenstr = m_obj["assertions"][id].string();\r
-    if (!tokenstr)\r
-        return NULL;\r
+    // Fetch from remoted cache.\r
+    DDF in("getAssertion::"REMOTED_SESSION_CACHE"::SessionCache");\r
+    DDFJanitor jin(in);\r
+    in.structure();\r
+    in.addmember("key").string(m_obj.name());\r
+    in.addmember("id").string(id);\r
+\r
+    DDF out=SPConfig::getConfig().getServiceProvider()->getListenerService()->send(in);\r
+    DDFJanitor jout(out);\r
     \r
     // Parse and bind the document into an XMLObject.\r
-    istringstream instr(tokenstr);\r
+    istringstream instr(out.string());\r
     DOMDocument* doc = XMLToolingConfig::getConfig().getParser().parse(instr); \r
     XercesJanitor<DOMDocument> janitor(doc);\r
     auto_ptr<XMLObject> xmlObject(XMLObjectBuilder::buildOneFromElement(doc->getDocumentElement(), true));\r
     janitor.release();\r
     \r
-    RootObject* token = dynamic_cast<RootObject*>(xmlObject.get());\r
-    if (!token || !token->isAssertion())\r
+    Assertion* token = dynamic_cast<Assertion*>(xmlObject.get());\r
+    if (!token)\r
         throw FatalProfileException("Cached assertion was of an unknown object type.");\r
 \r
     // Transfer ownership to us.\r
@@ -259,11 +288,10 @@ void RemotedSession::validate(const Application& application, const char* client
     if (local)\r
         return;\r
     \r
-    DDF in("touch::"REMOTED_SESSION_CACHE), out;\r
+    DDF in("touch::"REMOTED_SESSION_CACHE"::SessionCache"), out;\r
     DDFJanitor jin(in);\r
     in.structure();\r
     in.addmember("key").string(m_obj.name());\r
-    in.addmember("application_id").string(m_appId.c_str());\r
     in.addmember("version").integer(m_obj["version"].integer());\r
     if (timeout) {\r
         // On 64-bit Windows, time_t doesn't fit in a long, so I'm using ISO timestamps.  \r
@@ -279,23 +307,22 @@ void RemotedSession::validate(const Application& application, const char* client
     }\r
 \r
     try {\r
-        out=SPConfig::getConfig().getServiceProvider()->getListenerService()->send(in);\r
-        if (out.isstruct()) {\r
-            // We got an updated record back.\r
-            m_ids.clear();\r
-            for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<Attribute>());\r
-            m_attributes.clear();\r
-            m_obj.destroy();\r
-            m_obj = out;\r
-            \r
-            // TODO: handle attributes\r
-        }\r
+        out=application.getServiceProvider().getListenerService()->send(in);\r
     }\r
     catch (...) {\r
         out.destroy();\r
         throw;\r
     }\r
-    \r
+\r
+    if (out.isstruct()) {\r
+        // We got an updated record back.\r
+        m_ids.clear();\r
+        for_each(m_attributes.begin(), m_attributes.end(), cleanup_const_pair<string,Attribute>());\r
+        m_attributes.clear();\r
+        m_obj.destroy();\r
+        m_obj = out;\r
+    }\r
+\r
     m_lastAccess = now;\r
 }\r
 \r
@@ -332,11 +359,11 @@ string RemotedCache::insert(
     const char* session_index,\r
     const char* authncontext_class,\r
     const char* authncontext_decl,\r
-    const RootObject* ssoToken,\r
+    const vector<const Assertion*>* tokens,\r
     const vector<Attribute*>* attributes\r
     )\r
 {\r
-    DDF in("insert::"REMOTED_SESSION_CACHE);\r
+    DDF in("insert::"REMOTED_SESSION_CACHE"::SessionCache");\r
     DDFJanitor jin(in);\r
     in.structure();\r
     if (expires) {\r
@@ -369,11 +396,18 @@ string RemotedCache::insert(
     namestr << nameid;\r
     in.addmember("nameid").string(namestr.str().c_str());\r
 \r
-    if (ssoToken) {\r
-        ostringstream tokenstr;\r
-        tokenstr << *ssoToken;\r
-        auto_ptr_char tokenid(ssoToken->getID());\r
-        in.addmember("assertions").list().add(DDF(tokenid.get()).string(tokenstr.str().c_str()));\r
+    if (tokens) {\r
+        in.addmember("assertions").list();\r
+        in.addmember("tokens").list();\r
+        for (vector<const Assertion*>::const_iterator t = tokens->begin(); t!=tokens->end(); ++t) {\r
+            ostringstream tokenstr;\r
+            tokenstr << *(*t);\r
+            auto_ptr_char tokenid((*t)->getID());\r
+            DDF tokid = DDF(NULL).string(tokenid.get());\r
+            in["assertions"].add(tokid);\r
+            DDF tok = DDF(tokenid.get()).string(tokenstr.str().c_str());\r
+            in["tokens"].add(tok);\r
+        }\r
     }\r
     \r
     if (attributes) {\r
@@ -385,15 +419,13 @@ string RemotedCache::insert(
         }\r
     }\r
 \r
-    DDF out=SPConfig::getConfig().getServiceProvider()->getListenerService()->send(in);\r
+    DDF out=application.getServiceProvider().getListenerService()->send(in);\r
     DDFJanitor jout(out);\r
     if (out["key"].isstring()) {\r
-        for_each(attributes->begin(), attributes->end(), xmltooling::cleanup<Attribute>());\r
-\r
         // Transaction Logging\r
         auto_ptr_char name(nameid.getName());\r
         const char* pid = in["entity_id"].string();\r
-        TransactionLog* xlog = SPConfig::getConfig().getServiceProvider()->getTransactionLog();\r
+        TransactionLog* xlog = application.getServiceProvider().getTransactionLog();\r
         Locker locker(xlog);\r
         xlog->log.infoStream() <<\r
             "New session (ID: " <<\r
@@ -408,6 +440,19 @@ string RemotedCache::insert(
                 name.get() <<\r
             ")";\r
 \r
+        if (attributes) {\r
+            xlog->log.infoStream() <<\r
+                "Cached the following attributes with session (ID: " <<\r
+                    out["key"].string() <<\r
+                ") for (applicationId: " <<\r
+                    application.getId() <<\r
+                ") {";\r
+            for (vector<Attribute*>::const_iterator a=attributes->begin(); a!=attributes->end(); ++a)\r
+                xlog->log.infoStream() << "\t" << (*a)->getId() << " (" << (*a)->valueCount() << " values)";\r
+            xlog->log.info("}");\r
+            for_each(attributes->begin(), attributes->end(), xmltooling::cleanup<Attribute>());\r
+        }\r
+\r
         return out["key"].string();\r
     }\r
     throw RetryableProfileException("A remoted cache insertion operation did not return a usable session key.");\r
@@ -428,11 +473,10 @@ Session* RemotedCache::find(const char* key, const Application& application, con
         m_lock->unlock();\r
         m_log.debug("session not found locally, searching remote cache");\r
 \r
-        DDF in("find::"REMOTED_SESSION_CACHE), out;\r
+        DDF in("find::"REMOTED_SESSION_CACHE"::SessionCache"), out;\r
         DDFJanitor jin(in);\r
         in.structure();\r
         in.addmember("key").string(key);\r
-        in.addmember("application_id").string(application.getId());\r
         if (timeout) {\r
             // On 64-bit Windows, time_t doesn't fit in a long, so I'm using ISO timestamps.  \r
 #ifndef HAVE_GMTIME_R\r
@@ -447,7 +491,7 @@ Session* RemotedCache::find(const char* key, const Application& application, con
         }\r
         \r
         try {\r
-            out=SPConfig::getConfig().getServiceProvider()->getListenerService()->send(in);\r
+            out=application.getServiceProvider().getListenerService()->send(in);\r
             if (!out.isstruct()) {\r
                 out.destroy();\r
                 m_log.debug("session not found in remote cache");\r
@@ -455,7 +499,7 @@ Session* RemotedCache::find(const char* key, const Application& application, con
             }\r
             \r
             // Wrap the results in a local entry and save it.\r
-            session = new RemotedSession(this, application, out);\r
+            session = new RemotedSession(this, out);\r
             // The remote end has handled timeout issues, we handle address and expiration checks.\r
             localValidation = true;\r
         }\r
@@ -488,12 +532,20 @@ Session* RemotedCache::find(const char* key, const Application& application, con
         m_log.debug("session found locally, validating it for use");\r
     }\r
 \r
-    // Verify currency and update the timestamp. If the local switch is false, we also update the access time.\r
+    if (!XMLString::equals(session->getApplicationID(), application.getId())) {\r
+        m_log.error("an application (%s) tried to access another application's session", application.getId());\r
+        session->unlock();\r
+        return NULL;\r
+    }\r
+\r
+    // Verify currency and update the timestamp.\r
+    // If the local switch is false, we also update the access time.\r
     try {\r
         session->validate(application, client_addr, timeout, localValidation);\r
     }\r
     catch (...) {\r
         session->unlock();\r
+        remove(key, application, client_addr);\r
         throw;\r
     }\r
     \r
@@ -506,14 +558,14 @@ void RemotedCache::remove(const char* key, const Application& application, const
     dormant(key);\r
     \r
     // Now remote...\r
-    DDF in("remove::"REMOTED_SESSION_CACHE);\r
+    DDF in("remove::"REMOTED_SESSION_CACHE"::SessionCache");\r
     DDFJanitor jin(in);\r
     in.structure();\r
     in.addmember("key").string(key);\r
     in.addmember("application_id").string(application.getId());\r
     in.addmember("client_addr").string(client_addr);\r
     \r
-    DDF out = SPConfig::getConfig().getServiceProvider()->getListenerService()->send(in);\r
+    DDF out = application.getServiceProvider().getListenerService()->send(in);\r
     out.destroy();\r
 }\r
 \r
@@ -555,17 +607,16 @@ void RemotedCache::cleanup()
     xmltooling::NDC ndc("cleanup");\r
 #endif\r
 \r
-    int rerun_timer = 0;\r
     Mutex* mutex = Mutex::create();\r
   \r
     // Load our configuration details...\r
     static const XMLCh cleanupInterval[] = UNICODE_LITERAL_15(c,l,e,a,n,u,p,I,n,t,e,r,v,a,l);\r
-    const XMLCh* tag=m_root->getAttributeNS(NULL,cleanupInterval);\r
+    const XMLCh* tag=m_root ? m_root->getAttributeNS(NULL,cleanupInterval) : NULL;\r
+    int rerun_timer = 900;\r
     if (tag && *tag)\r
         rerun_timer = XMLString::parseInt(tag);\r
-\r
     if (rerun_timer <= 0)\r
-        rerun_timer = 300;        // rerun every 5 minutes\r
+        rerun_timer = 900;\r
 \r
     mutex->lock();\r
 \r
@@ -652,9 +703,9 @@ void RemotedSession::addAttributes(const vector<Attribute*>& attributes)
     m_attributes.insert(m_attributes.end(), attributes.begin(), attributes.end());\r
 }\r
 \r
-void RemotedSession::addAssertion(RootObject* assertion)\r
+void RemotedSession::addAssertion(Assertion* assertion)\r
 {\r
-    if (!assertion || !assertion->isAssertion())\r
+    if (!assertion)\r
         throw FatalProfileException("Unknown object type passed to session cache for storage.");\r
 \r
     DDF in("addAssertion::"REMOTED_SESSION_CACHE);\r