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