X-Git-Url: http://www.project-moonshot.org/gitweb/?a=blobdiff_plain;f=shibsp%2Fhandler%2Fimpl%2FSAML1Consumer.cpp;h=b098aab7264e8bb33311f4de980442705a2cbc87;hb=a62c416b6ecfc17d89853a8f14604249c94d2f60;hp=7fe68312d1fe080ae97bcbaec5161b271afbf71a;hpb=a2c9547f9e47a7c5b6c682353a1b9eb2e83ded32;p=shibboleth%2Fsp.git diff --git a/shibsp/handler/impl/SAML1Consumer.cpp b/shibsp/handler/impl/SAML1Consumer.cpp index 7fe6831..b098aab 100644 --- a/shibsp/handler/impl/SAML1Consumer.cpp +++ b/shibsp/handler/impl/SAML1Consumer.cpp @@ -1,6 +1,6 @@ /* - * Copyright 2001-2007 Internet2 - * + * Copyright 2001-2010 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 @@ -16,33 +16,42 @@ /** * SAML1Consumer.cpp - * - * SAML 1.x assertion consumer service + * + * SAML 1.x assertion consumer service. */ #include "internal.h" -#include "Application.h" -#include "exceptions.h" -#include "ServiceProvider.h" -#include "SessionCache.h" -#include "attribute/resolver/ResolutionContext.h" #include "handler/AssertionConsumerService.h" -#include -#include -#include -#include - -using namespace shibsp; +#ifndef SHIBSP_LITE +# include "Application.h" +# include "ServiceProvider.h" +# include "SessionCache.h" +# include "attribute/resolver/ResolutionContext.h" +# include +# include +# include +# include +# include +# include +# include +# include +# include using namespace opensaml::saml1; using namespace opensaml::saml1p; using namespace opensaml; -using namespace xmltooling; -using namespace log4cpp; -using namespace std; using saml2::NameID; using saml2::NameIDBuilder; using saml2md::EntityDescriptor; +using saml2md::SPSSODescriptor; +using saml2md::MetadataException; +#else +# include "lite/SAMLConstants.h" +#endif + +using namespace shibsp; +using namespace xmltooling; +using namespace std; namespace shibsp { @@ -50,26 +59,45 @@ namespace shibsp { #pragma warning( push ) #pragma warning( disable : 4250 ) #endif - + class SHIBSP_DLLLOCAL SAML1Consumer : public AssertionConsumerService { public: SAML1Consumer(const DOMElement* e, const char* appId) - : AssertionConsumerService(e, appId, Category::getInstance(SHIBSP_LOGCAT".SAML1")) { + : AssertionConsumerService(e, appId, Category::getInstance(SHIBSP_LOGCAT".SSO.SAML1")) { +#ifndef SHIBSP_LITE + m_ssoRule = nullptr; m_post = XMLString::equals(getString("Binding").second, samlconstants::SAML1_PROFILE_BROWSER_POST); + if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) + m_ssoRule = SAMLConfig::getConfig().SecurityPolicyRuleManager.newPlugin(SAML1BROWSERSSO_POLICY_RULE, e); +#endif + } + virtual ~SAML1Consumer() { +#ifndef SHIBSP_LITE + delete m_ssoRule; +#endif + } + +#ifndef SHIBSP_LITE + void generateMetadata(SPSSODescriptor& role, const char* handlerURL) const { + AssertionConsumerService::generateMetadata(role, handlerURL); + role.addSupport(samlconstants::SAML11_PROTOCOL_ENUM); + role.addSupport(samlconstants::SAML10_PROTOCOL_ENUM); } - virtual ~SAML1Consumer() {} - + private: - string implementProtocol( + void implementProtocol( const Application& application, const HTTPRequest& httpRequest, + HTTPResponse& httpResponse, SecurityPolicy& policy, const PropertySet* settings, const XMLObject& xmlObject ) const; bool m_post; + SecurityPolicyRule* m_ssoRule; +#endif }; #if defined (_MSC_VER) @@ -80,12 +108,27 @@ namespace shibsp { { return new SAML1Consumer(p.first, p.second); } - + +#ifndef SHIBSP_LITE + class SHIBSP_DLLLOCAL _rulenamed : std::unary_function + { + public: + _rulenamed(const char* name) : m_name(name) {} + bool operator()(const SecurityPolicyRule* rule) const { + return rule ? !strcmp(m_name, rule->getType()) : false; + } + private: + const char* m_name; + }; +#endif }; -string SAML1Consumer::implementProtocol( +#ifndef SHIBSP_LITE + +void SAML1Consumer::implementProtocol( const Application& application, const HTTPRequest& httpRequest, + HTTPResponse& httpResponse, SecurityPolicy& policy, const PropertySet* settings, const XMLObject& xmlObject @@ -94,19 +137,22 @@ string SAML1Consumer::implementProtocol( // Implementation of SAML 1.x SSO profile(s). m_log.debug("processing message against SAML 1.x SSO profile"); + // Check for errors...this will throw if it's not a successful message. + checkError(&xmlObject); + // With the binding aspects now moved out to the MessageDecoder, // the focus here is on the assertion content. For SAML 1.x POST, // all the security comes from the protocol layer, and signing // the assertion isn't sufficient. So we can check the policy - // object now and bail if it's not a secure message. - if (m_post && !policy.isSecure()) + // object now and bail if it's not a secured message. + if (m_post && !policy.isAuthenticated()) { + if (policy.getIssuer() && !policy.getIssuerMetadata()) + throw MetadataException("Security of SAML 1.x SSO POST response not established."); throw SecurityPolicyException("Security of SAML 1.x SSO POST response not established."); - - // Remember whether we already established trust. - bool alreadySecured = policy.isSecure(); + } - // Check for errors...this will throw if it's not a successful message. - checkError(&xmlObject); + // Remember whether we already established trust. + bool alreadySecured = policy.isAuthenticated(); const Response* response = dynamic_cast(&xmlObject); if (!response) @@ -116,53 +162,87 @@ string SAML1Consumer::implementProtocol( if (assertions.empty()) throw FatalProfileException("Incoming message contained no SAML assertions."); + pair minor = response->getMinorVersion(); + // Maintain list of "legit" tokens to feed to SP subsystems. - const AuthenticationStatement* ssoStatement=NULL; + const AuthenticationStatement* ssoStatement=nullptr; vector tokens; - // Profile validator. - time_t now = time(NULL); - BrowserSSOProfileValidator ssoValidator(application.getAudiences(), now); + // Also track "bad" tokens that we'll cache but not use. + // This is necessary because there may be valid tokens not aimed at us. + vector badtokens; // With this flag on, we ignore any unsigned assertions. - pair flag = settings->getBool("signedAssertions"); + const EntityDescriptor* entity = policy.getIssuerMetadata() ? dynamic_cast(policy.getIssuerMetadata()->getParent()) : nullptr; + pair flag = application.getRelyingParty(entity)->getBool("requireSignedAssertions"); - for (vector::const_iterator a = assertions.begin(); a!=assertions.end(); ++a) { - // Skip unsigned assertion? - if (!(*a)->getSignature() && flag.first && flag.second) { - m_log.warn("found unsigned assertion in SAML response, ignoring it per signedAssertions policy"); - continue; - } + // 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); + + // Saves off error messages potentially helpful for users. + string contextualError; + // Ensure the BrowserSSO rule is in the policy set. + if (find_if(policy.getRules(), _rulenamed(SAML1BROWSERSSO_POLICY_RULE)) == nullptr) + policy.getRules().push_back(m_ssoRule); + + // Populate recipient as audience. + policy.getAudiences().push_back(application.getRelyingParty(entity)->getXMLString("entityID").second); + + time_t now = time(nullptr); + for (vector::const_iterator a = assertions.begin(); a!=assertions.end(); ++a) { try { + // Skip unsigned assertion? + if (!(*a)->getSignature() && flag.first && flag.second) + throw SecurityPolicyException("The incoming assertion was unsigned, violating local security policy."); + // We clear the security flag, so we can tell whether the token was secured on its own. - policy.setSecure(false); - - // Run the policy over the assertion. Handles issuer consistency, replay, freshness, - // and signature verification, assuming the relevant rules are configured. - policy.evaluate(*(*a)); - - // If no security is in place now, we kick it. - if (!alreadySecured && !policy.isSecure()) - throw SecurityPolicyException("Unable to establish security of the assertion."); + policy.setAuthenticated(false); + policy.reset(true); + + // Extract message bits and re-verify Issuer information. + extractMessageDetails( + *(*a), (minor.first && minor.second==0) ? samlconstants::SAML10_PROTOCOL_ENUM : samlconstants::SAML11_PROTOCOL_ENUM, policy + ); - // Now do profile and core semantic validation to ensure we can use it for SSO. - ssoValidator.validateAssertion(*(*a)); + // Run the policy over the assertion. Handles replay, freshness, and + // signature verification, assuming the relevant rules are configured, + // along with condition and profile enforcement. + policy.evaluate(*(*a), &httpRequest); + + // If no security is in place now, we kick it. + if (!alreadySecured && !policy.isAuthenticated()) + throw SecurityPolicyException("Unable to establish security of incoming assertion."); // Track it as a valid token. tokens.push_back(*a); // Save off the first valid SSO statement. - if (!ssoStatement && !(*a)->getAuthenticationStatements().empty()) - ssoStatement = (*a)->getAuthenticationStatements().front(); + const vector& statements = const_cast(*a)->getAuthenticationStatements(); + for (vector::const_iterator s = statements.begin(); s!=statements.end(); ++s) { + if (authnskew.first && authnskew.second && + (*s)->getAuthenticationInstant() && (now - (*s)->getAuthenticationInstantEpoch() > authnskew.second)) + contextualError = "The gap between now and the time you logged into your identity provider exceeds the limit."; + else if (!ssoStatement) { + ssoStatement = *s; + break; + } + } } catch (exception& ex) { m_log.warn("detected a problem with assertion: %s", ex.what()); + if (!ssoStatement) + contextualError = ex.what(); + badtokens.push_back(*a); } } - if (!ssoStatement) - throw FatalProfileException("A valid authentication statement was not found in the incoming message."); + if (!ssoStatement) { + if (contextualError.empty()) + throw FatalProfileException("A valid authentication statement was not found in the incoming message."); + throw FatalProfileException(contextualError.c_str()); + } // Address checking. SubjectLocality* locality = ssoStatement->getSubjectLocality(); @@ -173,52 +253,65 @@ string SAML1Consumer::implementProtocol( m_log.debug("SSO profile processing completed successfully"); + NameIdentifier* n = ssoStatement->getSubject()->getNameIdentifier(); + + // Now we have to extract the authentication details for attribute and session setup. + + // Session expiration for SAML 1.x is purely SP-driven, and the method is mapped to a ctx class. + pair lifetime = sessionProps ? sessionProps->getUnsignedInt("lifetime") : pair(true,28800); + if (!lifetime.first || lifetime.second == 0) + lifetime.second = 28800; + // We've successfully "accepted" at least one SSO token, along with any additional valid tokens. - // To complete processing, we need to resolve attributes and then create the session. + // To complete processing, we need to extract and resolve attributes and then create the session. - // First, normalize the SAML 1.x NameIdentifier... - auto_ptr nameid(NameIDBuilder::buildNameID()); - NameIdentifier* n = ssoStatement->getSubject()->getNameIdentifier(); + // Normalize the SAML 1.x NameIdentifier... + auto_ptr nameid(n ? NameIDBuilder::buildNameID() : nullptr); if (n) { nameid->setName(n->getName()); nameid->setFormat(n->getFormat()); nameid->setNameQualifier(n->getNameQualifier()); } - const EntityDescriptor* issuerMetadata = dynamic_cast(policy.getIssuerMetadata()->getParent()); + // The context will handle deleting attributes and new tokens. auto_ptr ctx( - resolveAttributes(application, httpRequest, issuerMetadata, *nameid.get(), &tokens) + resolveAttributes( + application, + policy.getIssuerMetadata(), + (!response->getMinorVersion().first || response->getMinorVersion().second==1) ? + samlconstants::SAML11_PROTOCOL_ENUM : samlconstants::SAML10_PROTOCOL_ENUM, + n, + nameid.get(), + ssoStatement->getAuthenticationMethod(), + nullptr, + &tokens + ) ); - // Copy over any new tokens, but leave them in the context for cleanup. - tokens.insert(tokens.end(), ctx->getResolvedAssertions().begin(), ctx->getResolvedAssertions().end()); - - // Now we have to extract the authentication details for session setup. + 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()); + } - // Session expiration for SAML 1.x 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") : make_pair(true,28800); - if (!lifetime.first) - lifetime.second = 28800; - auto_ptr_char authnInstant( - ssoStatement->getAuthenticationInstant() ? ssoStatement->getAuthenticationInstant()->getRawData() : NULL - ); - auto_ptr_char authnMethod(ssoStatement->getAuthenticationMethod()); + // Now merge in bad tokens for caching. + tokens.insert(tokens.end(), badtokens.begin(), badtokens.end()); - vector& attrs = ctx->getResolvedAttributes(); - string key = application.getServiceProvider().getSessionCache()->insert( - lifetime.second ? now + lifetime.second : 0, + application.getServiceProvider().getSessionCache()->insert( application, - httpRequest.getRemoteAddr().c_str(), - issuerMetadata, - *nameid.get(), - authnInstant.get(), - NULL, - authnMethod.get(), - NULL, + httpRequest, + httpResponse, + now + lifetime.second, + entity, + (!response->getMinorVersion().first || response->getMinorVersion().second==1) ? + samlconstants::SAML11_PROTOCOL_ENUM : samlconstants::SAML10_PROTOCOL_ENUM, + nameid.get(), + ssoStatement->getAuthenticationInstant() ? ssoStatement->getAuthenticationInstant()->getRawData() : nullptr, + nullptr, + ssoStatement->getAuthenticationMethod(), + nullptr, &tokens, - &attrs + ctx.get() ? &ctx->getResolvedAttributes() : nullptr ); - attrs.clear(); // Attributes are owned by cache now. - return key; } + +#endif