main
  1# frozen_string_literal: true
  2
  3module Saml
  4  module Kit
  5    # This module is responsible for
  6    # validating the trustworthiness
  7    # of a saml document.
  8    module Trustable
  9      extend ActiveSupport::Concern
 10
 11      included do
 12        validate :must_have_valid_signature, unless: :signature_verified
 13        validate :must_be_registered
 14        validate :must_be_signed, if: :signature_required?
 15        validate :must_be_trusted
 16      end
 17
 18      # Returns true when this document must carry a signature to be valid.
 19      def signature_required?
 20        configuration.signature_required && signature_required_by_type?
 21      end
 22
 23      # Returns true when the document has an embedded XML Signature or has
 24      # been verified externally.
 25      def signed?
 26        signature_verified || signature.present?
 27      end
 28
 29      # @!visibility private
 30      def signature
 31        @signature ||= Signature.new(at_xpath("/samlp:#{name}/ds:Signature"))
 32      end
 33
 34      # Returns true when documents is signed and the signing certificate
 35      # belongs to a known service entity.
 36      def trusted?
 37        return true if signature_verified
 38        return false unless signed?
 39
 40        signature.trusted?(provider)
 41      end
 42
 43      # @!visibility private
 44      def provider
 45        registry.metadata_for(issuer)
 46      end
 47
 48      # @!visibility private
 49      def signature_verified!
 50        @signature_verified = true
 51      end
 52
 53      private
 54
 55      attr_reader :signature_verified
 56
 57      def must_have_valid_signature
 58        return if to_xml.blank?
 59        return unless signature.present?
 60
 61        signature.valid?
 62        signature.each_error do |attribute, error|
 63          errors.add(attribute, error)
 64        end
 65      end
 66
 67      def must_be_registered
 68        return unless expected_type?
 69        return if provider.present?
 70
 71        errors.add(:provider, error_message(:unregistered))
 72      end
 73
 74      # Whether a document of this type is worthless unless signed.
 75      #
 76      # A request may legitimately arrive unsigned, which is what
 77      # `WantAuthnRequestsSigned="false"` means, so the default is false. A
 78      # document that asserts an identity carries no authenticity guarantee at
 79      # all when unsigned, so Response and Assertion override this.
 80      def signature_required_by_type?
 81        false
 82      end
 83
 84      def must_be_signed
 85        return unless expected_type?
 86        return if signature_covers_document?
 87
 88        errors.add(:base, error_message(:unsigned))
 89      end
 90
 91      # Whether a signature exists over the content this document asserts.
 92      # Response overrides this, because a signature on either the Response or
 93      # the Assertion covers the identity being asserted.
 94      def signature_covers_document?
 95        signed?
 96      end
 97
 98      # The trust counterpart of `signature_covers_document?`. Kept separate
 99      # from the public `trusted?`, which answers the narrower question of
100      # whether *this* element's own signature is trusted.
101      def signature_trusted?
102        trusted?
103      end
104
105      def must_be_trusted
106        return if signature_trusted?
107        return if !signature_required? && provider.present? && !signed?
108
109        errors.add(:fingerprint, error_message(:invalid_fingerprint))
110      end
111    end
112  end
113end