lib/saml2/response.rb in saml2-3.1.2 vs lib/saml2/response.rb in saml2-3.1.3

- old
+ new

@@ -1,19 +1,17 @@ # frozen_string_literal: true -require 'nokogiri-xmlsec' -require 'time' +require "nokogiri-xmlsec" +require "time" -require 'saml2/assertion' -require 'saml2/authn_statement' -require 'saml2/status_response' -require 'saml2/subject' +require "saml2/assertion" +require "saml2/authn_statement" +require "saml2/status_response" +require "saml2/subject" module SAML2 class Response < StatusResponse - attr_reader :assertions - # Respond to an {AuthnRequest} # # {AuthnRequest#resolve} needs to have been previously called on the {AuthnRequest}. # @param authn_request [AuthnRequest] # @param issuer [NameID] @@ -96,41 +94,52 @@ def validate(service_provider:, identity_provider:, verification_time: nil, ignore_audience_condition: false) raise ArgumentError, "service_provider should be an Entity object" unless service_provider.is_a?(Entity) - raise ArgumentError, "service_provider should have at least one service_provider role" unless (sp = service_provider.service_providers.first) + unless (sp = service_provider.service_providers.first) + raise ArgumentError, + "service_provider should have at least one service_provider role" + end + # validate the schema super() return errors unless errors.empty? if verification_time.nil? verification_time = Time.now.utc # they issued it in the (near) future according to our clock; # use their clock instead - verification_time = issue_instant if issue_instant > verification_time && issue_instant < verification_time + 5 * 60 + if issue_instant > verification_time && issue_instant < verification_time + (5 * 60) + verification_time = issue_instant + end end # not finding the issuer is not exceptional if identity_provider.nil? errors << "could not find issuer of response" return errors end # getting the wrong data type is exceptional, and we should raise an error raise ArgumentError, "identity_provider should be an Entity object" unless identity_provider.is_a?(Entity) - raise ArgumentError, "identity_provider should have at least one identity_provider role" unless (idp = identity_provider.identity_providers.first) + unless (idp = identity_provider.identity_providers.first) + raise ArgumentError, + "identity_provider should have at least one identity_provider role" + end + issuer = self.issuer || assertions.first&.issuer unless identity_provider.entity_id == issuer&.id - errors << "received unexpected message from '#{issuer&.id}'; expected it to be from '#{identity_provider.entity_id}'" + errors << "received unexpected message from '#{issuer&.id}'; " \ + "expected it to be from '#{identity_provider.entity_id}'" return errors end - certificates = idp.signing_keys.map(&:certificate).compact - keys = idp.signing_keys.map(&:key).compact + certificates = idp.signing_keys.filter_map(&:certificate) + keys = idp.signing_keys.filter_map(&:key) if idp.fingerprints.empty? && certificates.empty? && keys.empty? errors << "could not find certificate to validate message" return errors end @@ -138,10 +147,11 @@ unless (signature_errors = validate_signature(key: keys, fingerprint: idp.fingerprints, cert: certificates)).empty? return errors.concat(signature_errors) end + response_signed = true end assertion = assertions.first @@ -150,28 +160,31 @@ unless (signature_errors = assertion.validate_signature(key: keys, fingerprint: idp.fingerprints, cert: certificates)).empty? return errors.concat(signature_errors) end + assertion_signed = true end - find_decryption_key = ->(embedded_certificates) do + find_decryption_key = lambda do |embedded_certificates| key = nil embedded_certificates.each do |cert_info| cert = case cert_info - when OpenSSL::X509::Certificate; cert_info - when Hash; sp.encryption_keys.map(&:certificate).find { |c| c.serial == cert_info[:serial] } + when OpenSSL::X509::Certificate then cert_info + when Hash then sp.encryption_keys.map(&:certificate).find { |c| c.serial == cert_info[:serial] } end next unless cert + key = sp.private_keys.find { |k| cert.check_private_key(k) } break if key end - if !key + unless key # couldn't figure out which key to use; just try them all next sp.private_keys end + key end unless sp.private_keys.empty? begin @@ -206,27 +219,28 @@ if assertion.signed? && !assertion_signed unless (signature_errors = assertion.validate_signature(fingerprint: idp.fingerprints, cert: certificates)).empty? return errors.concat(signature_errors) end + assertion_signed = true end # only do our own issue instant validation if the assertion # doesn't mandate any - unless assertion.conditions&.not_on_or_after - if assertion.issue_instant + 5 * 60 < verification_time || - assertion.issue_instant - 5 * 60 > verification_time - errors << "assertion not recently issued" - return errors - end + if !assertion.conditions&.not_on_or_after && (assertion.issue_instant + (5 * 60) < verification_time || + assertion.issue_instant - (5 * 60) > verification_time) + errors << "assertion not recently issued" + return errors end if assertion.conditions && - !(condition_errors = assertion.conditions.validate(verification_time: verification_time, - audience: service_provider.entity_id, - ignore_audience_condition: ignore_audience_condition)).empty? + !(condition_errors = assertion.conditions.validate( + verification_time: verification_time, + audience: service_provider.entity_id, + ignore_audience_condition: ignore_audience_condition + )).empty? return errors.concat(condition_errors) end if !response_signed && !assertion_signed errors << "neither response nor assertion were signed" @@ -251,13 +265,11 @@ errors end # @return [Array<Assertion>] def assertions - unless instance_variable_defined?(:@assertions) - @assertions = load_object_array(xml, 'saml:Assertion', Assertion) - end + @assertions = load_object_array(xml, "saml:Assertion", Assertion) unless instance_variable_defined?(:@assertions) @assertions end # (see Signable#sign) # Signs each assertion. @@ -273,12 +285,12 @@ end private def build(builder) - builder['samlp'].Response( - 'xmlns:samlp' => Namespaces::SAMLP, - 'xmlns:saml' => Namespaces::SAML + builder["samlp"].Response( + "xmlns:samlp" => Namespaces::SAMLP, + "xmlns:saml" => Namespaces::SAML ) do |response| super(response) assertions.each do |assertion| # we can't just call build, because it may already