saml2 3.1.2 → 3.1.4
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/Rakefile +6 -4
- data/exe/bulk_verify_responses +94 -0
- data/lib/saml2/assertion.rb +7 -7
- data/lib/saml2/attribute/x500.rb +31 -28
- data/lib/saml2/attribute.rb +53 -49
- data/lib/saml2/attribute_consuming_service.rb +29 -31
- data/lib/saml2/authn_request.rb +54 -47
- data/lib/saml2/authn_statement.rb +31 -20
- data/lib/saml2/base.rb +72 -63
- data/lib/saml2/bindings/http_post.rb +7 -7
- data/lib/saml2/bindings/http_redirect.rb +37 -33
- data/lib/saml2/bindings.rb +1 -1
- data/lib/saml2/conditions.rb +19 -16
- data/lib/saml2/contact.rb +19 -18
- data/lib/saml2/endpoint.rb +14 -11
- data/lib/saml2/entity.rb +27 -27
- data/lib/saml2/identity_provider.rb +13 -10
- data/lib/saml2/indexed_object.rb +15 -12
- data/lib/saml2/key.rb +43 -34
- data/lib/saml2/localized_name.rb +11 -10
- data/lib/saml2/logout_request.rb +8 -8
- data/lib/saml2/logout_response.rb +4 -4
- data/lib/saml2/message.rb +24 -20
- data/lib/saml2/name_id.rb +45 -41
- data/lib/saml2/namespaces.rb +8 -8
- data/lib/saml2/organization.rb +11 -10
- data/lib/saml2/organization_and_contacts.rb +5 -5
- data/lib/saml2/request.rb +3 -3
- data/lib/saml2/requested_authn_context.rb +4 -4
- data/lib/saml2/response.rb +45 -33
- data/lib/saml2/role.rb +11 -11
- data/lib/saml2/schemas.rb +13 -10
- data/lib/saml2/service_provider.rb +11 -12
- data/lib/saml2/signable.rb +23 -18
- data/lib/saml2/sso.rb +5 -5
- data/lib/saml2/status.rb +9 -7
- data/lib/saml2/status_response.rb +5 -5
- data/lib/saml2/subject.rb +28 -28
- data/lib/saml2/version.rb +1 -1
- data/lib/saml2.rb +7 -7
- metadata +78 -122
- data/spec/fixtures/FederationMetadata.xml +0 -670
- data/spec/fixtures/authnrequest.xml +0 -12
- data/spec/fixtures/certificate.pem +0 -24
- data/spec/fixtures/entities.xml +0 -13
- data/spec/fixtures/external-uri-reference-response.xml +0 -48
- data/spec/fixtures/identity_provider.xml +0 -46
- data/spec/fixtures/noconditions_response.xml +0 -1
- data/spec/fixtures/othercertificate.pem +0 -25
- data/spec/fixtures/privatekey.key +0 -27
- data/spec/fixtures/response_assertion_signed_reffed_from_response.xml +0 -6
- data/spec/fixtures/response_signed.xml +0 -46
- data/spec/fixtures/response_tampered_certificate.xml +0 -25
- data/spec/fixtures/response_tampered_signature.xml +0 -46
- data/spec/fixtures/response_with_attribute_signed.xml +0 -46
- data/spec/fixtures/response_with_encrypted_assertion.xml +0 -58
- data/spec/fixtures/response_with_rsa_key_value.xml +0 -1
- data/spec/fixtures/response_with_signed_assertion_and_encrypted_subject.xml +0 -116
- data/spec/fixtures/response_without_keyinfo.xml +0 -1
- data/spec/fixtures/service_provider.xml +0 -79
- data/spec/fixtures/test3-response.xml +0 -9
- data/spec/fixtures/test6-response.xml +0 -10
- data/spec/fixtures/test7-response.xml +0 -10
- data/spec/fixtures/xml_missigned_assertion.xml +0 -84
- data/spec/fixtures/xml_signature_wrapping_attack_duplicate_ids.xml +0 -11
- data/spec/fixtures/xml_signature_wrapping_attack_response_attributes.xml +0 -45
- data/spec/fixtures/xml_signature_wrapping_attack_response_nameid.xml +0 -44
- data/spec/fixtures/xslt-transform-response.xml +0 -57
- data/spec/lib/attribute_consuming_service_spec.rb +0 -129
- data/spec/lib/attribute_spec.rb +0 -149
- data/spec/lib/authn_request_spec.rb +0 -52
- data/spec/lib/bindings/http_redirect_spec.rb +0 -183
- data/spec/lib/conditions_spec.rb +0 -74
- data/spec/lib/entity_spec.rb +0 -58
- data/spec/lib/identity_provider_spec.rb +0 -43
- data/spec/lib/indexed_object_spec.rb +0 -71
- data/spec/lib/key_spec.rb +0 -23
- data/spec/lib/logout_request_spec.rb +0 -33
- data/spec/lib/logout_response_spec.rb +0 -33
- data/spec/lib/message_spec.rb +0 -23
- data/spec/lib/response_spec.rb +0 -293
- data/spec/lib/service_provider_spec.rb +0 -76
- data/spec/lib/signable_spec.rb +0 -15
- data/spec/spec_helper.rb +0 -8
data/lib/saml2/response.rb
CHANGED
@@ -1,17 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "nokogiri-xmlsec"
|
4
|
+
require "time"
|
5
5
|
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
6
|
+
require "saml2/assertion"
|
7
|
+
require "saml2/authn_statement"
|
8
|
+
require "saml2/status_response"
|
9
|
+
require "saml2/subject"
|
10
10
|
|
11
11
|
module SAML2
|
12
12
|
class Response < StatusResponse
|
13
|
-
attr_reader :assertions
|
14
|
-
|
15
13
|
# Respond to an {AuthnRequest}
|
16
14
|
#
|
17
15
|
# {AuthnRequest#resolve} needs to have been previously called on the {AuthnRequest}.
|
@@ -98,7 +96,11 @@ module SAML2
|
|
98
96
|
verification_time: nil,
|
99
97
|
ignore_audience_condition: false)
|
100
98
|
raise ArgumentError, "service_provider should be an Entity object" unless service_provider.is_a?(Entity)
|
101
|
-
|
99
|
+
|
100
|
+
unless (sp = service_provider.service_providers.first)
|
101
|
+
raise ArgumentError,
|
102
|
+
"service_provider should have at least one service_provider role"
|
103
|
+
end
|
102
104
|
|
103
105
|
# validate the schema
|
104
106
|
super()
|
@@ -108,7 +110,9 @@ module SAML2
|
|
108
110
|
verification_time = Time.now.utc
|
109
111
|
# they issued it in the (near) future according to our clock;
|
110
112
|
# use their clock instead
|
111
|
-
|
113
|
+
if issue_instant > verification_time && issue_instant < verification_time + (5 * 60)
|
114
|
+
verification_time = issue_instant
|
115
|
+
end
|
112
116
|
end
|
113
117
|
|
114
118
|
# not finding the issuer is not exceptional
|
@@ -119,16 +123,21 @@ module SAML2
|
|
119
123
|
|
120
124
|
# getting the wrong data type is exceptional, and we should raise an error
|
121
125
|
raise ArgumentError, "identity_provider should be an Entity object" unless identity_provider.is_a?(Entity)
|
122
|
-
|
126
|
+
|
127
|
+
unless (idp = identity_provider.identity_providers.first)
|
128
|
+
raise ArgumentError,
|
129
|
+
"identity_provider should have at least one identity_provider role"
|
130
|
+
end
|
123
131
|
|
124
132
|
issuer = self.issuer || assertions.first&.issuer
|
125
133
|
unless identity_provider.entity_id == issuer&.id
|
126
|
-
errors << "received unexpected message from '#{issuer&.id}';
|
134
|
+
errors << "received unexpected message from '#{issuer&.id}'; " \
|
135
|
+
"expected it to be from '#{identity_provider.entity_id}'"
|
127
136
|
return errors
|
128
137
|
end
|
129
138
|
|
130
|
-
certificates = idp.signing_keys.
|
131
|
-
keys = idp.signing_keys.
|
139
|
+
certificates = idp.signing_keys.filter_map(&:certificate)
|
140
|
+
keys = idp.signing_keys.filter_map(&:key)
|
132
141
|
if idp.fingerprints.empty? && certificates.empty? && keys.empty?
|
133
142
|
errors << "could not find certificate to validate message"
|
134
143
|
return errors
|
@@ -140,6 +149,7 @@ module SAML2
|
|
140
149
|
cert: certificates)).empty?
|
141
150
|
return errors.concat(signature_errors)
|
142
151
|
end
|
152
|
+
|
143
153
|
response_signed = true
|
144
154
|
end
|
145
155
|
|
@@ -152,24 +162,27 @@ module SAML2
|
|
152
162
|
cert: certificates)).empty?
|
153
163
|
return errors.concat(signature_errors)
|
154
164
|
end
|
165
|
+
|
155
166
|
assertion_signed = true
|
156
167
|
end
|
157
168
|
|
158
|
-
find_decryption_key =
|
169
|
+
find_decryption_key = lambda do |embedded_certificates|
|
159
170
|
key = nil
|
160
171
|
embedded_certificates.each do |cert_info|
|
161
172
|
cert = case cert_info
|
162
|
-
|
163
|
-
|
173
|
+
when OpenSSL::X509::Certificate then cert_info
|
174
|
+
when Hash then sp.encryption_keys.map(&:certificate).find { |c| c.serial == cert_info[:serial] }
|
164
175
|
end
|
165
176
|
next unless cert
|
177
|
+
|
166
178
|
key = sp.private_keys.find { |k| cert.check_private_key(k) }
|
167
179
|
break if key
|
168
180
|
end
|
169
|
-
|
181
|
+
unless key
|
170
182
|
# couldn't figure out which key to use; just try them all
|
171
183
|
next sp.private_keys
|
172
184
|
end
|
185
|
+
|
173
186
|
key
|
174
187
|
end
|
175
188
|
|
@@ -208,23 +221,24 @@ module SAML2
|
|
208
221
|
cert: certificates)).empty?
|
209
222
|
return errors.concat(signature_errors)
|
210
223
|
end
|
224
|
+
|
211
225
|
assertion_signed = true
|
212
226
|
end
|
213
227
|
|
214
228
|
# only do our own issue instant validation if the assertion
|
215
229
|
# doesn't mandate any
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
return errors
|
221
|
-
end
|
230
|
+
if !assertion.conditions&.not_on_or_after && (assertion.issue_instant + (5 * 60) < verification_time ||
|
231
|
+
assertion.issue_instant - (5 * 60) > verification_time)
|
232
|
+
errors << "assertion not recently issued"
|
233
|
+
return errors
|
222
234
|
end
|
223
235
|
|
224
236
|
if assertion.conditions &&
|
225
|
-
|
226
|
-
|
227
|
-
|
237
|
+
!(condition_errors = assertion.conditions.validate(
|
238
|
+
verification_time: verification_time,
|
239
|
+
audience: service_provider.entity_id,
|
240
|
+
ignore_audience_condition: ignore_audience_condition
|
241
|
+
)).empty?
|
228
242
|
return errors.concat(condition_errors)
|
229
243
|
end
|
230
244
|
|
@@ -253,9 +267,7 @@ module SAML2
|
|
253
267
|
|
254
268
|
# @return [Array<Assertion>]
|
255
269
|
def assertions
|
256
|
-
unless instance_variable_defined?(:@assertions)
|
257
|
-
@assertions = load_object_array(xml, 'saml:Assertion', Assertion)
|
258
|
-
end
|
270
|
+
@assertions = load_object_array(xml, "saml:Assertion", Assertion) unless instance_variable_defined?(:@assertions)
|
259
271
|
@assertions
|
260
272
|
end
|
261
273
|
|
@@ -275,9 +287,9 @@ module SAML2
|
|
275
287
|
private
|
276
288
|
|
277
289
|
def build(builder)
|
278
|
-
builder[
|
279
|
-
|
280
|
-
|
290
|
+
builder["samlp"].Response(
|
291
|
+
"xmlns:samlp" => Namespaces::SAMLP,
|
292
|
+
"xmlns:saml" => Namespaces::SAML
|
281
293
|
) do |response|
|
282
294
|
super(response)
|
283
295
|
|
data/lib/saml2/role.rb
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "set"
|
4
4
|
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
5
|
+
require "saml2/base"
|
6
|
+
require "saml2/key"
|
7
|
+
require "saml2/organization_and_contacts"
|
8
|
+
require "saml2/signable"
|
9
9
|
|
10
10
|
module SAML2
|
11
11
|
# @abstract
|
12
12
|
class Role < Base
|
13
13
|
module Protocols
|
14
|
-
SAML2 =
|
14
|
+
SAML2 = "urn:oasis:names:tc:SAML:2.0:protocol"
|
15
15
|
end
|
16
16
|
|
17
17
|
include OrganizationAndContacts
|
@@ -47,29 +47,29 @@ module SAML2
|
|
47
47
|
# @see Protocols
|
48
48
|
# @return [Array<String>]
|
49
49
|
def supported_protocols
|
50
|
-
@supported_protocols ||= xml[
|
50
|
+
@supported_protocols ||= xml["protocolSupportEnumeration"].split
|
51
51
|
end
|
52
52
|
|
53
53
|
# @return [Array<KeyDescriptor>]
|
54
54
|
def keys
|
55
|
-
@keys ||= load_object_array(xml,
|
55
|
+
@keys ||= load_object_array(xml, "md:KeyDescriptor", KeyDescriptor)
|
56
56
|
end
|
57
57
|
|
58
58
|
# @return [Array<KeyDescriptor>]
|
59
59
|
def signing_keys
|
60
|
-
keys.select
|
60
|
+
keys.select(&:signing?)
|
61
61
|
end
|
62
62
|
|
63
63
|
# @return [Array<KeyDescriptor>]
|
64
64
|
def encryption_keys
|
65
|
-
keys.select
|
65
|
+
keys.select(&:encryption?)
|
66
66
|
end
|
67
67
|
|
68
68
|
protected
|
69
69
|
|
70
70
|
# should be called from inside the role element
|
71
71
|
def build(builder)
|
72
|
-
builder.parent[
|
72
|
+
builder.parent["protocolSupportEnumeration"] = supported_protocols.to_a.join(" ")
|
73
73
|
keys.each do |key|
|
74
74
|
key.build(builder)
|
75
75
|
end
|
data/lib/saml2/schemas.rb
CHANGED
@@ -2,18 +2,21 @@
|
|
2
2
|
|
3
3
|
module SAML2
|
4
4
|
module Schemas
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
class << self
|
6
|
+
def metadata
|
7
|
+
@metadata ||= schema("metadata_combined.xsd")
|
8
|
+
end
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
|
10
|
+
def protocol
|
11
|
+
@protocol ||= schema("saml-schema-protocol-2.0.xsd")
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
12
15
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
16
|
+
def schema(filename)
|
17
|
+
Dir.chdir(File.expand_path(File.join(__FILE__, "../../../schemas"))) do
|
18
|
+
Nokogiri::XML::Schema(File.read(filename))
|
19
|
+
end
|
17
20
|
end
|
18
21
|
end
|
19
22
|
end
|
@@ -1,9 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "nokogiri"
|
4
4
|
|
5
|
-
require
|
6
|
-
require
|
5
|
+
require "saml2/endpoint"
|
6
|
+
require "saml2/sso"
|
7
7
|
|
8
8
|
module SAML2
|
9
9
|
class ServiceProvider < SSO
|
@@ -29,7 +29,7 @@ module SAML2
|
|
29
29
|
# @return [Boolean, nil]
|
30
30
|
def authn_requests_signed?
|
31
31
|
unless instance_variable_defined?(:@authn_requests_signed)
|
32
|
-
@authn_requests_signed = xml[
|
32
|
+
@authn_requests_signed = xml["AuthnRequestsSigned"] && xml["AuthnRequestsSigned"] == "true"
|
33
33
|
end
|
34
34
|
@authn_requests_signed
|
35
35
|
end
|
@@ -37,7 +37,7 @@ module SAML2
|
|
37
37
|
# @return [Boolean, nil]
|
38
38
|
def want_assertions_signed?
|
39
39
|
unless instance_variable_defined?(:@want_assertions_signed)
|
40
|
-
@want_assertions_signed = xml[
|
40
|
+
@want_assertions_signed = xml["WantAssertionsSigned"] && xml["WantAssertionsSigned"] == "true"
|
41
41
|
end
|
42
42
|
@want_assertions_signed
|
43
43
|
end
|
@@ -45,7 +45,7 @@ module SAML2
|
|
45
45
|
# @return [Endpoint::Indexed::Array]
|
46
46
|
def assertion_consumer_services
|
47
47
|
@assertion_consumer_services ||= begin
|
48
|
-
nodes = xml.xpath(
|
48
|
+
nodes = xml.xpath("md:AssertionConsumerService", Namespaces::ALL)
|
49
49
|
Endpoint::Indexed::Array.from_xml(nodes)
|
50
50
|
end
|
51
51
|
end
|
@@ -53,21 +53,21 @@ module SAML2
|
|
53
53
|
# @return [AttributeConsumingService::Array]
|
54
54
|
def attribute_consuming_services
|
55
55
|
@attribute_consuming_services ||= begin
|
56
|
-
nodes = xml.xpath(
|
56
|
+
nodes = xml.xpath("md:AttributeConsumingService", Namespaces::ALL)
|
57
57
|
AttributeConsumingService::Array.from_xml(nodes)
|
58
58
|
end
|
59
59
|
end
|
60
60
|
|
61
61
|
# (see Base#build)
|
62
62
|
def build(builder)
|
63
|
-
builder[
|
63
|
+
builder["md"].SPSSODescriptor do |sp_sso_descriptor|
|
64
64
|
super(sp_sso_descriptor)
|
65
65
|
|
66
|
-
sp_sso_descriptor.parent[
|
67
|
-
sp_sso_descriptor.parent[
|
66
|
+
sp_sso_descriptor.parent["AuthnRequestsSigned"] = authn_requests_signed? unless authn_requests_signed?.nil?
|
67
|
+
sp_sso_descriptor.parent["WantAssertionsSigned"] = want_assertions_signed? unless authn_requests_signed?.nil?
|
68
68
|
|
69
69
|
assertion_consumer_services.each do |acs|
|
70
|
-
acs.build(sp_sso_descriptor,
|
70
|
+
acs.build(sp_sso_descriptor, "AssertionConsumerService")
|
71
71
|
end
|
72
72
|
|
73
73
|
attribute_consuming_services.each do |acs|
|
@@ -77,4 +77,3 @@ module SAML2
|
|
77
77
|
end
|
78
78
|
end
|
79
79
|
end
|
80
|
-
|
data/lib/saml2/signable.rb
CHANGED
@@ -1,21 +1,21 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "saml2/key"
|
4
4
|
|
5
5
|
module SAML2
|
6
6
|
module Signable
|
7
7
|
# @return [Nokogiri::XML::Element, nil]
|
8
8
|
def signature
|
9
9
|
unless instance_variable_defined?(:@signature)
|
10
|
-
@signature = xml.xpath(
|
11
|
-
signed_node = signature.at_xpath(
|
12
|
-
if signed_node ==
|
10
|
+
@signature = xml.xpath("//dsig:Signature", Namespaces::ALL).find do |signature|
|
11
|
+
signed_node = signature.at_xpath("dsig:SignedInfo/dsig:Reference", Namespaces::ALL)["URI"]
|
12
|
+
if signed_node == ""
|
13
13
|
true if xml == xml.document.root
|
14
|
-
elsif signed_node != "##{xml[
|
14
|
+
elsif signed_node != "##{xml["ID"]}"
|
15
15
|
false
|
16
16
|
else
|
17
17
|
# validating the schema will automatically add ID attributes, so check that first
|
18
|
-
xml.set_id_attribute(
|
18
|
+
xml.set_id_attribute("ID") unless xml.document.get_id(xml["ID"])
|
19
19
|
true
|
20
20
|
end
|
21
21
|
end
|
@@ -27,7 +27,12 @@ module SAML2
|
|
27
27
|
def signing_key
|
28
28
|
unless instance_variable_defined?(:@signing_key)
|
29
29
|
# don't use `... if signature.at_xpath(...)` - we need to make sure we assign the nil
|
30
|
-
@signing_key = (key_info = signature.at_xpath(
|
30
|
+
@signing_key = if (key_info = signature.at_xpath("dsig:KeyInfo",
|
31
|
+
Namespaces::ALL))
|
32
|
+
KeyInfo.from_xml(key_info)
|
33
|
+
else
|
34
|
+
nil
|
35
|
+
end
|
31
36
|
end
|
32
37
|
@signing_key
|
33
38
|
end
|
@@ -69,19 +74,15 @@ module SAML2
|
|
69
74
|
certs = certs.uniq
|
70
75
|
|
71
76
|
trusted_keys = Array.wrap(key).map(&:to_s)
|
72
|
-
trusted_keys.concat(certs.map do |
|
73
|
-
|
74
|
-
|
77
|
+
trusted_keys.concat(certs.map do |certificate|
|
78
|
+
certificate = OpenSSL::X509::Certificate.new(certificate) if certificate.is_a?(String)
|
79
|
+
certificate.public_key.to_s
|
75
80
|
end)
|
76
81
|
|
77
|
-
if trusted_keys.include?(signing_key&.public_key&.to_s)
|
78
|
-
verification_key = signing_key.public_key.to_s
|
79
|
-
end
|
82
|
+
verification_key = signing_key.public_key.to_s if trusted_keys.include?(signing_key&.public_key&.to_s)
|
80
83
|
# signature doesn't say who signed it. hope and pray it's with the only certificate
|
81
84
|
# we know about
|
82
|
-
if signing_key.nil?
|
83
|
-
verification_key = trusted_keys.first
|
84
|
-
end
|
85
|
+
verification_key = trusted_keys.first if signing_key.nil?
|
85
86
|
|
86
87
|
return ["no trusted signing key found"] if verification_key.nil?
|
87
88
|
|
@@ -116,8 +117,12 @@ module SAML2
|
|
116
117
|
to_xml
|
117
118
|
|
118
119
|
xml = @document.root
|
119
|
-
xml.set_id_attribute(
|
120
|
-
xml.sign!(cert: x509_certificate,
|
120
|
+
xml.set_id_attribute("ID")
|
121
|
+
xml.sign!(cert: x509_certificate,
|
122
|
+
key: private_key,
|
123
|
+
digest_alg: algorithm_name.to_s,
|
124
|
+
signature_alg: "rsa-#{algorithm_name}",
|
125
|
+
uri: "##{id}")
|
121
126
|
# the Signature element must be the first element
|
122
127
|
signature = xml.at_xpath("dsig:Signature", Namespaces::ALL)
|
123
128
|
xml.children.first.add_previous_sibling(signature)
|
data/lib/saml2/sso.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "saml2/role"
|
4
4
|
|
5
5
|
module SAML2
|
6
6
|
# @abstract
|
@@ -20,12 +20,12 @@ module SAML2
|
|
20
20
|
|
21
21
|
# @return [Array<Endpoint>]
|
22
22
|
def single_logout_services
|
23
|
-
@single_logout_services ||= load_object_array(xml,
|
23
|
+
@single_logout_services ||= load_object_array(xml, "md:SingleLogoutService", Endpoint)
|
24
24
|
end
|
25
25
|
|
26
26
|
# @return [Array<String>]
|
27
27
|
def name_id_formats
|
28
|
-
@name_id_formats ||= load_string_array(xml,
|
28
|
+
@name_id_formats ||= load_string_array(xml, "md:NameIDFormat")
|
29
29
|
end
|
30
30
|
|
31
31
|
protected
|
@@ -35,10 +35,10 @@ module SAML2
|
|
35
35
|
super
|
36
36
|
|
37
37
|
single_logout_services.each do |slo|
|
38
|
-
slo.build(builder,
|
38
|
+
slo.build(builder, "SingleLogoutService")
|
39
39
|
end
|
40
40
|
name_id_formats.each do |nif|
|
41
|
-
builder[
|
41
|
+
builder["md"].NameIDFormat(nif)
|
42
42
|
end
|
43
43
|
end
|
44
44
|
end
|
data/lib/saml2/status.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "saml2/base"
|
4
4
|
|
5
5
|
module SAML2
|
6
6
|
class Status < Base
|
@@ -12,14 +12,16 @@ module SAML2
|
|
12
12
|
# @param code [String]
|
13
13
|
# @param message [String, nil]
|
14
14
|
def initialize(code = SUCCESS, message = nil)
|
15
|
-
|
15
|
+
super()
|
16
|
+
@code = code
|
17
|
+
@message = message
|
16
18
|
end
|
17
19
|
|
18
20
|
# (see Base#from_xml)
|
19
21
|
def from_xml(node)
|
20
22
|
super
|
21
|
-
self.code = node.at_xpath(
|
22
|
-
self.message = load_string_array(xml,
|
23
|
+
self.code = node.at_xpath("samlp:StatusCode", Namespaces::ALL)["Value"]
|
24
|
+
self.message = load_string_array(xml, "samlp:StatusMessage")
|
23
25
|
end
|
24
26
|
|
25
27
|
def success?
|
@@ -28,10 +30,10 @@ module SAML2
|
|
28
30
|
|
29
31
|
# (see Base#build)
|
30
32
|
def build(builder)
|
31
|
-
builder[
|
32
|
-
status[
|
33
|
+
builder["samlp"].Status do |status|
|
34
|
+
status["samlp"].StatusCode(Value: code)
|
33
35
|
Array(message).each do |m|
|
34
|
-
status[
|
36
|
+
status["samlp"].StatusMessage(m)
|
35
37
|
end
|
36
38
|
end
|
37
39
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "saml2/message"
|
4
|
+
require "saml2/status"
|
5
5
|
|
6
6
|
module SAML2
|
7
7
|
class StatusResponse < Message
|
@@ -19,12 +19,12 @@ module SAML2
|
|
19
19
|
super
|
20
20
|
@status = nil
|
21
21
|
remove_instance_variable(:@status)
|
22
|
-
@in_response_to = node[
|
22
|
+
@in_response_to = node["InResponseTo"]
|
23
23
|
end
|
24
24
|
|
25
25
|
# @return [Status]
|
26
26
|
def status
|
27
|
-
@status ||= Status.from_xml(xml.at_xpath(
|
27
|
+
@status ||= Status.from_xml(xml.at_xpath("samlp:Status", Namespaces::ALL))
|
28
28
|
end
|
29
29
|
|
30
30
|
protected
|
@@ -32,7 +32,7 @@ module SAML2
|
|
32
32
|
def build(status_response)
|
33
33
|
super(status_response)
|
34
34
|
|
35
|
-
status_response.parent[
|
35
|
+
status_response.parent["InResponseTo"] = in_response_to if in_response_to
|
36
36
|
|
37
37
|
status.build(status_response)
|
38
38
|
end
|
data/lib/saml2/subject.rb
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "saml2/name_id"
|
4
|
+
require "saml2/namespaces"
|
5
5
|
|
6
6
|
module SAML2
|
7
7
|
class Subject < Base
|
8
|
-
attr_writer :name_id
|
9
|
-
attr_writer :confirmations
|
8
|
+
attr_writer :name_id, :confirmations
|
10
9
|
|
11
10
|
def initialize
|
11
|
+
super
|
12
12
|
@confirmations = []
|
13
13
|
end
|
14
14
|
|
@@ -21,7 +21,7 @@ module SAML2
|
|
21
21
|
# @return [NameID]
|
22
22
|
def name_id
|
23
23
|
if xml && !instance_variable_defined?(:@name_id)
|
24
|
-
@name_id = NameID.from_xml(xml.at_xpath(
|
24
|
+
@name_id = NameID.from_xml(xml.at_xpath("saml:NameID", Namespaces::ALL))
|
25
25
|
end
|
26
26
|
@name_id
|
27
27
|
end
|
@@ -39,13 +39,13 @@ module SAML2
|
|
39
39
|
|
40
40
|
# @return [Confirmation, Array<Confirmation>]
|
41
41
|
def confirmations
|
42
|
-
@confirmations ||= load_object_array(xml,
|
42
|
+
@confirmations ||= load_object_array(xml, "saml:SubjectConfirmation", Confirmation)
|
43
43
|
end
|
44
44
|
|
45
45
|
# (see Base#build)
|
46
46
|
def build(builder)
|
47
|
-
builder[
|
48
|
-
name_id
|
47
|
+
builder["saml"].Subject do |subject|
|
48
|
+
name_id&.build(subject)
|
49
49
|
Array(confirmations).each do |confirmation|
|
50
50
|
confirmation.build(subject)
|
51
51
|
end
|
@@ -54,9 +54,9 @@ module SAML2
|
|
54
54
|
|
55
55
|
class Confirmation < Base
|
56
56
|
module Methods
|
57
|
-
BEARER =
|
58
|
-
HOLDER_OF_KEY =
|
59
|
-
SENDER_VOUCHES =
|
57
|
+
BEARER = "urn:oasis:names:tc:SAML:2.0:cm:bearer"
|
58
|
+
HOLDER_OF_KEY = "urn:oasis:names:tc:SAML:2.0:cm:holder-of-key"
|
59
|
+
SENDER_VOUCHES = "urn:oasis:names:tc:SAML:2.0:cm:sender-vouches"
|
60
60
|
end
|
61
61
|
|
62
62
|
# @see Methods
|
@@ -70,28 +70,28 @@ module SAML2
|
|
70
70
|
# (see Base#from_xml)
|
71
71
|
def from_xml(node)
|
72
72
|
super
|
73
|
-
self.method = node[
|
74
|
-
confirmation_data = node.at_xpath(
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
73
|
+
self.method = node["Method"]
|
74
|
+
confirmation_data = node.at_xpath("saml:SubjectConfirmationData", Namespaces::ALL)
|
75
|
+
return unless confirmation_data
|
76
|
+
|
77
|
+
self.not_before = Time.parse(confirmation_data["NotBefore"]) if confirmation_data["NotBefore"]
|
78
|
+
self.not_on_or_after = Time.parse(confirmation_data["NotOnOrAfter"]) if confirmation_data["NotOnOrAfter"]
|
79
|
+
self.recipient = confirmation_data["Recipient"]
|
80
|
+
self.in_response_to = confirmation_data["InResponseTo"]
|
81
81
|
end
|
82
82
|
|
83
83
|
# (see Base#build)
|
84
84
|
def build(builder)
|
85
|
-
builder[
|
85
|
+
builder["saml"].SubjectConfirmation("Method" => method) do |subject_confirmation|
|
86
86
|
if in_response_to ||
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
subject_confirmation[
|
91
|
-
subject_confirmation_data.parent[
|
92
|
-
subject_confirmation_data.parent[
|
93
|
-
subject_confirmation_data.parent[
|
94
|
-
subject_confirmation_data.parent[
|
87
|
+
recipient ||
|
88
|
+
not_before ||
|
89
|
+
not_on_or_after
|
90
|
+
subject_confirmation["saml"].SubjectConfirmationData do |subject_confirmation_data|
|
91
|
+
subject_confirmation_data.parent["NotBefore"] = not_before.iso8601 if not_before
|
92
|
+
subject_confirmation_data.parent["NotOnOrAfter"] = not_on_or_after.iso8601 if not_on_or_after
|
93
|
+
subject_confirmation_data.parent["Recipient"] = recipient if recipient
|
94
|
+
subject_confirmation_data.parent["InResponseTo"] = in_response_to if in_response_to
|
95
95
|
end
|
96
96
|
end
|
97
97
|
end
|
data/lib/saml2/version.rb
CHANGED
data/lib/saml2.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
3
|
+
require "saml2/authn_request"
|
4
|
+
require "saml2/entity"
|
5
|
+
require "saml2/logout_request"
|
6
|
+
require "saml2/logout_response"
|
7
|
+
require "saml2/response"
|
8
|
+
require "saml2/version"
|
9
9
|
|
10
|
-
require
|
10
|
+
require "saml2/engine" if defined?(Rails) && Rails::VERSION::MAJOR > 2
|
11
11
|
|
12
12
|
module SAML2
|
13
13
|
class << self
|