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 +4 -4
- data/lib/samlr/assertion.rb +7 -7
- data/lib/samlr/command.rb +8 -11
- data/lib/samlr/condition.rb +8 -8
- data/lib/samlr/fingerprint.rb +5 -5
- data/lib/samlr/fingerprint_sha1.rb +1 -1
- data/lib/samlr/fingerprint_sha256.rb +1 -1
- data/lib/samlr/reference.rb +2 -3
- data/lib/samlr/request.rb +2 -2
- data/lib/samlr/response.rb +1 -2
- data/lib/samlr/signature.rb +45 -81
- data/lib/samlr/tools/certificate_builder.rb +19 -20
- data/lib/samlr/tools/logout_request_builder.rb +5 -5
- data/lib/samlr/tools/logout_response_builder.rb +1 -1
- data/lib/samlr/tools/metadata_builder.rb +5 -7
- data/lib/samlr/tools/request_builder.rb +3 -5
- data/lib/samlr/tools/response_builder.rb +44 -46
- data/lib/samlr/tools/timestamp.rb +0 -2
- data/lib/samlr/tools.rb +25 -26
- data/lib/samlr/version.rb +1 -1
- data/lib/samlr.rb +14 -14
- metadata +13 -69
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f29fb87e4f9c2e892ff008b93ceaa0406683aefb33b5b7772933498db217324d
|
|
4
|
+
data.tar.gz: 87e0905875456dac62a5bb66a4436b6549ff10a1859d42d54dcd426acb7026db
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a540ead119f309d5bba8a85641fe379a15ca421a48f1be082327f99c2c3bc30e94b7b26db756e2a1490170937e9518ea9fd5aa917e316b15b79657fa79102b6c
|
|
7
|
+
data.tar.gz: 2bad133e71423fa9207488c470832244e20bf889a25befa6e5718ffca5176c46ecb0129521091dcd8dfa0421d33314d65db2ca330b91f463826284cbcc4eaf0f
|
data/lib/samlr/assertion.rb
CHANGED
|
@@ -5,7 +5,7 @@ module Samlr
|
|
|
5
5
|
|
|
6
6
|
def initialize(document, options)
|
|
7
7
|
@document = document
|
|
8
|
-
@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
|
|
34
|
+
name = statement["Name"]
|
|
35
35
|
values = statement.xpath("./saml:AttributeValue", NS_MAP)
|
|
36
36
|
|
|
37
|
-
if values.size == 0
|
|
38
|
-
|
|
37
|
+
value = if values.size == 0
|
|
38
|
+
nil
|
|
39
39
|
elsif values.size == 1
|
|
40
|
-
|
|
40
|
+
values.first.text
|
|
41
41
|
else
|
|
42
|
-
|
|
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 ||=
|
|
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 = [
|
|
7
|
+
COMMANDS = [:verify, :schema_validate, :print]
|
|
8
8
|
|
|
9
9
|
def self.execute(options, path = nil)
|
|
10
|
-
Samlr.logger.level
|
|
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(:
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
data/lib/samlr/condition.rb
CHANGED
|
@@ -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
|
|
7
|
-
@not_before
|
|
6
|
+
@options = options
|
|
7
|
+
@not_before = (condition || {})["NotBefore"]
|
|
8
8
|
@not_on_or_after = (condition || {})["NotOnOrAfter"]
|
|
9
|
-
@audience
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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(
|
|
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(
|
|
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)
|
data/lib/samlr/fingerprint.rb
CHANGED
|
@@ -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
|
-
|
|
6
|
+
@value = if value.is_a?(OpenSSL::X509::Certificate)
|
|
7
|
+
self.class.x509(value)
|
|
8
8
|
else
|
|
9
|
-
|
|
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.
|
|
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,
|
|
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
|
|
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
|
|
7
|
+
normalize(OpenSSL::Digest.new("SHA256").hexdigest(certificate.to_der))
|
|
8
8
|
end
|
|
9
9
|
end
|
|
10
10
|
end
|
data/lib/samlr/reference.rb
CHANGED
|
@@ -6,7 +6,7 @@ module Samlr
|
|
|
6
6
|
|
|
7
7
|
def initialize(node)
|
|
8
8
|
@node = node
|
|
9
|
-
@uri
|
|
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
|
|
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
|
|
51
|
+
value&.to_s
|
|
52
52
|
else
|
|
53
53
|
element
|
|
54
54
|
end
|
data/lib/samlr/response.rb
CHANGED
|
@@ -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
|
|
13
|
+
@options = options
|
|
15
14
|
@document = Response.parse(data)
|
|
16
15
|
end
|
|
17
16
|
|
data/lib/samlr/signature.rb
CHANGED
|
@@ -13,18 +13,12 @@ module Samlr
|
|
|
13
13
|
# Signature validations require document alterations
|
|
14
14
|
@original = original
|
|
15
15
|
@document = original.dup
|
|
16
|
-
@prefix
|
|
17
|
-
@options
|
|
16
|
+
@prefix = prefix
|
|
17
|
+
@options = options
|
|
18
18
|
@signature = nil
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
if
|
|
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
|
-
|
|
49
|
-
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
117
|
-
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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 ||=
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
18
|
-
[
|
|
19
|
-
[
|
|
20
|
-
[
|
|
21
|
-
[
|
|
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
|
|
26
|
-
certificate.issuer
|
|
24
|
+
certificate.subject = name
|
|
25
|
+
certificate.issuer = name
|
|
27
26
|
certificate.not_before = (Time.now - 5)
|
|
28
|
-
certificate.not_after
|
|
27
|
+
certificate.not_after = (Time.now + 60 * 60 * 24 * 365 * 20)
|
|
29
28
|
certificate.public_key = key_pair.public_key
|
|
30
|
-
certificate.serial
|
|
31
|
-
certificate.version
|
|
32
|
-
certificate.sign(key_pair, OpenSSL::Digest
|
|
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
|
|
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
|
|
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.
|
|
63
|
-
File.
|
|
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
|
|
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(:
|
|
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
|
|
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
|
|
25
|
-
options = {
|
|
26
|
-
options
|
|
27
|
-
options
|
|
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]
|
|
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
|
|
14
|
-
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
|
|
17
|
-
sign_metadata
|
|
14
|
+
metadata_id = options[:metadata_id] || Samlr::Tools.uuid
|
|
15
|
+
sign_metadata = options[:sign_metadata] || false
|
|
18
16
|
|
|
19
17
|
# Mandatory
|
|
20
|
-
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
|
|
9
|
+
issuer = options[:issuer]
|
|
11
10
|
name_identity_format = options[:name_identity_format]
|
|
12
|
-
allow_create
|
|
13
|
-
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
|
|
13
|
-
response_id
|
|
14
|
-
assertion_id
|
|
15
|
-
status_code
|
|
16
|
-
name_id_format
|
|
17
|
-
subject_conf_m
|
|
18
|
-
version
|
|
19
|
-
auth_context
|
|
20
|
-
issuer
|
|
21
|
-
attributes
|
|
22
|
-
name_id
|
|
23
|
-
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
|
|
28
|
-
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
|
|
31
|
-
audience
|
|
28
|
+
not_before = options.fetch(:not_before)
|
|
29
|
+
audience = options.fetch(:audience)
|
|
32
30
|
|
|
33
31
|
# Signature settings
|
|
34
|
-
sign_assertion
|
|
35
|
-
sign_response
|
|
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
|
|
36
|
+
skip_assertion = options[:skip_assertion]
|
|
39
37
|
skip_conditions = options[:skip_conditions]
|
|
40
38
|
|
|
41
|
-
builder = Nokogiri::XML::Builder.new(:
|
|
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 = {
|
|
55
|
-
name_id_options
|
|
56
|
-
name_id_options
|
|
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(:
|
|
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(:
|
|
106
|
-
response = sign(response, response_id, response_options)
|
|
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
|
|
113
|
-
element
|
|
114
|
-
digest
|
|
115
|
-
canoned
|
|
116
|
-
signature
|
|
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
|
-
|
|
122
|
-
xml.
|
|
123
|
-
xml.
|
|
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
|
|
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
|
|
139
|
-
sign_method
|
|
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
|
|
142
|
+
namespaces = options[:namespaces] || ["#default", "samlp", "saml", "ds", "xs", "xsi"]
|
|
143
143
|
|
|
144
|
-
canoned
|
|
145
|
-
digest_value
|
|
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(:
|
|
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[
|
|
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
|
|
19
|
-
256
|
|
20
|
-
384
|
|
21
|
-
512
|
|
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
|
|
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
|
-
|
|
38
|
+
node = if (path = options[:path])
|
|
39
|
+
document.at(path, NS_MAP)
|
|
40
40
|
else
|
|
41
|
-
|
|
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
|
|
56
|
-
|
|
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
|
|
64
|
-
inflater
|
|
65
|
-
inflated
|
|
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(:
|
|
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
|
|
82
|
-
bang
|
|
80
|
+
schema = options.fetch(:schema, SAML_SCHEMA)
|
|
81
|
+
bang = options.fetch(:bang, false)
|
|
83
82
|
|
|
84
|
-
if document.is_a?(Nokogiri::XML::Document)
|
|
85
|
-
|
|
83
|
+
xml = if document.is_a?(Nokogiri::XML::Document)
|
|
84
|
+
document
|
|
86
85
|
else
|
|
87
|
-
|
|
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
|
-
|
|
92
|
+
xsd = if schema.is_a?(Nokogiri::XML::Schema)
|
|
93
|
+
schema
|
|
95
94
|
else
|
|
96
|
-
|
|
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 =
|
|
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!(:
|
|
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
|
|
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
data/lib/samlr.rb
CHANGED
|
@@ -2,22 +2,22 @@ require "nokogiri"
|
|
|
2
2
|
require "logger"
|
|
3
3
|
|
|
4
4
|
module Samlr
|
|
5
|
-
C14N
|
|
6
|
-
COMPACT = {
|
|
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"
|
|
10
|
-
"ds"
|
|
11
|
-
"saml"
|
|
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"
|
|
14
|
-
"xsi"
|
|
15
|
-
"xs"
|
|
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
|
|
20
|
-
META_SCHEMA
|
|
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
|
|
32
|
-
self.logger
|
|
33
|
-
|
|
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.
|
|
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:
|
|
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:
|
|
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: :
|
|
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:
|
|
55
|
+
name: nokogiri
|
|
98
56
|
requirement: !ruby/object:Gem::Requirement
|
|
99
57
|
requirements:
|
|
100
58
|
- - ">="
|
|
101
59
|
- !ruby/object:Gem::Version
|
|
102
|
-
version:
|
|
103
|
-
type: :
|
|
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:
|
|
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:
|
|
69
|
+
name: uuidtools
|
|
126
70
|
requirement: !ruby/object:Gem::Requirement
|
|
127
71
|
requirements:
|
|
128
72
|
- - ">="
|
|
129
73
|
- !ruby/object:Gem::Version
|
|
130
|
-
version:
|
|
131
|
-
type: :
|
|
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:
|
|
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
|
|
134
|
+
version: '3.2'
|
|
191
135
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
192
136
|
requirements:
|
|
193
137
|
- - ">="
|