main
 1# frozen_string_literal: true
 2
 3module Saml
 4  module Kit
 5    # This module is responsible for validating
 6    # xml documents against the SAML XSD's
 7    module XsdValidatable
 8      PROTOCOL_XSD = File.expand_path(
 9        '../xsd/saml-schema-protocol-2.0.xsd', File.dirname(__FILE__)
10      ).freeze
11
12      METADATA_XSD = File.expand_path(
13        '../xsd/saml-schema-metadata-2.0.xsd', File.dirname(__FILE__)
14      ).freeze
15
16      @schemas = {}
17      @monitor = Mutex.new
18
19      class << self
20        # Validation treats a schema as read only, so one instance is shared
21        # across threads. The hit path reads without the lock, which at worst
22        # compiles the same schema twice on rubies that lack a global lock.
23        #
24        # @!visibility private
25        def schema_for(path)
26          @schemas[path] || @monitor.synchronize do
27            @schemas[path] ||= compile(path)
28          end
29        end
30
31        private
32
33        # The xsd is parsed as a document carrying its own path as a base uri,
34        # so the relative schemaLocation imports in our xsds resolve against
35        # the xsd's own directory. A bare String carries no base uri and would
36        # resolve them against the working directory instead, which is what
37        # Dir.chdir used to paper over at the cost of crashing under threads.
38        def compile(path)
39          Nokogiri::XML::Schema.from_document(
40            Nokogiri::XML::Document.parse(File.read(path), url: path)
41          )
42        end
43      end
44
45      # @!visibility private
46      def matches_xsd?(path)
47        return unless to_nokogiri.present?
48
49        schema = XsdValidatable.schema_for(path)
50        schema.validate(to_nokogiri.document).each do |error|
51          errors.add(:base, error.message)
52        end
53      end
54    end
55  end
56end