samlr 2.7.1.pre.3 → 2.7.2.pre.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 99111c7af501ed1d300602d81d603fe0869e042dc9b330cb38aa800c77a79517
4
- data.tar.gz: 755f39f3397bdff3454a05bd47c329ebbf0440c4103469153ed3518bf1aa47c1
3
+ metadata.gz: f29fb87e4f9c2e892ff008b93ceaa0406683aefb33b5b7772933498db217324d
4
+ data.tar.gz: 87e0905875456dac62a5bb66a4436b6549ff10a1859d42d54dcd426acb7026db
5
5
  SHA512:
6
- metadata.gz: 49bb0ebfda779df0a6950eaedac7487b5c1a03475b488573b028d4b86b51a1cbf16c641ad09f15e3bae9bc8717795572d59103590b456b82f22879347881a4d2
7
- data.tar.gz: f111bab5d4b7f933bf217b7bd0a2c89d179768409fc0215e78e9c0a739ebf8bf5860fb481ec4c94da9e352da7c6975f8293003acc19160c03d04e2ae38bd8577
6
+ metadata.gz: a540ead119f309d5bba8a85641fe379a15ca421a48f1be082327f99c2c3bc30e94b7b26db756e2a1490170937e9518ea9fd5aa917e316b15b79657fa79102b6c
7
+ data.tar.gz: 2bad133e71423fa9207488c470832244e20bf889a25befa6e5718ffca5176c46ecb0129521091dcd8dfa0421d33314d65db2ca330b91f463826284cbcc4eaf0f
@@ -5,7 +5,7 @@ module Samlr
5
5
 
6
6
  def initialize(document, options)
7
7
  @document = document
8
- @options = options
8
+ @options = options
9
9
  end
10
10
 
11
11
  def verify!
@@ -31,15 +31,15 @@ module Samlr
31
31
  def attributes
32
32
  @attributes ||= {}.tap do |attrs|
33
33
  assertion.xpath("./saml:AttributeStatement/saml:Attribute", NS_MAP).each do |statement|
34
- name = statement["Name"]
34
+ name = statement["Name"]
35
35
  values = statement.xpath("./saml:AttributeValue", NS_MAP)
36
36
 
37
- if values.size == 0
38
- value = nil
37
+ value = if values.size == 0
38
+ nil
39
39
  elsif values.size == 1
40
- value = values.first.text
40
+ values.first.text
41
41
  else
42
- value = values.map { |value| value.text }
42
+ values.map { |value| value.text }
43
43
  end
44
44
 
45
45
  attrs[name] = value
@@ -56,7 +56,7 @@ module Samlr
56
56
  end
57
57
 
58
58
  def name_id_options
59
- @name_id_options ||= Hash[name_id_node.attributes.map{|k,v| [k, v.value]}]
59
+ @name_id_options ||= name_id_node.attributes.map { |k, v| [k, v.value] }.to_h
60
60
  end
61
61
 
62
62
  def conditions
data/lib/samlr/command.rb CHANGED
@@ -4,10 +4,10 @@ require "logger"
4
4
  module Samlr
5
5
  # Helper module for command line options
6
6
  module Command
7
- COMMANDS = [ :verify, :schema_validate, :print ]
7
+ COMMANDS = [:verify, :schema_validate, :print]
8
8
 
9
9
  def self.execute(options, path = nil)
10
- Samlr.logger.level = Logger::DEBUG if options[:verbose]
10
+ Samlr.logger.level = Logger::DEBUG if options[:verbose]
11
11
  Samlr.validation_mode = :log if options[:skip_validation]
12
12
 
13
13
  if options[:verify]
@@ -21,21 +21,18 @@ module Samlr
21
21
  execute_verify(path, options)
22
22
  end
23
23
  elsif options[:schema_validate]
24
- Samlr::Tools.validate(:path => path)
24
+ Samlr::Tools.validate(path: path)
25
25
  elsif options[:print]
26
26
  Samlr::Response.parse(File.read(path)).to_xml
27
27
  end
28
28
  end
29
29
 
30
- private
31
-
32
30
  def self.execute_verify(path, options)
33
- begin
34
- Samlr::Response.new(File.read(path), options).verify!
35
- "Verification passed for #{path}"
36
- rescue Samlr::SamlrError => e
37
- "Verification failed for #{path}: #{e.message}"
38
- end
31
+ Samlr::Response.new(File.read(path), options).verify!
32
+ "Verification passed for #{path}"
33
+ rescue Samlr::SamlrError => e
34
+ "Verification failed for #{path}: #{e.message}"
39
35
  end
36
+ private_class_method :execute_verify
40
37
  end
41
38
  end
@@ -3,10 +3,10 @@ module Samlr
3
3
  attr_reader :audience, :not_before, :not_on_or_after, :options
4
4
 
5
5
  def initialize(condition, options)
6
- @options = options
7
- @not_before = (condition || {})["NotBefore"]
6
+ @options = options
7
+ @not_before = (condition || {})["NotBefore"]
8
8
  @not_on_or_after = (condition || {})["NotOnOrAfter"]
9
- @audience = extract_audience(condition)
9
+ @audience = extract_audience(condition)
10
10
  end
11
11
 
12
12
  def verify!
@@ -35,9 +35,9 @@ module Samlr
35
35
 
36
36
  def audience_satisfied?
37
37
  options[:audience].nil? ||
38
- audience.nil? ||
39
- audience.empty? ||
40
- audience.any? { |a| options[:audience] === a }
38
+ audience.nil? ||
39
+ audience.empty? ||
40
+ audience.any? { |a| options[:audience] === a }
41
41
  end
42
42
 
43
43
  private
@@ -45,10 +45,10 @@ module Samlr
45
45
  def extract_audience(condition)
46
46
  return unless condition
47
47
 
48
- audience_restriction_node = condition.at('./saml:AudienceRestriction', NS_MAP)
48
+ audience_restriction_node = condition.at("./saml:AudienceRestriction", NS_MAP)
49
49
  return unless audience_restriction_node
50
50
 
51
- audience_nodes = audience_restriction_node.search('./saml:Audience', NS_MAP)
51
+ audience_nodes = audience_restriction_node.search("./saml:Audience", NS_MAP)
52
52
  return unless audience_nodes.any?
53
53
 
54
54
  audience_nodes.map(&:text)
@@ -3,16 +3,16 @@ module Samlr
3
3
  attr_accessor :value
4
4
 
5
5
  def initialize(value)
6
- if value.is_a?(OpenSSL::X509::Certificate)
7
- @value = self.class.x509(value)
6
+ @value = if value.is_a?(OpenSSL::X509::Certificate)
7
+ self.class.x509(value)
8
8
  else
9
- @value = self.class.normalize(value)
9
+ self.class.normalize(value)
10
10
  end
11
11
  end
12
12
 
13
13
  def self.from_string(string)
14
14
  normalized = normalize(string)
15
- if string.gsub(':', '').length == 64
15
+ if string.delete(":").length == 64
16
16
  FingerprintSHA256.new(normalized)
17
17
  else
18
18
  FingerprintSHA1.new(normalized)
@@ -46,7 +46,7 @@ module Samlr
46
46
 
47
47
  # Extracts a fingerprint for an x509 certificate
48
48
  def self.x509(certificate)
49
- raise NotImplementedError, 'subclass must implement x509'
49
+ raise NotImplementedError, "subclass must implement x509"
50
50
  end
51
51
 
52
52
  # Converts a string to fingerprint normal form
@@ -4,7 +4,7 @@ module Samlr
4
4
  class FingerprintSHA1 < Fingerprint
5
5
  # Extracts a fingerprint for an x509 certificate
6
6
  def self.x509(certificate)
7
- normalize(OpenSSL::Digest::SHA1.new.hexdigest(certificate.to_der))
7
+ normalize(OpenSSL::Digest.new("SHA1").hexdigest(certificate.to_der))
8
8
  end
9
9
  end
10
10
  end
@@ -4,7 +4,7 @@ module Samlr
4
4
  class FingerprintSHA256 < Fingerprint
5
5
  # Extracts a fingerprint for an x509 certificate
6
6
  def self.x509(certificate)
7
- normalize(OpenSSL::Digest::SHA256.new.hexdigest(certificate.to_der))
7
+ normalize(OpenSSL::Digest.new("SHA256").hexdigest(certificate.to_der))
8
8
  end
9
9
  end
10
10
  end
@@ -6,7 +6,7 @@ module Samlr
6
6
 
7
7
  def initialize(node)
8
8
  @node = node
9
- @uri = node["URI"][1..-1]
9
+ @uri = node["URI"][1..]
10
10
  end
11
11
 
12
12
  def digest_method
@@ -14,7 +14,7 @@ module Samlr
14
14
  end
15
15
 
16
16
  def digest_value
17
- @digest_value ||= node.at("./ds:DigestValue", NS_MAP).text
17
+ @digest_value ||= node.at("./ds:DigestValue", NS_MAP).text
18
18
  end
19
19
 
20
20
  def decoded_digest_value
@@ -27,6 +27,5 @@ module Samlr
27
27
  attribute ? attribute.split(" ") : []
28
28
  end
29
29
  end
30
-
31
30
  end
32
31
  end
data/lib/samlr/request.rb CHANGED
@@ -41,14 +41,14 @@ module Samlr
41
41
  Samlr::Tools.parse(data, compressed: true)
42
42
  end
43
43
 
44
- def get_attribute_or_element(x_path,attribute=nil)
44
+ def get_attribute_or_element(x_path, attribute = nil)
45
45
  if document
46
46
  element = document.xpath(x_path)
47
47
  if element.length == 0
48
48
  nil
49
49
  elsif attribute
50
50
  value = element.attr(attribute)
51
- value.to_s if value
51
+ value&.to_s
52
52
  else
53
53
  element
54
54
  end
@@ -2,7 +2,6 @@ require "forwardable"
2
2
  require "nokogiri"
3
3
 
4
4
  module Samlr
5
-
6
5
  # This is the object interface to the XML response object.
7
6
  class Response
8
7
  extend Forwardable
@@ -11,7 +10,7 @@ module Samlr
11
10
  attr_reader :document, :options
12
11
 
13
12
  def initialize(data, options)
14
- @options = options
13
+ @options = options
15
14
  @document = Response.parse(data)
16
15
  end
17
16
 
@@ -13,18 +13,12 @@ module Samlr
13
13
  # Signature validations require document alterations
14
14
  @original = original
15
15
  @document = original.dup
16
- @prefix = prefix
17
- @options = options
16
+ @prefix = prefix
17
+ @options = options
18
18
  @signature = nil
19
19
 
20
- # TODO: This option exists only in a pre-release version to allow testing the feature; remove it from the final release
21
- if options[:skip_signature_reference_checking]
22
- @signature = @document.at("#{prefix}/ds:Signature", NS_MAP)
23
- @signature.remove if @signature # enveloped signatures only
24
- else
25
- id = @document.at("#{prefix}", NS_MAP)&.attribute('ID')
26
- @signature = find_signature_for_element_id(id) if id
27
- end
20
+ id = @document.at(prefix.to_s, NS_MAP)&.attribute("ID")
21
+ @signature = find_signature_for_element_id(id) if id
28
22
 
29
23
  @fingerprint = if options[:fingerprint]
30
24
  Fingerprint.from_string(options[:fingerprint])
@@ -45,30 +39,18 @@ module Samlr
45
39
  raise SignatureError.new("No signature at #{prefix}/ds:Signature") unless present?
46
40
 
47
41
  verify_fingerprint! unless options[:skip_fingerprint]
48
- if options[:skip_signature_reference_checking]
49
- verify_digests!
50
- verify_signature!
51
- else
52
- verify_signature! # <- Do this first while signature is still available
53
- verify_digests! # <- This can remove the signature
54
- end
42
+ verify_signature! # Do this first while signature is still available
43
+ verify_digests! # This may remove enveloped signatures
55
44
 
56
45
  true
57
46
  end
58
47
 
59
48
  def references
60
49
  @references ||= [].tap do |refs|
61
- if options[:skip_signature_reference_checking]
62
- original.xpath("#{prefix}/ds:Signature/ds:SignedInfo/ds:Reference[@URI]", NS_MAP).each do |ref|
63
- refs << Samlr::Reference.new(ref)
64
- end
65
- else
66
- refs_xpath = @signature.xpath("./ds:SignedInfo/ds:Reference[@URI]", NS_MAP)
67
- refs_xpath.each do |ref|
68
- refs << Samlr::Reference.new(ref)
69
- end
50
+ refs_xpath = @signature.xpath("./ds:SignedInfo/ds:Reference[@URI]", NS_MAP)
51
+ refs_xpath.each do |ref|
52
+ refs << Samlr::Reference.new(ref)
70
53
  end
71
-
72
54
  end
73
55
  end
74
56
 
@@ -85,58 +67,39 @@ module Samlr
85
67
 
86
68
  # Tests that the document content has not been edited
87
69
  def verify_digests!
88
- if options[:skip_signature_reference_checking]
89
- references.each do |reference|
90
- node = referenced_node(reference.uri)
91
- canoned = node.canonicalize(C14N, reference.namespaces)
92
- digest = reference.digest_method.digest(canoned)
93
-
94
- if digest != reference.decoded_digest_value
95
- raise SignatureError.new("Reference validation error: Digest mismatch for #{reference.uri}")
96
- end
97
- end
98
- else
99
- # Check if we need to remove an enveloped signature
100
- if @signature && !@signature_removed
101
- signed_element = @document.at("#{prefix}", NS_MAP)
102
- is_enveloped = signed_element&.xpath(".//ds:Signature", NS_MAP)&.include?(@signature)
103
-
104
- # Remove enveloped signature for digest verification
105
- if is_enveloped
106
- @signature.remove
107
- @signature_removed = true
108
- end
70
+ # Check if we need to remove an enveloped signature
71
+ if @signature && !@signature_removed
72
+ signed_element = @document.at(prefix.to_s, NS_MAP)
73
+ is_enveloped = signed_element&.xpath(".//ds:Signature", NS_MAP)&.include?(@signature)
74
+
75
+ # Remove enveloped signature for digest verification
76
+ if is_enveloped
77
+ @signature.remove
78
+ @signature_removed = true
109
79
  end
80
+ end
110
81
 
111
- references.each do |reference|
112
- node = referenced_node(reference.uri)
113
- canoned = node.canonicalize(C14N, reference.namespaces)
114
- digest = reference.digest_method.digest(canoned)
82
+ references.each do |reference|
83
+ node = referenced_node(reference.uri)
84
+ canoned = node.canonicalize(C14N, reference.namespaces)
85
+ digest = reference.digest_method.digest(canoned)
115
86
 
116
- if digest != reference.decoded_digest_value
117
- raise SignatureError.new("Reference validation error: Digest mismatch for #{reference.uri}")
118
- end
87
+ if digest != reference.decoded_digest_value
88
+ raise SignatureError.new("Reference validation error: Digest mismatch for #{reference.uri}")
119
89
  end
120
90
  end
121
91
  end
122
92
 
123
93
  # Tests correctness of the signature (and hence digests)
124
94
  def verify_signature!
125
- if options[:skip_signature_reference_checking]
126
- node = original.at("#{prefix}/ds:Signature/ds:SignedInfo", NS_MAP)
127
- canoned = node.canonicalize(C14N)
128
- unless x509.public_key.verify(signature_method.new, decoded_signature_value, canoned)
129
- raise SignatureError.new("Signature validation error: Possible canonicalization mismatch", "This canonicalizer returns #{canoned}")
130
- end
131
- else
132
- # Cache the canonicalized SignedInfo to avoid DOM issues with multiple verifications
133
- unless @canonicalized_signed_info
134
- node = @signature.at("./ds:SignedInfo", NS_MAP)
135
- @canonicalized_signed_info = node.canonicalize(C14N)
136
- end
137
- unless x509.public_key.verify(signature_method.new, decoded_signature_value, @canonicalized_signed_info)
138
- raise SignatureError.new("Signature validation error: Possible canonicalization mismatch", "This canonicalizer returns #{@canonicalized_signed_info}")
139
- end
95
+ # Cache the canonicalized SignedInfo to avoid DOM issues with multiple verifications
96
+ unless @canonicalized_signed_info
97
+ node = @signature.at("./ds:SignedInfo", NS_MAP)
98
+ @canonicalized_signed_info = node.canonicalize(C14N)
99
+ end
100
+
101
+ unless x509.public_key.verify(signature_method.new, decoded_signature_value, @canonicalized_signed_info)
102
+ raise SignatureError.new("Signature validation error: Possible canonicalization mismatch", "This canonicalizer returns #{@canonicalized_signed_info}")
140
103
  end
141
104
  end
142
105
 
@@ -164,14 +127,10 @@ module Samlr
164
127
  end
165
128
 
166
129
  def certificate
167
- @certificate ||= begin
168
- if node = certificate_node
169
- Certificate.new(Base64.decode64(node.text))
170
- elsif cert = options[:certificate]
171
- Certificate.new(cert)
172
- else
173
- nil
174
- end
130
+ @certificate ||= if (node = certificate_node)
131
+ Certificate.new(Base64.decode64(node.text))
132
+ elsif (cert = options[:certificate])
133
+ Certificate.new(cert)
175
134
  end
176
135
  end
177
136
 
@@ -186,9 +145,14 @@ module Samlr
186
145
  def find_signature_for_element_id(element_id)
187
146
  return nil unless element_id
188
147
 
189
- return @document.at_xpath("//ds:Signature[ds:SignedInfo/ds:Reference[@URI='##{element_id}']]", NS_MAP)
190
-
148
+ # Prevent XPath injection by using parameterized XPath queries with variable bindings.
149
+ # Nokogiri's xpath() method supports passing variables separately from the query,
150
+ # which prevents injection attacks like "_x'or'1'='1" from breaking out of the predicate.
151
+ @document.at_xpath(
152
+ "//ds:Signature[ds:SignedInfo/ds:Reference[@URI=$uri]]",
153
+ NS_MAP,
154
+ {uri: "##{element_id}"}
155
+ )
191
156
  end
192
-
193
157
  end
194
158
  end
@@ -1,35 +1,34 @@
1
1
  module Samlr
2
2
  module Tools
3
-
4
3
  # Container for generating/referencing X509 and keys
5
4
  class CertificateBuilder
6
5
  attr_reader :key_size
7
6
 
8
7
  def initialize(options = {})
9
8
  @key_size = options.fetch(:key_size, 4096)
10
- @x509 = options[:x509]
9
+ @x509 = options[:x509]
11
10
  @key_pair = options[:key_pair]
12
11
  end
13
12
 
14
13
  def x509
15
14
  @x509 ||= begin
16
15
  domain = "example.org"
17
- name = OpenSSL::X509::Name.new([
18
- [ 'C', 'US', OpenSSL::ASN1::PRINTABLESTRING ],
19
- [ 'O', domain, OpenSSL::ASN1::UTF8STRING ],
20
- [ 'OU', 'Samlr ResponseBuilder', OpenSSL::ASN1::UTF8STRING ],
21
- [ 'CN', 'CA' ]
22
- ])
16
+ name = OpenSSL::X509::Name.new([
17
+ ["C", "US", OpenSSL::ASN1::PRINTABLESTRING],
18
+ ["O", domain, OpenSSL::ASN1::UTF8STRING],
19
+ ["OU", "Samlr ResponseBuilder", OpenSSL::ASN1::UTF8STRING],
20
+ ["CN", "CA"]
21
+ ])
23
22
 
24
23
  certificate = OpenSSL::X509::Certificate.new
25
- certificate.subject = name
26
- certificate.issuer = name
24
+ certificate.subject = name
25
+ certificate.issuer = name
27
26
  certificate.not_before = (Time.now - 5)
28
- certificate.not_after = (Time.now + 60 * 60 * 24 * 365 * 20)
27
+ certificate.not_after = (Time.now + 60 * 60 * 24 * 365 * 20)
29
28
  certificate.public_key = key_pair.public_key
30
- certificate.serial = 1
31
- certificate.version = 2
32
- certificate.sign(key_pair, OpenSSL::Digest::SHA1.new)
29
+ certificate.serial = 1
30
+ certificate.version = 2
31
+ certificate.sign(key_pair, OpenSSL::Digest.new("SHA1"))
33
32
 
34
33
  certificate
35
34
  end
@@ -47,11 +46,11 @@ module Samlr
47
46
  end
48
47
 
49
48
  def sign(string)
50
- Base64.encode64(key_pair.sign(OpenSSL::Digest::SHA1.new, string)).delete("\n")
49
+ Base64.encode64(key_pair.sign(OpenSSL::Digest.new("SHA1"), string)).delete("\n")
51
50
  end
52
51
 
53
52
  def verify(signature, string)
54
- key_pair.public_key.verify(OpenSSL::Digest::SHA1.new, Base64.decode64(signature), string)
53
+ key_pair.public_key.verify(OpenSSL::Digest.new("SHA1"), Base64.decode64(signature), string)
55
54
  end
56
55
 
57
56
  def to_certificate
@@ -59,15 +58,15 @@ module Samlr
59
58
  end
60
59
 
61
60
  def self.dump(path, certificate, id = "samlr")
62
- File.open(File.join(path, "#{id}_private_key.pem"), "w") { |f| f.write(certificate.key_pair.to_pem) }
63
- File.open(File.join(path, "#{id}_certificate.pem"), "w") { |f| f.write(certificate.x509.to_pem) }
61
+ File.write(File.join(path, "#{id}_private_key.pem"), certificate.key_pair.to_pem)
62
+ File.write(File.join(path, "#{id}_certificate.pem"), certificate.x509.to_pem)
64
63
  end
65
64
 
66
65
  def self.load(path, id = "samlr")
67
- key_pair = OpenSSL::PKey::RSA.new(File.read(File.join(path, "#{id}_private_key.pem")))
66
+ key_pair = OpenSSL::PKey::RSA.new(File.read(File.join(path, "#{id}_private_key.pem")))
68
67
  x509_cert = OpenSSL::X509::Certificate.new(File.read(File.join(path, "#{id}_certificate.pem")))
69
68
 
70
- new(:key_pair => key_pair, :x509 => x509_cert)
69
+ new(key_pair: key_pair, x509: x509_cert)
71
70
  end
72
71
  end
73
72
  end
@@ -7,7 +7,7 @@ module Samlr
7
7
  def self.build(options = {})
8
8
  # Mandatory
9
9
  name_id = options.fetch(:name_id)
10
- issuer = options.fetch(:issuer)
10
+ issuer = options.fetch(:issuer)
11
11
 
12
12
  builder = Nokogiri::XML::Builder.new do |xml|
13
13
  xml.LogoutRequest("xmlns:samlp" => NS_MAP["samlp"], "xmlns:saml" => NS_MAP["saml"], "ID" => Samlr::Tools.uuid, "IssueInstant" => Samlr::Tools::Timestamp.stamp, "Version" => "2.0") do
@@ -21,10 +21,10 @@ module Samlr
21
21
  end
22
22
 
23
23
  def self.logout_options(options)
24
- name_id_options = options[:name_id_options] || {}
25
- options = { "Format" => format_option(options) }
26
- options.merge!("NameQualifier" => name_id_options[:name_qualifier]) if name_id_options[:name_qualifier]
27
- options.merge!("SPNameQualifier" => name_id_options[:spname_qualifier]) if name_id_options[:spname_qualifier]
24
+ name_id_options = options[:name_id_options] || {}
25
+ options = {"Format" => format_option(options)}
26
+ options["NameQualifier"] = name_id_options[:name_qualifier] if name_id_options[:name_qualifier]
27
+ options["SPNameQualifier"] = name_id_options[:spname_qualifier] if name_id_options[:spname_qualifier]
28
28
  options
29
29
  end
30
30
 
@@ -25,7 +25,7 @@ module Samlr
25
25
  "Version" => "2.0"
26
26
  }
27
27
  result["InResponseTo"] = options[:in_response_to] if options[:in_response_to]
28
- result["Destination"] = options[:destination] if options[:destination]
28
+ result["Destination"] = options[:destination] if options[:destination]
29
29
  result
30
30
  end
31
31
  end
@@ -1,6 +1,5 @@
1
1
  module Samlr
2
2
  module Tools
3
-
4
3
  # Builds you some SP metadata. Accepts a hash with the below keys. No support for arrays
5
4
  # of name id formats or asserion consumer services, build it if you need it.
6
5
  #
@@ -8,16 +7,15 @@ module Samlr
8
7
  # :name_identity_format => Samlr::EMAIL_FORMAT,
9
8
  # :consumer_service_url => "https://sp.example.org/saml"
10
9
  class MetadataBuilder
11
-
12
10
  def self.build(options = {})
13
- name_identity_format = options[:name_identity_format]
14
- consumer_service_url = options[:consumer_service_url]
11
+ name_identity_format = options[:name_identity_format]
12
+ consumer_service_url = options[:consumer_service_url]
15
13
  consumer_service_binding = options[:consumer_service_binding] || "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
16
- metadata_id = options[:metadata_id] || Samlr::Tools.uuid
17
- sign_metadata = options[:sign_metadata] || false
14
+ metadata_id = options[:metadata_id] || Samlr::Tools.uuid
15
+ sign_metadata = options[:sign_metadata] || false
18
16
 
19
17
  # Mandatory
20
- entity_id = options.fetch(:entity_id)
18
+ entity_id = options.fetch(:entity_id)
21
19
 
22
20
  builder = Nokogiri::XML::Builder.new do |xml|
23
21
  xml.EntityDescriptor("xmlns:md" => NS_MAP["md"], "ID" => metadata_id, "entityID" => entity_id) do
@@ -2,15 +2,14 @@ require "nokogiri"
2
2
 
3
3
  module Samlr
4
4
  module Tools
5
-
6
5
  # Use this for building the SAML auth request XML
7
6
  module RequestBuilder
8
7
  def self.build(options = {})
9
8
  consumer_service_url = options[:consumer_service_url]
10
- issuer = options[:issuer]
9
+ issuer = options[:issuer]
11
10
  name_identity_format = options[:name_identity_format]
12
- allow_create = options[:allow_create] || "true"
13
- authn_context = options[:authn_context]
11
+ allow_create = options[:allow_create] || "true"
12
+ authn_context = options[:authn_context]
14
13
 
15
14
  builder = Nokogiri::XML::Builder.new do |xml|
16
15
  xml.AuthnRequest("xmlns:samlp" => NS_MAP["samlp"], "xmlns:saml" => NS_MAP["saml"], "ID" => Samlr::Tools.uuid, "IssueInstant" => Samlr::Tools::Timestamp.stamp, "Version" => "2.0") do
@@ -38,7 +37,6 @@ module Samlr
38
37
 
39
38
  builder.to_xml(COMPACT)
40
39
  end
41
-
42
40
  end
43
41
  end
44
42
  end
@@ -4,41 +4,39 @@ require "uuidtools"
4
4
 
5
5
  module Samlr
6
6
  module Tools
7
-
8
7
  # Use this for building test data, not ready to use for production data
9
8
  module ResponseBuilder
10
-
11
9
  def self.build(options = {})
12
- issue_instant = options[:issue_instant] || Samlr::Tools::Timestamp.stamp
13
- response_id = options[:response_id] || Samlr::Tools.uuid
14
- assertion_id = options[:assertion_id] || Samlr::Tools.uuid
15
- status_code = options[:status_code] || "urn:oasis:names:tc:SAML:2.0:status:Success"
16
- name_id_format = options[:name_id_format] || EMAIL_FORMAT
17
- subject_conf_m = options[:subject_conf_m] || "urn:oasis:names:tc:SAML:2.0:cm:bearer"
18
- version = options[:version] || "2.0"
19
- auth_context = options[:auth_context] || "urn:oasis:names:tc:SAML:2.0:ac:classes:Password"
20
- issuer = options[:issuer] || "ResponseBuilder IdP"
21
- attributes = options[:attributes] || {}
22
- name_id = options[:name_id]
23
- name_qualifier = options[:name_qualifier]
10
+ issue_instant = options[:issue_instant] || Samlr::Tools::Timestamp.stamp
11
+ response_id = options[:response_id] || Samlr::Tools.uuid
12
+ assertion_id = options[:assertion_id] || Samlr::Tools.uuid
13
+ status_code = options[:status_code] || "urn:oasis:names:tc:SAML:2.0:status:Success"
14
+ name_id_format = options[:name_id_format] || EMAIL_FORMAT
15
+ subject_conf_m = options[:subject_conf_m] || "urn:oasis:names:tc:SAML:2.0:cm:bearer"
16
+ version = options[:version] || "2.0"
17
+ auth_context = options[:auth_context] || "urn:oasis:names:tc:SAML:2.0:ac:classes:Password"
18
+ issuer = options[:issuer] || "ResponseBuilder IdP"
19
+ attributes = options[:attributes] || {}
20
+ name_id = options[:name_id]
21
+ name_qualifier = options[:name_qualifier]
24
22
  sp_name_qualifier = options[:sp_name_qualifier]
25
23
 
26
24
  # Mandatory for responses
27
- destination = options.fetch(:destination)
28
- in_response_to = options.fetch(:in_response_to)
25
+ destination = options.fetch(:destination)
26
+ in_response_to = options.fetch(:in_response_to)
29
27
  not_on_or_after = options.fetch(:not_on_or_after)
30
- not_before = options.fetch(:not_before)
31
- audience = options.fetch(:audience)
28
+ not_before = options.fetch(:not_before)
29
+ audience = options.fetch(:audience)
32
30
 
33
31
  # Signature settings
34
- sign_assertion = [ true, false ].member?(options[:sign_assertion]) ? options[:sign_assertion] : true
35
- sign_response = [ true, false ].member?(options[:sign_response]) ? options[:sign_response] : true
32
+ sign_assertion = [true, false].member?(options[:sign_assertion]) ? options[:sign_assertion] : true
33
+ sign_response = [true, false].member?(options[:sign_response]) ? options[:sign_response] : true
36
34
 
37
35
  # Fixture controls
38
- skip_assertion = options[:skip_assertion]
36
+ skip_assertion = options[:skip_assertion]
39
37
  skip_conditions = options[:skip_conditions]
40
38
 
41
- builder = Nokogiri::XML::Builder.new(:encoding => "UTF-8") do |xml|
39
+ builder = Nokogiri::XML::Builder.new(encoding: "UTF-8") do |xml|
42
40
  xml.Response("xmlns:samlp" => NS_MAP["samlp"], "ID" => response_id, "InResponseTo" => in_response_to, "Version" => version, "IssueInstant" => issue_instant, "Destination" => destination) do
43
41
  xml.doc.root.add_namespace_definition("saml", NS_MAP["saml"])
44
42
  xml.doc.root.namespace = xml.doc.root.namespace_definitions.find { |ns| ns.prefix == "samlp" }
@@ -51,9 +49,9 @@ module Samlr
51
49
  xml["saml"].Issuer(issuer)
52
50
 
53
51
  xml["saml"].Subject do
54
- name_id_options = { "Format" => name_id_format}
55
- name_id_options.merge!("NameQualifier" => name_qualifier) unless name_qualifier.nil?
56
- name_id_options.merge!("SPNameQualifier" => sp_name_qualifier) unless sp_name_qualifier.nil?
52
+ name_id_options = {"Format" => name_id_format}
53
+ name_id_options["NameQualifier"] = name_qualifier unless name_qualifier.nil?
54
+ name_id_options["SPNameQualifier"] = sp_name_qualifier unless sp_name_qualifier.nil?
57
55
 
58
56
  xml["saml"].NameID(name_id, name_id_options) unless name_id.nil?
59
57
 
@@ -99,30 +97,32 @@ module Samlr
99
97
 
100
98
  # The core response is ready, not on to signing
101
99
  response = builder.doc
102
- assertion_options = options.merge(:skip_keyinfo => options[:skip_assertion_keyinfo])
100
+ assertion_options = options.merge(skip_keyinfo: options[:skip_assertion_keyinfo])
103
101
  response = sign(response, assertion_id, assertion_options) if sign_assertion
104
102
 
105
- response_options = options.merge(:skip_keyinfo => options[:skip_response_keyinfo])
106
- response = sign(response, response_id, response_options) if sign_response
103
+ response_options = options.merge(skip_keyinfo: options[:skip_response_keyinfo])
104
+ response = sign(response, response_id, response_options) if sign_response
107
105
 
108
106
  response.to_xml(COMPACT)
109
107
  end
110
108
 
111
109
  def self.sign(document, element_id, options)
112
- certificate = options[:certificate] || Samlr::Tools::CertificateBuilder.new
113
- element = document.at("//*[@ID='#{element_id}']")
114
- digest = digest(document, element, options)
115
- canoned = digest.at("./ds:SignedInfo", NS_MAP).canonicalize(C14N)
116
- signature = certificate.sign(canoned)
110
+ certificate = options[:certificate] || Samlr::Tools::CertificateBuilder.new
111
+ element = document.at("//*[@ID='#{element_id}']")
112
+ digest = digest(document, element, options)
113
+ canoned = digest.at("./ds:SignedInfo", NS_MAP).canonicalize(C14N)
114
+ signature = certificate.sign(canoned)
117
115
  skip_keyinfo = options[:skip_keyinfo]
118
116
 
119
117
  Nokogiri::XML::Builder.with(digest) do |xml|
120
118
  xml.SignatureValue(signature)
121
- xml.KeyInfo do
122
- xml.X509Data do
123
- xml.X509Certificate(certificate.x509_as_pem)
119
+ unless skip_keyinfo
120
+ xml.KeyInfo do
121
+ xml.X509Data do
122
+ xml.X509Certificate(certificate.x509_as_pem)
123
+ end
124
124
  end
125
- end unless skip_keyinfo
125
+ end
126
126
  end
127
127
  # digest.root.last_element_child.after "<SignatureValue>#{signature}</SignatureValue>"
128
128
  if element.at("./saml:Issuer", NS_MAP)
@@ -135,23 +135,22 @@ module Samlr
135
135
  end
136
136
 
137
137
  def self.digest(document, element, options)
138
- c14n_method = options[:c14n_method] || "http://www.w3.org/2001/10/xml-exc-c14n#"
139
- sign_method = options[:sign_method] || "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
138
+ c14n_method = options[:c14n_method] || "http://www.w3.org/2001/10/xml-exc-c14n#"
139
+ sign_method = options[:sign_method] || "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
140
140
  digest_method = options[:digest_method] || "http://www.w3.org/2000/09/xmldsig#sha1"
141
141
  env_signature = options[:env_signature] || "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
142
- namespaces = options[:namespaces] || [ "#default", "samlp", "saml", "ds", "xs", "xsi" ]
142
+ namespaces = options[:namespaces] || ["#default", "samlp", "saml", "ds", "xs", "xsi"]
143
143
 
144
- canoned = element.canonicalize(C14N, namespaces)
145
- digest_value = Base64.encode64(OpenSSL::Digest::SHA1.new.digest(canoned)).delete("\n")
144
+ canoned = element.canonicalize(C14N, namespaces)
145
+ digest_value = Base64.encode64(OpenSSL::Digest.new("SHA1").digest(canoned)).delete("\n")
146
146
 
147
- builder = Nokogiri::XML::Builder.new(:encoding => "UTF-8") do |xml|
147
+ builder = Nokogiri::XML::Builder.new(encoding: "UTF-8") do |xml|
148
148
  xml.Signature("xmlns" => NS_MAP["ds"]) do
149
-
150
149
  xml.SignedInfo do
151
150
  xml.CanonicalizationMethod("Algorithm" => c14n_method)
152
151
  xml.SignatureMethod("Algorithm" => sign_method)
153
152
 
154
- xml.Reference("URI" => "##{element['ID']}") do
153
+ xml.Reference("URI" => "##{element["ID"]}") do
155
154
  xml.Transforms do
156
155
  xml.Transform("Algorithm" => env_signature)
157
156
  xml.Transform("Algorithm" => c14n_method) do
@@ -167,7 +166,6 @@ module Samlr
167
166
 
168
167
  builder.doc.root
169
168
  end
170
-
171
169
  end
172
170
  end
173
171
  end
@@ -1,7 +1,6 @@
1
1
  module Samlr
2
2
  module Tools
3
3
  module Timestamp
4
-
5
4
  # Generate a current timestamp in ISO8601 format
6
5
  def self.stamp(time = Time.now)
7
6
  time.utc.iso8601
@@ -20,7 +19,6 @@ module Samlr
20
19
  def self.not_before?(time)
21
20
  Time.now.to_i >= (time.to_i - Samlr.jitter.to_i)
22
21
  end
23
-
24
22
  end
25
23
  end
26
24
  end
data/lib/samlr/tools.rb CHANGED
@@ -15,10 +15,10 @@ require "samlr/tools/logout_response_builder"
15
15
  module Samlr
16
16
  module Tools
17
17
  SHA_MAP = {
18
- 1 => OpenSSL::Digest::SHA1,
19
- 256 => OpenSSL::Digest::SHA256,
20
- 384 => OpenSSL::Digest::SHA384,
21
- 512 => OpenSSL::Digest::SHA512
18
+ 1 => OpenSSL::Digest::SHA1,
19
+ 256 => OpenSSL::Digest::SHA256,
20
+ 384 => OpenSSL::Digest::SHA384,
21
+ 512 => OpenSSL::Digest::SHA512
22
22
  }
23
23
 
24
24
  # Convert algorithm attribute value to Ruby implementation
@@ -32,13 +32,13 @@ module Samlr
32
32
 
33
33
  # Accepts a document and optionally :path => xpath, :c14n_mode => c14n_mode
34
34
  def self.canonicalize(xml, options = {})
35
- options = { :c14n_mode => C14N }.merge(options)
35
+ options = {c14n_mode: C14N}.merge(options)
36
36
  document = Nokogiri::XML(xml) { |c| c.strict.noblanks }
37
37
 
38
- if path = options[:path]
39
- node = document.at(path, NS_MAP)
38
+ node = if (path = options[:path])
39
+ document.at(path, NS_MAP)
40
40
  else
41
- node = document
41
+ document
42
42
  end
43
43
 
44
44
  node.canonicalize(options[:c14n_mode], options[:namespaces])
@@ -52,17 +52,16 @@ module Samlr
52
52
  # Deflates, Base64 encodes and CGI escapes a string
53
53
  def self.encode(string)
54
54
  deflated = Zlib::Deflate.deflate(string, 9)[2..-5]
55
- encoded = Base64.encode64(deflated)
56
- escaped = CGI.escape(encoded)
57
- escaped
55
+ encoded = Base64.encode64(deflated)
56
+ CGI.escape(encoded)
58
57
  end
59
58
 
60
59
  # CGI unescapes, Base64 decodes and inflates a string
61
60
  def self.decode(string)
62
61
  unescaped = CGI.unescape(string)
63
- decoded = Base64.decode64(unescaped)
64
- inflater = Zlib::Inflate.new(-Zlib::MAX_WBITS)
65
- inflated = inflater.inflate(decoded)
62
+ decoded = Base64.decode64(unescaped)
63
+ inflater = Zlib::Inflate.new(-Zlib::MAX_WBITS)
64
+ inflated = inflater.inflate(decoded)
66
65
 
67
66
  inflater.finish
68
67
  inflater.close
@@ -71,29 +70,29 @@ module Samlr
71
70
  end
72
71
 
73
72
  def self.validate!(options = {})
74
- validate(options.merge(:bang => true))
73
+ validate(options.merge(bang: true))
75
74
  end
76
75
 
77
76
  # Validate a SAML request or response against an XSD. Supply either :path or :document in the options and
78
77
  # a :schema (defaults to SAML validation)
79
78
  def self.validate(options = {})
80
79
  document = options[:document] || File.read(options[:path])
81
- schema = options.fetch(:schema, SAML_SCHEMA)
82
- bang = options.fetch(:bang, false)
80
+ schema = options.fetch(:schema, SAML_SCHEMA)
81
+ bang = options.fetch(:bang, false)
83
82
 
84
- if document.is_a?(Nokogiri::XML::Document)
85
- xml = document
83
+ xml = if document.is_a?(Nokogiri::XML::Document)
84
+ document
86
85
  else
87
- xml = Nokogiri::XML(document) { |c| c.strict }
86
+ Nokogiri::XML(document) { |c| c.strict }
88
87
  end
89
88
 
90
89
  # All bundled schemas are using relative schemaLocation. This means we'll have to
91
90
  # change working directory to find them during validation.
92
91
  Dir.chdir(Samlr.schema_location) do
93
- if schema.is_a?(Nokogiri::XML::Schema)
94
- xsd = schema
92
+ xsd = if schema.is_a?(Nokogiri::XML::Schema)
93
+ schema
95
94
  else
96
- xsd = Nokogiri::XML::Schema(File.read(schema))
95
+ Nokogiri::XML::Schema(File.read(schema))
97
96
  end
98
97
 
99
98
  result = xsd.validate(xml)
@@ -113,7 +112,7 @@ module Samlr
113
112
  def self.parse(data, compressed: false)
114
113
  return unless data
115
114
  decoded = Base64.decode64(data)
116
- decoded = self.inflate(decoded) if compressed
115
+ decoded = inflate(decoded) if compressed
117
116
  return unless decoded
118
117
  begin
119
118
  doc = Nokogiri::XML(decoded) { |config| config.strict }
@@ -126,7 +125,7 @@ module Samlr
126
125
  end
127
126
 
128
127
  begin
129
- Samlr::Tools.validate!(:document => doc)
128
+ Samlr::Tools.validate!(document: doc)
130
129
  rescue Samlr::SamlrError => e
131
130
  Samlr.logger.warn("Accepting non schema conforming response: #{e.message}, #{e.details}")
132
131
  raise e unless Samlr.validation_mode == :log
@@ -135,7 +134,7 @@ module Samlr
135
134
  end
136
135
 
137
136
  def self.inflate(data)
138
- inflater = Zlib::Inflate.new(-Zlib::MAX_WBITS)
137
+ inflater = Zlib::Inflate.new(-Zlib::MAX_WBITS)
139
138
  decoded = inflater.inflate(data)
140
139
  inflater.finish
141
140
  inflater.close
data/lib/samlr/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Samlr
2
- VERSION = "2.7.1.pre.3"
2
+ VERSION = "2.7.2.pre.1"
3
3
  end
data/lib/samlr.rb CHANGED
@@ -2,22 +2,22 @@ require "nokogiri"
2
2
  require "logger"
3
3
 
4
4
  module Samlr
5
- C14N = Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
6
- COMPACT = { :indent => 0, :save_with => Nokogiri::XML::Node::SaveOptions::AS_XML }
5
+ C14N = Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
6
+ COMPACT = {indent: 0, save_with: Nokogiri::XML::Node::SaveOptions::AS_XML}
7
7
 
8
- NS_MAP = {
9
- "c14n" => "http://www.w3.org/2001/10/xml-exc-c14n#",
10
- "ds" => "http://www.w3.org/2000/09/xmldsig#",
11
- "saml" => "urn:oasis:names:tc:SAML:2.0:assertion",
8
+ NS_MAP = {
9
+ "c14n" => "http://www.w3.org/2001/10/xml-exc-c14n#",
10
+ "ds" => "http://www.w3.org/2000/09/xmldsig#",
11
+ "saml" => "urn:oasis:names:tc:SAML:2.0:assertion",
12
12
  "samlp" => "urn:oasis:names:tc:SAML:2.0:protocol",
13
- "md" => "urn:oasis:names:tc:SAML:2.0:metadata",
14
- "xsi" => "http://www.w3.org/2001/XMLSchema-instance",
15
- "xs" => "http://www.w3.org/2001/XMLSchema"
13
+ "md" => "urn:oasis:names:tc:SAML:2.0:metadata",
14
+ "xsi" => "http://www.w3.org/2001/XMLSchema-instance",
15
+ "xs" => "http://www.w3.org/2001/XMLSchema"
16
16
  }
17
17
 
18
18
  EMAIL_FORMAT = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
19
- SAML_SCHEMA = "saml-schema-protocol-2.0.xsd"
20
- META_SCHEMA = "saml-schema-metadata-2.0.xsd"
19
+ SAML_SCHEMA = "saml-schema-protocol-2.0.xsd"
20
+ META_SCHEMA = "saml-schema-metadata-2.0.xsd"
21
21
 
22
22
  class << self
23
23
  attr_accessor :schema_location
@@ -28,9 +28,9 @@ module Samlr
28
28
 
29
29
  self.schema_location = File.join(File.dirname(__FILE__), "..", "config", "schemas")
30
30
  self.validation_mode = :reject
31
- self.jitter = 0
32
- self.logger = Logger.new(STDERR)
33
- self.logger.level = Logger::UNKNOWN
31
+ self.jitter = 0
32
+ self.logger = Logger.new($stderr)
33
+ logger.level = Logger::UNKNOWN
34
34
  end
35
35
 
36
36
  unless Object.new.respond_to?(:try)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: samlr
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.7.1.pre.3
4
+ version: 2.7.2.pre.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Morten Primdahl
@@ -9,34 +9,6 @@ bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
- - !ruby/object:Gem::Dependency
13
- name: nokogiri
14
- requirement: !ruby/object:Gem::Requirement
15
- requirements:
16
- - - ">="
17
- - !ruby/object:Gem::Version
18
- version: 1.5.5
19
- type: :runtime
20
- prerelease: false
21
- version_requirements: !ruby/object:Gem::Requirement
22
- requirements:
23
- - - ">="
24
- - !ruby/object:Gem::Version
25
- version: 1.5.5
26
- - !ruby/object:Gem::Dependency
27
- name: uuidtools
28
- requirement: !ruby/object:Gem::Requirement
29
- requirements:
30
- - - ">="
31
- - !ruby/object:Gem::Version
32
- version: 2.1.3
33
- type: :runtime
34
- prerelease: false
35
- version_requirements: !ruby/object:Gem::Requirement
36
- requirements:
37
- - - ">="
38
- - !ruby/object:Gem::Version
39
- version: 2.1.3
40
12
  - !ruby/object:Gem::Dependency
41
13
  name: base64
42
14
  requirement: !ruby/object:Gem::Requirement
@@ -52,7 +24,7 @@ dependencies:
52
24
  - !ruby/object:Gem::Version
53
25
  version: '0'
54
26
  - !ruby/object:Gem::Dependency
55
- name: logger
27
+ name: cgi
56
28
  requirement: !ruby/object:Gem::Requirement
57
29
  requirements:
58
30
  - - ">="
@@ -66,27 +38,13 @@ dependencies:
66
38
  - !ruby/object:Gem::Version
67
39
  version: '0'
68
40
  - !ruby/object:Gem::Dependency
69
- name: rake
70
- requirement: !ruby/object:Gem::Requirement
71
- requirements:
72
- - - ">="
73
- - !ruby/object:Gem::Version
74
- version: '0'
75
- type: :development
76
- prerelease: false
77
- version_requirements: !ruby/object:Gem::Requirement
78
- requirements:
79
- - - ">="
80
- - !ruby/object:Gem::Version
81
- version: '0'
82
- - !ruby/object:Gem::Dependency
83
- name: bundler
41
+ name: logger
84
42
  requirement: !ruby/object:Gem::Requirement
85
43
  requirements:
86
44
  - - ">="
87
45
  - !ruby/object:Gem::Version
88
46
  version: '0'
89
- type: :development
47
+ type: :runtime
90
48
  prerelease: false
91
49
  version_requirements: !ruby/object:Gem::Requirement
92
50
  requirements:
@@ -94,47 +52,33 @@ dependencies:
94
52
  - !ruby/object:Gem::Version
95
53
  version: '0'
96
54
  - !ruby/object:Gem::Dependency
97
- name: bump
55
+ name: nokogiri
98
56
  requirement: !ruby/object:Gem::Requirement
99
57
  requirements:
100
58
  - - ">="
101
59
  - !ruby/object:Gem::Version
102
- version: '0'
103
- type: :development
60
+ version: 1.5.5
61
+ type: :runtime
104
62
  prerelease: false
105
63
  version_requirements: !ruby/object:Gem::Requirement
106
64
  requirements:
107
65
  - - ">="
108
66
  - !ruby/object:Gem::Version
109
- version: '0'
110
- - !ruby/object:Gem::Dependency
111
- name: minitest
112
- requirement: !ruby/object:Gem::Requirement
113
- requirements:
114
- - - "~>"
115
- - !ruby/object:Gem::Version
116
- version: '6.0'
117
- type: :development
118
- prerelease: false
119
- version_requirements: !ruby/object:Gem::Requirement
120
- requirements:
121
- - - "~>"
122
- - !ruby/object:Gem::Version
123
- version: '6.0'
67
+ version: 1.5.5
124
68
  - !ruby/object:Gem::Dependency
125
- name: minitest-mock
69
+ name: uuidtools
126
70
  requirement: !ruby/object:Gem::Requirement
127
71
  requirements:
128
72
  - - ">="
129
73
  - !ruby/object:Gem::Version
130
- version: '0'
131
- type: :development
74
+ version: 2.1.3
75
+ type: :runtime
132
76
  prerelease: false
133
77
  version_requirements: !ruby/object:Gem::Requirement
134
78
  requirements:
135
79
  - - ">="
136
80
  - !ruby/object:Gem::Version
137
- version: '0'
81
+ version: 2.1.3
138
82
  description: Helps you implement a SAML SP
139
83
  email: primdahl@me.com
140
84
  executables:
@@ -187,7 +131,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
187
131
  requirements:
188
132
  - - ">="
189
133
  - !ruby/object:Gem::Version
190
- version: '2.7'
134
+ version: '3.2'
191
135
  required_rubygems_version: !ruby/object:Gem::Requirement
192
136
  requirements:
193
137
  - - ">="