A "simple" attribute resolver, and token validation.
authorcantor <cantor@cb58f699-b61c-0410-a6fe-9272a202ed29>
Sat, 24 Feb 2007 22:04:37 +0000 (22:04 +0000)
committercantor <cantor@cb58f699-b61c-0410-a6fe-9272a202ed29>
Sat, 24 Feb 2007 22:04:37 +0000 (22:04 +0000)
git-svn-id: https://svn.middleware.georgetown.edu/cpp-sp/trunk@2177 cb58f699-b61c-0410-a6fe-9272a202ed29

20 files changed:
.cdtproject
schemas/Makefile.am
schemas/catalog.xml.in
schemas/shibboleth-2.0-simple-resolver.xsd [new file with mode: 0644]
schemas/shibboleth-spconfig-2.0.xsd
shibd/shibd.cpp
shibsp/Application.h
shibsp/Makefile.am
shibsp/SPConfig.cpp
shibsp/SPConfig.h
shibsp/SessionCache.h
shibsp/attribute/resolver/AttributeResolver.h [new file with mode: 0644]
shibsp/attribute/resolver/ResolutionContext.h [new file with mode: 0644]
shibsp/attribute/resolver/impl/AttributeResolver.cpp [new file with mode: 0644]
shibsp/attribute/resolver/impl/SimpleAttributeResolver.cpp [new file with mode: 0644]
shibsp/binding/SOAPClient.h
shibsp/exceptions.h
shibsp/impl/XMLServiceProvider.cpp
shibsp/shibsp.vcproj
util/samlquery.cpp

index 79144f7..fa13b21 100644 (file)
@@ -1,15 +1,17 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<?xml version="1.0" encoding="UTF-8"?>
 <?eclipse-cdt version="2.0"?>
 
 <cdtproject id="org.eclipse.cdt.make.core.make">
-<extension id="org.eclipse.cdt.core.domsourceindexer" point="org.eclipse.cdt.core.CIndexer"/>
 <extension id="org.eclipse.cdt.core.PE" point="org.eclipse.cdt.core.BinaryParser"/>
+<extension id="org.eclipse.cdt.core.domsourceindexer" point="org.eclipse.cdt.core.CIndexer"/>
 <data>
         
     <item id="org.eclipse.cdt.core.pathentry">
 <pathentry kind="out" path=""/>
-<pathentry excluding="util/|impl/|security/|metadata/|remoting/|remoting/impl/|attribute/|binding/|binding/impl/" kind="src" path="shibsp"/>
-<pathentry kind="src" path="shibsp/attribute"/>
+<pathentry excluding="util/|impl/|security/|metadata/|remoting/|remoting/impl/|attribute/|binding/|binding/impl/|attribute/resolver/|attribute/resolver/impl/" kind="src" path="shibsp"/>
+<pathentry excluding="resolver/|resolver/impl/" kind="src" path="shibsp/attribute"/>
+<pathentry excluding="impl/" kind="src" path="shibsp/attribute/resolver"/>
+<pathentry kind="src" path="shibsp/attribute/resolver/impl"/>
 <pathentry excluding="impl/" kind="src" path="shibsp/binding"/>
 <pathentry kind="src" path="shibsp/binding/impl"/>
 <pathentry kind="src" path="shibsp/impl"/>
index 13d853c..5d83cd5 100644 (file)
@@ -10,6 +10,7 @@ pkgxml_DATA = \
     shibboleth-metadata-1.0.xsd \
     shibboleth-spconfig-2.0.xsd \
     shibboleth-2.0-attribute-resolver.xsd \
+    shibboleth-2.0-simple-resolver.xsd \
     metadata_v12_to_v13.xsl \
     metadata_v13_to_v12.xsl \
     trust_v13_to_v12.xsl
index 7d90453..d1ea637 100644 (file)
@@ -2,5 +2,7 @@
 <catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">
     <system systemId="urn:mace:shibboleth:metadata:1.0" uri="@-PKGXMLDIR-@/shibboleth-metadata-1.0.xsd"/>
     <system systemId="urn:mace:shibboleth:sp:config:2.0" uri="@-PKGXMLDIR-@/shibboleth-spconfig-2.0.xsd"/>
+    <system systemId="urn:mace:shibboleth:2.0:resolver" uri="@-PKGXMLDIR-@/shibboleth-2.0-attribute-resolver.xsd"/>
+    <system systemId="urn:mace:shibboleth:2.0:resolver:simple" uri="@-PKGXMLDIR-@/shibboleth-2.0-simple-resolver.xsd"/>
     <system systemId="urn:mace:shibboleth:1.0" uri="@-PKGXMLDIR-@/shibboleth.xsd"/>
 </catalog>
diff --git a/schemas/shibboleth-2.0-simple-resolver.xsd b/schemas/shibboleth-2.0-simple-resolver.xsd
new file mode 100644 (file)
index 0000000..2e0ac6d
--- /dev/null
@@ -0,0 +1,30 @@
+<schema targetNamespace="urn:mace:shibboleth:2.0:resolver:simple" xmlns="http://www.w3.org/2001/XMLSchema"
+    xmlns:resolver="urn:mace:shibboleth:2.0:resolver:simple"
+    xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
+
+    <annotation>
+        <documentation>Shibboleth 2.0 Simple Attribute Resolver configuration schema</documentation>
+    </annotation>
+
+    <import namespace="urn:oasis:names:tc:SAML:2.0:assertion" schemaLocation="saml-schema-assertion-2.0.xsd"/>
+    
+    <element name="AttributeResolver" type="resolver:AttributeResolverType">
+        <annotation>
+            <documentation>Root of the simple attribute resolver configuration file.</documentation>
+        </annotation>
+    </element>
+    <complexType name="AttributeResolverType">
+        <sequence>
+            <element ref="saml:Attribute" minOccurs="0" maxOccurs="unbounded"/>
+        </sequence>
+        <attribute name="allowQuery" type="boolean" default="true"/>
+    </complexType>
+
+    <simpleType name="string">
+        <restriction base="string">
+            <minLength value="1"/>
+        </restriction>
+    </simpleType>
+
+    <attribute name="decoderType" type="resolver:string"/>
+</schema>
index de85b41..ef03152 100644 (file)
@@ -9,8 +9,8 @@
        blockDefault="substitution"\r
        version="2.0">\r
 \r
-       <import namespace="urn:oasis:names:tc:SAML:2.0:assertion" schemaLocation="../../cpp-opensaml2/schemas/saml-schema-assertion-2.0.xsd"/>\r
-       <import namespace="urn:oasis:names:tc:SAML:2.0:metadata" schemaLocation="../../cpp-opensaml2/schemas/saml-schema-metadata-2.0.xsd"/>\r
+       <import namespace="urn:oasis:names:tc:SAML:2.0:assertion" schemaLocation="saml-schema-assertion-2.0.xsd"/>\r
+       <import namespace="urn:oasis:names:tc:SAML:2.0:metadata" schemaLocation="saml-schema-metadata-2.0.xsd"/>\r
 \r
        <annotation>\r
                <documentation>\r
index 7b72b09..3d0632a 100644 (file)
@@ -114,7 +114,7 @@ int real_main(int preinit)
             SPConfig::Metadata |\r
             SPConfig::Trust |\r
             SPConfig::Credentials |\r
-            SPConfig::AttributeResolver |\r
+            SPConfig::AttributeResolution |\r
             SPConfig::OutOfProcess |\r
             (shar_checkonly ? (SPConfig::InProcess | SPConfig::RequestMapping) : SPConfig::Logging)\r
             );\r
@@ -278,7 +278,7 @@ int main(int argc, char *argv[])
         SPConfig::Metadata |\r
         SPConfig::Trust |\r
         SPConfig::Credentials |\r
-        SPConfig::AttributeResolver |\r
+        SPConfig::AttributeResolution |\r
         SPConfig::OutOfProcess |\r
         (shar_checkonly ? (SPConfig::InProcess | SPConfig::RequestMapping) : SPConfig::Logging)\r
         );\r
index c8f262d..eb90c61 100644 (file)
@@ -26,6 +26,7 @@
 #include <shibsp/util/PropertySet.h>
 #include <saml/saml2/metadata/MetadataProvider.h>
 #include <xmltooling/security/TrustEngine.h>
+#include <xmltooling/validation/Validator.h>
 
 namespace shibsp {
     
@@ -155,6 +156,17 @@ namespace shibsp {
          * @return set of audience values associated with the Application
          */
         virtual const std::vector<const XMLCh*>& getAudiences() const=0;
+
+        /**
+         * Returns a validator for applying verification rules to incoming SAML tokens.
+         *
+         * <p>The validator must be freed by the caller.
+         * 
+         * @param ts    timestamp against which to evaluate the token's validity, or 0 to ignore
+         * @param role  metadata role of token issuer, if known
+         * @return a validator
+         */
+        virtual xmltooling::Validator* getTokenValidator(time_t ts=0, const opensaml::saml2md::RoleDescriptor* role=NULL) const=0;
     };
 };
 
index 85a5bf5..11b5fc4 100644 (file)
@@ -9,6 +9,8 @@ libshibspincludedir = $(includedir)/shibsp
 
 attrincludedir = $(includedir)/shibsp/attribute
 
+attrresincludedir = $(includedir)/shibsp/attribute/resolver
+
 bindincludedir = $(includedir)/shibsp/binding
 
 mdincludedir = $(includedir)/shibsp/metadata
@@ -43,6 +45,10 @@ attrinclude_HEADERS = \
        attribute/ScopedAttribute.h \
        attribute/SimpleAttribute.h
 
+attrresinclude_HEADERS = \
+       attribute/resolver/AttributeResolver.h \
+       attribute/resolver/ResolutionContext.h
+
 bindinclude_HEADERS = \
        binding/SOAPClient.h
        
@@ -77,6 +83,8 @@ libshibsp_la_SOURCES = \
        attribute/NameIDAttributeDecoder.cpp \
        attribute/SimpleAttributeDecoder.cpp \
        attribute/ScopedAttributeDecoder.cpp \
+       attribute/resolver/impl/AttributeResolver.cpp \
+       attribute/resolver/impl/SimpleAttributeResolver.cpp \
        binding/impl/SOAPClient.cpp \
        impl/RemotedSessionCache.cpp \
        impl/StorageServiceSessionCache.cpp \
index 4bcd466..cefcc17 100644 (file)
@@ -30,6 +30,7 @@
 #include "SessionCache.h"
 #include "SPConfig.h"
 #include "attribute/AttributeDecoder.h"
+#include "attribute/resolver/AttributeResolver.h"
 #include "metadata/MetadataExt.h"
 #include "remoting/ListenerService.h"
 #include "security/PKIXTrustEngine.h"
@@ -44,6 +45,8 @@ using namespace opensaml;
 using namespace xmltooling;
 using namespace log4cpp;
 
+DECL_XMLTOOLING_EXCEPTION_FACTORY(AttributeException,shibsp);
+DECL_XMLTOOLING_EXCEPTION_FACTORY(AttributeResolutionException,shibsp);
 DECL_XMLTOOLING_EXCEPTION_FACTORY(ConfigurationException,shibsp);
 DECL_XMLTOOLING_EXCEPTION_FACTORY(ListenerException,shibsp);
 
@@ -94,18 +97,22 @@ bool SPInternalConfig::init(const char* catalog_path)
     XMLToolingConfig::getConfig().setTemplateEngine(new TemplateEngine());
     XMLToolingConfig::getConfig().getTemplateEngine()->setTagPrefix("shibmlp");
     
+    REGISTER_XMLTOOLING_EXCEPTION_FACTORY(AttributeException,shibsp);
+    REGISTER_XMLTOOLING_EXCEPTION_FACTORY(AttributeResolutionException,shibsp);
     REGISTER_XMLTOOLING_EXCEPTION_FACTORY(ConfigurationException,shibsp);
     REGISTER_XMLTOOLING_EXCEPTION_FACTORY(ListenerException,shibsp);
     
     registerMetadataExtClasses();
     registerPKIXTrustEngine();
+
     registerAccessControls();
+    registerAttributeDecoders();
+    registerAttributeFactories();
+    registerAttributeResolvers();
     registerListenerServices();
     registerRequestMappers();
     registerSessionCaches();
     registerServiceProviders();
-    registerAttributeFactories();
-    registerAttributeDecoders();
     
     log.info("library initialization complete");
     return true;
@@ -122,17 +129,19 @@ void SPInternalConfig::term()
     delete m_serviceProvider;
     m_serviceProvider = NULL;
 
-    SingleLogoutServiceManager.deregisterFactories();
+    AssertionConsumerServiceManager.deregisterFactories();
+    ManageNameIDServiceManager.deregisterFactories();
     SessionInitiatorManager.deregisterFactories();
-    SessionCacheManager.deregisterFactories();
+    SingleLogoutServiceManager.deregisterFactories();
+    
     ServiceProviderManager.deregisterFactories();
+    SessionCacheManager.deregisterFactories();
     RequestMapperManager.deregisterFactories();
-    ManageNameIDServiceManager.deregisterFactories();
     ListenerServiceManager.deregisterFactories();
     HandlerManager.deregisterFactories();
-    Attribute::deregisterFactories();
+    AttributeResolverManager.deregisterFactories();
     AttributeDecoderManager.deregisterFactories();
-    AssertionConsumerServiceManager.deregisterFactories();
+    Attribute::deregisterFactories();
     AccessControlManager.deregisterFactories();
 
     SAMLConfig::getConfig().term();
index 78a4ff8..c785e87 100644 (file)
@@ -35,6 +35,7 @@ namespace shibsp {
 
     class SHIBSP_API AccessControl;
     class SHIBSP_API AttributeDecoder;
+    class SHIBSP_API AttributeResolver;
     class SHIBSP_API Handler;
     class SHIBSP_API ListenerService;
     class SHIBSP_API RequestMapper;
@@ -71,7 +72,7 @@ namespace shibsp {
             Metadata = 4,
             Trust = 8,
             Credentials = 16,
-            AttributeResolver = 32,
+            AttributeResolution = 32,
             RequestMapping = 64,
             OutOfProcess = 128,
             InProcess = 256,
@@ -148,6 +149,11 @@ namespace shibsp {
         xmltooling::PluginManager<AttributeDecoder,const xercesc::DOMElement*> AttributeDecoderManager;
 
         /**
+         * Manages factories for AttributeResolver plugins.
+         */
+        xmltooling::PluginManager<AttributeResolver,const xercesc::DOMElement*> AttributeResolverManager;
+
+        /**
          * Manages factories for Handler plugins that implement AssertionConsumerService functionality.
          */
         xmltooling::PluginManager<Handler,const xercesc::DOMElement*> AssertionConsumerServiceManager;
index d350927..a187757 100644 (file)
@@ -175,8 +175,8 @@ namespace shibsp {
          * @param application       reference to Application that owns the Session
          * @param client_addr       network address of client
          * @param issuer            issuing metadata of assertion issuer, if known
-         * @param authn_instant     UTC timestamp of authentication at IdP
          * @param nameid            principal identifier, normalized to SAML 2
+         * @param authn_instant     UTC timestamp of authentication at IdP
          * @param session_index     index of session between principal and IdP
          * @param authncontext_class    method/category of authentication event
          * @param authncontext_decl specifics of authentication event 
diff --git a/shibsp/attribute/resolver/AttributeResolver.h b/shibsp/attribute/resolver/AttributeResolver.h
new file mode 100644 (file)
index 0000000..78921a6
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+ *  Copyright 2001-2007 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.
+ */
+
+/**
+ * @file shibsp/attribute/resolver/AttributeResolver.h
+ * 
+ * The service that resolves the attributes for a particular subject.
+ */
+
+#ifndef __shibsp_resolver_h__
+#define __shibsp_resolver_h__
+
+#include <shibsp/base.h>
+
+#include <saml/saml1/core/Assertions.h>
+#include <saml/saml2/metadata/Metadata.h>
+#include <xmltooling/Lockable.h>
+
+namespace shibsp {
+
+    class SHIBSP_API Application;
+    class SHIBSP_API Attribute;
+    class SHIBSP_API Session;
+    class SHIBSP_API ResolutionContext;
+
+#if defined (_MSC_VER)
+    #pragma warning( push )
+    #pragma warning( disable : 4250 4251 )
+#endif
+
+    /**
+     * The service that resolves the attributes for a particular subject.
+     */
+    class SHIBSP_API AttributeResolver : public virtual xmltooling::Lockable
+    {
+        MAKE_NONCOPYABLE(AttributeResolver);
+    protected:
+        AttributeResolver() {}
+    public:
+        virtual ~AttributeResolver() {}
+
+        /**
+         * Creates a ResolutionContext based on session bootstrap material.
+         * 
+         * <p>This enables resolution to occur ahead of session creation so that
+         * Attributes can be supplied while creating the session.
+         * 
+         * @param application       reference to Application that owns the eventual Session
+         * @param client_addr       network address of client
+         * @param issuer            issuing metadata of assertion issuer, if known
+         * @param nameid            principal identifier, normalized to SAML 2
+         * @param ssoToken          SSO assertion initiating the session, if any
+         * @return  newly created ResolutionContext, owned by caller
+         */
+        virtual ResolutionContext* createResolutionContext(
+            const Application& application,
+            const char* client_addr,
+            const opensaml::saml2md::EntityDescriptor* issuer,
+            const opensaml::saml2::NameID& nameid,
+            const opensaml::RootObject* ssoToken=NULL
+            ) const=0;
+
+        /**
+         * Creates a ResolutionContext for an existing Session.
+         * 
+         * @param application   reference to Application that owns the Session
+         * @param session       reference to Session
+         * @return  newly created ResolutionContext, owned by caller
+         */
+        virtual ResolutionContext* createResolutionContext(const Application& application, const Session& session) const=0;
+        
+
+        /**
+         * Gets the attributes for a given subject and returns them in the supplied context.
+         * 
+         * @param ctx           resolution context to use to resolve attributes
+         * @param attributes    list of attributes to resolve or NULL to resolve all attributes
+         * 
+         * @throws AttributeResolutionException thrown if there is a problem resolving the attributes for the subject
+         */
+        virtual void resolveAttributes(ResolutionContext& ctx, const std::vector<const char*>* attributes=NULL) const=0;
+    };
+
+#if defined (_MSC_VER)
+    #pragma warning( pop )
+#endif
+
+    /**
+     * Registers AttributeResolver classes into the runtime.
+     */
+    void SHIBSP_API registerAttributeResolvers();
+
+    /** AttributeResolver based on a simple mapping of SAML information. */
+    #define SIMPLE_ATTRIBUTE_RESOLVER "Simple"
+};
+
+#endif /* __shibsp_resolver_h__ */
diff --git a/shibsp/attribute/resolver/ResolutionContext.h b/shibsp/attribute/resolver/ResolutionContext.h
new file mode 100644 (file)
index 0000000..c005688
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ *  Copyright 2001-2007 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.
+ */
+
+/**
+ * @file shibsp/attribute/resolver/ResolutionContext.h
+ * 
+ * A context for a resolution request.
+ */
+
+#ifndef __shibsp_resctx_h__
+#define __shibsp_resctx_h__
+
+#include <shibsp/base.h>
+
+#include <saml/saml1/core/Assertions.h>
+#include <saml/saml2/metadata/Metadata.h>
+
+namespace shibsp {
+
+    class SHIBSP_API Application;
+    class SHIBSP_API Session;
+
+    /**
+     * A context for a resolution request.
+     */
+    class SHIBSP_API ResolutionContext
+    {
+        MAKE_NONCOPYABLE(ResolutionContext);
+    protected:
+        ResolutionContext() {}
+    public:
+        virtual ~ResolutionContext() {}
+
+        /**
+         * Returns the application resolving the attributes.
+         * 
+         * @return  the resolving application
+         */
+        virtual const Application& getApplication() const=0;
+        
+        /**
+         * Returns the address of the client associated with the subject.
+         * 
+         * @return  the client's network address
+         */
+        virtual const char* getClientAddress() const=0;
+
+        /**
+         * Returns the metadata for the IdP associated with the subject, if any.
+         * 
+         * @return the IdP's metadata, or NULL
+         */
+        virtual const opensaml::saml2md::EntityDescriptor* getEntityDescriptor() const=0;
+
+        /**
+         * Returns the NameID associated with the subject
+         * 
+         * <p>SAML 1.x identifiers will be promoted to the 2.0 type.
+         * 
+         * @return reference to a SAML 2.0 NameID
+         */
+        virtual const opensaml::saml2::NameID& getNameID() const=0;
+
+        /**
+         * Returns the SSO token associated with the subject, if any.
+         * 
+         * @return the SSO token, or NULL
+         */
+        virtual const opensaml::RootObject* getSSOToken() const=0;
+        
+        /**
+         * Returns the active session associated with the subject, if any.
+         * 
+         * @return the active, locked session, or NULL
+         */
+        virtual const Session* getSession() const=0;
+        
+        /**
+         * Returns the set of Attributes resolved and added to the context.
+         * 
+         * <p>Any Attributes left in the returned container will be freed by the
+         * context, so the caller should modify/clear the container after copying
+         * objects for its own use.
+         * 
+         * @return  a mutable array of Attributes.
+         */
+        virtual std::vector<Attribute*>& getResolvedAttributes()=0;
+
+        /**
+         * Returns the set of assertions resolved and added to the context.
+         * 
+         * <p>Any assertions left in the returned container will be freed by the
+         * context, so the caller should modify/clear the container after copying
+         * objects for its own use.
+         * 
+         * @return  a mutable array of Assertions
+         */
+        virtual std::vector<opensaml::RootObject*>& getResolvedAssertions()=0;
+    };
+};
+
+#endif /* __shibsp_resctx_h__ */
diff --git a/shibsp/attribute/resolver/impl/AttributeResolver.cpp b/shibsp/attribute/resolver/impl/AttributeResolver.cpp
new file mode 100644 (file)
index 0000000..7050cc6
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ *  Copyright 2001-2007 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.
+ */
+
+/**
+ * AttributeResolver.cpp
+ * 
+ * The service that resolves the attributes for a particular subject.
+ */
+
+#include "internal.h"
+#include "attribute/resolver/AttributeResolver.h"
+
+#include <xercesc/dom/DOM.hpp>
+
+using namespace shibsp;
+using namespace xmltooling;
+using namespace xercesc;
+using namespace std;
+
+namespace shibsp {
+    SHIBSP_DLLLOCAL PluginManager<AttributeResolver,const DOMElement*>::Factory SimpleAttributeResolverFactory;
+};
+
+void SHIBSP_API shibsp::registerAttributeResolvers()
+{
+    SPConfig& conf=SPConfig::getConfig();
+    conf.AttributeResolverManager.registerFactory(SIMPLE_ATTRIBUTE_RESOLVER, SimpleAttributeResolverFactory);
+}
diff --git a/shibsp/attribute/resolver/impl/SimpleAttributeResolver.cpp b/shibsp/attribute/resolver/impl/SimpleAttributeResolver.cpp
new file mode 100644 (file)
index 0000000..294bc63
--- /dev/null
@@ -0,0 +1,684 @@
+/*\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
+ * You may obtain a copy of the License at\r
+ *\r
+ *     http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+/**\r
+ * SimpleAttributeResolver.cpp\r
+ * \r
+ * AttributeResolver based on a simple mapping of SAML information.\r
+ */\r
+\r
+#include "internal.h"\r
+#include "Application.h"\r
+#include "SessionCache.h"\r
+#include "attribute/AttributeDecoder.h"\r
+#include "attribute/resolver/AttributeResolver.h"\r
+#include "attribute/resolver/ResolutionContext.h"\r
+#include "binding/SOAPClient.h"\r
+#include "util/SPConstants.h"\r
+\r
+\r
+#include <log4cpp/Category.hh>\r
+#include <saml/binding/SecurityPolicy.h>\r
+#include <saml/saml1/binding/SAML1SOAPClient.h>\r
+#include <saml/saml1/core/Assertions.h>\r
+#include <saml/saml1/core/Protocols.h>\r
+#include <saml/saml2/binding/SAML2SOAPClient.h>\r
+#include <saml/saml2/core/Protocols.h>\r
+#include <saml/saml2/metadata/Metadata.h>\r
+#include <saml/saml2/metadata/MetadataProvider.h>\r
+#include <xmltooling/util/NDC.h>\r
+#include <xmltooling/util/ReloadableXMLFile.h>\r
+#include <xmltooling/util/XMLHelper.h>\r
+#include <xercesc/util/XMLUniDefs.hpp>\r
+\r
+using namespace shibsp;\r
+using namespace opensaml::saml1;\r
+using namespace opensaml::saml1p;\r
+using namespace opensaml::saml2;\r
+using namespace opensaml::saml2p;\r
+using namespace opensaml::saml2md;\r
+using namespace opensaml;\r
+using namespace xmltooling;\r
+using namespace log4cpp;\r
+using namespace std;\r
+\r
+namespace shibsp {\r
+\r
+    class SHIBSP_DLLLOCAL SimpleContext : public ResolutionContext\r
+    {\r
+    public:\r
+        SimpleContext(const Application& application, const Session& session)\r
+            : m_app(application), m_session(&session), m_metadata(NULL), m_entity(NULL),\r
+                m_nameid(session.getNameID()), m_token(NULL) {\r
+        }\r
+        \r
+        SimpleContext(\r
+            const Application& application,\r
+            const char* client_addr,\r
+            const EntityDescriptor* issuer,\r
+            const NameID& nameid,\r
+            const opensaml::RootObject* ssoToken=NULL\r
+            ) : m_app(application), m_session(NULL), m_entity(issuer), m_nameid(nameid), m_token(ssoToken) {\r
+            if (client_addr)\r
+                m_client_addr = client_addr;\r
+        }\r
+        \r
+        ~SimpleContext() {\r
+            if (m_metadata)\r
+                m_metadata->unlock();\r
+            for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<shibsp::Attribute>());\r
+            for_each(m_assertions.begin(), m_assertions.end(), xmltooling::cleanup<opensaml::RootObject>());\r
+        }\r
+    \r
+        const Application& getApplication() const {\r
+            return m_app;\r
+        }\r
+        const char* getClientAddress() const {\r
+            return m_session ? m_session->getClientAddress() : m_client_addr.c_str();\r
+        }\r
+        const EntityDescriptor* getEntityDescriptor() const {\r
+            if (m_entity)\r
+                return m_entity;\r
+            if (m_session && m_session->getEntityID()) {\r
+                m_metadata = m_app.getMetadataProvider();\r
+                if (m_metadata) {\r
+                    m_metadata->lock();\r
+                    return m_entity = m_metadata->getEntityDescriptor(m_session->getEntityID());\r
+                }\r
+            }\r
+            return NULL;\r
+        }\r
+        const NameID& getNameID() const {\r
+            return m_nameid;\r
+        }\r
+        const opensaml::RootObject* getSSOToken() const {\r
+            if (m_token)\r
+                return m_token;\r
+            if (m_session) {\r
+                const vector<const char*>& ids = m_session->getAssertionIDs();\r
+                if (!ids.empty())\r
+                    return m_token = m_session->getAssertion(ids.front());\r
+            }\r
+            return NULL;\r
+        }\r
+        const Session* getSession() const {\r
+            return m_session;\r
+        }        \r
+        vector<shibsp::Attribute*>& getResolvedAttributes() {\r
+            return m_attributes;\r
+        }\r
+        vector<opensaml::RootObject*>& getResolvedAssertions() {\r
+            return m_assertions;\r
+        }\r
+\r
+    private:\r
+        const Application& m_app;\r
+        const Session* m_session;\r
+        string m_client_addr;\r
+        mutable MetadataProvider* m_metadata;\r
+        mutable const EntityDescriptor* m_entity;\r
+        const NameID& m_nameid;\r
+        mutable const opensaml::RootObject* m_token;\r
+        vector<shibsp::Attribute*> m_attributes;\r
+        vector<opensaml::RootObject*> m_assertions;\r
+    };\r
+    \r
+#if defined (_MSC_VER)\r
+    #pragma warning( push )\r
+    #pragma warning( disable : 4250 )\r
+#endif\r
+\r
+    class SimpleResolverImpl\r
+    {\r
+    public:\r
+        SimpleResolverImpl(const DOMElement* e);\r
+        ~SimpleResolverImpl() {\r
+            for_each(m_decoderMap.begin(), m_decoderMap.end(), cleanup_pair<string,AttributeDecoder>());\r
+            if (m_document)\r
+                m_document->release();\r
+        }\r
+\r
+        void setDocument(DOMDocument* doc) {\r
+            m_document = doc;\r
+        }\r
+\r
+        void query(\r
+            ResolutionContext& ctx, const opensaml::saml1::Assertion* token, const vector<const char*>* attributes=NULL\r
+            ) const;\r
+        void query(\r
+            ResolutionContext& ctx, const opensaml::saml2::Assertion* token, const vector<const char*>* attributes=NULL\r
+            ) const;\r
+        void resolve(\r
+            ResolutionContext& ctx, const opensaml::saml1::Assertion* token, const vector<const char*>* attributes=NULL\r
+            ) const;\r
+        void resolve(\r
+            ResolutionContext& ctx, const opensaml::saml2::Assertion* token, const vector<const char*>* attributes=NULL\r
+            ) const;\r
+\r
+    private:\r
+        DOMDocument* m_document;\r
+        bool m_allowQuery;\r
+        map<string,AttributeDecoder*> m_decoderMap;\r
+#ifdef HAVE_GOOD_STL\r
+        map< pair<xstring,xstring>,pair<const AttributeDecoder*,string> > m_attrMap;\r
+#else\r
+        map< pair<string,string>,pair<const AttributeDecoder*,string> > m_attrMap;\r
+#endif\r
+    };\r
+    \r
+    class SimpleResolver : public AttributeResolver, public ReloadableXMLFile\r
+    {\r
+    public:\r
+        SimpleResolver(const DOMElement* e) : ReloadableXMLFile(e), m_impl(NULL) {\r
+            load();\r
+        }\r
+        ~SimpleResolver() {\r
+            delete m_impl;\r
+        }\r
+        \r
+        ResolutionContext* createResolutionContext(\r
+            const Application& application,\r
+            const char* client_addr,\r
+            const EntityDescriptor* issuer,\r
+            const NameID& nameid,\r
+            const opensaml::RootObject* ssoToken=NULL\r
+            ) const {\r
+            return new SimpleContext(application,client_addr,issuer,nameid,ssoToken);\r
+        }\r
+\r
+        ResolutionContext* createResolutionContext(const Application& application, const Session& session) const {\r
+            return new SimpleContext(application,session);\r
+        }\r
+        \r
+        void resolveAttributes(ResolutionContext& ctx, const vector<const char*>* attributes=NULL) const;\r
+\r
+    protected:\r
+        pair<bool,DOMElement*> load();\r
+        SimpleResolverImpl* m_impl;\r
+    };\r
+\r
+#if defined (_MSC_VER)\r
+    #pragma warning( pop )\r
+#endif\r
+\r
+    AttributeResolver* SHIBSP_DLLLOCAL SimpleAttributeResolverFactory(const DOMElement* const & e)\r
+    {\r
+        return new SimpleResolver(e);\r
+    }\r
+    \r
+    static const XMLCh SIMPLE_NS[] = {\r
+        chLatin_u, chLatin_r, chLatin_n, chColon, chLatin_m, chLatin_a, chLatin_c, chLatin_e, chColon,\r
+        chLatin_s, chLatin_h, chLatin_i, chLatin_b, chLatin_b, chLatin_o, chLatin_l, chLatin_e, chLatin_t, chLatin_h, chColon,\r
+        chDigit_2, chPeriod, chDigit_0, chColon,\r
+        chLatin_r, chLatin_e, chLatin_s, chLatin_o, chLatin_l, chLatin_v, chLatin_e, chLatin_r, chColon,\r
+        chLatin_s, chLatin_i, chLatin_m, chLatin_p, chLatin_l, chLatin_e, chNull\r
+    };\r
+    static const XMLCh _AttributeResolver[] =   UNICODE_LITERAL_17(A,t,t,r,i,b,u,t,e,R,e,s,o,l,v,e,r);\r
+    static const XMLCh allowQuery[] =           UNICODE_LITERAL_10(a,l,l,o,w,Q,u,e,r,y);\r
+    static const XMLCh decoderType[] =          UNICODE_LITERAL_11(d,e,c,o,d,e,r,T,y,p,e);\r
+};\r
+\r
+SimpleResolverImpl::SimpleResolverImpl(const DOMElement* e) : m_document(NULL), m_allowQuery(true)\r
+{\r
+#ifdef _DEBUG\r
+    xmltooling::NDC ndc("SimpleResolverImpl");\r
+#endif\r
+    Category& log=Category::getInstance(SHIBSP_LOGCAT".AttributeResolver");\r
+    \r
+    if (!XMLHelper::isNodeNamed(e, SIMPLE_NS, _AttributeResolver))\r
+        throw ConfigurationException("Simple resolver requires resolver:AttributeResolver at root of configuration.");\r
+    \r
+    const XMLCh* flag = e->getAttributeNS(NULL,allowQuery);\r
+    if (flag && (*flag==chLatin_f || *flag==chDigit_0)) {\r
+        log.info("SAML attribute queries disabled");\r
+        m_allowQuery = false;\r
+    }\r
+    \r
+    e = XMLHelper::getFirstChildElement(e, samlconstants::SAML20_NS, opensaml::saml2::Attribute::LOCAL_NAME);\r
+    while (e) {\r
+        // Check for missing Name.\r
+        const XMLCh* name = e->getAttributeNS(NULL, opensaml::saml2::Attribute::NAME_ATTRIB_NAME);\r
+        if (!name || !*name) {\r
+            log.warn("skipping saml:Attribute declared with no Name");\r
+            e = XMLHelper::getNextSiblingElement(e, samlconstants::SAML20_NS, opensaml::saml2::Attribute::LOCAL_NAME);\r
+            continue;\r
+        }\r
+\r
+        auto_ptr_char id(e->getAttributeNS(NULL, opensaml::saml2::Attribute::FRIENDLYNAME_ATTRIB_NAME));\r
+        if (!id.get() || !*id.get()) {\r
+            log.warn("skipping saml:Attribute declared with no FriendlyName");\r
+            e = XMLHelper::getNextSiblingElement(e, samlconstants::SAML20_NS, opensaml::saml2::Attribute::LOCAL_NAME);\r
+            continue;\r
+        }\r
+        \r
+        auto_ptr_char d(e->getAttributeNS(SIMPLE_NS, decoderType));\r
+        const char* dtype = d.get();\r
+        if (!dtype || !*dtype)\r
+            dtype = SIMPLE_ATTRIBUTE_DECODER;\r
+        AttributeDecoder*& decoder = m_decoderMap[dtype];\r
+        if (!decoder) {\r
+            try {\r
+                decoder = SPConfig::getConfig().AttributeDecoderManager.newPlugin(dtype, NULL);\r
+            }\r
+            catch (exception& ex) {\r
+                log.error("error building AttributeDecoder: %s", ex.what());\r
+                e = XMLHelper::getNextSiblingElement(e, samlconstants::SAML20_NS, opensaml::saml2::Attribute::LOCAL_NAME);\r
+                continue;\r
+            }\r
+        }\r
+                \r
+        // Empty NameFormat implies the usual Shib URI naming defaults.\r
+        const XMLCh* format = e->getAttributeNS(NULL, opensaml::saml2::Attribute::NAMEFORMAT_ATTRIB_NAME);\r
+        if (!format || XMLString::equals(format, shibspconstants::SHIB1_ATTRIBUTE_NAMESPACE_URI) ||\r
+                XMLString::equals(format, opensaml::saml2::Attribute::URI_REFERENCE))\r
+            format = &chNull;  // ignore default Format/Namespace values\r
+\r
+        // Fetch/create the map entry and see if it's a duplicate rule.\r
+#ifdef HAVE_GOOD_STL\r
+        pair<const AttributeDecoder*,string>& decl = m_attrMap[make_pair(name,format)];\r
+#else\r
+        auto_ptr_char n(name);\r
+        auto_ptr_char f(format);\r
+        pair<const AttributeDecoder*,string>& decl = m_attrMap[make_pair(n.get(),f.get())];\r
+#endif\r
+        if (decl.first) {\r
+            log.warn("skipping duplicate saml:Attribute declaration (same Name and NameFormat)");\r
+            e = XMLHelper::getNextSiblingElement(e, samlconstants::SAML20_NS, opensaml::saml2::Attribute::LOCAL_NAME);\r
+            continue;\r
+        }\r
+\r
+        if (log.isDebugEnabled()) {\r
+#ifdef HAVE_GOOD_STL\r
+            auto_ptr_char n(name);\r
+            auto_ptr_char f(format);\r
+#endif\r
+            log.debug("creating declaration for (Name=%s) %s%s)", n.get(), *f.get() ? "(Format/Namespace=" : "", f.get());\r
+        }\r
+        \r
+        decl.first = decoder;\r
+        decl.second = id.get();\r
+        \r
+        e = XMLHelper::getNextSiblingElement(e, samlconstants::SAML20_NS, opensaml::saml2::Attribute::LOCAL_NAME);\r
+    }\r
+}\r
+\r
+void SimpleResolverImpl::resolve(\r
+    ResolutionContext& ctx, const opensaml::saml1::Assertion* token, const vector<const char*>* attributes\r
+    ) const\r
+{\r
+    set<string> aset;\r
+    if (attributes)\r
+        for(vector<const char*>::const_iterator i=attributes->begin(); i!=attributes->end(); ++i)\r
+            aset.insert(*i);\r
+\r
+    vector<shibsp::Attribute*>& resolved = ctx.getResolvedAttributes();\r
+\r
+#ifdef HAVE_GOOD_STL\r
+    map< pair<xstring,xstring>,pair<const AttributeDecoder*,string> >::const_iterator rule;\r
+#else\r
+    map< pair<string,string>,pair<const AttributeDecoder*,string> >::const_iterator rule;\r
+#endif\r
+\r
+    // Check the NameID based on the format.\r
+    const XMLCh* name;\r
+    const XMLCh* format = ctx.getNameID().getFormat();\r
+    if (!format) {\r
+        format = NameID::UNSPECIFIED;\r
+#ifdef HAVE_GOOD_STL\r
+        if ((rule=m_attrMap.find(make_pair(format,xstring()))) != m_attrMap.end()) {\r
+#else\r
+        auto_ptr_char temp(format);\r
+        if ((rule=m_attrMap.find(make_pair(temp.get(),string()))) != m_attrMap.end()) {\r
+#endif\r
+            if (aset.empty() || aset.count(rule->second.second))\r
+                resolved.push_back(rule->second.first->decode(rule->second.second.c_str(), &ctx.getNameID()));\r
+        }\r
+    }\r
+\r
+    const vector<opensaml::saml1::AttributeStatement*>& statements = token->getAttributeStatements();\r
+    for (vector<opensaml::saml1::AttributeStatement*>::const_iterator s = statements.begin(); s!=statements.end(); ++s) {\r
+        const vector<opensaml::saml1::Attribute*>& attrs = const_cast<const opensaml::saml1::AttributeStatement*>(*s)->getAttributes();\r
+        for (vector<opensaml::saml1::Attribute*>::const_iterator a = attrs.begin(); a!=attrs.end(); ++a) {\r
+            name = (*a)->getAttributeName();\r
+            format = (*a)->getAttributeNamespace();\r
+            if (!name || !*name)\r
+                continue;\r
+            if (!format)\r
+                format = &chNull;\r
+#ifdef HAVE_GOOD_STL\r
+            if ((rule=m_attrMap.find(make_pair(name,format))) != m_attrMap.end()) {\r
+#else\r
+            auto_ptr_char temp1(name);\r
+            auto_ptr_char temp2(format);\r
+            if ((rule=m_attrMap.find(make_pair(temp1.get(),temp2.get()))) != m_attrMap.end()) {\r
+#endif\r
+            if (aset.empty() || aset.count(rule->second.second))\r
+                resolved.push_back(rule->second.first->decode(rule->second.second.c_str(), *a));\r
+            }\r
+        }\r
+    }\r
+}\r
+\r
+void SimpleResolverImpl::resolve(\r
+    ResolutionContext& ctx, const opensaml::saml2::Assertion* token, const vector<const char*>* attributes\r
+    ) const\r
+{\r
+    set<string> aset;\r
+    if (attributes)\r
+        for(vector<const char*>::const_iterator i=attributes->begin(); i!=attributes->end(); ++i)\r
+            aset.insert(*i);\r
+\r
+    vector<shibsp::Attribute*>& resolved = ctx.getResolvedAttributes();\r
+\r
+#ifdef HAVE_GOOD_STL\r
+    map< pair<xstring,xstring>,pair<const AttributeDecoder*,string> >::const_iterator rule;\r
+#else\r
+    map< pair<string,string>,pair<const AttributeDecoder*,string> >::const_iterator rule;\r
+#endif\r
+\r
+    // Check the NameID based on the format.\r
+    const XMLCh* name;\r
+    const XMLCh* format = ctx.getNameID().getFormat();\r
+    if (!format) {\r
+        format = NameID::UNSPECIFIED;\r
+#ifdef HAVE_GOOD_STL\r
+        if ((rule=m_attrMap.find(make_pair(format,xstring()))) != m_attrMap.end()) {\r
+#else\r
+        auto_ptr_char temp(format);\r
+        if ((rule=m_attrMap.find(make_pair(temp.get(),string()))) != m_attrMap.end()) {\r
+#endif\r
+            if (aset.empty() || aset.count(rule->second.second))\r
+                resolved.push_back(rule->second.first->decode(rule->second.second.c_str(), &ctx.getNameID()));\r
+        }\r
+    }\r
+\r
+    const vector<opensaml::saml2::AttributeStatement*>& statements = token->getAttributeStatements();\r
+    for (vector<opensaml::saml2::AttributeStatement*>::const_iterator s = statements.begin(); s!=statements.end(); ++s) {\r
+        const vector<opensaml::saml2::Attribute*>& attrs = const_cast<const opensaml::saml2::AttributeStatement*>(*s)->getAttributes();\r
+        for (vector<opensaml::saml2::Attribute*>::const_iterator a = attrs.begin(); a!=attrs.end(); ++a) {\r
+            name = (*a)->getName();\r
+            format = (*a)->getNameFormat();\r
+            if (!name || !*name)\r
+                continue;\r
+            if (!format)\r
+                format = &chNull;\r
+#ifdef HAVE_GOOD_STL\r
+            if ((rule=m_attrMap.find(make_pair(name,format))) != m_attrMap.end()) {\r
+#else\r
+            auto_ptr_char temp1(name);\r
+            auto_ptr_char temp2(format);\r
+            if ((rule=m_attrMap.find(make_pair(temp1.get(),temp2.get()))) != m_attrMap.end()) {\r
+#endif\r
+            if (aset.empty() || aset.count(rule->second.second))\r
+                resolved.push_back(rule->second.first->decode(rule->second.second.c_str(), *a));\r
+            }\r
+        }\r
+    }\r
+}\r
+\r
+void SimpleResolverImpl::query(ResolutionContext& ctx, const opensaml::saml1::Assertion* token, const vector<const char*>* attributes) const\r
+{\r
+    if (!m_allowQuery)\r
+        return;\r
+\r
+#ifdef _DEBUG\r
+    xmltooling::NDC ndc("query");\r
+#endif\r
+    Category& log=Category::getInstance(SHIBSP_LOGCAT".AttributeResolver");\r
+\r
+    const EntityDescriptor* entity = ctx.getEntityDescriptor();\r
+    if (!entity) {\r
+        log.debug("no issuer information available, skipping query");\r
+        return;\r
+    }\r
+    const AttributeAuthorityDescriptor* AA =\r
+        entity->getAttributeAuthorityDescriptor(\r
+            token->getMinorVersion().second==1 ? samlconstants::SAML11_PROTOCOL_ENUM : samlconstants::SAML10_PROTOCOL_ENUM\r
+            );\r
+    if (!AA) {\r
+        log.debug("no SAML 1.%d AttributeAuthority role found in metadata", token->getMinorVersion().second);\r
+        return;\r
+    }\r
+\r
+    SecurityPolicy policy;\r
+    shibsp::SOAPClient soaper(ctx.getApplication(),policy);\r
+\r
+    auto_ptr_XMLCh binding(samlconstants::SAML1_BINDING_SOAP);\r
+    opensaml::saml1p::Response* response=NULL;\r
+    const vector<AttributeService*>& endpoints=AA->getAttributeServices();\r
+    for (vector<AttributeService*>::const_iterator ep=endpoints.begin(); !response && ep!=endpoints.end(); ++ep) {\r
+        try {\r
+            if (!XMLString::equals((*ep)->getBinding(),binding.get()))\r
+                continue;\r
+            auto_ptr_char loc((*ep)->getLocation());\r
+            auto_ptr_XMLCh issuer(ctx.getApplication().getString("providerId").second);\r
+            opensaml::saml1::Subject* subject = opensaml::saml1::SubjectBuilder::buildSubject();\r
+            subject->setNameIdentifier(token->getAuthenticationStatements().front()->getSubject()->getNameIdentifier()->cloneNameIdentifier());\r
+            opensaml::saml1p::AttributeQuery* query = opensaml::saml1p::AttributeQueryBuilder::buildAttributeQuery();\r
+            query->setSubject(subject);\r
+            Request* request = RequestBuilder::buildRequest();\r
+            request->setAttributeQuery(query);\r
+            query->setResource(issuer.get());\r
+            request->setMinorVersion(token->getMinorVersion().second);\r
+            SAML1SOAPClient client(soaper);\r
+            client.sendSAML(request, *AA, loc.get());\r
+            response = client.receiveSAML();\r
+        }\r
+        catch (exception& ex) {\r
+            log.error("exception making SAML query: %s", ex.what());\r
+            soaper.reset();\r
+        }\r
+    }\r
+\r
+    if (!response) {\r
+        log.error("unable to successfully query for attributes");\r
+        return;\r
+    }\r
+\r
+    time_t now = time(NULL);\r
+    const Validator* tokval = ctx.getApplication().getTokenValidator(now, AA);\r
+    const vector<opensaml::saml1::Assertion*>& assertions = const_cast<const opensaml::saml1p::Response*>(response)->getAssertions();\r
+    if (assertions.size()==1) {\r
+        auto_ptr<opensaml::saml1p::Response> wrapper(response);\r
+        opensaml::saml1::Assertion* newtoken = assertions.front();\r
+        if (!XMLString::equals(policy.getIssuer() ? policy.getIssuer()->getName() : NULL, newtoken->getIssuer())) {\r
+            log.error("assertion issued by someone other than AA, rejecting it");\r
+            return;\r
+        }\r
+        try {\r
+            tokval->validate(newtoken);\r
+        }\r
+        catch (exception& ex) {\r
+            log.error("assertion failed validation check: %s", ex.what());\r
+        }\r
+        newtoken->detach();\r
+        wrapper.release();\r
+        ctx.getResolvedAssertions().push_back(newtoken);\r
+        resolve(ctx, newtoken, attributes);\r
+    }\r
+    else {\r
+        auto_ptr<opensaml::saml1p::Response> wrapper(response);\r
+        for (vector<opensaml::saml1::Assertion*>::const_iterator a = assertions.begin(); a!=assertions.end(); ++a) {\r
+            if (!XMLString::equals(policy.getIssuer() ? policy.getIssuer()->getName() : NULL, (*a)->getIssuer())) {\r
+                log.error("assertion issued by someone other than AA, rejecting it");\r
+                continue;\r
+            }\r
+            try {\r
+                tokval->validate(*a);\r
+            }\r
+            catch (exception& ex) {\r
+                log.error("assertion failed validation check: %s", ex.what());\r
+            }\r
+            resolve(ctx, *a, attributes);\r
+            ctx.getResolvedAssertions().push_back((*a)->cloneAssertion());\r
+        }\r
+    }\r
+}\r
+\r
+void SimpleResolverImpl::query(ResolutionContext& ctx, const opensaml::saml2::Assertion* token, const vector<const char*>* attributes) const\r
+{\r
+    if (!m_allowQuery)\r
+        return;\r
+\r
+#ifdef _DEBUG\r
+    xmltooling::NDC ndc("query");\r
+#endif\r
+    Category& log=Category::getInstance(SHIBSP_LOGCAT".AttributeResolver");\r
+\r
+    const EntityDescriptor* entity = ctx.getEntityDescriptor();\r
+    if (!entity) {\r
+        log.debug("no issuer information available, skipping query");\r
+        return;\r
+    }\r
+    const AttributeAuthorityDescriptor* AA = entity->getAttributeAuthorityDescriptor(samlconstants::SAML20P_NS);\r
+    if (!AA) {\r
+        log.debug("no SAML 2 AttributeAuthority role found in metadata");\r
+        return;\r
+    }\r
+\r
+    SecurityPolicy policy;\r
+    shibsp::SOAPClient soaper(ctx.getApplication(),policy);\r
+\r
+    auto_ptr_XMLCh binding(samlconstants::SAML20_BINDING_SOAP);\r
+    opensaml::saml2p::StatusResponseType* srt=NULL;\r
+    const vector<AttributeService*>& endpoints=AA->getAttributeServices();\r
+    for (vector<AttributeService*>::const_iterator ep=endpoints.begin(); !srt && ep!=endpoints.end(); ++ep) {\r
+        try {\r
+            if (!XMLString::equals((*ep)->getBinding(),binding.get()))\r
+                continue;\r
+            auto_ptr_char loc((*ep)->getLocation());\r
+            auto_ptr_XMLCh issuer(ctx.getApplication().getString("providerId").second);\r
+            opensaml::saml2::Subject* subject = opensaml::saml2::SubjectBuilder::buildSubject();\r
+            subject->setNameID(token->getSubject()->getNameID()->cloneNameID());\r
+            opensaml::saml2p::AttributeQuery* query = opensaml::saml2p::AttributeQueryBuilder::buildAttributeQuery();\r
+            query->setSubject(subject);\r
+            Issuer* iss = IssuerBuilder::buildIssuer();\r
+            query->setIssuer(iss);\r
+            iss->setName(issuer.get());\r
+            SAML2SOAPClient client(soaper);\r
+            client.sendSAML(query, *AA, loc.get());\r
+            srt = client.receiveSAML();\r
+        }\r
+        catch (exception& ex) {\r
+            log.error("exception making SAML query: %s", ex.what());\r
+            soaper.reset();\r
+        }\r
+    }\r
+\r
+    if (!srt) {\r
+        log.error("unable to successfully query for attributes");\r
+        return;\r
+    }\r
+    opensaml::saml2p::Response* response = dynamic_cast<opensaml::saml2p::Response*>(srt);\r
+    if (!response) {\r
+        delete srt;\r
+        log.error("message was not a samlp:Response");\r
+        return;\r
+    }\r
+\r
+    time_t now = time(NULL);\r
+    const Validator* tokval = ctx.getApplication().getTokenValidator(now, AA);\r
+    const vector<opensaml::saml2::Assertion*>& assertions = const_cast<const opensaml::saml2p::Response*>(response)->getAssertions();\r
+    if (assertions.size()==1) {\r
+        auto_ptr<opensaml::saml2p::Response> wrapper(response);\r
+        opensaml::saml2::Assertion* newtoken = assertions.front();\r
+        if (!XMLString::equals(policy.getIssuer() ? policy.getIssuer()->getName() : NULL, newtoken->getIssuer() ? newtoken->getIssuer()->getName() : NULL)) {\r
+            log.error("assertion issued by someone other than AA, rejecting it");\r
+            return;\r
+        }\r
+        try {\r
+            tokval->validate(newtoken);\r
+        }\r
+        catch (exception& ex) {\r
+            log.error("assertion failed validation check: %s", ex.what());\r
+        }\r
+        newtoken->detach();\r
+        wrapper.release();\r
+        ctx.getResolvedAssertions().push_back(newtoken);\r
+        resolve(ctx, newtoken, attributes);\r
+    }\r
+    else {\r
+        auto_ptr<opensaml::saml2p::Response> wrapper(response);\r
+        for (vector<opensaml::saml2::Assertion*>::const_iterator a = assertions.begin(); a!=assertions.end(); ++a) {\r
+            if (!XMLString::equals(policy.getIssuer() ? policy.getIssuer()->getName() : NULL, (*a)->getIssuer() ? (*a)->getIssuer()->getName() : NULL)) {\r
+                log.error("assertion issued by someone other than AA, rejecting it");\r
+                return;\r
+            }\r
+            try {\r
+                tokval->validate(*a);\r
+            }\r
+            catch (exception& ex) {\r
+                log.error("assertion failed validation check: %s", ex.what());\r
+            }\r
+            resolve(ctx, *a, attributes);\r
+            ctx.getResolvedAssertions().push_back((*a)->cloneAssertion());\r
+        }\r
+    }\r
+}\r
+\r
+void SimpleResolver::resolveAttributes(ResolutionContext& ctx, const vector<const char*>* attributes) const\r
+{\r
+#ifdef _DEBUG\r
+    xmltooling::NDC ndc("resolveAttributes");\r
+#endif\r
+    Category& log=Category::getInstance(SHIBSP_LOGCAT".AttributeResolver");\r
+    \r
+    log.debug("examining incoming SSO token");\r
+\r
+    const opensaml::RootObject* token = ctx.getSSOToken();\r
+    if (!token) {\r
+        log.warn("no SSO token supplied to resolver, returning nothing");\r
+        return;\r
+    }\r
+    const opensaml::saml2::Assertion* token2 = dynamic_cast<const opensaml::saml2::Assertion*>(token);\r
+    if (token2) {\r
+        if (!token2->getAttributeStatements().empty()) {\r
+            log.debug("found SAML 2 SSO token with an AttributeStatement");\r
+            return m_impl->resolve(ctx, token2, attributes);\r
+        }\r
+        return m_impl->query(ctx, token2, attributes);\r
+    }\r
+\r
+    const opensaml::saml1::Assertion* token1 = dynamic_cast<const opensaml::saml1::Assertion*>(token);\r
+    if (token1) {\r
+        if (!token1->getAttributeStatements().empty()) {\r
+            log.debug("found SAML 1 SSO token with an AttributeStatement");\r
+            return m_impl->resolve(ctx, token1, attributes);\r
+        }\r
+        return m_impl->query(ctx, token1, attributes);\r
+    }\r
+\r
+    log.warn("unrecognized token type, returning nothing");\r
+}\r
+\r
+pair<bool,DOMElement*> SimpleResolver::load()\r
+{\r
+    // Load from source using base class.\r
+    pair<bool,DOMElement*> raw = ReloadableXMLFile::load();\r
+    \r
+    // If we own it, wrap it.\r
+    XercesJanitor<DOMDocument> docjanitor(raw.first ? raw.second->getOwnerDocument() : NULL);\r
+\r
+    SimpleResolverImpl* impl = new SimpleResolverImpl(raw.second);\r
+    \r
+    // If we held the document, transfer it to the impl. If we didn't, it's a no-op.\r
+    impl->setDocument(docjanitor.release());\r
+\r
+    delete m_impl;\r
+    m_impl = impl;\r
+\r
+    return make_pair(false,(DOMElement*)NULL);\r
+}\r
index 6267048..6ca9064 100644 (file)
@@ -25,6 +25,7 @@
 
 #include <shibsp/Application.h>
 #include <saml/binding/SOAPClient.h>
+#include <xmltooling/signature/CredentialResolver.h>
 
 namespace shibsp {
 
index c686094..7e6c8ee 100644 (file)
@@ -29,6 +29,7 @@
 namespace shibsp {
     
     DECL_XMLTOOLING_EXCEPTION(AttributeException,SHIBSP_EXCEPTIONAPI(SHIBSP_API),shibsp,xmltooling::XMLToolingException,Exceptions during attribute processing.);
+    DECL_XMLTOOLING_EXCEPTION(AttributeResolutionException,SHIBSP_EXCEPTIONAPI(SHIBSP_API),shibsp,shibsp::AttributeException,Exceptions during attribute resolution.);
     DECL_XMLTOOLING_EXCEPTION(ConfigurationException,SHIBSP_EXCEPTIONAPI(SHIBSP_API),shibsp,xmltooling::XMLToolingException,Exceptions during configuration.);
     DECL_XMLTOOLING_EXCEPTION(ListenerException,SHIBSP_EXCEPTIONAPI(SHIBSP_API),shibsp,xmltooling::XMLToolingException,Exceptions during inter-process communication.);
 
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
index f4b68a1..cf1f41c 100644 (file)
                                        RelativePath=".\attribute\SimpleAttributeDecoder.cpp"\r
                                        >\r
                                </File>\r
+                               <Filter\r
+                                       Name="resolver"\r
+                                       >\r
+                                       <Filter\r
+                                               Name="impl"\r
+                                               >\r
+                                               <File\r
+                                                       RelativePath=".\attribute\resolver\impl\AttributeResolver.cpp"\r
+                                                       >\r
+                                               </File>\r
+                                               <File\r
+                                                       RelativePath=".\attribute\resolver\impl\SimpleAttributeResolver.cpp"\r
+                                                       >\r
+                                               </File>\r
+                                       </Filter>\r
+                               </Filter>\r
                        </Filter>\r
                        <Filter\r
                                Name="binding"\r
                                        >\r
                                </File>\r
                                <File\r
+                                       RelativePath=".\attribute\AttributeDecoder.h"\r
+                                       >\r
+                               </File>\r
+                               <File\r
                                        RelativePath=".\attribute\NameIDAttribute.h"\r
                                        >\r
                                </File>\r
                                        RelativePath=".\attribute\SimpleAttribute.h"\r
                                        >\r
                                </File>\r
+                               <Filter\r
+                                       Name="resolver"\r
+                                       >\r
+                                       <File\r
+                                               RelativePath=".\attribute\resolver\AttributeResolver.h"\r
+                                               >\r
+                                       </File>\r
+                                       <File\r
+                                               RelativePath=".\attribute\resolver\ResolutionContext.h"\r
+                                               >\r
+                                       </File>\r
+                               </Filter>\r
                        </Filter>\r
                        <Filter\r
                                Name="binding"\r
index 101fc6e..13ccf86 100644 (file)
@@ -104,7 +104,6 @@ int main(int argc,char* argv[])
         SPConfig::Metadata |\r
         SPConfig::Trust |\r
         SPConfig::Credentials |\r
-        SPConfig::AttributeResolver |\r
         SPConfig::OutOfProcess\r
         );\r
     if (!conf.init(path))\r