X-Git-Url: http://www.project-moonshot.org/gitweb/?a=blobdiff_plain;f=shibsp%2Fhandler%2Fimpl%2FSAML1Consumer.cpp;h=310c1e244c1490c53e3cbceebe1a08a176f983ec;hb=c51bfd77603cf0ddb0b5e374c35586a8435895d6;hp=7fe68312d1fe080ae97bcbaec5161b271afbf71a;hpb=8b1a2374afbbcbc833c78ff8a914b43c668f72bf;p=shibboleth%2Fcpp-sp.git diff --git a/shibsp/handler/impl/SAML1Consumer.cpp b/shibsp/handler/impl/SAML1Consumer.cpp index 7fe6831..310c1e2 100644 --- a/shibsp/handler/impl/SAML1Consumer.cpp +++ b/shibsp/handler/impl/SAML1Consumer.cpp @@ -1,48 +1,65 @@ -/* - * 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 +/** + * Licensed to the University Corporation for Advanced Internet + * Development, Inc. (UCAID) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + * + * UCAID licenses this file to you 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 + * 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. + * 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. */ /** * 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 "TransactionLog.h" +# include "attribute/resolver/ResolutionContext.h" +# include +# include +# 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 namespace boost; 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 +67,44 @@ 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_post = XMLString::equals(getString("Binding").second, samlconstants::SAML1_PROFILE_BROWSER_POST); + if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) + m_ssoRule.reset(SAMLConfig::getConfig().SecurityPolicyRuleManager.newPlugin(SAML1BROWSERSSO_POLICY_RULE, e)); +#endif } virtual ~SAML1Consumer() {} - + +#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); + } + private: - string implementProtocol( + void implementProtocol( const Application& application, const HTTPRequest& httpRequest, + HTTPResponse& httpResponse, SecurityPolicy& policy, - const PropertySet* settings, + const PropertySet*, const XMLObject& xmlObject ) const; bool m_post; + scoped_ptr m_ssoRule; +#else + const XMLCh* getProtocolFamily() const { + return samlconstants::SAML11_PROTOCOL_ENUM; + } +#endif }; #if defined (_MSC_VER) @@ -80,33 +115,51 @@ 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 PropertySet*, const XMLObject& xmlObject ) const { // 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, policy.getIssuerMetadata()); + // 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 +169,98 @@ 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.get()); + + // Populate recipient as audience. + policy.getAudiences().push_back(application.getRelyingParty(entity)->getXMLString("entityID").second); + time_t now = time(nullptr); + for (indirect_iterator::const_iterator> a = make_indirect_iterator(assertions.begin()); + a != make_indirect_iterator(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 + ); + + // 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); - // Now do profile and core semantic validation to ensure we can use it for SSO. - ssoValidator.validateAssertion(*(*a)); + // 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); + 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 (indirect_iterator::const_iterator> s = make_indirect_iterator(statements.begin()); + s != make_indirect_iterator(statements.end()); ++s) { + if (s->getAuthenticationInstant() && + s->getAuthenticationInstantEpoch() - XMLToolingConfig::getConfig().clock_skew_secs > now) { + contextualError = "The login time at your identity provider was future-dated."; + } + else if (authnskew.first && authnskew.second && s->getAuthenticationInstant() && + s->getAuthenticationInstantEpoch() <= now && (now - s->getAuthenticationInstantEpoch() > authnskew.second)) { + contextualError = "The gap between now and the time you logged into your identity provider exceeds the allowed limit."; + } + else if (authnskew.first && authnskew.second && s->getAuthenticationInstant() == nullptr) { + contextualError = "Your identity provider did not supply a time of login, violating local policy."; + } + else if (!ssoStatement) { + ssoStatement = &(*s); + break; + } + } } - catch (exception& ex) { + catch (std::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 +271,88 @@ 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... + scoped_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()); - auto_ptr ctx( - resolveAttributes(application, httpRequest, issuerMetadata, *nameid.get(), &tokens) + // The context will handle deleting attributes and new tokens. + scoped_ptr ctx( + resolveAttributes( + application, + &httpRequest, + policy.getIssuerMetadata(), + (!response->getMinorVersion().first || response->getMinorVersion().second==1) ? + samlconstants::SAML11_PROTOCOL_ENUM : samlconstants::SAML10_PROTOCOL_ENUM, + response, + n, + ssoStatement, + nameid.get(), + nullptr, + 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()); + if (ctx) { + // 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. + // Now merge in bad tokens for caching. + tokens.insert(tokens.end(), badtokens.begin(), badtokens.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()); - - vector& attrs = ctx->getResolvedAttributes(); - string key = application.getServiceProvider().getSessionCache()->insert( - lifetime.second ? now + lifetime.second : 0, + string session_id; + application.getServiceProvider().getSessionCache()->insert( + session_id, 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 ? &ctx->getResolvedAttributes() : nullptr ); - attrs.clear(); // Attributes are owned by cache now. - return key; + + scoped_ptr login_event(newLoginEvent(application, httpRequest)); + if (login_event) { + login_event->m_sessionID = session_id.c_str(); + login_event->m_peer = entity; + auto_ptr_char prot( + (!response->getMinorVersion().first || response->getMinorVersion().second==1) ? + samlconstants::SAML11_PROTOCOL_ENUM : samlconstants::SAML10_PROTOCOL_ENUM + ); + login_event->m_protocol = prot.get(); + login_event->m_nameID = nameid.get(); + login_event->m_saml1AuthnStatement = ssoStatement; + login_event->m_saml1Response = response; + if (ctx) + login_event->m_attributes = &ctx->getResolvedAttributes(); + application.getServiceProvider().getTransactionLog()->write(*login_event); + } } + +#endif