saml2 3.1.2 → 3.1.3
Sign up to get free protection for your applications and to get access to all the features.
- 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 -137
- data/schemas/MetadataExchange.xsd +0 -112
- data/schemas/metadata_combined.xsd +0 -13
- data/schemas/oasis-200401-wss-wssecurity-secext-1.0.xsd +0 -195
- data/schemas/oasis-200401-wss-wssecurity-utility-1.0.xsd +0 -108
- data/schemas/saml-schema-assertion-2.0.xsd +0 -283
- data/schemas/saml-schema-metadata-2.0.xsd +0 -339
- data/schemas/saml-schema-protocol-2.0.xsd +0 -302
- data/schemas/sstc-saml-metadata-ext-query.xsd +0 -66
- data/schemas/ws-addr.xsd +0 -137
- data/schemas/ws-authorization.xsd +0 -145
- data/schemas/ws-federation.xsd +0 -471
- data/schemas/ws-securitypolicy-1.2.xsd +0 -1205
- data/schemas/xenc-schema.xsd +0 -136
- data/schemas/xml.xsd +0 -287
- data/schemas/xmldsig-core-schema.xsd +0 -309
- 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
|