From b49f8d37d75c1dc3974b060347ae029b499efb08 Mon Sep 17 00:00:00 2001 From: cantor Date: Sat, 24 Feb 2007 22:04:37 +0000 Subject: [PATCH] A "simple" attribute resolver, and token validation. git-svn-id: https://svn.middleware.georgetown.edu/cpp-sp/trunk@2177 cb58f699-b61c-0410-a6fe-9272a202ed29 --- .cdtproject | 10 +- schemas/Makefile.am | 1 + schemas/catalog.xml.in | 2 + schemas/shibboleth-2.0-simple-resolver.xsd | 30 + schemas/shibboleth-spconfig-2.0.xsd | 4 +- shibd/shibd.cpp | 4 +- shibsp/Application.h | 12 + shibsp/Makefile.am | 8 + shibsp/SPConfig.cpp | 23 +- shibsp/SPConfig.h | 8 +- shibsp/SessionCache.h | 2 +- shibsp/attribute/resolver/AttributeResolver.h | 110 ++++ shibsp/attribute/resolver/ResolutionContext.h | 115 ++++ .../attribute/resolver/impl/AttributeResolver.cpp | 41 ++ .../resolver/impl/SimpleAttributeResolver.cpp | 684 +++++++++++++++++++++ shibsp/binding/SOAPClient.h | 1 + shibsp/exceptions.h | 1 + shibsp/impl/XMLServiceProvider.cpp | 140 ++++- shibsp/shibsp.vcproj | 32 + util/samlquery.cpp | 1 - 20 files changed, 1208 insertions(+), 21 deletions(-) create mode 100644 schemas/shibboleth-2.0-simple-resolver.xsd create mode 100644 shibsp/attribute/resolver/AttributeResolver.h create mode 100644 shibsp/attribute/resolver/ResolutionContext.h create mode 100644 shibsp/attribute/resolver/impl/AttributeResolver.cpp create mode 100644 shibsp/attribute/resolver/impl/SimpleAttributeResolver.cpp diff --git a/.cdtproject b/.cdtproject index 79144f7..fa13b21 100644 --- a/.cdtproject +++ b/.cdtproject @@ -1,15 +1,17 @@ - + - + - - + + + + diff --git a/schemas/Makefile.am b/schemas/Makefile.am index 13d853c..5d83cd5 100644 --- a/schemas/Makefile.am +++ b/schemas/Makefile.am @@ -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 diff --git a/schemas/catalog.xml.in b/schemas/catalog.xml.in index 7d90453..d1ea637 100644 --- a/schemas/catalog.xml.in +++ b/schemas/catalog.xml.in @@ -2,5 +2,7 @@ + + diff --git a/schemas/shibboleth-2.0-simple-resolver.xsd b/schemas/shibboleth-2.0-simple-resolver.xsd new file mode 100644 index 0000000..2e0ac6d --- /dev/null +++ b/schemas/shibboleth-2.0-simple-resolver.xsd @@ -0,0 +1,30 @@ + + + + Shibboleth 2.0 Simple Attribute Resolver configuration schema + + + + + + + Root of the simple attribute resolver configuration file. + + + + + + + + + + + + + + + + + diff --git a/schemas/shibboleth-spconfig-2.0.xsd b/schemas/shibboleth-spconfig-2.0.xsd index de85b41..ef03152 100644 --- a/schemas/shibboleth-spconfig-2.0.xsd +++ b/schemas/shibboleth-spconfig-2.0.xsd @@ -9,8 +9,8 @@ blockDefault="substitution" version="2.0"> - - + + diff --git a/shibd/shibd.cpp b/shibd/shibd.cpp index 7b72b09..3d0632a 100644 --- a/shibd/shibd.cpp +++ b/shibd/shibd.cpp @@ -114,7 +114,7 @@ int real_main(int preinit) SPConfig::Metadata | SPConfig::Trust | SPConfig::Credentials | - SPConfig::AttributeResolver | + SPConfig::AttributeResolution | SPConfig::OutOfProcess | (shar_checkonly ? (SPConfig::InProcess | SPConfig::RequestMapping) : SPConfig::Logging) ); @@ -278,7 +278,7 @@ int main(int argc, char *argv[]) SPConfig::Metadata | SPConfig::Trust | SPConfig::Credentials | - SPConfig::AttributeResolver | + SPConfig::AttributeResolution | SPConfig::OutOfProcess | (shar_checkonly ? (SPConfig::InProcess | SPConfig::RequestMapping) : SPConfig::Logging) ); diff --git a/shibsp/Application.h b/shibsp/Application.h index c8f262d..eb90c61 100644 --- a/shibsp/Application.h +++ b/shibsp/Application.h @@ -26,6 +26,7 @@ #include #include #include +#include namespace shibsp { @@ -155,6 +156,17 @@ namespace shibsp { * @return set of audience values associated with the Application */ virtual const std::vector& getAudiences() const=0; + + /** + * Returns a validator for applying verification rules to incoming SAML tokens. + * + *

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; }; }; diff --git a/shibsp/Makefile.am b/shibsp/Makefile.am index 85a5bf5..11b5fc4 100644 --- a/shibsp/Makefile.am +++ b/shibsp/Makefile.am @@ -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 \ diff --git a/shibsp/SPConfig.cpp b/shibsp/SPConfig.cpp index 4bcd466..cefcc17 100644 --- a/shibsp/SPConfig.cpp +++ b/shibsp/SPConfig.cpp @@ -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(); diff --git a/shibsp/SPConfig.h b/shibsp/SPConfig.h index 78a4ff8..c785e87 100644 --- a/shibsp/SPConfig.h +++ b/shibsp/SPConfig.h @@ -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 AttributeDecoderManager; /** + * Manages factories for AttributeResolver plugins. + */ + xmltooling::PluginManager AttributeResolverManager; + + /** * Manages factories for Handler plugins that implement AssertionConsumerService functionality. */ xmltooling::PluginManager AssertionConsumerServiceManager; diff --git a/shibsp/SessionCache.h b/shibsp/SessionCache.h index d350927..a187757 100644 --- a/shibsp/SessionCache.h +++ b/shibsp/SessionCache.h @@ -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 index 0000000..78921a6 --- /dev/null +++ b/shibsp/attribute/resolver/AttributeResolver.h @@ -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 + +#include +#include +#include + +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. + * + *

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* 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 index 0000000..c005688 --- /dev/null +++ b/shibsp/attribute/resolver/ResolutionContext.h @@ -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 + +#include +#include + +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 + * + *

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. + * + *

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& getResolvedAttributes()=0; + + /** + * Returns the set of assertions resolved and added to the context. + * + *

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& 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 index 0000000..7050cc6 --- /dev/null +++ b/shibsp/attribute/resolver/impl/AttributeResolver.cpp @@ -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 + +using namespace shibsp; +using namespace xmltooling; +using namespace xercesc; +using namespace std; + +namespace shibsp { + SHIBSP_DLLLOCAL PluginManager::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 index 0000000..294bc63 --- /dev/null +++ b/shibsp/attribute/resolver/impl/SimpleAttributeResolver.cpp @@ -0,0 +1,684 @@ +/* + * 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. + */ + +/** + * SimpleAttributeResolver.cpp + * + * AttributeResolver based on a simple mapping of SAML information. + */ + +#include "internal.h" +#include "Application.h" +#include "SessionCache.h" +#include "attribute/AttributeDecoder.h" +#include "attribute/resolver/AttributeResolver.h" +#include "attribute/resolver/ResolutionContext.h" +#include "binding/SOAPClient.h" +#include "util/SPConstants.h" + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace shibsp; +using namespace opensaml::saml1; +using namespace opensaml::saml1p; +using namespace opensaml::saml2; +using namespace opensaml::saml2p; +using namespace opensaml::saml2md; +using namespace opensaml; +using namespace xmltooling; +using namespace log4cpp; +using namespace std; + +namespace shibsp { + + class SHIBSP_DLLLOCAL SimpleContext : public ResolutionContext + { + public: + SimpleContext(const Application& application, const Session& session) + : m_app(application), m_session(&session), m_metadata(NULL), m_entity(NULL), + m_nameid(session.getNameID()), m_token(NULL) { + } + + SimpleContext( + const Application& application, + const char* client_addr, + const EntityDescriptor* issuer, + const NameID& nameid, + const opensaml::RootObject* ssoToken=NULL + ) : m_app(application), m_session(NULL), m_entity(issuer), m_nameid(nameid), m_token(ssoToken) { + if (client_addr) + m_client_addr = client_addr; + } + + ~SimpleContext() { + if (m_metadata) + m_metadata->unlock(); + for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup()); + for_each(m_assertions.begin(), m_assertions.end(), xmltooling::cleanup()); + } + + const Application& getApplication() const { + return m_app; + } + const char* getClientAddress() const { + return m_session ? m_session->getClientAddress() : m_client_addr.c_str(); + } + const EntityDescriptor* getEntityDescriptor() const { + if (m_entity) + return m_entity; + if (m_session && m_session->getEntityID()) { + m_metadata = m_app.getMetadataProvider(); + if (m_metadata) { + m_metadata->lock(); + return m_entity = m_metadata->getEntityDescriptor(m_session->getEntityID()); + } + } + return NULL; + } + const NameID& getNameID() const { + return m_nameid; + } + const opensaml::RootObject* getSSOToken() const { + if (m_token) + return m_token; + if (m_session) { + const vector& ids = m_session->getAssertionIDs(); + if (!ids.empty()) + return m_token = m_session->getAssertion(ids.front()); + } + return NULL; + } + const Session* getSession() const { + return m_session; + } + vector& getResolvedAttributes() { + return m_attributes; + } + vector& getResolvedAssertions() { + return m_assertions; + } + + private: + const Application& m_app; + const Session* m_session; + string m_client_addr; + mutable MetadataProvider* m_metadata; + mutable const EntityDescriptor* m_entity; + const NameID& m_nameid; + mutable const opensaml::RootObject* m_token; + vector m_attributes; + vector m_assertions; + }; + +#if defined (_MSC_VER) + #pragma warning( push ) + #pragma warning( disable : 4250 ) +#endif + + class SimpleResolverImpl + { + public: + SimpleResolverImpl(const DOMElement* e); + ~SimpleResolverImpl() { + for_each(m_decoderMap.begin(), m_decoderMap.end(), cleanup_pair()); + if (m_document) + m_document->release(); + } + + void setDocument(DOMDocument* doc) { + m_document = doc; + } + + void query( + ResolutionContext& ctx, const opensaml::saml1::Assertion* token, const vector* attributes=NULL + ) const; + void query( + ResolutionContext& ctx, const opensaml::saml2::Assertion* token, const vector* attributes=NULL + ) const; + void resolve( + ResolutionContext& ctx, const opensaml::saml1::Assertion* token, const vector* attributes=NULL + ) const; + void resolve( + ResolutionContext& ctx, const opensaml::saml2::Assertion* token, const vector* attributes=NULL + ) const; + + private: + DOMDocument* m_document; + bool m_allowQuery; + map m_decoderMap; +#ifdef HAVE_GOOD_STL + map< pair,pair > m_attrMap; +#else + map< pair,pair > m_attrMap; +#endif + }; + + class SimpleResolver : public AttributeResolver, public ReloadableXMLFile + { + public: + SimpleResolver(const DOMElement* e) : ReloadableXMLFile(e), m_impl(NULL) { + load(); + } + ~SimpleResolver() { + delete m_impl; + } + + ResolutionContext* createResolutionContext( + const Application& application, + const char* client_addr, + const EntityDescriptor* issuer, + const NameID& nameid, + const opensaml::RootObject* ssoToken=NULL + ) const { + return new SimpleContext(application,client_addr,issuer,nameid,ssoToken); + } + + ResolutionContext* createResolutionContext(const Application& application, const Session& session) const { + return new SimpleContext(application,session); + } + + void resolveAttributes(ResolutionContext& ctx, const vector* attributes=NULL) const; + + protected: + pair load(); + SimpleResolverImpl* m_impl; + }; + +#if defined (_MSC_VER) + #pragma warning( pop ) +#endif + + AttributeResolver* SHIBSP_DLLLOCAL SimpleAttributeResolverFactory(const DOMElement* const & e) + { + return new SimpleResolver(e); + } + + static const XMLCh SIMPLE_NS[] = { + chLatin_u, chLatin_r, chLatin_n, chColon, chLatin_m, chLatin_a, chLatin_c, chLatin_e, chColon, + chLatin_s, chLatin_h, chLatin_i, chLatin_b, chLatin_b, chLatin_o, chLatin_l, chLatin_e, chLatin_t, chLatin_h, chColon, + chDigit_2, chPeriod, chDigit_0, chColon, + chLatin_r, chLatin_e, chLatin_s, chLatin_o, chLatin_l, chLatin_v, chLatin_e, chLatin_r, chColon, + chLatin_s, chLatin_i, chLatin_m, chLatin_p, chLatin_l, chLatin_e, chNull + }; + static const XMLCh _AttributeResolver[] = UNICODE_LITERAL_17(A,t,t,r,i,b,u,t,e,R,e,s,o,l,v,e,r); + static const XMLCh allowQuery[] = UNICODE_LITERAL_10(a,l,l,o,w,Q,u,e,r,y); + static const XMLCh decoderType[] = UNICODE_LITERAL_11(d,e,c,o,d,e,r,T,y,p,e); +}; + +SimpleResolverImpl::SimpleResolverImpl(const DOMElement* e) : m_document(NULL), m_allowQuery(true) +{ +#ifdef _DEBUG + xmltooling::NDC ndc("SimpleResolverImpl"); +#endif + Category& log=Category::getInstance(SHIBSP_LOGCAT".AttributeResolver"); + + if (!XMLHelper::isNodeNamed(e, SIMPLE_NS, _AttributeResolver)) + throw ConfigurationException("Simple resolver requires resolver:AttributeResolver at root of configuration."); + + const XMLCh* flag = e->getAttributeNS(NULL,allowQuery); + if (flag && (*flag==chLatin_f || *flag==chDigit_0)) { + log.info("SAML attribute queries disabled"); + m_allowQuery = false; + } + + e = XMLHelper::getFirstChildElement(e, samlconstants::SAML20_NS, opensaml::saml2::Attribute::LOCAL_NAME); + while (e) { + // Check for missing Name. + const XMLCh* name = e->getAttributeNS(NULL, opensaml::saml2::Attribute::NAME_ATTRIB_NAME); + if (!name || !*name) { + log.warn("skipping saml:Attribute declared with no Name"); + e = XMLHelper::getNextSiblingElement(e, samlconstants::SAML20_NS, opensaml::saml2::Attribute::LOCAL_NAME); + continue; + } + + auto_ptr_char id(e->getAttributeNS(NULL, opensaml::saml2::Attribute::FRIENDLYNAME_ATTRIB_NAME)); + if (!id.get() || !*id.get()) { + log.warn("skipping saml:Attribute declared with no FriendlyName"); + e = XMLHelper::getNextSiblingElement(e, samlconstants::SAML20_NS, opensaml::saml2::Attribute::LOCAL_NAME); + continue; + } + + auto_ptr_char d(e->getAttributeNS(SIMPLE_NS, decoderType)); + const char* dtype = d.get(); + if (!dtype || !*dtype) + dtype = SIMPLE_ATTRIBUTE_DECODER; + AttributeDecoder*& decoder = m_decoderMap[dtype]; + if (!decoder) { + try { + decoder = SPConfig::getConfig().AttributeDecoderManager.newPlugin(dtype, NULL); + } + catch (exception& ex) { + log.error("error building AttributeDecoder: %s", ex.what()); + e = XMLHelper::getNextSiblingElement(e, samlconstants::SAML20_NS, opensaml::saml2::Attribute::LOCAL_NAME); + continue; + } + } + + // Empty NameFormat implies the usual Shib URI naming defaults. + const XMLCh* format = e->getAttributeNS(NULL, opensaml::saml2::Attribute::NAMEFORMAT_ATTRIB_NAME); + if (!format || XMLString::equals(format, shibspconstants::SHIB1_ATTRIBUTE_NAMESPACE_URI) || + XMLString::equals(format, opensaml::saml2::Attribute::URI_REFERENCE)) + format = &chNull; // ignore default Format/Namespace values + + // Fetch/create the map entry and see if it's a duplicate rule. +#ifdef HAVE_GOOD_STL + pair& decl = m_attrMap[make_pair(name,format)]; +#else + auto_ptr_char n(name); + auto_ptr_char f(format); + pair& decl = m_attrMap[make_pair(n.get(),f.get())]; +#endif + if (decl.first) { + log.warn("skipping duplicate saml:Attribute declaration (same Name and NameFormat)"); + e = XMLHelper::getNextSiblingElement(e, samlconstants::SAML20_NS, opensaml::saml2::Attribute::LOCAL_NAME); + continue; + } + + if (log.isDebugEnabled()) { +#ifdef HAVE_GOOD_STL + auto_ptr_char n(name); + auto_ptr_char f(format); +#endif + log.debug("creating declaration for (Name=%s) %s%s)", n.get(), *f.get() ? "(Format/Namespace=" : "", f.get()); + } + + decl.first = decoder; + decl.second = id.get(); + + e = XMLHelper::getNextSiblingElement(e, samlconstants::SAML20_NS, opensaml::saml2::Attribute::LOCAL_NAME); + } +} + +void SimpleResolverImpl::resolve( + ResolutionContext& ctx, const opensaml::saml1::Assertion* token, const vector* attributes + ) const +{ + set aset; + if (attributes) + for(vector::const_iterator i=attributes->begin(); i!=attributes->end(); ++i) + aset.insert(*i); + + vector& resolved = ctx.getResolvedAttributes(); + +#ifdef HAVE_GOOD_STL + map< pair,pair >::const_iterator rule; +#else + map< pair,pair >::const_iterator rule; +#endif + + // Check the NameID based on the format. + const XMLCh* name; + const XMLCh* format = ctx.getNameID().getFormat(); + if (!format) { + format = NameID::UNSPECIFIED; +#ifdef HAVE_GOOD_STL + if ((rule=m_attrMap.find(make_pair(format,xstring()))) != m_attrMap.end()) { +#else + auto_ptr_char temp(format); + if ((rule=m_attrMap.find(make_pair(temp.get(),string()))) != m_attrMap.end()) { +#endif + if (aset.empty() || aset.count(rule->second.second)) + resolved.push_back(rule->second.first->decode(rule->second.second.c_str(), &ctx.getNameID())); + } + } + + const vector& statements = token->getAttributeStatements(); + for (vector::const_iterator s = statements.begin(); s!=statements.end(); ++s) { + const vector& attrs = const_cast(*s)->getAttributes(); + for (vector::const_iterator a = attrs.begin(); a!=attrs.end(); ++a) { + name = (*a)->getAttributeName(); + format = (*a)->getAttributeNamespace(); + if (!name || !*name) + continue; + if (!format) + format = &chNull; +#ifdef HAVE_GOOD_STL + if ((rule=m_attrMap.find(make_pair(name,format))) != m_attrMap.end()) { +#else + auto_ptr_char temp1(name); + auto_ptr_char temp2(format); + if ((rule=m_attrMap.find(make_pair(temp1.get(),temp2.get()))) != m_attrMap.end()) { +#endif + if (aset.empty() || aset.count(rule->second.second)) + resolved.push_back(rule->second.first->decode(rule->second.second.c_str(), *a)); + } + } + } +} + +void SimpleResolverImpl::resolve( + ResolutionContext& ctx, const opensaml::saml2::Assertion* token, const vector* attributes + ) const +{ + set aset; + if (attributes) + for(vector::const_iterator i=attributes->begin(); i!=attributes->end(); ++i) + aset.insert(*i); + + vector& resolved = ctx.getResolvedAttributes(); + +#ifdef HAVE_GOOD_STL + map< pair,pair >::const_iterator rule; +#else + map< pair,pair >::const_iterator rule; +#endif + + // Check the NameID based on the format. + const XMLCh* name; + const XMLCh* format = ctx.getNameID().getFormat(); + if (!format) { + format = NameID::UNSPECIFIED; +#ifdef HAVE_GOOD_STL + if ((rule=m_attrMap.find(make_pair(format,xstring()))) != m_attrMap.end()) { +#else + auto_ptr_char temp(format); + if ((rule=m_attrMap.find(make_pair(temp.get(),string()))) != m_attrMap.end()) { +#endif + if (aset.empty() || aset.count(rule->second.second)) + resolved.push_back(rule->second.first->decode(rule->second.second.c_str(), &ctx.getNameID())); + } + } + + const vector& statements = token->getAttributeStatements(); + for (vector::const_iterator s = statements.begin(); s!=statements.end(); ++s) { + const vector& attrs = const_cast(*s)->getAttributes(); + for (vector::const_iterator a = attrs.begin(); a!=attrs.end(); ++a) { + name = (*a)->getName(); + format = (*a)->getNameFormat(); + if (!name || !*name) + continue; + if (!format) + format = &chNull; +#ifdef HAVE_GOOD_STL + if ((rule=m_attrMap.find(make_pair(name,format))) != m_attrMap.end()) { +#else + auto_ptr_char temp1(name); + auto_ptr_char temp2(format); + if ((rule=m_attrMap.find(make_pair(temp1.get(),temp2.get()))) != m_attrMap.end()) { +#endif + if (aset.empty() || aset.count(rule->second.second)) + resolved.push_back(rule->second.first->decode(rule->second.second.c_str(), *a)); + } + } + } +} + +void SimpleResolverImpl::query(ResolutionContext& ctx, const opensaml::saml1::Assertion* token, const vector* attributes) const +{ + if (!m_allowQuery) + return; + +#ifdef _DEBUG + xmltooling::NDC ndc("query"); +#endif + Category& log=Category::getInstance(SHIBSP_LOGCAT".AttributeResolver"); + + const EntityDescriptor* entity = ctx.getEntityDescriptor(); + if (!entity) { + log.debug("no issuer information available, skipping query"); + return; + } + const AttributeAuthorityDescriptor* AA = + entity->getAttributeAuthorityDescriptor( + token->getMinorVersion().second==1 ? samlconstants::SAML11_PROTOCOL_ENUM : samlconstants::SAML10_PROTOCOL_ENUM + ); + if (!AA) { + log.debug("no SAML 1.%d AttributeAuthority role found in metadata", token->getMinorVersion().second); + return; + } + + SecurityPolicy policy; + shibsp::SOAPClient soaper(ctx.getApplication(),policy); + + auto_ptr_XMLCh binding(samlconstants::SAML1_BINDING_SOAP); + opensaml::saml1p::Response* response=NULL; + const vector& endpoints=AA->getAttributeServices(); + for (vector::const_iterator ep=endpoints.begin(); !response && ep!=endpoints.end(); ++ep) { + try { + if (!XMLString::equals((*ep)->getBinding(),binding.get())) + continue; + auto_ptr_char loc((*ep)->getLocation()); + auto_ptr_XMLCh issuer(ctx.getApplication().getString("providerId").second); + opensaml::saml1::Subject* subject = opensaml::saml1::SubjectBuilder::buildSubject(); + subject->setNameIdentifier(token->getAuthenticationStatements().front()->getSubject()->getNameIdentifier()->cloneNameIdentifier()); + opensaml::saml1p::AttributeQuery* query = opensaml::saml1p::AttributeQueryBuilder::buildAttributeQuery(); + query->setSubject(subject); + Request* request = RequestBuilder::buildRequest(); + request->setAttributeQuery(query); + query->setResource(issuer.get()); + request->setMinorVersion(token->getMinorVersion().second); + SAML1SOAPClient client(soaper); + client.sendSAML(request, *AA, loc.get()); + response = client.receiveSAML(); + } + catch (exception& ex) { + log.error("exception making SAML query: %s", ex.what()); + soaper.reset(); + } + } + + if (!response) { + log.error("unable to successfully query for attributes"); + return; + } + + time_t now = time(NULL); + const Validator* tokval = ctx.getApplication().getTokenValidator(now, AA); + const vector& assertions = const_cast(response)->getAssertions(); + if (assertions.size()==1) { + auto_ptr wrapper(response); + opensaml::saml1::Assertion* newtoken = assertions.front(); + if (!XMLString::equals(policy.getIssuer() ? policy.getIssuer()->getName() : NULL, newtoken->getIssuer())) { + log.error("assertion issued by someone other than AA, rejecting it"); + return; + } + try { + tokval->validate(newtoken); + } + catch (exception& ex) { + log.error("assertion failed validation check: %s", ex.what()); + } + newtoken->detach(); + wrapper.release(); + ctx.getResolvedAssertions().push_back(newtoken); + resolve(ctx, newtoken, attributes); + } + else { + auto_ptr wrapper(response); + for (vector::const_iterator a = assertions.begin(); a!=assertions.end(); ++a) { + if (!XMLString::equals(policy.getIssuer() ? policy.getIssuer()->getName() : NULL, (*a)->getIssuer())) { + log.error("assertion issued by someone other than AA, rejecting it"); + continue; + } + try { + tokval->validate(*a); + } + catch (exception& ex) { + log.error("assertion failed validation check: %s", ex.what()); + } + resolve(ctx, *a, attributes); + ctx.getResolvedAssertions().push_back((*a)->cloneAssertion()); + } + } +} + +void SimpleResolverImpl::query(ResolutionContext& ctx, const opensaml::saml2::Assertion* token, const vector* attributes) const +{ + if (!m_allowQuery) + return; + +#ifdef _DEBUG + xmltooling::NDC ndc("query"); +#endif + Category& log=Category::getInstance(SHIBSP_LOGCAT".AttributeResolver"); + + const EntityDescriptor* entity = ctx.getEntityDescriptor(); + if (!entity) { + log.debug("no issuer information available, skipping query"); + return; + } + const AttributeAuthorityDescriptor* AA = entity->getAttributeAuthorityDescriptor(samlconstants::SAML20P_NS); + if (!AA) { + log.debug("no SAML 2 AttributeAuthority role found in metadata"); + return; + } + + SecurityPolicy policy; + shibsp::SOAPClient soaper(ctx.getApplication(),policy); + + auto_ptr_XMLCh binding(samlconstants::SAML20_BINDING_SOAP); + opensaml::saml2p::StatusResponseType* srt=NULL; + const vector& endpoints=AA->getAttributeServices(); + for (vector::const_iterator ep=endpoints.begin(); !srt && ep!=endpoints.end(); ++ep) { + try { + if (!XMLString::equals((*ep)->getBinding(),binding.get())) + continue; + auto_ptr_char loc((*ep)->getLocation()); + auto_ptr_XMLCh issuer(ctx.getApplication().getString("providerId").second); + opensaml::saml2::Subject* subject = opensaml::saml2::SubjectBuilder::buildSubject(); + subject->setNameID(token->getSubject()->getNameID()->cloneNameID()); + opensaml::saml2p::AttributeQuery* query = opensaml::saml2p::AttributeQueryBuilder::buildAttributeQuery(); + query->setSubject(subject); + Issuer* iss = IssuerBuilder::buildIssuer(); + query->setIssuer(iss); + iss->setName(issuer.get()); + SAML2SOAPClient client(soaper); + client.sendSAML(query, *AA, loc.get()); + srt = client.receiveSAML(); + } + catch (exception& ex) { + log.error("exception making SAML query: %s", ex.what()); + soaper.reset(); + } + } + + if (!srt) { + log.error("unable to successfully query for attributes"); + return; + } + opensaml::saml2p::Response* response = dynamic_cast(srt); + if (!response) { + delete srt; + log.error("message was not a samlp:Response"); + return; + } + + time_t now = time(NULL); + const Validator* tokval = ctx.getApplication().getTokenValidator(now, AA); + const vector& assertions = const_cast(response)->getAssertions(); + if (assertions.size()==1) { + auto_ptr wrapper(response); + opensaml::saml2::Assertion* newtoken = assertions.front(); + if (!XMLString::equals(policy.getIssuer() ? policy.getIssuer()->getName() : NULL, newtoken->getIssuer() ? newtoken->getIssuer()->getName() : NULL)) { + log.error("assertion issued by someone other than AA, rejecting it"); + return; + } + try { + tokval->validate(newtoken); + } + catch (exception& ex) { + log.error("assertion failed validation check: %s", ex.what()); + } + newtoken->detach(); + wrapper.release(); + ctx.getResolvedAssertions().push_back(newtoken); + resolve(ctx, newtoken, attributes); + } + else { + auto_ptr wrapper(response); + for (vector::const_iterator a = assertions.begin(); a!=assertions.end(); ++a) { + if (!XMLString::equals(policy.getIssuer() ? policy.getIssuer()->getName() : NULL, (*a)->getIssuer() ? (*a)->getIssuer()->getName() : NULL)) { + log.error("assertion issued by someone other than AA, rejecting it"); + return; + } + try { + tokval->validate(*a); + } + catch (exception& ex) { + log.error("assertion failed validation check: %s", ex.what()); + } + resolve(ctx, *a, attributes); + ctx.getResolvedAssertions().push_back((*a)->cloneAssertion()); + } + } +} + +void SimpleResolver::resolveAttributes(ResolutionContext& ctx, const vector* attributes) const +{ +#ifdef _DEBUG + xmltooling::NDC ndc("resolveAttributes"); +#endif + Category& log=Category::getInstance(SHIBSP_LOGCAT".AttributeResolver"); + + log.debug("examining incoming SSO token"); + + const opensaml::RootObject* token = ctx.getSSOToken(); + if (!token) { + log.warn("no SSO token supplied to resolver, returning nothing"); + return; + } + const opensaml::saml2::Assertion* token2 = dynamic_cast(token); + if (token2) { + if (!token2->getAttributeStatements().empty()) { + log.debug("found SAML 2 SSO token with an AttributeStatement"); + return m_impl->resolve(ctx, token2, attributes); + } + return m_impl->query(ctx, token2, attributes); + } + + const opensaml::saml1::Assertion* token1 = dynamic_cast(token); + if (token1) { + if (!token1->getAttributeStatements().empty()) { + log.debug("found SAML 1 SSO token with an AttributeStatement"); + return m_impl->resolve(ctx, token1, attributes); + } + return m_impl->query(ctx, token1, attributes); + } + + log.warn("unrecognized token type, returning nothing"); +} + +pair SimpleResolver::load() +{ + // Load from source using base class. + pair raw = ReloadableXMLFile::load(); + + // If we own it, wrap it. + XercesJanitor docjanitor(raw.first ? raw.second->getOwnerDocument() : NULL); + + SimpleResolverImpl* impl = new SimpleResolverImpl(raw.second); + + // If we held the document, transfer it to the impl. If we didn't, it's a no-op. + impl->setDocument(docjanitor.release()); + + delete m_impl; + m_impl = impl; + + return make_pair(false,(DOMElement*)NULL); +} diff --git a/shibsp/binding/SOAPClient.h b/shibsp/binding/SOAPClient.h index 6267048..6ca9064 100644 --- a/shibsp/binding/SOAPClient.h +++ b/shibsp/binding/SOAPClient.h @@ -25,6 +25,7 @@ #include #include +#include namespace shibsp { diff --git a/shibsp/exceptions.h b/shibsp/exceptions.h index c686094..7e6c8ee 100644 --- a/shibsp/exceptions.h +++ b/shibsp/exceptions.h @@ -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.); diff --git a/shibsp/impl/XMLServiceProvider.cpp b/shibsp/impl/XMLServiceProvider.cpp index bff5781..e6d9da8 100644 --- a/shibsp/impl/XMLServiceProvider.cpp +++ b/shibsp/impl/XMLServiceProvider.cpp @@ -66,6 +66,18 @@ namespace { #pragma warning( disable : 4250 ) #endif + class SHIBSP_DLLLOCAL TokenValidator : public Validator + { + public: + TokenValidator(const Application& app, time_t ts=0, const RoleDescriptor* role=NULL) : m_app(app), m_ts(ts), m_role(role) {} + void validate(const XMLObject*) const; + + private: + const Application& m_app; + time_t m_ts; + const RoleDescriptor* m_role; + }; + static vector g_noHandlers; // Application configuration wrapper @@ -89,7 +101,6 @@ namespace { const char* getHash() const {return m_hash.c_str();} MetadataProvider* getMetadataProvider() const; TrustEngine* getTrustEngine() const; - const vector& getAudiences() const; const PropertySet* getCredentialUse(const EntityDescriptor* provider) const; const Handler* getDefaultSessionInitiator() const; @@ -98,7 +109,14 @@ namespace { const Handler* getAssertionConsumerServiceByIndex(unsigned short index) const; const vector& getAssertionConsumerServicesByBinding(const XMLCh* binding) const; const Handler* getHandler(const char* path) const; - + + const vector& getAudiences() const; + Validator* getTokenValidator(time_t ts=0, const opensaml::saml2md::RoleDescriptor* role=NULL) const { + return new TokenValidator(*this, ts, role); + } + + void validator(const XMLObject* xmlObject) const; + // Provides filter to exclude special config elements. short acceptNode(const DOMNode* node) const; @@ -327,6 +345,122 @@ namespace shibsp { } }; +void TokenValidator::validate(const XMLObject* xmlObject) const +{ +#ifdef _DEBUG + xmltooling::NDC ndc("validate"); +#endif + Category& log=Category::getInstance(SHIBSP_LOGCAT".Application"); + + const opensaml::RootObject* root = NULL; + const opensaml::saml2::Assertion* token2 = dynamic_cast(xmlObject); + if (token2) { + const opensaml::saml2::Conditions* conds = token2->getConditions(); + // First verify the time conditions, using the specified timestamp, if non-zero. + if (m_ts>0 && conds) { + unsigned int skew = XMLToolingConfig::getConfig().clock_skew_secs; + time_t t=conds->getNotBeforeEpoch(); + if (m_ts+skew < t) + throw ValidationException("Assertion is not yet valid."); + t=conds->getNotOnOrAfterEpoch(); + if (t <= m_ts-skew) + throw ValidationException("Assertion is no longer valid."); + } + + // Now we process conditions. Only audience restrictions at the moment. + const vector& convec = conds->getConditions(); + for (vector::const_iterator c = convec.begin(); c!=convec.end(); ++c) { + const opensaml::saml2::AudienceRestriction* ac=dynamic_cast(*c); + if (!ac) { + log.error("unrecognized Condition in assertion (%s)", + (*c)->getSchemaType() ? (*c)->getSchemaType()->toString().c_str() : (*c)->getElementQName().toString().c_str()); + throw ValidationException("Assertion contains an unrecognized condition."); + } + + bool found = false; + const vector& auds1 = ac->getAudiences(); + const vector& auds2 = m_app.getAudiences(); + for (vector::const_iterator a = auds1.begin(); !found && a!=auds1.end(); ++a) { + for (vector::const_iterator a2 = auds2.begin(); !found && a2!=auds2.end(); ++a2) { + found = XMLString::equals((*a)->getAudienceURI(), *a2); + } + } + + if (!found) { + ostringstream os; + os << *ac; + log.error("unacceptable AudienceRestriction in assertion (%s)", os.str().c_str()); + throw ValidationException("Assertion contains an unacceptable AudienceRestriction."); + } + } + + root = token2; + } + else { + const opensaml::saml1::Assertion* token1 = dynamic_cast(xmlObject); + if (token1) { + const opensaml::saml1::Conditions* conds = token1->getConditions(); + // First verify the time conditions, using the specified timestamp, if non-zero. + if (m_ts>0 && conds) { + unsigned int skew = XMLToolingConfig::getConfig().clock_skew_secs; + time_t t=conds->getNotBeforeEpoch(); + if (m_ts+skew < t) + throw ValidationException("Assertion is not yet valid."); + t=conds->getNotOnOrAfterEpoch(); + if (t <= m_ts-skew) + throw ValidationException("Assertion is no longer valid."); + } + + // Now we process conditions. Only audience restrictions at the moment. + const vector& convec = conds->getConditions(); + for (vector::const_iterator c = convec.begin(); c!=convec.end(); ++c) { + const opensaml::saml1::AudienceRestrictionCondition* ac=dynamic_cast(*c); + if (!ac) { + log.error("unrecognized Condition in assertion (%s)", + (*c)->getSchemaType() ? (*c)->getSchemaType()->toString().c_str() : (*c)->getElementQName().toString().c_str()); + throw ValidationException("Assertion contains an unrecognized condition."); + } + + bool found = false; + const vector& auds1 = ac->getAudiences(); + const vector& auds2 = m_app.getAudiences(); + for (vector::const_iterator a = auds1.begin(); !found && a!=auds1.end(); ++a) { + for (vector::const_iterator a2 = auds2.begin(); !found && a2!=auds2.end(); ++a2) { + found = XMLString::equals((*a)->getAudienceURI(), *a2); + } + } + + if (!found) { + ostringstream os; + os << *ac; + log.error("unacceptable AudienceRestrictionCondition in assertion (%s)", os.str().c_str()); + throw ValidationException("Assertion contains an unacceptable AudienceRestrictionCondition."); + } + } + + root = token1; + } + else { + throw ValidationException("Unknown object type passed to token validator."); + } + } + + if (!m_role || !m_app.getTrustEngine()) { + log.warn("no issuer role or TrustEngine provided, so no signature validation performed"); + return; + } + + const PropertySet* policy=m_app.getServiceProvider().getPolicySettings(m_app.getString("policyId").second); + pair signedAssertions=policy ? policy->getBool("signedAssertions") : make_pair(false,false); + + if (root->getSignature()) { + if (!m_app.getTrustEngine()->validate(*(root->getSignature()),*m_role)) + throw ValidationException("Assertion signature did not validate."); + } + else if (signedAssertions.first && signedAssertions.second) + throw ValidationException("Assertion was unsigned, violating policy."); +} + XMLApplication::XMLApplication( const ServiceProvider* sp, const DOMElement* e, @@ -466,7 +600,7 @@ XMLApplication::XMLApplication( // Always include our own providerId as an audience. m_audiences.push_back(getXMLString("providerId").second); - if (conf.isEnabled(SPConfig::AttributeResolver)) { + if (conf.isEnabled(SPConfig::AttributeResolution)) { // TODO } diff --git a/shibsp/shibsp.vcproj b/shibsp/shibsp.vcproj index f4b68a1..cf1f41c 100644 --- a/shibsp/shibsp.vcproj +++ b/shibsp/shibsp.vcproj @@ -317,6 +317,22 @@ RelativePath=".\attribute\SimpleAttributeDecoder.cpp" > + + + + + + + + + + @@ -471,6 +491,18 @@ RelativePath=".\attribute\SimpleAttribute.h" > + + + + + +