main
  1# frozen_string_literal: true
  2
  3module Saml
  4  module Kit
  5    # This class validates the Assertion
  6    # element nested in a Response element
  7    # of a SAML document.
  8    class Assertion < Document
  9      extend Forwardable
 10      XPATH = [
 11        '/samlp:Response/saml:Assertion',
 12        '/samlp:Response/saml:EncryptedAssertion'
 13      ].join('|')
 14      def_delegators :conditions, :started_at, :expired_at, :audiences
 15      def_delegators :attribute_statement, :attributes
 16
 17      validate :must_be_decryptable
 18      validate :must_match_issuer, if: :decryptable?
 19      validate :must_be_active_session, if: :decryptable?
 20      validate :must_have_valid_signature, if: :decryptable?
 21      attr_reader :name, :configuration
 22      attr_accessor :occurred_at
 23
 24      def initialize(
 25        node, configuration: Saml::Kit.configuration, private_keys: []
 26      )
 27        @name = 'Assertion'
 28        @to_nokogiri = node.is_a?(String) ? Nokogiri::XML(node).root : node
 29        @configuration = configuration
 30        @occurred_at = Time.current
 31        @cannot_decrypt = false
 32        @encrypted = false
 33        keys = configuration.private_keys(use: :encryption) + private_keys
 34        decrypt(::Xml::Kit::Decryption.new(private_keys: keys.uniq))
 35        super(to_s, name: 'Assertion', configuration: configuration)
 36      end
 37
 38      def id
 39        at_xpath('./@ID').try(:value)
 40      end
 41
 42      def issuer
 43        at_xpath('./saml:Issuer').try(:text)
 44      end
 45
 46      def version
 47        at_xpath('./@Version').try(:value)
 48      end
 49
 50      def name_id
 51        at_xpath('./saml:Subject/saml:NameID').try(:text)
 52      end
 53
 54      def name_id_format
 55        at_xpath('./saml:Subject/saml:NameID').attribute('Format').try(:value)
 56      end
 57
 58      def signed?
 59        signature.present?
 60      end
 61
 62      def signature
 63        @signature ||= Signature.new(at_xpath('./ds:Signature'))
 64      end
 65
 66      def expired?(now = occurred_at)
 67        now > expired_at
 68      end
 69
 70      def active?(now = occurred_at)
 71        drifted_started_at = started_at - configuration.clock_drift.to_i.seconds
 72        now > drifted_started_at && !expired?(now)
 73      end
 74
 75      def expected_type?
 76        at_xpath('../saml:Assertion|../saml:EncryptedAssertion').present?
 77      end
 78
 79      def attribute_statement(xpath = './saml:AttributeStatement')
 80        @attribute_statement ||= AttributeStatement.new(search(xpath))
 81      end
 82
 83      def conditions
 84        @conditions ||= Conditions.new(search('./saml:Conditions'))
 85      end
 86
 87      def encrypted?
 88        @encrypted
 89      end
 90
 91      def decryptable?
 92        return true unless encrypted?
 93
 94        !@cannot_decrypt
 95      end
 96
 97      def to_s
 98        @to_nokogiri.to_s
 99      end
100
101      private
102
103      def decrypt(decryptor)
104        encrypted_assertion = at_xpath('./xmlenc:EncryptedData')
105        @encrypted = encrypted_assertion.present?
106        return unless @encrypted
107
108        @to_nokogiri = decryptor.decrypt_node(encrypted_assertion)
109      rescue StandardError => error
110        @cannot_decrypt = true
111        Saml::Kit.logger.error(error)
112      end
113
114      def must_match_issuer
115        return if audiences.empty? || audiences.include?(configuration.entity_id)
116
117        errors.add(:audience, error_message(:must_match_issuer))
118      end
119
120      def must_be_active_session
121        return if active?
122
123        errors.add(:base, error_message(:expired))
124      end
125
126      def must_have_valid_signature
127        return if !signed? || signature.valid?
128
129        signature.each_error do |attribute, message|
130          errors.add(attribute, message)
131        end
132      end
133
134      def must_be_decryptable
135        errors.add(:base, error_message(:cannot_decrypt)) unless decryptable?
136      end
137    end
138  end
139end