A "simple" attribute resolver, and token validation.
[shibboleth/sp.git] / shibsp / impl / XMLServiceProvider.cpp
index bff5781..e6d9da8 100644 (file)
@@ -66,6 +66,18 @@ namespace {
     #pragma warning( disable : 4250 )\r
 #endif\r
 \r
+    class SHIBSP_DLLLOCAL TokenValidator : public Validator\r
+    {\r
+    public:\r
+        TokenValidator(const Application& app, time_t ts=0, const RoleDescriptor* role=NULL) : m_app(app), m_ts(ts), m_role(role) {}\r
+        void validate(const XMLObject*) const;\r
+\r
+    private:\r
+        const Application& m_app;\r
+        time_t m_ts;\r
+        const RoleDescriptor* m_role;\r
+    };\r
+\r
     static vector<const Handler*> g_noHandlers;\r
 \r
     // Application configuration wrapper\r
@@ -89,7 +101,6 @@ namespace {
         const char* getHash() const {return m_hash.c_str();}\r
         MetadataProvider* getMetadataProvider() const;\r
         TrustEngine* getTrustEngine() const;\r
-        const vector<const XMLCh*>& getAudiences() const;\r
         const PropertySet* getCredentialUse(const EntityDescriptor* provider) const;\r
 \r
         const Handler* getDefaultSessionInitiator() const;\r
@@ -98,7 +109,14 @@ namespace {
         const Handler* getAssertionConsumerServiceByIndex(unsigned short index) const;\r
         const vector<const Handler*>& getAssertionConsumerServicesByBinding(const XMLCh* binding) const;\r
         const Handler* getHandler(const char* path) const;\r
-        \r
+\r
+        const vector<const XMLCh*>& getAudiences() const;\r
+        Validator* getTokenValidator(time_t ts=0, const opensaml::saml2md::RoleDescriptor* role=NULL) const {\r
+            return new TokenValidator(*this, ts, role);\r
+        }\r
+\r
+        void validator(const XMLObject* xmlObject) const;\r
+\r
         // Provides filter to exclude special config elements.\r
         short acceptNode(const DOMNode* node) const;\r
     \r
@@ -327,6 +345,122 @@ namespace shibsp {
     }\r
 };\r
 \r
+void TokenValidator::validate(const XMLObject* xmlObject) const\r
+{\r
+#ifdef _DEBUG\r
+    xmltooling::NDC ndc("validate");\r
+#endif\r
+    Category& log=Category::getInstance(SHIBSP_LOGCAT".Application");\r
+\r
+    const opensaml::RootObject* root = NULL;\r
+    const opensaml::saml2::Assertion* token2 = dynamic_cast<const opensaml::saml2::Assertion*>(xmlObject);\r
+    if (token2) {\r
+        const opensaml::saml2::Conditions* conds = token2->getConditions();\r
+        // First verify the time conditions, using the specified timestamp, if non-zero.\r
+        if (m_ts>0 && conds) {\r
+            unsigned int skew = XMLToolingConfig::getConfig().clock_skew_secs;\r
+            time_t t=conds->getNotBeforeEpoch();\r
+            if (m_ts+skew < t)\r
+                throw ValidationException("Assertion is not yet valid.");\r
+            t=conds->getNotOnOrAfterEpoch();\r
+            if (t <= m_ts-skew)\r
+                throw ValidationException("Assertion is no longer valid.");\r
+        }\r
+\r
+        // Now we process conditions. Only audience restrictions at the moment.\r
+        const vector<opensaml::saml2::Condition*>& convec = conds->getConditions();\r
+        for (vector<opensaml::saml2::Condition*>::const_iterator c = convec.begin(); c!=convec.end(); ++c) {\r
+            const opensaml::saml2::AudienceRestriction* ac=dynamic_cast<const opensaml::saml2::AudienceRestriction*>(*c);\r
+            if (!ac) {\r
+                log.error("unrecognized Condition in assertion (%s)",\r
+                    (*c)->getSchemaType() ? (*c)->getSchemaType()->toString().c_str() : (*c)->getElementQName().toString().c_str());\r
+                throw ValidationException("Assertion contains an unrecognized condition.");\r
+            }\r
+\r
+            bool found = false;\r
+            const vector<opensaml::saml2::Audience*>& auds1 = ac->getAudiences();\r
+            const vector<const XMLCh*>& auds2 = m_app.getAudiences();\r
+            for (vector<opensaml::saml2::Audience*>::const_iterator a = auds1.begin(); !found && a!=auds1.end(); ++a) {\r
+                for (vector<const XMLCh*>::const_iterator a2 = auds2.begin(); !found && a2!=auds2.end(); ++a2) {\r
+                    found = XMLString::equals((*a)->getAudienceURI(), *a2);\r
+                }\r
+            }\r
+\r
+            if (!found) {\r
+                ostringstream os;\r
+                os << *ac;\r
+                log.error("unacceptable AudienceRestriction in assertion (%s)", os.str().c_str());\r
+                throw ValidationException("Assertion contains an unacceptable AudienceRestriction.");\r
+            }\r
+        }\r
+\r
+        root = token2;\r
+    }\r
+    else {\r
+        const opensaml::saml1::Assertion* token1 = dynamic_cast<const opensaml::saml1::Assertion*>(xmlObject);\r
+        if (token1) {\r
+            const opensaml::saml1::Conditions* conds = token1->getConditions();\r
+            // First verify the time conditions, using the specified timestamp, if non-zero.\r
+            if (m_ts>0 && conds) {\r
+                unsigned int skew = XMLToolingConfig::getConfig().clock_skew_secs;\r
+                time_t t=conds->getNotBeforeEpoch();\r
+                if (m_ts+skew < t)\r
+                    throw ValidationException("Assertion is not yet valid.");\r
+                t=conds->getNotOnOrAfterEpoch();\r
+                if (t <= m_ts-skew)\r
+                    throw ValidationException("Assertion is no longer valid.");\r
+            }\r
+\r
+            // Now we process conditions. Only audience restrictions at the moment.\r
+            const vector<opensaml::saml1::Condition*>& convec = conds->getConditions();\r
+            for (vector<opensaml::saml1::Condition*>::const_iterator c = convec.begin(); c!=convec.end(); ++c) {\r
+                const opensaml::saml1::AudienceRestrictionCondition* ac=dynamic_cast<const opensaml::saml1::AudienceRestrictionCondition*>(*c);\r
+                if (!ac) {\r
+                    log.error("unrecognized Condition in assertion (%s)",\r
+                        (*c)->getSchemaType() ? (*c)->getSchemaType()->toString().c_str() : (*c)->getElementQName().toString().c_str());\r
+                    throw ValidationException("Assertion contains an unrecognized condition.");\r
+                }\r
+\r
+                bool found = false;\r
+                const vector<opensaml::saml1::Audience*>& auds1 = ac->getAudiences();\r
+                const vector<const XMLCh*>& auds2 = m_app.getAudiences();\r
+                for (vector<opensaml::saml1::Audience*>::const_iterator a = auds1.begin(); !found && a!=auds1.end(); ++a) {\r
+                    for (vector<const XMLCh*>::const_iterator a2 = auds2.begin(); !found && a2!=auds2.end(); ++a2) {\r
+                        found = XMLString::equals((*a)->getAudienceURI(), *a2);\r
+                    }\r
+                }\r
+\r
+                if (!found) {\r
+                    ostringstream os;\r
+                    os << *ac;\r
+                    log.error("unacceptable AudienceRestrictionCondition in assertion (%s)", os.str().c_str());\r
+                    throw ValidationException("Assertion contains an unacceptable AudienceRestrictionCondition.");\r
+                }\r
+            }\r
+\r
+            root = token1;\r
+        }\r
+        else {\r
+            throw ValidationException("Unknown object type passed to token validator.");\r
+        }\r
+    }\r
+\r
+    if (!m_role || !m_app.getTrustEngine()) {\r
+        log.warn("no issuer role or TrustEngine provided, so no signature validation performed");\r
+        return;\r
+    }\r
+\r
+    const PropertySet* policy=m_app.getServiceProvider().getPolicySettings(m_app.getString("policyId").second);\r
+    pair<bool,bool> signedAssertions=policy ? policy->getBool("signedAssertions") : make_pair(false,false);\r
+\r
+    if (root->getSignature()) {\r
+        if (!m_app.getTrustEngine()->validate(*(root->getSignature()),*m_role))\r
+            throw ValidationException("Assertion signature did not validate.");\r
+    }\r
+    else if (signedAssertions.first && signedAssertions.second)\r
+        throw ValidationException("Assertion was unsigned, violating policy.");\r
+}\r
+\r
 XMLApplication::XMLApplication(\r
     const ServiceProvider* sp,\r
     const DOMElement* e,\r
@@ -466,7 +600,7 @@ XMLApplication::XMLApplication(
         // Always include our own providerId as an audience.\r
         m_audiences.push_back(getXMLString("providerId").second);\r
 \r
-        if (conf.isEnabled(SPConfig::AttributeResolver)) {\r
+        if (conf.isEnabled(SPConfig::AttributeResolution)) {\r
             // TODO\r
         }\r
 \r