lyrebird 0.0.0 → 1.0.0.alpha1
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/.github/workflows/ci.yml +25 -0
- data/.github/workflows/publish.yml +20 -0
- data/README.md +116 -17
- data/Rakefile +8 -1
- data/lib/lyrebird/assertion.rb +104 -0
- data/lib/lyrebird/certificate.rb +68 -0
- data/lib/lyrebird/defaults.rb +41 -0
- data/lib/lyrebird/id.rb +9 -0
- data/lib/lyrebird/namespaces.rb +14 -0
- data/lib/lyrebird/response.rb +66 -0
- data/lib/lyrebird/signature.rb +75 -0
- data/lib/lyrebird/version.rb +1 -1
- data/lib/lyrebird.rb +13 -1
- data/lyrebird.gemspec +25 -0
- data/test/lyrebird/assertion_test.rb +314 -0
- data/test/lyrebird/certificate_test.rb +87 -0
- data/test/lyrebird/defaults_test.rb +11 -0
- data/test/lyrebird/id_test.rb +11 -0
- data/test/lyrebird/response_test.rb +168 -0
- data/test/lyrebird/signature_test.rb +137 -0
- data/test/test_helper.rb +4 -0
- metadata +87 -8
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
module Lyrebird
|
|
6
|
+
class AssertionTest < Minitest::Test
|
|
7
|
+
def setup
|
|
8
|
+
@assertion = Assertion.new.document
|
|
9
|
+
@root = @assertion.root
|
|
10
|
+
@subject = @root.elements["saml:Subject"]
|
|
11
|
+
@sc = @subject.elements["saml:SubjectConfirmation"]
|
|
12
|
+
@scd = @sc.elements["saml:SubjectConfirmationData"]
|
|
13
|
+
@conditions = @root.elements["saml:Conditions"]
|
|
14
|
+
@audience_restriction = @conditions.elements["saml:AudienceRestriction"]
|
|
15
|
+
@authn_statement = @root.elements["saml:AuthnStatement"]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def test_root_name
|
|
19
|
+
assert_equal "Assertion", @root.name
|
|
20
|
+
assert_equal "saml", @root.prefix
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def test_root_namespace
|
|
24
|
+
assert_equal SAML_ASSERTION_NS, @root.namespace
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def test_root_id
|
|
28
|
+
assert @root.attributes["ID"].start_with?("_")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def test_root_version
|
|
32
|
+
assert_equal "2.0", @root.attributes["Version"]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def test_root_issue_instant
|
|
36
|
+
instant = Time.iso8601(@root.attributes["IssueInstant"])
|
|
37
|
+
assert_in_delta Time.now.to_i, instant.to_i, 1
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def test_issuer
|
|
41
|
+
issuer = @root.elements["saml:Issuer"]
|
|
42
|
+
assert_equal "Issuer", issuer.name
|
|
43
|
+
assert_equal "saml", issuer.prefix
|
|
44
|
+
assert_equal DEFAULTS.issuer, issuer.text
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def test_issuer_override
|
|
48
|
+
assertion = Assertion.new(issuer: "https://test.example.com").document
|
|
49
|
+
issuer = assertion.root.elements["saml:Issuer"]
|
|
50
|
+
assert_equal "https://test.example.com", issuer.text
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def test_subject
|
|
54
|
+
assert_equal "Subject", @subject.name
|
|
55
|
+
assert_equal "saml", @subject.prefix
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def test_name_id
|
|
59
|
+
name_id = @subject.elements["saml:NameID"]
|
|
60
|
+
assert_equal "NameID", name_id.name
|
|
61
|
+
assert_equal "saml", name_id.prefix
|
|
62
|
+
assert_equal DEFAULTS.name_id, name_id.text
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def test_name_id_format_default
|
|
66
|
+
name_id = @subject.elements["saml:NameID"]
|
|
67
|
+
assert_equal NAMEID_EMAIL, name_id.attributes["Format"]
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def test_name_id_override
|
|
71
|
+
email = "user@test.com"
|
|
72
|
+
refute_equal email, DEFAULTS.name_id
|
|
73
|
+
assertion = Assertion.new(name_id: email).document
|
|
74
|
+
name_id = assertion.root.elements["saml:Subject/saml:NameID"]
|
|
75
|
+
assert_equal email, name_id.text
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def test_name_id_format_override
|
|
79
|
+
format = NAMEID_PERSISTENT
|
|
80
|
+
refute_equal format, DEFAULTS.name_id_format
|
|
81
|
+
assertion = Assertion.new(name_id_format: format).document
|
|
82
|
+
name_id = assertion.root.elements["saml:Subject/saml:NameID"]
|
|
83
|
+
assert_equal format, name_id.attributes["Format"]
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def test_subject_confirmation
|
|
87
|
+
assert_equal "SubjectConfirmation", @sc.name
|
|
88
|
+
assert_equal "saml", @sc.prefix
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def test_subject_confirmation_method
|
|
92
|
+
assert_equal CM_BEARER, @sc.attributes["Method"]
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def test_subject_confirmation_data
|
|
96
|
+
assert_equal "SubjectConfirmationData", @scd.name
|
|
97
|
+
assert_equal "saml", @scd.prefix
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def test_valid_for_default
|
|
101
|
+
not_on_or_after = Time.iso8601(@scd.attributes["NotOnOrAfter"])
|
|
102
|
+
expected = Time.now.utc + DEFAULTS.valid_for
|
|
103
|
+
assert_in_delta expected.to_i, not_on_or_after.to_i, 1
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def test_recipient_default
|
|
107
|
+
assert_equal DEFAULTS.recipient, @scd.attributes["Recipient"]
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def test_in_response_to_default
|
|
111
|
+
assert_equal DEFAULTS.in_response_to, @scd.attributes["InResponseTo"]
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def test_recipient_override
|
|
115
|
+
recipient = "https://custom.example.com/acs"
|
|
116
|
+
refute_equal recipient, DEFAULTS.recipient
|
|
117
|
+
assertion = Assertion.new(recipient: recipient).document
|
|
118
|
+
subject = assertion.root.elements["saml:Subject"]
|
|
119
|
+
sc = subject.elements["saml:SubjectConfirmation"]
|
|
120
|
+
scd = sc.elements["saml:SubjectConfirmationData"]
|
|
121
|
+
assert_equal recipient, scd.attributes["Recipient"]
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def test_in_response_to_override
|
|
125
|
+
in_response_to = "_custom_request"
|
|
126
|
+
refute_equal in_response_to, DEFAULTS.in_response_to
|
|
127
|
+
assertion = Assertion.new(in_response_to: in_response_to).document
|
|
128
|
+
subject = assertion.root.elements["saml:Subject"]
|
|
129
|
+
sc = subject.elements["saml:SubjectConfirmation"]
|
|
130
|
+
scd = sc.elements["saml:SubjectConfirmationData"]
|
|
131
|
+
assert_equal in_response_to, scd.attributes["InResponseTo"]
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def test_valid_for_override
|
|
135
|
+
valid_for = 600 # 10 minutes
|
|
136
|
+
refute_equal valid_for, DEFAULTS.valid_for
|
|
137
|
+
assertion = Assertion.new(valid_for: valid_for).document
|
|
138
|
+
subject = assertion.root.elements["saml:Subject"]
|
|
139
|
+
sc = subject.elements["saml:SubjectConfirmation"]
|
|
140
|
+
scd = sc.elements["saml:SubjectConfirmationData"]
|
|
141
|
+
not_on_or_after = Time.iso8601(scd.attributes["NotOnOrAfter"])
|
|
142
|
+
expected = Time.now.utc + valid_for
|
|
143
|
+
assert_in_delta expected.to_i, not_on_or_after.to_i, 1
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def test_conditions
|
|
147
|
+
assert_equal "Conditions", @conditions.name
|
|
148
|
+
assert_equal "saml", @conditions.prefix
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def test_conditions_not_before
|
|
152
|
+
not_before = Time.iso8601(@conditions.attributes["NotBefore"])
|
|
153
|
+
assert_in_delta Time.now.to_i, not_before.to_i, 1
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def test_conditions_not_on_or_after
|
|
157
|
+
not_on_or_after = Time.iso8601(@conditions.attributes["NotOnOrAfter"])
|
|
158
|
+
expected = Time.now.utc + DEFAULTS.valid_for
|
|
159
|
+
assert_in_delta expected.to_i, not_on_or_after.to_i, 1
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def test_conditions_valid_for_override
|
|
163
|
+
valid_for = 600 # 10 minutes
|
|
164
|
+
refute_equal valid_for, DEFAULTS.valid_for
|
|
165
|
+
assertion = Assertion.new(valid_for: valid_for).document
|
|
166
|
+
conditions = assertion.root.elements["saml:Conditions"]
|
|
167
|
+
not_on_or_after = Time.iso8601(conditions.attributes["NotOnOrAfter"])
|
|
168
|
+
expected = Time.now.utc + valid_for
|
|
169
|
+
assert_in_delta expected.to_i, not_on_or_after.to_i, 1
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def test_audience_restriction
|
|
173
|
+
assert_equal "AudienceRestriction", @audience_restriction.name
|
|
174
|
+
assert_equal "saml", @audience_restriction.prefix
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def test_audience
|
|
178
|
+
audience = @audience_restriction.elements["saml:Audience"]
|
|
179
|
+
assert_equal "Audience", audience.name
|
|
180
|
+
assert_equal "saml", audience.prefix
|
|
181
|
+
assert_equal DEFAULTS.audience, audience.text
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def test_audience_override
|
|
185
|
+
audience = "https://custom.sp.example.com"
|
|
186
|
+
refute_equal audience, DEFAULTS.audience
|
|
187
|
+
assertion = Assertion.new(audience: audience).document
|
|
188
|
+
conditions = assertion.root.elements["saml:Conditions"]
|
|
189
|
+
ar = conditions.elements["saml:AudienceRestriction"]
|
|
190
|
+
assert_equal audience, ar.elements["saml:Audience"].text
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def test_authn_statement
|
|
194
|
+
assert_equal "AuthnStatement", @authn_statement.name
|
|
195
|
+
assert_equal "saml", @authn_statement.prefix
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def test_authn_statement_authn_instant
|
|
199
|
+
authn_instant = Time.iso8601(@authn_statement.attributes["AuthnInstant"])
|
|
200
|
+
assert_in_delta Time.now.to_i, authn_instant.to_i, 1
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def test_authn_statement_session_index
|
|
204
|
+
assert @authn_statement.attributes["SessionIndex"].start_with?("_")
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def test_authn_context
|
|
208
|
+
authn_context = @authn_statement.elements["saml:AuthnContext"]
|
|
209
|
+
assert_equal "AuthnContext", authn_context.name
|
|
210
|
+
assert_equal "saml", authn_context.prefix
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def test_authn_context_class_ref
|
|
214
|
+
ac = @authn_statement.elements["saml:AuthnContext"]
|
|
215
|
+
class_ref = ac.elements["saml:AuthnContextClassRef"]
|
|
216
|
+
assert_equal "AuthnContextClassRef", class_ref.name
|
|
217
|
+
assert_equal "saml", class_ref.prefix
|
|
218
|
+
assert_equal DEFAULTS.authn_context, class_ref.text
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def test_authn_context_override
|
|
222
|
+
custom_ref = "urn:oasis:names:tc:SAML:2.0:ac:classes:Password"
|
|
223
|
+
refute_equal custom_ref, DEFAULTS.authn_context
|
|
224
|
+
assertion = Assertion.new(authn_context: custom_ref).document
|
|
225
|
+
as = assertion.root.elements["saml:AuthnStatement"]
|
|
226
|
+
ac = as.elements["saml:AuthnContext"]
|
|
227
|
+
class_ref = ac.elements["saml:AuthnContextClassRef"]
|
|
228
|
+
assert_equal custom_ref, class_ref.text
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def test_default_attributes
|
|
232
|
+
as = @root.elements["saml:AttributeStatement"]
|
|
233
|
+
attrs = as.elements.to_a("saml:Attribute")
|
|
234
|
+
assert_equal 2, attrs.size
|
|
235
|
+
|
|
236
|
+
first = attrs.find { |a| a.attributes["Name"] == "first_name" }
|
|
237
|
+
assert_equal "Test", first.elements["saml:AttributeValue"].text
|
|
238
|
+
|
|
239
|
+
last = attrs.find { |a| a.attributes["Name"] == "last_name" }
|
|
240
|
+
assert_equal "User", last.elements["saml:AttributeValue"].text
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def test_no_attribute_statement_when_empty
|
|
244
|
+
assertion = Assertion.new(attributes: {}).document
|
|
245
|
+
assert_nil assertion.root.elements["saml:AttributeStatement"]
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def test_attribute_statement_with_single_value
|
|
249
|
+
attributes = { "email" => "user@example.com" }
|
|
250
|
+
assertion = Assertion.new(attributes: attributes).document
|
|
251
|
+
as = assertion.root.elements["saml:AttributeStatement"]
|
|
252
|
+
assert_equal "AttributeStatement", as.name
|
|
253
|
+
assert_equal "saml", as.prefix
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def test_attribute_name_and_format
|
|
257
|
+
attributes = { "email" => "user@example.com" }
|
|
258
|
+
assertion = Assertion.new(attributes: attributes).document
|
|
259
|
+
attr = assertion.root.elements["saml:AttributeStatement/saml:Attribute"]
|
|
260
|
+
assert_equal "Attribute", attr.name
|
|
261
|
+
assert_equal "saml", attr.prefix
|
|
262
|
+
assert_equal "email", attr.attributes["Name"]
|
|
263
|
+
assert_equal ATTR_NAME_FORMAT, attr.attributes["NameFormat"]
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def test_attribute_single_value
|
|
267
|
+
attributes = { "email" => "user@example.com" }
|
|
268
|
+
assertion = Assertion.new(attributes: attributes).document
|
|
269
|
+
attr = assertion.root.elements["saml:AttributeStatement/saml:Attribute"]
|
|
270
|
+
value = attr.elements["saml:AttributeValue"]
|
|
271
|
+
assert_equal "AttributeValue", value.name
|
|
272
|
+
assert_equal "saml", value.prefix
|
|
273
|
+
assert_equal "user@example.com", value.text
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def test_attribute_multi_value
|
|
277
|
+
attributes = { "groups" => ["admin", "users", "developers"] }
|
|
278
|
+
assertion = Assertion.new(attributes: attributes).document
|
|
279
|
+
attr = assertion.root.elements["saml:AttributeStatement/saml:Attribute"]
|
|
280
|
+
values = attr.elements.to_a("saml:AttributeValue")
|
|
281
|
+
assert_equal 3, values.size
|
|
282
|
+
assert_equal "admin", values[0].text
|
|
283
|
+
assert_equal "users", values[1].text
|
|
284
|
+
assert_equal "developers", values[2].text
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def test_multiple_attributes
|
|
288
|
+
attributes = {
|
|
289
|
+
email: "user@example.com",
|
|
290
|
+
name: "Test User",
|
|
291
|
+
groups: ["admin", "users"]
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
assertion = Assertion.new(attributes: attributes).document
|
|
295
|
+
as = assertion.root.elements["saml:AttributeStatement"]
|
|
296
|
+
attrs = as.elements.to_a("saml:Attribute")
|
|
297
|
+
assert_equal 3, attrs.size
|
|
298
|
+
|
|
299
|
+
email_attr = attrs.find { |a| a.attributes["Name"] == "email" }
|
|
300
|
+
email_value = email_attr.elements["saml:AttributeValue"].text
|
|
301
|
+
assert_equal "user@example.com", email_value
|
|
302
|
+
|
|
303
|
+
name_attr = attrs.find { |a| a.attributes["Name"] == "name" }
|
|
304
|
+
name_value = name_attr.elements["saml:AttributeValue"].text
|
|
305
|
+
assert_equal "Test User", name_value
|
|
306
|
+
|
|
307
|
+
groups_attr = attrs.find { |a| a.attributes["Name"] == "groups" }
|
|
308
|
+
group_values = groups_attr.elements.to_a("saml:AttributeValue")
|
|
309
|
+
assert_equal 2, group_values.size
|
|
310
|
+
assert_equal "admin", group_values[0].text
|
|
311
|
+
assert_equal "users", group_values[1].text
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
module Lyrebird
|
|
6
|
+
class CertificateTest < Minitest::Test
|
|
7
|
+
def setup
|
|
8
|
+
@certificate = Certificate.generate
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def test_private_key_is_rsa
|
|
12
|
+
assert_instance_of OpenSSL::PKey::RSA, @certificate.private_key
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def test_private_key_defaults_to_2048_bits
|
|
16
|
+
assert_equal 2048, @certificate.private_key.n.num_bits
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def test_private_key_with_custom_bits
|
|
20
|
+
certificate = Certificate.generate(bits: 4096)
|
|
21
|
+
assert_equal 4096, certificate.private_key.n.num_bits
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def test_certificate_is_x509
|
|
25
|
+
assert_instance_of OpenSSL::X509::Certificate, @certificate.certificate
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def test_certificate_not_before_is_now
|
|
29
|
+
assert_in_delta Time.now, @certificate.certificate.not_before, 1
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def test_certificate_not_after_is_one_year_from_now
|
|
33
|
+
one_year = 365 * 24 * 60 * 60
|
|
34
|
+
assert_in_delta Time.now + one_year, @certificate.certificate.not_after, 1
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def test_certificate_is_signed
|
|
38
|
+
assert @certificate.certificate.verify(@certificate.private_key)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def test_certificate_with_custom_subject
|
|
42
|
+
certificate = Certificate.generate(cn: "Test", o: "Acme")
|
|
43
|
+
assert_equal "/CN=Test/O=Acme", certificate.certificate.subject.to_s
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def test_certificate_with_custom_valid_for
|
|
47
|
+
certificate = Certificate.generate(valid_for: 30).certificate
|
|
48
|
+
thirty_days = 30 * 24 * 60 * 60
|
|
49
|
+
assert_in_delta Time.now + thirty_days, certificate.not_after, 1
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def test_certificate_with_valid_until
|
|
53
|
+
valid_until = Time.new(2030, 1, 1)
|
|
54
|
+
certificate = Certificate.generate(valid_until: valid_until)
|
|
55
|
+
assert_equal valid_until, certificate.certificate.not_after
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def test_private_key_pem
|
|
59
|
+
assert @certificate.private_key_pem.start_with?("-----BEGIN")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def test_certificate_pem
|
|
63
|
+
assert @certificate.certificate_pem.start_with?("-----BEGIN")
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def test_fingerprint
|
|
67
|
+
der = @certificate.certificate.to_der
|
|
68
|
+
expected = OpenSSL::Digest::SHA256.hexdigest(der)
|
|
69
|
+
assert_equal expected, @certificate.fingerprint
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def test_base64
|
|
73
|
+
der = @certificate.certificate.to_der
|
|
74
|
+
expected = Base64.strict_encode64(der)
|
|
75
|
+
assert_equal expected, @certificate.base64
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def test_load
|
|
79
|
+
certificate = Certificate.load(
|
|
80
|
+
private_key_pem: @certificate.private_key_pem,
|
|
81
|
+
certificate_pem: @certificate.certificate_pem,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
assert_equal @certificate.fingerprint, certificate.fingerprint
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
module Lyrebird
|
|
6
|
+
class ResponseTest < Minitest::Test
|
|
7
|
+
def setup
|
|
8
|
+
@response = Response.new
|
|
9
|
+
@root = @response.document.root
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def test_mimic_returns_base64
|
|
13
|
+
encoded = @response.mimic
|
|
14
|
+
decoded = Base64.strict_decode64(encoded)
|
|
15
|
+
assert decoded.include?("<samlp:Response")
|
|
16
|
+
assert decoded.include?("<saml:Assertion")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def test_root_name
|
|
20
|
+
assert_equal "Response", @root.name
|
|
21
|
+
assert_equal "samlp", @root.prefix
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def test_root_namespace
|
|
25
|
+
assert_equal SAML_PROTOCOL_NS, @root.namespace
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def test_saml_namespace_declared
|
|
29
|
+
assert_equal SAML_ASSERTION_NS, @root.namespace("saml")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def test_root_id
|
|
33
|
+
assert @root.attributes["ID"].start_with?("_")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def test_root_version
|
|
37
|
+
assert_equal "2.0", @root.attributes["Version"]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def test_root_issue_instant
|
|
41
|
+
instant = Time.iso8601(@root.attributes["IssueInstant"])
|
|
42
|
+
assert_in_delta Time.now.to_i, instant.to_i, 1
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def test_destination_default
|
|
46
|
+
assert_equal DEFAULTS.recipient, @root.attributes["Destination"]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def test_destination_override
|
|
50
|
+
destination = "https://test.example.com/acs"
|
|
51
|
+
refute_equal destination, DEFAULTS.recipient
|
|
52
|
+
root = Response.new(destination: destination).document.root
|
|
53
|
+
assert_equal destination, root.attributes["Destination"]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def test_in_response_to_default
|
|
57
|
+
assert_equal DEFAULTS.in_response_to, @root.attributes["InResponseTo"]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def test_in_response_to_override
|
|
61
|
+
in_response_to = "_test_request"
|
|
62
|
+
refute_equal in_response_to, DEFAULTS.in_response_to
|
|
63
|
+
root = Response.new(in_response_to: in_response_to).document.root
|
|
64
|
+
assert_equal in_response_to, root.attributes["InResponseTo"]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def test_issuer
|
|
68
|
+
issuer = @root.elements["saml:Issuer"]
|
|
69
|
+
assert_equal "Issuer", issuer.name
|
|
70
|
+
assert_equal "saml", issuer.prefix
|
|
71
|
+
assert_equal DEFAULTS.issuer, issuer.text
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def test_issuer_override
|
|
75
|
+
url = "https://test.idp.example.com"
|
|
76
|
+
refute_equal url, DEFAULTS.issuer
|
|
77
|
+
root = Response.new(issuer: url).document.root
|
|
78
|
+
issuer = root.elements["saml:Issuer"]
|
|
79
|
+
assert_equal url, issuer.text
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def test_status
|
|
83
|
+
status = @root.elements["samlp:Status"]
|
|
84
|
+
assert_equal "Status", status.name
|
|
85
|
+
assert_equal "samlp", status.prefix
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def test_status_code
|
|
89
|
+
status_code = @root.elements["samlp:Status/samlp:StatusCode"]
|
|
90
|
+
assert_equal "StatusCode", status_code.name
|
|
91
|
+
assert_equal "samlp", status_code.prefix
|
|
92
|
+
assert_equal STATUS_SUCCESS, status_code.attributes["Value"]
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def test_assertion_embedded
|
|
96
|
+
assertion = @root.elements["saml:Assertion"]
|
|
97
|
+
assert_equal "Assertion", assertion.name
|
|
98
|
+
assert_equal "saml", assertion.prefix
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def test_assertion_has_id
|
|
102
|
+
assertion = @root.elements["saml:Assertion"]
|
|
103
|
+
assert assertion.attributes["ID"].start_with?("_")
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def test_assertion_inherits_issuer
|
|
107
|
+
url = "https://test.idp.example.com"
|
|
108
|
+
refute_equal url, DEFAULTS.issuer
|
|
109
|
+
root = Response.new(issuer: url).document.root
|
|
110
|
+
assertion = root.elements["saml:Assertion"]
|
|
111
|
+
issuer = assertion.elements["saml:Issuer"]
|
|
112
|
+
assert_equal url, issuer.text
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def test_assertion_options_flow_through
|
|
116
|
+
email = "test@example.com"
|
|
117
|
+
refute_equal email, DEFAULTS.name_id
|
|
118
|
+
root = Response.new(name_id: email).document.root
|
|
119
|
+
assertion = root.elements["saml:Assertion"]
|
|
120
|
+
name_id = assertion.elements["saml:Subject/saml:NameID"]
|
|
121
|
+
assert_equal email, name_id.text
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def test_assertion_unsigned_by_default
|
|
125
|
+
assertion = @root.elements["saml:Assertion"]
|
|
126
|
+
assert_nil assertion.elements["ds:Signature"]
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def test_sign_assertion_adds_signature
|
|
130
|
+
cert = Certificate.generate
|
|
131
|
+
root = Response.new(certificate: cert, sign_assertion: true).document.root
|
|
132
|
+
assertion = root.elements["saml:Assertion"]
|
|
133
|
+
signature = assertion.elements["ds:Signature"]
|
|
134
|
+
assert_equal "Signature", signature.name
|
|
135
|
+
assert_equal "ds", signature.prefix
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def test_response_unsigned_by_default
|
|
139
|
+
assert_nil @root.elements["ds:Signature"]
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def test_sign_response_adds_signature
|
|
143
|
+
args = { certificate: Certificate.generate, sign_response: true }
|
|
144
|
+
root = Response.new(**args).document.root
|
|
145
|
+
signature = root.elements["ds:Signature"]
|
|
146
|
+
assert_equal "Signature", signature.name
|
|
147
|
+
assert_equal "ds", signature.prefix
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def test_sign_both_response_and_assertion
|
|
151
|
+
args = {
|
|
152
|
+
certificate: Certificate.generate,
|
|
153
|
+
sign_assertion: true,
|
|
154
|
+
sign_response: true
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
root = Response.new(**args).document.root
|
|
158
|
+
refute_nil root.elements["ds:Signature"]
|
|
159
|
+
refute_nil root.elements["saml:Assertion/ds:Signature"]
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def test_sign_neither_response_nor_assertion
|
|
163
|
+
root = Response.new(certificate: Certificate.generate).document.root
|
|
164
|
+
assert_nil root.elements["ds:Signature"]
|
|
165
|
+
assert_nil root.elements["saml:Assertion/ds:Signature"]
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|