From 5d254f0abe039780dde02efc07f4002394af35b4 Mon Sep 17 00:00:00 2001 From: cantor Date: Wed, 4 Apr 2007 04:04:33 +0000 Subject: [PATCH] Improve property inheritance, first batch of SessionInitiators, rename providerId. git-svn-id: https://svn.middleware.georgetown.edu/cpp-sp/trunk@2215 cb58f699-b61c-0410-a6fe-9272a202ed29 --- configs/shibboleth.xml.in | 35 ++--- nsapi_shib/nsapi_shib.cpp | 1 + schemas/shibboleth-spconfig-2.0.xsd | 37 ++--- shibsp/Makefile.am | 2 + .../resolver/impl/SimpleAttributeResolver.cpp | 10 +- shibsp/binding/impl/ArtifactResolver.cpp | 2 +- shibsp/handler/AbstractHandler.h | 14 ++ shibsp/handler/AssertionConsumerService.h | 6 +- shibsp/handler/Handler.h | 6 + shibsp/handler/impl/AbstractHandler.cpp | 90 +++++++++++- shibsp/handler/impl/AssertionConsumerService.cpp | 32 ++--- shibsp/handler/impl/ChainingSessionInitiator.cpp | 108 ++++++++++++++ shibsp/handler/impl/SAML2Consumer.cpp | 4 +- shibsp/handler/impl/Shib1SessionInitiator.cpp | 156 +++++++++++++++++++++ shibsp/impl/XMLServiceProvider.cpp | 73 ++-------- shibsp/shibsp.vcproj | 8 ++ shibsp/util/DOMPropertySet.cpp | 56 ++++---- shibsp/util/DOMPropertySet.h | 7 +- shibsp/util/PropertySet.h | 7 + util/samlquery.cpp | 2 +- 20 files changed, 495 insertions(+), 161 deletions(-) create mode 100644 shibsp/handler/impl/ChainingSessionInitiator.cpp create mode 100644 shibsp/handler/impl/Shib1SessionInitiator.cpp diff --git a/configs/shibboleth.xml.in b/configs/shibboleth.xml.in index ec09c7d..6f07abf 100644 --- a/configs/shibboleth.xml.in +++ b/configs/shibboleth.xml.in @@ -109,7 +109,7 @@ Resource requests are mapped in the Local section into an applicationId that points into to this section. --> - + + + - - + + diff --git a/nsapi_shib/nsapi_shib.cpp b/nsapi_shib/nsapi_shib.cpp index 56a5976..d9717d2 100644 --- a/nsapi_shib/nsapi_shib.cpp +++ b/nsapi_shib/nsapi_shib.cpp @@ -439,6 +439,7 @@ public: void unlock() { m_stKey->setData(NULL); m_propsKey->setData(NULL); m_mapper->unlock(); } Settings getSettings(const SPRequest& request) const; + void setParent(const PropertySet*) {} pair getBool(const char* name, const char* ns=NULL) const; pair getString(const char* name, const char* ns=NULL) const; pair getXMLString(const char* name, const char* ns=NULL) const; diff --git a/schemas/shibboleth-spconfig-2.0.xsd b/schemas/shibboleth-spconfig-2.0.xsd index c145ce2..5d222f1 100644 --- a/schemas/shibboleth-spconfig-2.0.xsd +++ b/schemas/shibboleth-spconfig-2.0.xsd @@ -347,9 +347,9 @@ - + - + @@ -371,9 +371,9 @@ - + - + @@ -425,23 +425,26 @@ - Used to specify handlers that can issue AuthnRequests + Used to specify handlers that can issue AuthnRequests or perform discovery - - - - - - - - - - - + + + + + + + + + + + + + + - + Container for error templates and associated details diff --git a/shibsp/Makefile.am b/shibsp/Makefile.am index 88f50e4..b3045c6 100644 --- a/shibsp/Makefile.am +++ b/shibsp/Makefile.am @@ -96,9 +96,11 @@ libshibsp_la_SOURCES = \ binding/impl/SOAPClient.cpp \ handler/impl/AbstractHandler.cpp \ handler/impl/AssertionConsumerService.cpp \ + handler/impl/ChainingSessionInitiator.cpp \ handler/impl/RemotedHandler.cpp \ handler/impl/SAML1Consumer.cpp \ handler/impl/SAML2Consumer.cpp \ + handler/impl/Shib1SessionInitiator.cpp \ impl/RemotedSessionCache.cpp \ impl/StorageServiceSessionCache.cpp \ impl/XMLAccessControl.cpp \ diff --git a/shibsp/attribute/resolver/impl/SimpleAttributeResolver.cpp b/shibsp/attribute/resolver/impl/SimpleAttributeResolver.cpp index 90ac16a..4424921 100644 --- a/shibsp/attribute/resolver/impl/SimpleAttributeResolver.cpp +++ b/shibsp/attribute/resolver/impl/SimpleAttributeResolver.cpp @@ -325,7 +325,7 @@ void SimpleResolverImpl::resolve( vector& resolved = ctx.getResolvedAttributes(); auto_ptr_char assertingParty(ctx.getEntityDescriptor() ? ctx.getEntityDescriptor()->getEntityID() : NULL); - const char* relyingParty = ctx.getApplication().getString("providerId").second; + const char* relyingParty = ctx.getApplication().getString("entityID").second; #ifdef HAVE_GOOD_STL map< pair,pair >::const_iterator rule; @@ -391,7 +391,7 @@ void SimpleResolverImpl::resolve( vector& resolved = ctx.getResolvedAttributes(); auto_ptr_char assertingParty(ctx.getEntityDescriptor() ? ctx.getEntityDescriptor()->getEntityID() : NULL); - const char* relyingParty = ctx.getApplication().getString("providerId").second; + const char* relyingParty = ctx.getApplication().getString("entityID").second; #ifdef HAVE_GOOD_STL map< pair,pair >::const_iterator rule; @@ -452,7 +452,7 @@ void SimpleResolverImpl::resolve( const vector& encattrs = const_cast(*s)->getEncryptedAttributes(); if (!encattrs.empty()) { - const XMLCh* recipient = ctx.getApplication().getXMLString("providerId").second; + const XMLCh* recipient = ctx.getApplication().getXMLString("entityID").second; CredentialResolver* cr = ctx.getApplication().getCredentialResolver(); if (!cr) { Category::getInstance(SHIBSP_LOGCAT".AttributeResolver").warn( @@ -533,7 +533,7 @@ void SimpleResolverImpl::query(ResolutionContext& ctx, const NameIdentifier& nam if (!XMLString::equals((*ep)->getBinding(),binding.get())) continue; auto_ptr_char loc((*ep)->getLocation()); - auto_ptr_XMLCh issuer(ctx.getApplication().getString("providerId").second); + auto_ptr_XMLCh issuer(ctx.getApplication().getString("entityID").second); saml1::Subject* subject = saml1::SubjectBuilder::buildSubject(); subject->setNameIdentifier(nameid.cloneNameIdentifier()); saml1p::AttributeQuery* query = saml1p::AttributeQueryBuilder::buildAttributeQuery(); @@ -645,7 +645,7 @@ void SimpleResolverImpl::query(ResolutionContext& ctx, const NameID& nameid, con if (!XMLString::equals((*ep)->getBinding(),binding.get())) continue; auto_ptr_char loc((*ep)->getLocation()); - auto_ptr_XMLCh issuer(ctx.getApplication().getString("providerId").second); + auto_ptr_XMLCh issuer(ctx.getApplication().getString("entityID").second); saml2::Subject* subject = saml2::SubjectBuilder::buildSubject(); subject->setNameID(nameid.cloneNameID()); saml2p::AttributeQuery* query = saml2p::AttributeQueryBuilder::buildAttributeQuery(); diff --git a/shibsp/binding/impl/ArtifactResolver.cpp b/shibsp/binding/impl/ArtifactResolver.cpp index 3f85c48..eb6bd20 100644 --- a/shibsp/binding/impl/ArtifactResolver.cpp +++ b/shibsp/binding/impl/ArtifactResolver.cpp @@ -106,7 +106,7 @@ ArtifactResponse* ArtifactResolver::resolve( if (!XMLString::equals((*ep)->getBinding(),binding.get())) continue; auto_ptr_char loc((*ep)->getLocation()); - auto_ptr_XMLCh issuer(sppolicy.getApplication().getString("providerId").second); + auto_ptr_XMLCh issuer(sppolicy.getApplication().getString("entityID").second); ArtifactResolve* request = ArtifactResolveBuilder::buildArtifactResolve(); Issuer* iss = IssuerBuilder::buildIssuer(); request->setIssuer(iss); diff --git a/shibsp/handler/AbstractHandler.h b/shibsp/handler/AbstractHandler.h index 46db17a..31a5cbf 100644 --- a/shibsp/handler/AbstractHandler.h +++ b/shibsp/handler/AbstractHandler.h @@ -32,6 +32,8 @@ namespace shibsp { + class SHIBSP_API SPRequest; + #if defined (_MSC_VER) #pragma warning( push ) #pragma warning( disable : 4250 ) @@ -68,6 +70,18 @@ namespace shibsp { virtual void checkError(const xmltooling::XMLObject* response) const; /** + * Implements various mechanisms to preserve RelayState, + * such as cookies or StorageService-backed keys. + * + *

If a supported mechanism can be identified, the input parameter will be + * replaced with a suitable state key, URL-encoded. + * + * @param request the active SPRequest + * @param relayState RelayState token to supply with message + */ + virtual void preserveRelayState(SPRequest& request, std::string& relayState) const; + + /** * Implements various mechanisms to recover RelayState, * such as cookies or StorageService-backed keys. * diff --git a/shibsp/handler/AssertionConsumerService.h b/shibsp/handler/AssertionConsumerService.h index 99d0750..8ff6475 100644 --- a/shibsp/handler/AssertionConsumerService.h +++ b/shibsp/handler/AssertionConsumerService.h @@ -113,15 +113,15 @@ namespace shibsp { std::string processMessage( const Application& application, opensaml::HTTPRequest& httpRequest, - std::string& providerId, + std::string& entityID, std::string& relayState ) const; std::pair sendRedirect( - SPRequest& request, const char* key, const char* providerId, const char* relayState + SPRequest& request, const char* key, const char* entityID, const char* relayState ) const; - void maintainHistory(SPRequest& request, const char* providerId, const char* cookieProps) const; + void maintainHistory(SPRequest& request, const char* entityID, const char* cookieProps) const; opensaml::MessageDecoder* m_decoder; xmltooling::auto_ptr_char m_configNS; diff --git a/shibsp/handler/Handler.h b/shibsp/handler/Handler.h index 94ccefa..2103d34 100644 --- a/shibsp/handler/Handler.h +++ b/shibsp/handler/Handler.h @@ -55,6 +55,12 @@ namespace shibsp { /** Registers Handler implementations. */ void SHIBSP_API registerHandlers(); + + /** SessionInitiator that iterates through a set of protocol-specific versions. */ + #define CHAINING_SESSION_INITIATOR "Chaining" + + /** SessionInitiator that supports Shibboleth V1 AuthnRequest/WAYF redirects. */ + #define SHIB1_SESSION_INITIATOR "Shibboleth" }; #endif /* __shibsp_handler_h__ */ diff --git a/shibsp/handler/impl/AbstractHandler.cpp b/shibsp/handler/impl/AbstractHandler.cpp index 1bf4593..9048453 100644 --- a/shibsp/handler/impl/AbstractHandler.cpp +++ b/shibsp/handler/impl/AbstractHandler.cpp @@ -23,35 +23,46 @@ #include "internal.h" #include "Application.h" #include "exceptions.h" +#include "ServiceProvider.h" #include "SPRequest.h" #include "handler/AbstractHandler.h" +#include "remoting/ListenerService.h" +#include #include #include #include #include +#include #include using namespace shibsp; using namespace samlconstants; using namespace opensaml; using namespace xmltooling; +using namespace log4cpp; using namespace xercesc; using namespace std; namespace shibsp { SHIBSP_DLLLOCAL PluginManager>::Factory SAML1ConsumerFactory; SHIBSP_DLLLOCAL PluginManager>::Factory SAML2ConsumerFactory; + SHIBSP_DLLLOCAL PluginManager>::Factory ChainingSessionInitiatorFactory; + SHIBSP_DLLLOCAL PluginManager>::Factory Shib1SessionInitiatorFactory; }; void SHIBSP_API shibsp::registerHandlers() { SPConfig& conf=SPConfig::getConfig(); + conf.AssertionConsumerServiceManager.registerFactory(SAML1_PROFILE_BROWSER_ARTIFACT, SAML1ConsumerFactory); conf.AssertionConsumerServiceManager.registerFactory(SAML1_PROFILE_BROWSER_POST, SAML1ConsumerFactory); conf.AssertionConsumerServiceManager.registerFactory(SAML20_BINDING_HTTP_ARTIFACT, SAML2ConsumerFactory); conf.AssertionConsumerServiceManager.registerFactory(SAML20_BINDING_HTTP_POST, SAML2ConsumerFactory); conf.AssertionConsumerServiceManager.registerFactory(SAML20_BINDING_HTTP_POST_SIMPLESIGN, SAML2ConsumerFactory); + + conf.SessionInitiatorManager.registerFactory(CHAINING_SESSION_INITIATOR, ChainingSessionInitiatorFactory); + conf.SessionInitiatorManager.registerFactory(SHIB1_SESSION_INITIATOR, Shib1SessionInitiatorFactory); } AbstractHandler::AbstractHandler( @@ -108,15 +119,84 @@ void AbstractHandler::checkError(const XMLObject* response) const } } +void AbstractHandler::preserveRelayState(SPRequest& request, string& relayState) const +{ + pair mech=getString("relayState"); + const URLEncoder* urlenc = XMLToolingConfig::getConfig().getURLEncoder(); + + // No setting means just pass it by value. + if (!mech.first || !mech.second || !*mech.second) { + relayState = urlenc->encode(relayState.c_str()); + } + else if (!strcmp(mech.second, "cookie")) { + // Here we store the state in a cookie and send a fixed + // value so we can recognize it on the way back. + pair shib_cookie=request.getApplication().getCookieNameProps("_shibstate_"); + string stateval = urlenc->encode(relayState.c_str()) + shib_cookie.second; + request.setCookie(shib_cookie.first.c_str(),stateval.c_str()); + relayState = "cookie"; + } + else if (strstr(mech.second,"ss:")==mech.second) { + mech.second+=3; + if (*mech.second) { + DDF out,in = DDF("set::RelayState").structure(); + in.addmember("id").string(mech.second); + in.addmember("value").string(relayState.c_str()); + DDFJanitor jin(in),jout(out); + out = request.getServiceProvider().getListenerService()->send(in); + if (!out.isstring()) + throw IOException("StorageService-backed RelayState mechanism did not return a state key."); + relayState = string(mech.second-3) + ':' + urlenc->encode(out.string()); + } + } + else + throw ConfigurationException("Unsupported relayState mechanism ($1).", params(1,mech.second)); +} + void AbstractHandler::recoverRelayState(HTTPRequest& httpRequest, string& relayState) const { SPConfig& conf = SPConfig::getConfig(); - if (conf.isEnabled(SPConfig::OutOfProcess)) { - // Out of process, we look for StorageService-backed state. - // TODO: something like ss:SSID:key? + + // Look for StorageService-backed state of the form "ss:SSID:key". + const char* state = relayState.c_str(); + if (strstr(state,"ss:")==state) { + state += 3; + const char* key = strchr(state,':'); + if (key) { + string ssid = relayState.substr(3, key - state); + key++; + if (!ssid.empty() && *key) { + if (conf.isEnabled(SPConfig::OutOfProcess)) { + StorageService* storage = conf.getServiceProvider()->getStorageService(ssid.c_str()); + if (storage) { + if (storage->readString("RelayState",key,&relayState)>0) + storage->deleteString("RelayState",key); + else + relayState = "default"; + } + else { + Category::getInstance(SHIBSP_LOGCAT".Handler").error( + "Storage-backed RelayState with invalid StorageService ID (%s)", ssid.c_str() + ); + relayState = "default"; + } + } + else if (conf.isEnabled(SPConfig::InProcess)) { + // In process, we should be able to cast down to a full SPRequest. + SPRequest& request = dynamic_cast(httpRequest); + DDF out,in = DDF("get::RelayState").structure(); + in.addmember("id").string(ssid.c_str()); + in.addmember("key").string(key); + DDFJanitor jin(in),jout(out); + out = request.getServiceProvider().getListenerService()->send(in); + if (!out.isstring()) + throw IOException("StorageService-backed RelayState mechanism did not return a state value."); + relayState = out.string(); + } + } + } } - - if (conf.isEnabled(SPConfig::InProcess)) { + else if (conf.isEnabled(SPConfig::InProcess)) { // In process, we should be able to cast down to a full SPRequest. SPRequest& request = dynamic_cast(httpRequest); if (relayState.empty() || relayState == "cookie") { diff --git a/shibsp/handler/impl/AssertionConsumerService.cpp b/shibsp/handler/impl/AssertionConsumerService.cpp index e664fc9..c5e9080 100644 --- a/shibsp/handler/impl/AssertionConsumerService.cpp +++ b/shibsp/handler/impl/AssertionConsumerService.cpp @@ -69,9 +69,9 @@ pair AssertionConsumerService::run(SPRequest& request, bool isHandler if (conf.isEnabled(SPConfig::OutOfProcess)) { // When out of process, we run natively and directly process the message. // RelayState will be fully handled during message processing. - string providerId; - string key = processMessage(request.getApplication(), request, providerId, relayState); - return sendRedirect(request, key.c_str(), providerId.c_str(), relayState.c_str()); + string entityID; + string key = processMessage(request.getApplication(), request, entityID, relayState); + return sendRedirect(request, key.c_str(), entityID.c_str(), relayState.c_str()); } else { // When not out of process, we remote all the message processing. @@ -105,7 +105,7 @@ pair AssertionConsumerService::run(SPRequest& request, bool isHandler throw FatalProfileException("Remote processing of SSO profile did not return a usable session key."); // Take care of cookie business and wrap it up. - return sendRedirect(request, out["key"].string(), out["provider_id"].string(), relayState.c_str()); + return sendRedirect(request, out["key"].string(), out["entity_id"].string(), relayState.c_str()); } } catch (XMLToolingException& ex) { @@ -131,16 +131,16 @@ void AssertionConsumerService::receive(DDF& in, ostream& out) auto_ptr http(getRequest(in)); // Do the work. - string relayState, providerId; + string relayState, entityID; try { - string key = processMessage(*app, *http.get(), providerId, relayState); + string key = processMessage(*app, *http.get(), entityID, relayState); // Repack for return to caller. DDF ret=DDF(NULL).structure(); DDFJanitor jret(ret); ret.addmember("key").string(key.c_str()); - if (!providerId.empty()) - ret.addmember("provider_id").string(providerId.c_str()); + if (!entityID.empty()) + ret.addmember("entity_id").string(entityID.c_str()); if (!relayState.empty()) ret.addmember("RelayState").string(relayState.c_str()); out << ret; @@ -154,7 +154,7 @@ void AssertionConsumerService::receive(DDF& in, ostream& out) } string AssertionConsumerService::processMessage( - const Application& application, HTTPRequest& httpRequest, string& providerId, string& relayState + const Application& application, HTTPRequest& httpRequest, string& entityID, string& relayState ) const { // Locate policy key. @@ -187,13 +187,13 @@ string AssertionConsumerService::processMessage( auto_ptr_char issuer(policy.getIssuer() ? policy.getIssuer()->getName() : NULL); if (issuer.get()) - providerId = issuer.get(); + entityID = issuer.get(); return key; } pair AssertionConsumerService::sendRedirect( - SPRequest& request, const char* key, const char* providerId, const char* relayState + SPRequest& request, const char* key, const char* entityID, const char* relayState ) const { // We've got a good session, so set the session cookie. @@ -203,7 +203,7 @@ pair AssertionConsumerService::sendRedirect( request.setCookie(shib_cookie.first.c_str(), k.c_str()); // History cookie. - maintainHistory(request, providerId, shib_cookie.second); + maintainHistory(request, entityID, shib_cookie.second); // Now redirect to the state value. By now, it should be set to *something* usable. return make_pair(true, request.sendRedirect(relayState)); @@ -260,9 +260,9 @@ ResolutionContext* AssertionConsumerService::resolveAttributes( return NULL; } -void AssertionConsumerService::maintainHistory(SPRequest& request, const char* providerId, const char* cookieProps) const +void AssertionConsumerService::maintainHistory(SPRequest& request, const char* entityID, const char* cookieProps) const { - if (!providerId) + if (!entityID) return; const PropertySet* sessionProps=request.getApplication().getPropertySet("Sessions"); @@ -274,7 +274,7 @@ void AssertionConsumerService::maintainHistory(SPRequest& request, const char* p // Either leave in memory or set an expiration. pair days=sessionProps->getUnsignedInt("idpHistoryDays"); if (!days.first || days.second==0) { - string c = string(cdc.set(providerId)) + cookieProps; + string c = string(cdc.set(entityID)) + cookieProps; request.setCookie(CommonDomainCookie::CDCName, c.c_str()); } else { @@ -287,7 +287,7 @@ void AssertionConsumerService::maintainHistory(SPRequest& request, const char* p #endif char timebuf[64]; strftime(timebuf,64,"%a, %d %b %Y %H:%M:%S GMT",ptime); - string c = string(cdc.set(providerId)) + cookieProps + "; expires=" + timebuf; + string c = string(cdc.set(entityID)) + cookieProps + "; expires=" + timebuf; request.setCookie(CommonDomainCookie::CDCName, c.c_str()); } } diff --git a/shibsp/handler/impl/ChainingSessionInitiator.cpp b/shibsp/handler/impl/ChainingSessionInitiator.cpp new file mode 100644 index 0000000..df52c08 --- /dev/null +++ b/shibsp/handler/impl/ChainingSessionInitiator.cpp @@ -0,0 +1,108 @@ +/* + * 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. + */ + +/** + * ChainingSessionInitiator.cpp + * + * Chains together multiple SessionInitiator handlers in sequence. + */ + +#include "internal.h" +#include "exceptions.h" +#include "handler/AbstractHandler.h" +#include "util/SPConstants.h" + +#include +#include + +using namespace shibsp; +using namespace xmltooling; +using namespace log4cpp; +using namespace std; + +namespace shibsp { + +#if defined (_MSC_VER) + #pragma warning( push ) + #pragma warning( disable : 4250 ) +#endif + + class SHIBSP_DLLLOCAL ChainingSessionInitiator : public AbstractHandler + { + public: + ChainingSessionInitiator(const DOMElement* e, const char* appId); + virtual ~ChainingSessionInitiator() { + for_each(m_handlers.begin(), m_handlers.end(), xmltooling::cleanup()); + } + + pair run(SPRequest& request, bool isHandler=true) const; + + private: + vector m_handlers; + }; + +#if defined (_MSC_VER) + #pragma warning( pop ) +#endif + + Handler* SHIBSP_DLLLOCAL ChainingSessionInitiatorFactory(const pair& p) + { + return new ChainingSessionInitiator(p.first, p.second); + } + + static const XMLCh SessionInitiator[] = UNICODE_LITERAL_16(S,e,s,s,i,o,n,I,n,i,t,i,a,t,o,r); + static const XMLCh _type[] = UNICODE_LITERAL_4(t,y,p,e); + + class SHIBSP_DLLLOCAL SessionInitiatorNodeFilter : public DOMNodeFilter + { + public: + short acceptNode(const DOMNode* node) const { + if (XMLHelper::isNodeNamed(node,shibspconstants::SHIB2SPCONFIG_NS,SessionInitiator)) + return FILTER_REJECT; + return FILTER_ACCEPT; + } + }; + + static SHIBSP_DLLLOCAL SessionInitiatorNodeFilter g_SINFilter; +}; + +ChainingSessionInitiator::ChainingSessionInitiator(const DOMElement* e, const char* appId) + : AbstractHandler(e, Category::getInstance(SHIBSP_LOGCAT".SessionInitiator"), &g_SINFilter) +{ + SPConfig& conf = SPConfig::getConfig(); + + // Load up the chain of handlers. + e = e ? XMLHelper::getFirstChildElement(e, SessionInitiator) : NULL; + while (e) { + auto_ptr_char type(e->getAttributeNS(NULL,_type)); + if (type.get() && *(type.get())) { + m_handlers.push_back(conf.SessionInitiatorManager.newPlugin(type.get(),make_pair(e, appId))); + m_handlers.back()->setParent(this); + } + e = XMLHelper::getNextSiblingElement(e, SessionInitiator); + } +} + +pair ChainingSessionInitiator::run(SPRequest& request, bool isHandler) const +{ + pair ret; + for (vector::const_iterator i = m_handlers.begin(); i!=m_handlers.end(); ++i) { + ret = (*i)->run(request, isHandler); + if (ret.first) + return ret; + } + throw ConfigurationException("None of the configured SessionInitiators handled the request."); +} diff --git a/shibsp/handler/impl/SAML2Consumer.cpp b/shibsp/handler/impl/SAML2Consumer.cpp index 0d79176..6d5cda4 100644 --- a/shibsp/handler/impl/SAML2Consumer.cpp +++ b/shibsp/handler/impl/SAML2Consumer.cpp @@ -201,7 +201,7 @@ string SAML2Consumer::implementProtocol( saml2::Assertion* decrypted=NULL; try { Locker credlocker(cr); - auto_ptr wrapper((*ea)->decrypt(*cr, application.getXMLString("providerId").second, &cc)); + auto_ptr wrapper((*ea)->decrypt(*cr, application.getXMLString("entityID").second, &cc)); decrypted = dynamic_cast(wrapper.get()); if (decrypted) { wrapper.release(); @@ -291,7 +291,7 @@ string SAML2Consumer::implementProtocol( else { Locker credlocker(cr); try { - auto_ptr decryptedID(encname->decrypt(*cr,application.getXMLString("providerId").second,&cc)); + auto_ptr decryptedID(encname->decrypt(*cr,application.getXMLString("entityID").second,&cc)); ssoName = dynamic_cast(decryptedID.get()); if (ssoName) { ownedName = true; diff --git a/shibsp/handler/impl/Shib1SessionInitiator.cpp b/shibsp/handler/impl/Shib1SessionInitiator.cpp new file mode 100644 index 0000000..aa89402 --- /dev/null +++ b/shibsp/handler/impl/Shib1SessionInitiator.cpp @@ -0,0 +1,156 @@ +/* + * 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. + */ + +/** + * Shib1SessionInitiator.cpp + * + * Shibboleth 1.x AuthnRequest/WAYF support. + */ + +#include "internal.h" +#include "Application.h" +#include "exceptions.h" +#include "SPRequest.h" +#include "handler/AbstractHandler.h" +#include "util/SPConstants.h" + +#include +#include +#include +#include +#include +#include + +using namespace shibsp; +using namespace opensaml::saml2md; +using namespace opensaml; +using namespace xmltooling; +using namespace log4cpp; +using namespace std; + +namespace shibsp { + +#if defined (_MSC_VER) + #pragma warning( push ) + #pragma warning( disable : 4250 ) +#endif + + class SHIBSP_DLLLOCAL Shib1SessionInitiator : public AbstractHandler + { + public: + Shib1SessionInitiator(const DOMElement* e, const char* appId) + : AbstractHandler(e, Category::getInstance(SHIBSP_LOGCAT".SessionInitiator")) {} + virtual ~Shib1SessionInitiator() {} + + pair run(SPRequest& request, bool isHandler=true) const; + + private: + pair doAuthnRequest(SPRequest& request, const Handler* shire, const char* dest, string& target) const; + }; + +#if defined (_MSC_VER) + #pragma warning( pop ) +#endif + + Handler* SHIBSP_DLLLOCAL Shib1SessionInitiatorFactory(const pair& p) + { + return new Shib1SessionInitiator(p.first, p.second); + } + +}; + +pair Shib1SessionInitiator::run(SPRequest& request, bool isHandler) const +{ + string target; + const char* entityID=NULL; + const Handler* ACS=NULL; + const Application& app=request.getApplication(); + + if (isHandler) { + entityID=request.getParameter("acsIndex"); + if (entityID) + ACS=app.getAssertionConsumerServiceByIndex(atoi(entityID)); + + entityID = request.getParameter("target"); + if (entityID) + target = entityID; + recoverRelayState(request, target); + + // Try and establish which IdP to use. + entityID=request.getParameter("entityID"); + if (!entityID || !*entityID) + entityID=request.getParameter("providerId"); + if (!entityID || !*entityID) + entityID=getString("entityID").second; + } + else { + // We're running as a "virtual handler" from within the filter. + // The target resource is the current one and everything else is defaulted. + entityID=getString("entityID").second; + target=request.getRequestURL(); + } + + if (entityID && *entityID) { + m_log.debug("attempting to initiate session using SAML 1.x with provider (%s)", entityID); + + // Use metadata to invoke the SSO service directly. + MetadataProvider* m=app.getMetadataProvider(); + Locker locker(m); + const EntityDescriptor* entity=m->getEntityDescriptor(entityID); + if (!entity) { + m_log.error("unable to locate metadata for provider (%s)", entityID); + return make_pair(false,0); + } + const IDPSSODescriptor* role=entity->getIDPSSODescriptor(shibspconstants::SHIB1_PROTOCOL_ENUM); + if (!role) { + m_log.error("unable to locate Shibboleth-aware identity provider role for provider (%s)", entityID); + return make_pair(false,0); + } + const EndpointType* ep=EndpointManager(role->getSingleSignOnServices()).getByBinding( + shibspconstants::SHIB1_AUTHNREQUEST_PROFILE_URI + ); + if (!ep) { + m_log.error("unable to locate compatible SSO service for provider (%s)", entityID); + return make_pair(false,0); + } + auto_ptr_char dest(ep->getLocation()); + return doAuthnRequest(request, ACS ? ACS : app.getDefaultAssertionConsumerService(), dest.get(), target); + } + + // Fall back to optional legacy discovery service. + pair wayfURL=getString("wayfURL"); + if (!wayfURL.first) + return make_pair(false,0); + return doAuthnRequest(request, ACS ? ACS : app.getDefaultAssertionConsumerService(), wayfURL.second, target); +} + +pair Shib1SessionInitiator::doAuthnRequest(SPRequest& request, const Handler* shire, const char* dest, string& target) const +{ + // Compute the ACS URL. We add the ACS location to the base handlerURL. + string ACSloc=request.getHandlerURL(target.c_str()); + pair loc=shire ? shire->getString("Location") : pair(false,NULL); + if (loc.first) ACSloc+=loc.second; + + preserveRelayState(request, target); + + char timebuf[16]; + sprintf(timebuf,"%u",time(NULL)); + const URLEncoder* urlenc = XMLToolingConfig::getConfig().getURLEncoder(); + string req=string(dest) + "?shire=" + urlenc->encode(ACSloc.c_str()) + "&time=" + timebuf + "&target=" + target + + "&providerId=" + urlenc->encode(request.getApplication().getString("entityID").second); + + return make_pair(true, request.sendRedirect(req.c_str())); +} diff --git a/shibsp/impl/XMLServiceProvider.cpp b/shibsp/impl/XMLServiceProvider.cpp index e5c96a6..725970a 100644 --- a/shibsp/impl/XMLServiceProvider.cpp +++ b/shibsp/impl/XMLServiceProvider.cpp @@ -75,14 +75,6 @@ namespace { XMLApplication(const ServiceProvider*, const DOMElement* e, const XMLApplication* base=NULL); ~XMLApplication() { cleanup(); } - // PropertySet - pair getBool(const char* name, const char* ns=NULL) const; - pair getString(const char* name, const char* ns=NULL) const; - pair getXMLString(const char* name, const char* ns=NULL) const; - pair getUnsignedInt(const char* name, const char* ns=NULL) const; - pair getInt(const char* name, const char* ns=NULL) const; - const PropertySet* getPropertySet(const char* name, const char* ns="urn:mace:shibboleth:sp:config:2.0") const; - // Application const ServiceProvider& getServiceProvider() const {return *m_sp;} const char* getId() const {return getString("id").second;} @@ -214,6 +206,7 @@ namespace { } // PropertySet + void setParent(const PropertySet* parent) {return m_impl->setParent(parent);} pair getBool(const char* name, const char* ns=NULL) const {return m_impl->getBool(name,ns);} pair getString(const char* name, const char* ns=NULL) const {return m_impl->getString(name,ns);} pair getXMLString(const char* name, const char* ns=NULL) const {return m_impl->getXMLString(name,ns);} @@ -356,13 +349,15 @@ XMLApplication::XMLApplication( try { // First load any property sets. load(e,log,this); + if (base) + setParent(base); SPConfig& conf=SPConfig::getConfig(); SAMLConfig& samlConf=SAMLConfig::getConfig(); XMLToolingConfig& xmlConf=XMLToolingConfig::getConfig(); m_hash=getId(); - m_hash+=getString("providerId").second; + m_hash+=getString("entityID").second; m_hash=samlConf.hashSHA1(m_hash.c_str(), true); pair attributes = getString("attributeIds"); @@ -423,13 +418,13 @@ XMLApplication::XMLApplication( } } else if (XMLString::equals(child->getLocalName(),SessionInitiator)) { - auto_ptr_char bindprop(child->getAttributeNS(NULL,EndpointType::BINDING_ATTRIB_NAME)); - if (!bindprop.get() || !*(bindprop.get())) { - log.warn("SessionInitiator element has no Binding attribute, skipping it..."); + auto_ptr_char type(child->getAttributeNS(NULL,_type)); + if (!type.get() || !*(type.get())) { + log.warn("SessionInitiator element has no type attribute, skipping it..."); child = XMLHelper::getNextSiblingElement(child); continue; } - handler=conf.SessionInitiatorManager.newPlugin(bindprop.get(),make_pair(child, getId())); + handler=conf.SessionInitiatorManager.newPlugin(type.get(),make_pair(child, getId())); pair si_id=handler->getString("id"); if (si_id.first && si_id.second) m_sessionInitMap[si_id.second]=handler; @@ -496,8 +491,8 @@ XMLApplication::XMLApplication( if (nlist->item(i)->getParentNode()->isSameNode(e) && nlist->item(i)->hasChildNodes()) m_audiences.push_back(nlist->item(i)->getFirstChild()->getNodeValue()); - // Always include our own providerId as an audience. - m_audiences.push_back(getXMLString("providerId").second); + // Always include our own entityID as an audience. + m_audiences.push_back(getXMLString("entityID").second); if (conf.isEnabled(SPConfig::Metadata)) { child = XMLHelper::getFirstChildElement(e,_MetadataProvider); @@ -627,54 +622,6 @@ short XMLApplication::acceptNode(const DOMNode* node) const return FILTER_ACCEPT; } -pair XMLApplication::getBool(const char* name, const char* ns) const -{ - pair ret=DOMPropertySet::getBool(name,ns); - if (ret.first) - return ret; - return m_base ? m_base->getBool(name,ns) : ret; -} - -pair XMLApplication::getString(const char* name, const char* ns) const -{ - pair ret=DOMPropertySet::getString(name,ns); - if (ret.first) - return ret; - return m_base ? m_base->getString(name,ns) : ret; -} - -pair XMLApplication::getXMLString(const char* name, const char* ns) const -{ - pair ret=DOMPropertySet::getXMLString(name,ns); - if (ret.first) - return ret; - return m_base ? m_base->getXMLString(name,ns) : ret; -} - -pair XMLApplication::getUnsignedInt(const char* name, const char* ns) const -{ - pair ret=DOMPropertySet::getUnsignedInt(name,ns); - if (ret.first) - return ret; - return m_base ? m_base->getUnsignedInt(name,ns) : ret; -} - -pair XMLApplication::getInt(const char* name, const char* ns) const -{ - pair ret=DOMPropertySet::getInt(name,ns); - if (ret.first) - return ret; - return m_base ? m_base->getInt(name,ns) : ret; -} - -const PropertySet* XMLApplication::getPropertySet(const char* name, const char* ns) const -{ - const PropertySet* ret=DOMPropertySet::getPropertySet(name,ns); - if (ret || !m_base) - return ret; - return m_base->getPropertySet(name,ns); -} - const PropertySet* XMLApplication::getRelyingParty(const EntityDescriptor* provider) const { if (!m_partyDefault && m_base) diff --git a/shibsp/shibsp.vcproj b/shibsp/shibsp.vcproj index eceb68d..81b1f08 100644 --- a/shibsp/shibsp.vcproj +++ b/shibsp/shibsp.vcproj @@ -361,6 +361,10 @@ > + + @@ -372,6 +376,10 @@ RelativePath=".\handler\impl\SAML2Consumer.cpp" > + + diff --git a/shibsp/util/DOMPropertySet.cpp b/shibsp/util/DOMPropertySet.cpp index 58632cb..3f45ad6 100644 --- a/shibsp/util/DOMPropertySet.cpp +++ b/shibsp/util/DOMPropertySet.cpp @@ -127,7 +127,6 @@ void DOMPropertySet::load( pair DOMPropertySet::getBool(const char* name, const char* ns) const { - pair ret(false,false); map >::const_iterator i; if (ns) @@ -135,11 +134,11 @@ pair DOMPropertySet::getBool(const char* name, const char* ns) const else i=m_map.find(name); - if (i!=m_map.end()) { - ret.first=true; - ret.second=(!strcmp(i->second.first,"true") || !strcmp(i->second.first,"1")); - } - return ret; + if (i!=m_map.end()) + return make_pair(true,(!strcmp(i->second.first,"true") || !strcmp(i->second.first,"1"))); + else if (m_parent) + return m_parent->getBool(name,ns); + return make_pair(false,false); } pair DOMPropertySet::getString(const char* name, const char* ns) const @@ -152,16 +151,15 @@ pair DOMPropertySet::getString(const char* name, const char* n else i=m_map.find(name); - if (i!=m_map.end()) { - ret.first=true; - ret.second=i->second.first; - } - return ret; + if (i!=m_map.end()) + return make_pair(true,i->second.first); + else if (m_parent) + return m_parent->getString(name,ns); + return pair(false,NULL); } pair DOMPropertySet::getXMLString(const char* name, const char* ns) const { - pair ret(false,NULL); map >::const_iterator i; if (ns) @@ -169,16 +167,15 @@ pair DOMPropertySet::getXMLString(const char* name, const cha else i=m_map.find(name); - if (i!=m_map.end()) { - ret.first=true; - ret.second=i->second.second; - } - return ret; + if (i!=m_map.end()) + return make_pair(true,i->second.second); + else if (m_parent) + return m_parent->getXMLString(name,ns); + return pair(false,NULL); } pair DOMPropertySet::getUnsignedInt(const char* name, const char* ns) const { - pair ret(false,0); map >::const_iterator i; if (ns) @@ -186,16 +183,15 @@ pair DOMPropertySet::getUnsignedInt(const char* name, const c else i=m_map.find(name); - if (i!=m_map.end()) { - ret.first=true; - ret.second=strtol(i->second.first,NULL,10); - } - return ret; + if (i!=m_map.end()) + return pair(true,strtol(i->second.first,NULL,10)); + else if (m_parent) + return m_parent->getUnsignedInt(name,ns); + return pair(false,0); } pair DOMPropertySet::getInt(const char* name, const char* ns) const { - pair ret(false,0); map >::const_iterator i; if (ns) @@ -203,11 +199,11 @@ pair DOMPropertySet::getInt(const char* name, const char* ns) const else i=m_map.find(name); - if (i!=m_map.end()) { - ret.first=true; - ret.second=atoi(i->second.first); - } - return ret; + if (i!=m_map.end()) + return pair(true,atoi(i->second.first)); + else if (m_parent) + return m_parent->getInt(name,ns); + return pair(false,0); } const PropertySet* DOMPropertySet::getPropertySet(const char* name, const char* ns) const @@ -219,5 +215,5 @@ const PropertySet* DOMPropertySet::getPropertySet(const char* name, const char* else i=m_nested.find(name); - return (i!=m_nested.end()) ? i->second : NULL; + return (i!=m_nested.end()) ? i->second : (m_parent ? m_parent->getPropertySet(name,ns) : NULL); } diff --git a/shibsp/util/DOMPropertySet.h b/shibsp/util/DOMPropertySet.h index b29b307..196a287 100644 --- a/shibsp/util/DOMPropertySet.h +++ b/shibsp/util/DOMPropertySet.h @@ -34,10 +34,14 @@ namespace shibsp { class SHIBSP_API DOMPropertySet : public virtual PropertySet { public: - DOMPropertySet() : m_root(NULL) {} + DOMPropertySet() : m_parent(NULL), m_root(NULL) {} virtual ~DOMPropertySet(); + void setParent(const PropertySet* parent) { + m_parent = parent; + } + std::pair getBool(const char* name, const char* ns=NULL) const; std::pair getString(const char* name, const char* ns=NULL) const; std::pair getXMLString(const char* name, const char* ns=NULL) const; @@ -65,6 +69,7 @@ namespace shibsp { ); private: + const PropertySet* m_parent; const xercesc::DOMElement* m_root; std::map > m_map; std::map m_nested; diff --git a/shibsp/util/PropertySet.h b/shibsp/util/PropertySet.h index cc20b60..019a05d 100644 --- a/shibsp/util/PropertySet.h +++ b/shibsp/util/PropertySet.h @@ -40,6 +40,13 @@ namespace shibsp { virtual ~PropertySet() {} /** + * Establishes a "parent" PropertySet to supply inherited settings. + * + * @param parent the parent PropertySet to use + */ + virtual void setParent(const PropertySet* parent)=0; + + /** * Returns a boolean-valued property. * * @param name property name diff --git a/util/samlquery.cpp b/util/samlquery.cpp index cbc1cdf..08986a9 100644 --- a/util/samlquery.cpp +++ b/util/samlquery.cpp @@ -139,7 +139,7 @@ int main(int argc,char* argv[]) auto_ptr_XMLCh domain(q_param); auto_ptr_XMLCh name(n_param); auto_ptr_XMLCh format(f_param); - auto_ptr_XMLCh issuer(app->getString("providerId").second); + auto_ptr_XMLCh issuer(app->getString("entityID").second); MetadataProvider* m=app->getMetadataProvider(); xmltooling::Locker mlocker(m); -- 2.1.4