X-Git-Url: http://www.project-moonshot.org/gitweb/?a=blobdiff_plain;f=adfs%2Fadfs.cpp;h=b2be93ba107d22b8e285ae26a7d0afdb9b86ce65;hb=392d1448deb48beb75f219532ac248b4776f16db;hp=9fdc32ad0e6e3d3682969c7e802910f5614962e3;hpb=b62519e522628bb2f9b91b3b5421fe7a31c5e61b;p=shibboleth%2Fsp.git diff --git a/adfs/adfs.cpp b/adfs/adfs.cpp index 9fdc32a..b2be93b 100644 --- a/adfs/adfs.cpp +++ b/adfs/adfs.cpp @@ -1,25 +1,25 @@ -/* - * Copyright 2001-2005 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. - */ - -/** - * adfs.cpp - * - * ADFSv1 extension library - */ - +/* + * Copyright 2001-2005 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. + */ + +/** + * adfs.cpp + * + * ADFSv1 extension library + */ + #if defined (_MSC_VER) || defined(__BORLANDC__) # include "config_win32.h" #else @@ -29,54 +29,73 @@ #ifdef WIN32 # define _CRT_NONSTDC_NO_DEPRECATE 1 # define _CRT_SECURE_NO_DEPRECATE 1 -# define ADFS_EXPORTS __declspec(dllexport) -#else -# define ADFS_EXPORTS +# define ADFS_EXPORTS __declspec(dllexport) +#else +# define ADFS_EXPORTS #endif - -#include -#include -#include -#include -#include -#include -#include -#include + +#include +#include +#include +#include +#include +#include +#include +#include #include +#include #include #include #include -#include #include - -#ifndef SHIBSP_LITE -# include -# include -# include -# include + +#ifndef SHIBSP_LITE # include -# include -# include -# include -# include -# include +# include +# include +# include +# include +# include # include # include -# include -using namespace opensaml::saml2md; -#endif -using namespace shibsp; -using namespace opensaml; -using namespace xmltooling; -using namespace xercesc; -using namespace log4cpp; -using namespace std; - -#define WSFED_NS "http://schemas.xmlsoap.org/ws/2003/07/secext" -#define WSTRUST_NS "http://schemas.xmlsoap.org/ws/2005/02/trust" - -namespace { - +# include +using namespace opensaml::saml2md; +#endif +using namespace shibsp; +using namespace opensaml; +using namespace xmltooling::logging; +using namespace xmltooling; +using namespace xercesc; +using namespace std; + +#define WSFED_NS "http://schemas.xmlsoap.org/ws/2003/07/secext" +#define WSTRUST_NS "http://schemas.xmlsoap.org/ws/2005/02/trust" + +namespace { + +#ifndef SHIBSP_LITE + class SHIBSP_DLLLOCAL ADFSDecoder : public MessageDecoder + { + auto_ptr_XMLCh m_ns; + public: + ADFSDecoder() : m_ns(WSTRUST_NS) {} + virtual ~ADFSDecoder() {} + + XMLObject* decode(string& relayState, const GenericRequest& genericRequest, SecurityPolicy& policy) const; + + protected: + void extractMessageDetails( + const XMLObject& message, const GenericRequest& req, const XMLCh* protocol, SecurityPolicy& policy + ) const { + } + }; + + MessageDecoder* ADFSDecoderFactory(const pair& p) + { + return new ADFSDecoder(); + } +#endif + #if defined (_MSC_VER) #pragma warning( push ) #pragma warning( disable : 4250 ) @@ -86,7 +105,7 @@ namespace { { public: ADFSSessionInitiator(const DOMElement* e, const char* appId) - : AbstractHandler(e, Category::getInstance(SHIBSP_LOGCAT".SessionInitiator")), m_appId(appId), m_binding(WSFED_NS) { + : AbstractHandler(e, Category::getInstance(SHIBSP_LOGCAT".SessionInitiator.ADFS")), m_appId(appId), m_binding(WSFED_NS) { // If Location isn't set, defer address registration until the setParent call. pair loc = getString("Location"); if (loc.first) { @@ -127,31 +146,38 @@ namespace { { public: ADFSConsumer(const DOMElement* e, const char* appId) - : shibsp::AssertionConsumerService(e, appId, Category::getInstance(SHIBSP_LOGCAT".ADFSSSO")) + : shibsp::AssertionConsumerService(e, appId, Category::getInstance(SHIBSP_LOGCAT".SSO.ADFS")) #ifndef SHIBSP_LITE - ,m_binding(WSFED_NS) + ,m_protocol(WSFED_NS) #endif {} virtual ~ADFSConsumer() {} - private: #ifndef SHIBSP_LITE - string implementProtocol( + void generateMetadata(SPSSODescriptor& role, const char* handlerURL) const { + AssertionConsumerService::generateMetadata(role, handlerURL); + role.addSupport(m_protocol.get()); + } + + auto_ptr_XMLCh m_protocol; + + private: + void implementProtocol( const Application& application, const HTTPRequest& httpRequest, + HTTPResponse& httpResponse, SecurityPolicy& policy, const PropertySet* settings, const XMLObject& xmlObject ) const; - auto_ptr_XMLCh m_binding; #endif }; - class SHIBSP_DLLLOCAL ADFSLogoutInitiator : public AbstractHandler, public LogoutHandler + class SHIBSP_DLLLOCAL ADFSLogoutInitiator : public AbstractHandler, public RemotedHandler { public: ADFSLogoutInitiator(const DOMElement* e, const char* appId) - : AbstractHandler(e, Category::getInstance(SHIBSP_LOGCAT".SessionInitiator")), m_appId(appId), m_binding(WSFED_NS) { + : AbstractHandler(e, Category::getInstance(SHIBSP_LOGCAT".LogoutInitiator.ADFS")), m_appId(appId), m_binding(WSFED_NS) { // If Location isn't set, defer address registration until the setParent call. pair loc = getString("Location"); if (loc.first) { @@ -176,10 +202,14 @@ namespace { void receive(DDF& in, ostream& out); pair run(SPRequest& request, bool isHandler=true) const; +#ifndef SHIBSP_LITE + const char* getType() const { + return "LogoutInitiator"; + } +#endif + private: - pair doRequest( - const Application& application, const char* requestURL, Session* session, HTTPResponse& httpResponse - ) const; + pair doRequest(const Application& application, const char* entityID, HTTPResponse& httpResponse) const; string m_appId; auto_ptr_XMLCh m_binding; @@ -201,6 +231,26 @@ namespace { pair run(SPRequest& request, bool isHandler=true) const; +#ifndef SHIBSP_LITE + void generateMetadata(SPSSODescriptor& role, const char* handlerURL) const { + m_login.generateMetadata(role, handlerURL); + const char* loc = getString("Location").second; + string hurl(handlerURL); + if (*loc != '/') + hurl += '/'; + hurl += loc; + auto_ptr_XMLCh widen(hurl.c_str()); + SingleLogoutService* ep = SingleLogoutServiceBuilder::buildSingleLogoutService(); + ep->setLocation(widen.get()); + ep->setBinding(m_login.m_protocol.get()); + role.getSingleLogoutServices().push_back(ep); + } + + const char* getType() const { + return m_login.getType(); + } +#endif + private: ADFSConsumer m_login; }; @@ -209,23 +259,6 @@ namespace { #pragma warning( pop ) #endif -#ifndef SHIBSP_LITE - class ADFSDecoder : public MessageDecoder - { - auto_ptr_XMLCh m_ns; - public: - ADFSDecoder() : m_ns(WSTRUST_NS) {} - virtual ~ADFSDecoder() {} - - XMLObject* decode(string& relayState, const GenericRequest& genericRequest, SecurityPolicy& policy) const; - }; - - MessageDecoder* ADFSDecoderFactory(const pair& p) - { - return new ADFSDecoder(); - } -#endif - SessionInitiator* ADFSSessionInitiatorFactory(const pair& p) { return new ADFSSessionInitiator(p.first, p.second); @@ -235,18 +268,18 @@ namespace { { return new ADFSLogout(p.first, p.second); } - + Handler* ADFSLogoutInitiatorFactory(const pair& p) { return new ADFSLogoutInitiator(p.first, p.second); - } - - const XMLCh RequestedSecurityToken[] = UNICODE_LITERAL_22(R,e,q,u,e,s,t,e,d,S,e,c,u,r,i,t,y,T,o,k,e,n); - const XMLCh RequestSecurityTokenResponse[] =UNICODE_LITERAL_28(R,e,q,u,e,s,t,S,e,c,u,r,i,t,y,T,o,k,e,n,R,e,s,p,o,n,s,e); -}; - -extern "C" int ADFS_EXPORTS xmltooling_extension_init(void*) -{ + } + + const XMLCh RequestedSecurityToken[] = UNICODE_LITERAL_22(R,e,q,u,e,s,t,e,d,S,e,c,u,r,i,t,y,T,o,k,e,n); + const XMLCh RequestSecurityTokenResponse[] =UNICODE_LITERAL_28(R,e,q,u,e,s,t,S,e,c,u,r,i,t,y,T,o,k,e,n,R,e,s,p,o,n,s,e); +}; + +extern "C" int ADFS_EXPORTS xmltooling_extension_init(void*) +{ SPConfig& conf=SPConfig::getConfig(); conf.SessionInitiatorManager.registerFactory("ADFS", ADFSSessionInitiatorFactory); conf.LogoutInitiatorManager.registerFactory("ADFS", ADFSLogoutInitiatorFactory); @@ -255,30 +288,30 @@ extern "C" int ADFS_EXPORTS xmltooling_extension_init(void*) #ifndef SHIBSP_LITE SAMLConfig::getConfig().MessageDecoderManager.registerFactory(WSFED_NS, ADFSDecoderFactory); XMLObjectBuilder::registerBuilder(QName(WSTRUST_NS,"RequestedSecurityToken"), new AnyElementBuilder()); - XMLObjectBuilder::registerBuilder(QName(WSTRUST_NS,"RequestedSecurityTokenResponse"), new AnyElementBuilder()); -#endif - return 0; -} - -extern "C" void ADFS_EXPORTS xmltooling_extension_term() -{ - /* should get unregistered during normal shutdown... - SPConfig& conf=SPConfig::getConfig(); - conf.SessionInitiatorManager.deregisterFactory("ADFS"); - conf.LogoutInitiatorManager.deregisterFactory("ADFS"); - conf.AssertionConsumerServiceManager.deregisterFactory("ADFS"); - conf.AssertionConsumerServiceManager.deregisterFactory(WSFED_NS); + XMLObjectBuilder::registerBuilder(QName(WSTRUST_NS,"RequestSecurityTokenResponse"), new AnyElementBuilder()); +#endif + return 0; +} + +extern "C" void ADFS_EXPORTS xmltooling_extension_term() +{ + /* should get unregistered during normal shutdown... + SPConfig& conf=SPConfig::getConfig(); + conf.SessionInitiatorManager.deregisterFactory("ADFS"); + conf.LogoutInitiatorManager.deregisterFactory("ADFS"); + conf.AssertionConsumerServiceManager.deregisterFactory("ADFS"); + conf.AssertionConsumerServiceManager.deregisterFactory(WSFED_NS); #ifndef SHIBSP_LITE SAMLConfig::getConfig().MessageDecoderManager.deregisterFactory(WSFED_NS); -#endif - */ -} - +#endif + */ +} + pair ADFSSessionInitiator::run(SPRequest& request, const char* entityID, bool isHandler) const { // We have to know the IdP to function. if (!entityID || !*entityID) - return make_pair(false,0); + return make_pair(false,0L); string target; const Handler* ACS=NULL; @@ -292,7 +325,7 @@ pair ADFSSessionInitiator::run(SPRequest& request, const char* entity // Since we're passing the ACS by value, we need to compute the return URL, // so we'll need the target resource for real. - recoverRelayState(request.getApplication(), request, target, false); + recoverRelayState(request.getApplication(), request, request, target, false); } else { // We're running as a "virtual handler" from within the filter. @@ -301,29 +334,27 @@ pair ADFSSessionInitiator::run(SPRequest& request, const char* entity } // Since we're not passing by index, we need to fully compute the return URL. - if (!ACS) { - // Get all the ADFS endpoints. - const vector& handlers = app.getAssertionConsumerServicesByBinding(m_binding.get()); - - // Index comes from request, or default set in the handler, or we just pick the first endpoint. - pair index = make_pair(false,0); - if (isHandler) { - option = request.getParameter("acsIndex"); - if (option) - index = make_pair(true, atoi(option)); - } - if (!index.first) - index = getUnsignedInt("defaultACSIndex"); - if (index.first) { - for (vector::const_iterator h = handlers.begin(); !ACS && h!=handlers.end(); ++h) { - if (index.second == (*h)->getUnsignedInt("index").second) - ACS = *h; - } - } - else if (!handlers.empty()) { - ACS = handlers.front(); + // Get all the ADFS endpoints. + const vector& handlers = app.getAssertionConsumerServicesByBinding(m_binding.get()); + + // Index comes from request, or default set in the handler, or we just pick the first endpoint. + pair index(false,0); + if (isHandler) { + option = request.getParameter("acsIndex"); + if (option) + index = pair(true, atoi(option)); + } + if (!index.first) + index = getUnsignedInt("defaultACSIndex"); + if (index.first) { + for (vector::const_iterator h = handlers.begin(); !ACS && h!=handlers.end(); ++h) { + if (index.second == (*h)->getUnsignedInt("index").second) + ACS = *h; } } + else if (!handlers.empty()) { + ACS = handlers.front(); + } if (!ACS) throw ConfigurationException("Unable to locate ADFS response endpoint."); @@ -403,36 +434,41 @@ pair ADFSSessionInitiator::doRequest( // 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); - throw MetadataException("Unable to locate metadata for identity provider ($entityID)", - namedparams(1, "entityID", entityID)); + MetadataProvider::Criteria mc(entityID, &IDPSSODescriptor::ELEMENT_QNAME, m_binding.get()); + pair entity=m->getEntityDescriptor(mc); + if (!entity.first) { + m_log.warn("unable to locate metadata for provider (%s)", entityID); + throw MetadataException("Unable to locate metadata for identity provider ($entityID)", namedparams(1, "entityID", entityID)); } - const IDPSSODescriptor* role=entity->getIDPSSODescriptor(m_binding.get()); - if (!role) { - m_log.error("unable to locate ADFS-aware identity provider role for provider (%s)", entityID); - return make_pair(false,0); + else if (!entity.second) { + m_log.warn("unable to locate ADFS-aware identity provider role for provider (%s)", entityID); + if (getParent()) + return make_pair(false,0L); + throw MetadataException("Unable to locate ADFS-aware identity provider role for provider ($entityID)", namedparams(1, "entityID", entityID)); } - const EndpointType* ep=EndpointManager(role->getSingleSignOnServices()).getByBinding(m_binding.get()); + const EndpointType* ep = EndpointManager( + dynamic_cast(entity.second)->getSingleSignOnServices() + ).getByBinding(m_binding.get()); if (!ep) { - m_log.error("unable to locate compatible SSO service for provider (%s)", entityID); - return make_pair(false,0); + m_log.warn("unable to locate compatible SSO service for provider (%s)", entityID); + if (getParent()) + return make_pair(false,0L); + throw MetadataException("Unable to locate compatible SSO service for provider ($entityID)", namedparams(1, "entityID", entityID)); } preserveRelayState(app, httpResponse, relayState); - // UTC timestamp -#ifndef HAVE_GMTIME_R - time_t epoch=time(NULL); - struct tm* ptime=gmtime(&epoch); -#else - struct tm res; - struct tm* ptime=gmtime_r(&epoch,&res); -#endif - char timebuf[32]; - strftime(timebuf,32,"%Y-%m-%dT%H:%M:%SZ",ptime); - + // UTC timestamp + time_t epoch=time(NULL); +#ifndef HAVE_GMTIME_R + struct tm* ptime=gmtime(&epoch); +#else + struct tm res; + struct tm* ptime=gmtime_r(&epoch,&res); +#endif + char timebuf[32]; + strftime(timebuf,32,"%Y-%m-%dT%H:%M:%SZ",ptime); + auto_ptr_char dest(ep->getLocation()); const URLEncoder* urlenc = XMLToolingConfig::getConfig().getURLEncoder(); @@ -443,61 +479,62 @@ pair ADFSSessionInitiator::doRequest( return make_pair(true, httpResponse.sendRedirect(req.c_str())); #else - return make_pair(false,0); + return make_pair(false,0L); #endif } - + #ifndef SHIBSP_LITE -XMLObject* ADFSDecoder::decode(string& relayState, const GenericRequest& genericRequest, SecurityPolicy& policy) const -{ -#ifdef _DEBUG - xmltooling::NDC ndc("decode"); -#endif - Category& log = Category::getInstance(SHIBSP_LOGCAT".MessageDecoder.ADFS"); - - log.debug("validating input"); - const HTTPRequest* httpRequest=dynamic_cast(&genericRequest); - if (!httpRequest) - throw BindingException("Unable to cast request object to HTTPRequest type."); - if (strcmp(httpRequest->getMethod(),"POST")) - throw BindingException("Invalid HTTP method ($1).", params(1, httpRequest->getMethod())); - const char* param = httpRequest->getParameter("wa"); - if (!param || strcmp(param, "wsignin1.0")) - throw BindingException("Missing or invalid wa parameter (should be wsignin1.0)."); - param = httpRequest->getParameter("wctx"); - if (param) - relayState = param; - - param = httpRequest->getParameter("wresult"); - if (!param) - throw BindingException("Request missing wresult parameter."); - - log.debug("decoded ADFS response:\n%s", param); - - // Parse and bind the document into an XMLObject. - istringstream is(param); - DOMDocument* doc = (policy.getValidating() ? XMLToolingConfig::getConfig().getValidatingParser() - : XMLToolingConfig::getConfig().getParser()).parse(is); - XercesJanitor janitor(doc); - auto_ptr xmlObject(XMLObjectBuilder::buildOneFromElement(doc->getDocumentElement(), true)); - janitor.release(); - - if (!XMLHelper::isNodeNamed(xmlObject->getDOM(), m_ns.get(), RequestSecurityTokenResponse)) - throw BindingException("Decoded message was not of the appropriate type."); - - if (!policy.getValidating()) - SchemaValidators.validate(xmlObject.get()); - - // Run through the policy. - policy.evaluate(*xmlObject.get(), &genericRequest); - - return xmlObject.release(); +XMLObject* ADFSDecoder::decode(string& relayState, const GenericRequest& genericRequest, SecurityPolicy& policy) const +{ +#ifdef _DEBUG + xmltooling::NDC ndc("decode"); +#endif + Category& log = Category::getInstance(SHIBSP_LOGCAT".MessageDecoder.ADFS"); + + log.debug("validating input"); + const HTTPRequest* httpRequest=dynamic_cast(&genericRequest); + if (!httpRequest) + throw BindingException("Unable to cast request object to HTTPRequest type."); + if (strcmp(httpRequest->getMethod(),"POST")) + throw BindingException("Invalid HTTP method ($1).", params(1, httpRequest->getMethod())); + const char* param = httpRequest->getParameter("wa"); + if (!param || strcmp(param, "wsignin1.0")) + throw BindingException("Missing or invalid wa parameter (should be wsignin1.0)."); + param = httpRequest->getParameter("wctx"); + if (param) + relayState = param; + + param = httpRequest->getParameter("wresult"); + if (!param) + throw BindingException("Request missing wresult parameter."); + + log.debug("decoded ADFS response:\n%s", param); + + // Parse and bind the document into an XMLObject. + istringstream is(param); + DOMDocument* doc = (policy.getValidating() ? XMLToolingConfig::getConfig().getValidatingParser() + : XMLToolingConfig::getConfig().getParser()).parse(is); + XercesJanitor janitor(doc); + auto_ptr xmlObject(XMLObjectBuilder::buildOneFromElement(doc->getDocumentElement(), true)); + janitor.release(); + + if (!XMLHelper::isNodeNamed(xmlObject->getDOM(), m_ns.get(), RequestSecurityTokenResponse)) + throw BindingException("Decoded message was not of the appropriate type."); + + if (!policy.getValidating()) + SchemaValidators.validate(xmlObject.get()); + + // Skip policy step here, there's no security in the wrapper. + // policy.evaluate(*xmlObject.get(), &genericRequest); + + return xmlObject.release(); } -string ADFSConsumer::implementProtocol( +void ADFSConsumer::implementProtocol( const Application& application, const HTTPRequest& httpRequest, + HTTPResponse& httpResponse, SecurityPolicy& policy, const PropertySet* settings, const XMLObject& xmlObject @@ -518,28 +555,42 @@ string ADFSConsumer::implementProtocol( if (!token || !token->getSignature()) throw FatalProfileException("Incoming message did not contain a signed SAML 1.1 assertion."); - // Run the policy over the assertion. Handles issuer consistency, replay, freshness, - // and signature verification, assuming the relevant rules are configured. + // Extract message and issuer details from assertion. + extractMessageDetails(*token, m_protocol.get(), policy); + + // Run the policy over the assertion. Handles replay, freshness, and + // signature verification, assuming the relevant rules are configured. policy.evaluate(*token); // If no security is in place now, we kick it. - if (!policy.isSecure()) + if (!policy.isAuthenticated()) throw SecurityPolicyException("Unable to establish security of incoming assertion."); + const EntityDescriptor* entity = policy.getIssuerMetadata() ? dynamic_cast(policy.getIssuerMetadata()->getParent()) : NULL; + // Now do profile and core semantic validation to ensure we can use it for SSO. // Profile validator. time_t now = time(NULL); - saml1::AssertionValidator ssoValidator(application.getAudiences(), now); + saml1::AssertionValidator ssoValidator(application.getRelyingParty(entity)->getXMLString("entityID").second, application.getAudiences(), now); ssoValidator.validateAssertion(*token); if (!token->getConditions() || !token->getConditions()->getNotBefore() || !token->getConditions()->getNotOnOrAfter()) throw FatalProfileException("Assertion did not contain time conditions."); else if (token->getAuthenticationStatements().empty()) throw FatalProfileException("Assertion did not contain an authentication statement."); + // With ADFS, we only have one token, but we need to put it in a vector. vector tokens(1,token); const saml1::AuthenticationStatement* ssoStatement=token->getAuthenticationStatements().front(); + // authnskew allows rejection of SSO if AuthnInstant is too old. + const PropertySet* sessionProps = application.getPropertySet("Sessions"); + pair authnskew = sessionProps ? sessionProps->getUnsignedInt("maxTimeSinceAuthn") : pair(false,0); + + if (authnskew.first && authnskew.second && + ssoStatement->getAuthenticationInstant() && (now - ssoStatement->getAuthenticationInstantEpoch() > authnskew.second)) + throw FatalProfileException("The gap between now and the time you logged into your identity provider exceeds the limit."); + // Address checking. saml1::SubjectLocality* locality = ssoStatement->getSubjectLocality(); if (locality && locality->getIPAddress()) { @@ -554,48 +605,12 @@ string ADFSConsumer::implementProtocol( // Now we have to extract the authentication details for attribute and session setup. // Session expiration for ADFS is purely SP-driven, and the method is mapped to a ctx class. - const PropertySet* sessionProps = application.getPropertySet("Sessions"); pair lifetime = sessionProps ? sessionProps->getUnsignedInt("lifetime") : pair(true,28800); if (!lifetime.first || lifetime.second == 0) lifetime.second = 28800; // We've successfully "accepted" the SSO token. // To complete processing, we need to extract and resolve attributes and then create the session. - multimap resolvedAttributes; - AttributeExtractor* extractor = application.getAttributeExtractor(); - if (extractor) { - m_log.debug("extracting pushed attributes..."); - Locker extlocker(extractor); - if (n) { - try { - extractor->extractAttributes(application, policy.getIssuerMetadata(), *n, resolvedAttributes); - } - catch (exception& ex) { - m_log.error("caught exception extracting attributes: %s", ex.what()); - } - } - try { - extractor->extractAttributes(application, policy.getIssuerMetadata(), *token, resolvedAttributes); - } - catch (exception& ex) { - m_log.error("caught exception extracting attributes: %s", ex.what()); - } - - AttributeFilter* filter = application.getAttributeFilter(); - if (filter && !resolvedAttributes.empty()) { - BasicFilteringContext fc(application, resolvedAttributes, policy.getIssuerMetadata(), ssoStatement->getAuthenticationMethod()); - Locker filtlocker(filter); - try { - filter->filterAttributes(fc, resolvedAttributes); - } - catch (exception& ex) { - m_log.error("caught exception filtering attributes: %s", ex.what()); - m_log.error("dumping extracted attributes due to filtering exception"); - for_each(resolvedAttributes.begin(), resolvedAttributes.end(), cleanup_pair()); - resolvedAttributes.clear(); - } - } - } // Normalize the SAML 1.x NameIdentifier... auto_ptr nameid(n ? saml2::NameIDBuilder::buildNameID() : NULL); @@ -605,95 +620,83 @@ string ADFSConsumer::implementProtocol( nameid->setNameQualifier(n->getNameQualifier()); } - const EntityDescriptor* issuerMetadata = - policy.getIssuerMetadata() ? dynamic_cast(policy.getIssuerMetadata()->getParent()) : NULL; + // The context will handle deleting attributes and new tokens. auto_ptr ctx( resolveAttributes( application, - issuerMetadata, - m_binding.get(), + policy.getIssuerMetadata(), + m_protocol.get(), + n, nameid.get(), ssoStatement->getAuthenticationMethod(), NULL, - &tokens, - &resolvedAttributes + &tokens ) ); if (ctx.get()) { // Copy over any new tokens, but leave them in the context for cleanup. tokens.insert(tokens.end(), ctx->getResolvedAssertions().begin(), ctx->getResolvedAssertions().end()); - - // Copy over new attributes, and transfer ownership. - resolvedAttributes.insert(ctx->getResolvedAttributes().begin(), ctx->getResolvedAttributes().end()); - ctx->getResolvedAttributes().clear(); } - try { - string key = application.getServiceProvider().getSessionCache()->insert( - now + lifetime.second, - application, - httpRequest.getRemoteAddr().c_str(), - issuerMetadata, - m_binding.get(), - nameid.get(), - ssoStatement->getAuthenticationInstant() ? ssoStatement->getAuthenticationInstant()->getRawData() : NULL, - NULL, - ssoStatement->getAuthenticationMethod(), - NULL, - &tokens, - &resolvedAttributes - ); - for_each(resolvedAttributes.begin(), resolvedAttributes.end(), cleanup_pair()); - return key; - } - catch (exception&) { - for_each(resolvedAttributes.begin(), resolvedAttributes.end(), cleanup_pair()); - throw; - } + application.getServiceProvider().getSessionCache()->insert( + application, + httpRequest, + httpResponse, + now + lifetime.second, + entity, + m_protocol.get(), + nameid.get(), + ssoStatement->getAuthenticationInstant() ? ssoStatement->getAuthenticationInstant()->getRawData() : NULL, + NULL, + ssoStatement->getAuthenticationMethod(), + NULL, + &tokens, + ctx.get() ? &ctx->getResolvedAttributes() : NULL + ); } #endif pair ADFSLogoutInitiator::run(SPRequest& request, bool isHandler) const { - // Defer to base class for front-channel loop first. - pair ret = LogoutHandler::run(request, isHandler); - if (ret.first) - return ret; - - // At this point we know the front-channel is handled. - // We need the session to do any other work. + // Normally we'd do notifications and session clearage here, but ADFS logout + // is missing the needed request/response features, so we have to rely on + // the IdP half to notify us back about the logout and do the work there. + // Basically we have no way to tell in the Logout receiving handler whether + // we initiated the logout or not. Session* session = NULL; try { session = request.getSession(false, true, false); // don't cache it and ignore all checks if (!session) - return make_pair(false,0); + return make_pair(false,0L); - // We only handle SAML 2.0 sessions. - if (!XMLString::equals(session->getProtocol(), WSFED_NS)) { + // We only handle ADFS sessions. + if (!XMLString::equals(session->getProtocol(), WSFED_NS) || !session->getEntityID()) { session->unlock(); - return make_pair(false,0); + return make_pair(false,0L); } } catch (exception& ex) { m_log.error("error accessing current session: %s", ex.what()); - return make_pair(false,0); + return make_pair(false,0L); } + string entityID(session->getEntityID()); + session->unlock(); + if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) { // When out of process, we run natively. - return doRequest(request.getApplication(), request.getRequestURL(), session, request); + return doRequest(request.getApplication(), entityID.c_str(), request); } else { // When not out of process, we remote the request. - Locker locker(session); + Locker locker(session, false); DDF out,in(m_address.c_str()); DDFJanitor jin(in), jout(out); in.addmember("application_id").string(request.getApplication().getId()); - in.addmember("session_id").string(session->getID()); - in.addmember("url").string(request.getRequestURL()); + in.addmember("entity_id").string(entityID.c_str()); out=request.getServiceProvider().getListenerService()->send(in); return unwrap(request, out); } @@ -716,76 +719,41 @@ void ADFSLogoutInitiator::receive(DDF& in, ostream& out) DDFJanitor jout(ret); auto_ptr resp(getResponse(ret)); - Session* session = NULL; - try { - session = app->getServiceProvider().getSessionCache()->find(in["session_id"].string(), *app, NULL, NULL); - } - catch (exception& ex) { - m_log.error("error accessing current session: %s", ex.what()); - } + // Since we're remoted, the result should either be a throw, which we pass on, + // a false/0 return, which we just return as an empty structure, or a response/redirect, + // which we capture in the facade and send back. + doRequest(*app, in["entity_id"].string(), *resp.get()); - // With no session, we just skip the request and let it fall through to an empty struct return. - if (session) { - if (session->getEntityID()) { - // Since we're remoted, the result should either be a throw, which we pass on, - // a false/0 return, which we just return as an empty structure, or a response/redirect, - // which we capture in the facade and send back. - doRequest(*app, in["url"].string(), session, *resp.get()); - } - else { - m_log.error("no issuing entityID found in session"); - session->unlock(); - session = NULL; - app->getServiceProvider().getSessionCache()->remove(in["session_id"].string(), *app); - } - } out << ret; #else throw ConfigurationException("Cannot perform logout using lite version of shibsp library."); #endif } -pair ADFSLogoutInitiator::doRequest( - const Application& application, const char* requestURL, Session* session, HTTPResponse& response - ) const +pair ADFSLogoutInitiator::doRequest(const Application& application, const char* entityID, HTTPResponse& response) const { - string entityID(session->getEntityID()); - vector sessions(1, session->getID()); - - // Do back channel notification. - if (!notifyBackChannel(application, requestURL, sessions, false)) { - session->unlock(); - application.getServiceProvider().getSessionCache()->remove(sessions.front().c_str(), application); - return sendLogoutPage(application, response, true, "Partial logout failure."); - } - - session->unlock(); - application.getServiceProvider().getSessionCache()->remove(sessions.front().c_str(), application); - #ifndef SHIBSP_LITE try { - // With a session in hand, we can create a request message, if we can find a compatible endpoint. - Locker metadataLocker(application.getMetadataProvider()); - const EntityDescriptor* entity = application.getMetadataProvider()->getEntityDescriptor(entityID.c_str()); - if (!entity) { - throw MetadataException( - "Unable to locate metadata for identity provider ($entityID)", - namedparams(1, "entityID", entityID.c_str()) - ); - } - const IDPSSODescriptor* role = entity->getIDPSSODescriptor(m_binding.get()); - if (!role) { - throw MetadataException( - "Unable to locate ADFS IdP role for identity provider ($entityID).", - namedparams(1, "entityID", entityID.c_str()) - ); - } + if (!entityID) + throw ConfigurationException("Missing entityID parameter."); - const EndpointType* ep = EndpointManager(role->getSingleLogoutServices()).getByBinding(m_binding.get()); + // With a session in hand, we can create a request message, if we can find a compatible endpoint. + MetadataProvider* m=application.getMetadataProvider(); + Locker locker(m); + MetadataProvider::Criteria mc(entityID, &IDPSSODescriptor::ELEMENT_QNAME, m_binding.get()); + pair entity=m->getEntityDescriptor(mc); + if (!entity.first) + throw MetadataException("Unable to locate metadata for identity provider ($entityID)", namedparams(1, "entityID", entityID)); + else if (!entity.second) + throw MetadataException("Unable to locate ADFS IdP role for identity provider ($entityID).", namedparams(1, "entityID", entityID)); + + const EndpointType* ep = EndpointManager( + dynamic_cast(entity.second)->getSingleLogoutServices() + ).getByBinding(m_binding.get()); if (!ep) { throw MetadataException( "Unable to locate ADFS single logout service for identity provider ($entityID).", - namedparams(1, "entityID", entityID.c_str()) + namedparams(1, "entityID", entityID) ); } @@ -798,7 +766,7 @@ pair ADFSLogoutInitiator::doRequest( m_log.error("error issuing ADFS logout request: %s", ex.what()); } - return make_pair(false,0); + return make_pair(false,0L); #else throw ConfigurationException("Cannot perform logout using lite version of shibsp library."); #endif @@ -829,10 +797,6 @@ pair ADFSLogout::run(SPRequest& request, bool isHandler) const param = request.getParameter("wreply"); const Application& app = request.getApplication(); - // Get the session_id. - pair shib_cookie = app.getCookieNameProps("_shibsession_"); - const char* session_id = request.getCookie(shib_cookie.first.c_str()); - if (!returning) { // Pass control to the first front channel notification point, if any. map parammap; @@ -844,18 +808,19 @@ pair ADFSLogout::run(SPRequest& request, bool isHandler) const } // Best effort on back channel and to remove the user agent's session. - if (session_id) { + string session_id = app.getServiceProvider().getSessionCache()->active(app, request); + if (!session_id.empty()) { vector sessions(1,session_id); notifyBackChannel(app, request.getRequestURL(), sessions, false); try { - app.getServiceProvider().getSessionCache()->remove(session_id, app); + app.getServiceProvider().getSessionCache()->remove(app, request, &request); } catch (exception& ex) { - m_log.error("error removing session (%s): %s", session_id, ex.what()); + m_log.error("error removing session (%s): %s", session_id.c_str(), ex.what()); } } if (param) return make_pair(true, request.sendRedirect(param)); - return sendLogoutPage(app, request, false, "Logout complete."); + return sendLogoutPage(app, request, request, false, "Logout complete."); }