ruby-saml 0.8.10 → 0.8.15

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.

Potentially problematic release.


This version of ruby-saml might be problematic. Click here for more details.

Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +3 -1
  3. data/Rakefile +0 -14
  4. data/lib/onelogin/ruby-saml/authrequest.rb +3 -2
  5. data/lib/onelogin/ruby-saml/logoutrequest.rb +3 -0
  6. data/lib/onelogin/ruby-saml/logoutresponse.rb +1 -24
  7. data/lib/onelogin/ruby-saml/response.rb +206 -20
  8. data/lib/onelogin/ruby-saml/setting_error.rb +6 -0
  9. data/lib/onelogin/ruby-saml/settings.rb +26 -0
  10. data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +3 -2
  11. data/lib/onelogin/ruby-saml/utils.rb +90 -0
  12. data/lib/onelogin/ruby-saml/version.rb +1 -1
  13. data/lib/xml_security.rb +222 -87
  14. data/test/certificates/ruby-saml-2.crt +15 -0
  15. data/test/logoutrequest_test.rb +124 -126
  16. data/test/logoutresponse_test.rb +22 -28
  17. data/test/response_test.rb +348 -129
  18. data/test/responses/adfs_response_xmlns.xml +45 -0
  19. data/test/responses/encrypted_new_attack.xml.base64 +1 -0
  20. data/test/responses/invalids/multiple_signed.xml.base64 +1 -0
  21. data/test/responses/invalids/no_signature.xml.base64 +1 -0
  22. data/test/responses/invalids/response_with_concealed_signed_assertion.xml +51 -0
  23. data/test/responses/invalids/response_with_doubled_signed_assertion.xml +49 -0
  24. data/test/responses/invalids/signature_wrapping_attack.xml.base64 +1 -0
  25. data/test/responses/response_with_concealed_signed_assertion.xml +51 -0
  26. data/test/responses/response_with_doubled_signed_assertion.xml +49 -0
  27. data/test/responses/response_with_signed_assertion_3.xml +30 -0
  28. data/test/responses/response_with_signed_message_and_assertion.xml +34 -0
  29. data/test/responses/response_with_undefined_recipient.xml.base64 +1 -0
  30. data/test/responses/response_wrapped.xml.base64 +150 -0
  31. data/test/responses/valid_response.xml.base64 +1 -0
  32. data/test/responses/valid_response_without_x509certificate.xml.base64 +1 -0
  33. data/test/settings_test.rb +5 -5
  34. data/test/test_helper.rb +110 -41
  35. data/test/utils_test.rb +10 -10
  36. data/test/xml_security_test.rb +359 -68
  37. metadata +38 -5
@@ -1,13 +1,16 @@
1
1
  require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
2
2
 
3
- class ResponseTest < Test::Unit::TestCase
3
+ class ResponseTest < Minitest::Test
4
4
 
5
- context "Response" do
6
- should "raise an exception when response is initialized with nil" do
7
- assert_raises(ArgumentError) { OneLogin::RubySaml::Response.new(nil) }
5
+ describe "Response" do
6
+ it "raise an exception when response is initialized with nil" do
7
+ err = assert_raises(ArgumentError) do
8
+ OneLogin::RubySaml::Response.new(nil)
9
+ end
10
+ assert_equal "Response cannot be nil", err.message
8
11
  end
9
12
 
10
- should "be able to parse a document which contains ampersands" do
13
+ it "be able to parse a document which contains ampersands" do
11
14
  XMLSecurity::SignedDocument.any_instance.stubs(:digests_match?).returns(true)
12
15
  OneLogin::RubySaml::Response.any_instance.stubs(:validate_conditions).returns(true)
13
16
 
@@ -18,7 +21,7 @@ class ResponseTest < Test::Unit::TestCase
18
21
  response.validate!
19
22
  end
20
23
 
21
- should "adapt namespace" do
24
+ it "adapt namespace" do
22
25
  response = OneLogin::RubySaml::Response.new(response_document)
23
26
  assert !response.name_id.nil?
24
27
  response = OneLogin::RubySaml::Response.new(response_document_2)
@@ -27,14 +30,14 @@ class ResponseTest < Test::Unit::TestCase
27
30
  assert !response.name_id.nil?
28
31
  end
29
32
 
30
- should "default to raw input when a response is not Base64 encoded" do
33
+ it "default to raw input when a response is not Base64 encoded" do
31
34
  decoded = Base64.decode64(response_document_2)
32
35
  response = OneLogin::RubySaml::Response.new(decoded)
33
36
  assert response.document
34
37
  end
35
38
 
36
- context "Assertion" do
37
- should "only retreive an assertion with an ID that matches the signature's reference URI" do
39
+ describe "Assertion" do
40
+ it "only retreive an assertion with an ID that matches the signature's reference URI" do
38
41
  response = OneLogin::RubySaml::Response.new(wrapped_response_2)
39
42
  response.stubs(:conditions).returns(nil)
40
43
  settings = OneLogin::RubySaml::Settings.new
@@ -44,40 +47,109 @@ class ResponseTest < Test::Unit::TestCase
44
47
  end
45
48
  end
46
49
 
47
- context "#validate!" do
48
- should "raise when encountering a condition that prevents the document from being valid" do
50
+ describe "#validate!" do
51
+ it "raise when settings not initialized" do
49
52
  response = OneLogin::RubySaml::Response.new(response_document)
50
- assert_raise(OneLogin::RubySaml::ValidationError) do
53
+ err = assert_raises(OneLogin::RubySaml::ValidationError) do
54
+ response.validate!
55
+ end
56
+ assert_equal "No settings on response", err.message
57
+ end
58
+
59
+ it "raise when encountering a condition that prevents the document from being valid" do
60
+ response = OneLogin::RubySaml::Response.new(response_document)
61
+ response.settings = settings
62
+ err = assert_raises(OneLogin::RubySaml::ValidationError) do
63
+ response.validate!
64
+ end
65
+ assert_equal "Current time is on or after NotOnOrAfter condition", err.message
66
+ end
67
+
68
+ it "raises an exception when no cert or fingerprint provided" do
69
+ response = OneLogin::RubySaml::Response.new(response_document_valid_signed)
70
+ response.stubs(:conditions).returns(nil)
71
+ settings = OneLogin::RubySaml::Settings.new
72
+ response.settings = settings
73
+ settings.idp_cert = nil
74
+ settings.idp_cert_fingerprint = nil
75
+ err = assert_raises(OneLogin::RubySaml::ValidationError) do
51
76
  response.validate!
52
77
  end
78
+ assert_equal "No fingerprint or certificate on settings", err.message
79
+ end
80
+
81
+ it "raise when no signature" do
82
+ response_no_signed_elements = OneLogin::RubySaml::Response.new(read_invalid_response("no_signature.xml.base64"))
83
+ settings.idp_cert_fingerprint = signature_fingerprint_1
84
+ response_no_signed_elements.settings = settings
85
+ err = assert_raises(OneLogin::RubySaml::ValidationError) do
86
+ response_no_signed_elements.validate!
87
+ end
88
+ assert_equal "Found an unexpected number of Signature Element. SAML Response rejected", err.message
53
89
  end
90
+
91
+ it "raise when multiple signatures" do
92
+ response_multiple_signed = OneLogin::RubySaml::Response.new(read_invalid_response("multiple_signed.xml.base64"))
93
+ settings.idp_cert_fingerprint = signature_fingerprint_1
94
+ response_multiple_signed.settings = settings
95
+ response_multiple_signed.stubs(:validate_structure).returns(true)
96
+ err = assert_raises(OneLogin::RubySaml::ValidationError) do
97
+ response_multiple_signed.validate!
98
+ end
99
+ assert_equal "Duplicated ID. SAML Response rejected", err.message
100
+ end
101
+
102
+ it "raise when fingerprint missmatch" do
103
+ resp_xml = Base64.decode64(response_document_valid_signed)
104
+ response = OneLogin::RubySaml::Response.new(Base64.encode64(resp_xml))
105
+ response.stubs(:conditions).returns(nil)
106
+ settings = OneLogin::RubySaml::Settings.new
107
+ settings.idp_cert_fingerprint = signature_fingerprint_1
108
+ response.settings = settings
109
+
110
+ err = assert_raises(OneLogin::RubySaml::ValidationError) do
111
+ response.validate!
112
+ end
113
+ assert_equal 'Fingerprint mismatch', err.message
114
+ end
115
+
54
116
  end
55
117
 
56
- context "#is_valid?" do
57
- should "return false when response is initialized with blank data" do
118
+ describe "#is_valid?" do
119
+ it "return false when response is initialized with blank data" do
58
120
  response = OneLogin::RubySaml::Response.new('')
59
121
  assert !response.is_valid?
60
122
  end
61
123
 
62
- should "return false if settings have not been set" do
124
+ it "return false if settings have not been set" do
63
125
  response = OneLogin::RubySaml::Response.new(response_document)
64
126
  assert !response.is_valid?
65
127
  end
66
128
 
67
- should "return true when the response is initialized with valid data" do
68
- response = OneLogin::RubySaml::Response.new(response_document_4)
129
+ it "return false when no cert or fingerprint provided" do
130
+ response = OneLogin::RubySaml::Response.new(response_document_valid_signed)
131
+ response.stubs(:conditions).returns(nil)
132
+ settings = OneLogin::RubySaml::Settings.new
133
+ response.settings = settings
134
+ settings.idp_cert = nil
135
+ settings.idp_cert_fingerprint = nil
136
+ assert !response.is_valid?
137
+ end
138
+
139
+ it "return true when the response is initialized with valid data" do
140
+ response = OneLogin::RubySaml::Response.new(response_document_valid_signed)
69
141
  response.stubs(:conditions).returns(nil)
70
142
  assert !response.is_valid?
71
143
  settings = OneLogin::RubySaml::Settings.new
72
144
  assert !response.is_valid?
73
145
  response.settings = settings
74
146
  assert !response.is_valid?
75
- settings.idp_cert_fingerprint = signature_fingerprint_1
76
- assert response.is_valid?
147
+ response.settings.idp_cert_fingerprint = signature_fingerprint_valid_res
148
+ response.validate!
77
149
  end
78
150
 
79
- should "should be idempotent when the response is initialized with invalid data" do
80
- response = OneLogin::RubySaml::Response.new(response_document_4)
151
+ it "should be idempotent when the response is initialized with invalid data" do
152
+ response = OneLogin::RubySaml::Response.new(response_document_valid_signed)
81
153
  response.stubs(:conditions).returns(nil)
82
154
  settings = OneLogin::RubySaml::Settings.new
83
155
  response.settings = settings
@@ -85,36 +157,58 @@ class ResponseTest < Test::Unit::TestCase
85
157
  assert !response.is_valid?
86
158
  end
87
159
 
88
- should "should be idempotent when the response is initialized with valid data" do
89
- response = OneLogin::RubySaml::Response.new(response_document_4)
160
+ it "should be idempotent when the response is initialized with valid data" do
161
+ response = OneLogin::RubySaml::Response.new(response_document_valid_signed)
90
162
  response.stubs(:conditions).returns(nil)
91
163
  settings = OneLogin::RubySaml::Settings.new
92
164
  response.settings = settings
93
- settings.idp_cert_fingerprint = signature_fingerprint_1
165
+ response.settings.idp_cert_fingerprint = signature_fingerprint_valid_res
94
166
  assert response.is_valid?
95
167
  assert response.is_valid?
96
168
  end
97
169
 
98
- should "return true when using certificate instead of fingerprint" do
99
- response = OneLogin::RubySaml::Response.new(response_document_4)
170
+ it "return true when valid response and using fingerprint" do
171
+ response = OneLogin::RubySaml::Response.new(response_document_valid_signed)
172
+ response.stubs(:conditions).returns(nil)
173
+ settings = OneLogin::RubySaml::Settings.new
174
+ response.settings = settings
175
+ settings.idp_cert = nil
176
+ settings.idp_cert_fingerprint = "4B:68:C4:53:C7:D9:94:AA:D9:02:5C:99:D5:EF:CF:56:62:87:FE:8D"
177
+ assert response.is_valid?
178
+ end
179
+
180
+ it "return true when valid response using certificate" do
181
+ response = OneLogin::RubySaml::Response.new(response_document_valid_signed)
100
182
  response.stubs(:conditions).returns(nil)
101
183
  settings = OneLogin::RubySaml::Settings.new
102
184
  response.settings = settings
103
- settings.idp_cert = signature_1
185
+ settings.idp_cert = valid_cert
104
186
  assert response.is_valid?
105
187
  end
106
188
 
107
- should "not allow signature wrapping attack" do
189
+ it "not allow signature wrapping attack" do
108
190
  response = OneLogin::RubySaml::Response.new(response_document_4)
109
191
  response.stubs(:conditions).returns(nil)
110
192
  settings = OneLogin::RubySaml::Settings.new
111
193
  settings.idp_cert_fingerprint = signature_fingerprint_1
112
194
  response.settings = settings
113
- assert response.is_valid?
195
+ assert !response.is_valid?
114
196
  assert response.name_id == "test@onelogin.com"
115
197
  end
116
198
 
117
- should "support dynamic namespace resolution on signature elements" do
199
+ it "not allow element wrapping attack" do
200
+ response_wrapped = OneLogin::RubySaml::Response.new(response_document_wrapped)
201
+ response_wrapped.stubs(:conditions).returns(nil)
202
+ response_wrapped.stubs(:validate_subject_confirmation).returns(true)
203
+ settings = OneLogin::RubySaml::Settings.new
204
+ response_wrapped.settings = settings
205
+ response_wrapped.settings.idp_cert_fingerprint = signature_fingerprint_1
206
+
207
+ assert !response_wrapped.is_valid?
208
+ assert_nil response_wrapped.name_id
209
+ end
210
+
211
+ it "support dynamic namespace resolution on signature elements" do
118
212
  response = OneLogin::RubySaml::Response.new(fixture("no_signature_ns.xml"))
119
213
  response.stubs(:conditions).returns(nil)
120
214
  settings = OneLogin::RubySaml::Settings.new
@@ -124,35 +218,70 @@ class ResponseTest < Test::Unit::TestCase
124
218
  assert response.validate!
125
219
  end
126
220
 
127
- should "validate ADFS assertions" do
128
- response = OneLogin::RubySaml::Response.new(fixture(:adfs_response_sha256))
221
+ it "support signature elements with no KeyInfo if cert provided" do
222
+ response = OneLogin::RubySaml::Response.new(response_document_valid_signed_without_x509certificate)
129
223
  response.stubs(:conditions).returns(nil)
130
224
  settings = OneLogin::RubySaml::Settings.new
131
- settings.idp_cert_fingerprint = "28:74:9B:E8:1F:E8:10:9C:A8:7C:A9:C3:E3:C5:01:6C:92:1C:B4:BA"
132
225
  response.settings = settings
226
+ settings.idp_cert = ruby_saml_cert
227
+ settings.idp_cert_fingerprint = nil
228
+ XMLSecurity::SignedDocument.any_instance.expects(:validate_signature).returns(true)
133
229
  assert response.validate!
134
230
  end
135
231
 
136
- should "validate the digest" do
137
- response = OneLogin::RubySaml::Response.new(r1_response_document_6)
232
+ it "support signature elements with no KeyInfo if cert provided as text" do
233
+ response = OneLogin::RubySaml::Response.new(response_document_valid_signed_without_x509certificate)
138
234
  response.stubs(:conditions).returns(nil)
139
235
  settings = OneLogin::RubySaml::Settings.new
140
- settings.idp_cert = Base64.decode64(r1_signature_2)
141
236
  response.settings = settings
237
+ settings.idp_cert = ruby_saml_cert_text
238
+ settings.idp_cert_fingerprint = nil
239
+ XMLSecurity::SignedDocument.any_instance.expects(:validate_signature).returns(true)
142
240
  assert response.validate!
143
241
  end
144
242
 
145
- should "validate SAML 2.0 XML structure" do
146
- resp_xml = Base64.decode64(response_document_4).gsub(/emailAddress/,'test')
147
- response = OneLogin::RubySaml::Response.new(Base64.encode64(resp_xml))
243
+ it "returns an error if the signature contains no KeyInfo, cert is not provided and soft" do
244
+ response = OneLogin::RubySaml::Response.new(response_document_valid_signed_without_x509certificate)
148
245
  response.stubs(:conditions).returns(nil)
149
246
  settings = OneLogin::RubySaml::Settings.new
150
- settings.idp_cert_fingerprint = signature_fingerprint_1
151
247
  response.settings = settings
152
- assert_raises(OneLogin::RubySaml::ValidationError, 'Digest mismatch'){ response.validate! }
248
+ settings.idp_cert = nil
249
+ settings.idp_cert_fingerprint = "28:74:9B:E8:1F:E8:10:9C:A8:7C:A9:C3:E3:C5:01:6C:92:1C:B4:BA"
250
+ assert !response.is_valid?
153
251
  end
154
252
 
155
- should "Prevent node text with comment (VU#475445) attack" do
253
+ it "raises an exception if the signature contains no KeyInfo, cert is not provided and no soft" do
254
+ response = OneLogin::RubySaml::Response.new(response_document_valid_signed_without_x509certificate)
255
+ response.stubs(:conditions).returns(nil)
256
+ settings = OneLogin::RubySaml::Settings.new
257
+ response.settings = settings
258
+ settings.idp_cert = nil
259
+ settings.idp_cert_fingerprint = "28:74:9B:E8:1F:E8:10:9C:A8:7C:A9:C3:E3:C5:01:6C:92:1C:B4:BA"
260
+ err = assert_raises(OneLogin::RubySaml::ValidationError) do
261
+ response.validate!
262
+ end
263
+ assert_equal "Certificate element missing in response (ds:X509Certificate) and not cert provided at settings", err.message
264
+ end
265
+
266
+ it "validate ADFS assertions" do
267
+ response = OneLogin::RubySaml::Response.new(fixture(:adfs_response_sha256))
268
+ response.stubs(:conditions).returns(nil)
269
+ settings = OneLogin::RubySaml::Settings.new
270
+ settings.idp_cert_fingerprint = "28:74:9B:E8:1F:E8:10:9C:A8:7C:A9:C3:E3:C5:01:6C:92:1C:B4:BA"
271
+ response.settings = settings
272
+ assert response.validate!
273
+ end
274
+
275
+ it "validate the digest" do
276
+ response = OneLogin::RubySaml::Response.new(r1_response_document_6)
277
+ response.stubs(:conditions).returns(nil)
278
+ settings = OneLogin::RubySaml::Settings.new
279
+ settings.idp_cert = Base64.decode64(r1_signature_2)
280
+ response.settings = settings
281
+ assert response.validate!
282
+ end
283
+
284
+ it "Prevent node text with comment (VU#475445) attack" do
156
285
  response_doc = File.read(File.join(File.dirname(__FILE__), "responses", 'response_node_text_attack.xml.base64'))
157
286
  response = OneLogin::RubySaml::Response.new(response_doc)
158
287
 
@@ -160,42 +289,42 @@ class ResponseTest < Test::Unit::TestCase
160
289
  assert_equal "smith", response.attributes["surname"]
161
290
  end
162
291
 
163
- context '#validate_audience' do
164
- should "return true when sp_entity_id not set or empty" do
165
- response = OneLogin::RubySaml::Response.new(response_document_4)
292
+ describe '#validate_audience' do
293
+ it "return true when sp_entity_id not set or empty" do
294
+ response = OneLogin::RubySaml::Response.new(response_document_valid_signed)
166
295
  response.stubs(:conditions).returns(nil)
167
296
  settings = OneLogin::RubySaml::Settings.new
168
297
  response.settings = settings
169
- settings.idp_cert_fingerprint = signature_fingerprint_1
298
+ settings.idp_cert_fingerprint = signature_fingerprint_valid_res
170
299
  assert response.is_valid?
171
300
  settings.sp_entity_id = ''
172
301
  assert response.is_valid?
173
302
  end
174
303
 
175
- should "return false when sp_entity_id set to incorrectly" do
176
- response = OneLogin::RubySaml::Response.new(response_document_4)
304
+ it "return false when sp_entity_id set to incorrectly" do
305
+ response = OneLogin::RubySaml::Response.new(response_document_valid_signed)
177
306
  response.stubs(:conditions).returns(nil)
178
307
  settings = OneLogin::RubySaml::Settings.new
179
308
  response.settings = settings
180
- settings.idp_cert_fingerprint = signature_fingerprint_1
309
+ settings.idp_cert_fingerprint = signature_fingerprint_valid_res
181
310
  settings.sp_entity_id = 'wrong_audience'
182
311
  assert !response.is_valid?
183
312
  end
184
313
 
185
- should "return true when sp_entity_id set to correctly" do
186
- response = OneLogin::RubySaml::Response.new(response_document_4)
314
+ it "return true when sp_entity_id set to correctly" do
315
+ response = OneLogin::RubySaml::Response.new(response_document_valid_signed)
187
316
  response.stubs(:conditions).returns(nil)
188
317
  settings = OneLogin::RubySaml::Settings.new
189
318
  response.settings = settings
190
- settings.idp_cert_fingerprint = signature_fingerprint_1
191
- settings.sp_entity_id = 'audience'
319
+ settings.idp_cert_fingerprint = signature_fingerprint_valid_res
320
+ settings.sp_entity_id = 'https://someone.example.com/audience'
192
321
  assert response.is_valid?
193
322
  end
194
323
  end
195
324
  end
196
325
 
197
- context "#name_id" do
198
- should "extract the value of the name id element" do
326
+ describe "#name_id" do
327
+ it "extract the value of the name id element" do
199
328
  response = OneLogin::RubySaml::Response.new(response_document)
200
329
  assert_equal "support@onelogin.com", response.name_id
201
330
 
@@ -203,19 +332,19 @@ class ResponseTest < Test::Unit::TestCase
203
332
  assert_equal "someone@example.com", response.name_id
204
333
  end
205
334
 
206
- should "be extractable from an OpenSAML response" do
335
+ it "be extractable from an OpenSAML response" do
207
336
  response = OneLogin::RubySaml::Response.new(fixture(:open_saml))
208
337
  assert_equal "someone@example.org", response.name_id
209
338
  end
210
339
 
211
- should "be extractable from a Simple SAML PHP response" do
340
+ it "be extractable from a Simple SAML PHP response" do
212
341
  response = OneLogin::RubySaml::Response.new(fixture(:simple_saml_php))
213
342
  assert_equal "someone@example.com", response.name_id
214
343
  end
215
344
  end
216
345
 
217
- context "#check_conditions" do
218
- should "check time conditions" do
346
+ describe "#check_conditions" do
347
+ it "check time conditions" do
219
348
  response = OneLogin::RubySaml::Response.new(response_document)
220
349
  assert !response.send(:validate_conditions, true)
221
350
  response = OneLogin::RubySaml::Response.new(response_document_6)
@@ -226,75 +355,122 @@ class ResponseTest < Test::Unit::TestCase
226
355
  assert response.send(:validate_conditions, true)
227
356
  end
228
357
 
229
- should "optionally allow for clock drift" do
358
+ it "optionally allow for clock drift" do
230
359
  # The NotBefore condition in the document is 2011-06-14T18:21:01.516Z
231
- Time.stubs(:now).returns(Time.parse("2011-06-14T18:21:01Z"))
360
+ expected_time = Time.parse("2011-06-14T18:21:01Z")
361
+ Time.stubs(:now).returns(expected_time)
232
362
  response = OneLogin::RubySaml::Response.new(response_document_5, :allowed_clock_drift => 0.515)
233
363
  assert !response.send(:validate_conditions, true)
234
364
 
235
- Time.stubs(:now).returns(Time.parse("2011-06-14T18:21:01Z"))
365
+ expected_time = Time.parse("2011-06-14T18:21:01Z")
366
+ Time.stubs(:now).returns(expected_time)
236
367
  response = OneLogin::RubySaml::Response.new(response_document_5, :allowed_clock_drift => 0.516)
237
368
  assert response.send(:validate_conditions, true)
238
369
  end
239
370
  end
240
371
 
241
- context "#attributes" do
242
- should "extract the first attribute in a hash accessed via its symbol" do
243
- response = OneLogin::RubySaml::Response.new(response_document)
244
- assert_equal "demo", response.attributes[:uid]
372
+ describe "validate_signature" do
373
+ it "raises an exception when no cert or fingerprint provided" do
374
+ response = OneLogin::RubySaml::Response.new(response_document_valid_signed)
375
+ settings = OneLogin::RubySaml::Settings.new
376
+ response.settings = settings
377
+ settings.idp_cert = nil
378
+ settings.idp_cert_fingerprint = nil
379
+ err = assert_raises(OneLogin::RubySaml::ValidationError) do
380
+ response.send(:validate_signature, false)
381
+ end
382
+ assert_equal "No fingerprint or certificate on settings", err.message
245
383
  end
246
384
 
247
- should "extract the first attribute in a hash accessed via its name" do
248
- response = OneLogin::RubySaml::Response.new(response_document)
249
- assert_equal "demo", response.attributes["uid"]
385
+ it "raises an exception when wrong cert provided" do
386
+ response = OneLogin::RubySaml::Response.new(response_document_valid_signed)
387
+ settings = OneLogin::RubySaml::Settings.new
388
+ response.settings = settings
389
+ settings.idp_cert = ruby_saml_cert2
390
+ settings.idp_cert_fingerprint = nil
391
+ err = assert_raises(OneLogin::RubySaml::ValidationError) do
392
+ response.send(:validate_signature, false)
393
+ end
394
+ assert_equal "Fingerprint mismatch", err.message
250
395
  end
251
396
 
252
- should "extract all attributes" do
253
- response = OneLogin::RubySaml::Response.new(response_document)
254
- assert_equal "demo", response.attributes[:uid]
255
- assert_equal "value", response.attributes[:another_value]
397
+ it "raises an exception when wrong fingerprint provided" do
398
+ response = OneLogin::RubySaml::Response.new(response_document_valid_signed)
399
+ settings = OneLogin::RubySaml::Settings.new
400
+ response.settings = settings
401
+ settings.idp_cert = nil
402
+ settings.idp_cert_fingerprint = "28:74:9B:E8:1F:E8:10:9C:A8:7C:A9:C3:E3:C5:01:6C:92:1C:B4:BA"
403
+ err = assert_raises(OneLogin::RubySaml::ValidationError) do
404
+ response.send(:validate_signature, false)
405
+ end
406
+ assert_equal "Fingerprint mismatch", err.message
256
407
  end
257
408
 
258
- should "work for implicit namespaces" do
259
- response = OneLogin::RubySaml::Response.new(response_document_3)
260
- assert_equal "someone@example.com", response.attributes["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"]
409
+ it "raises an exception when no signature" do
410
+ response_no_signed_elements = OneLogin::RubySaml::Response.new(read_invalid_response("no_signature.xml.base64"))
411
+ settings.idp_cert_fingerprint = signature_fingerprint_1
412
+ response_no_signed_elements.settings = settings
413
+ err = assert_raises(OneLogin::RubySaml::ValidationError) do
414
+ response_no_signed_elements.validate!
415
+ end
416
+ assert_equal "Found an unexpected number of Signature Element. SAML Response rejected", err.message
261
417
  end
418
+ end
262
419
 
263
- should "not raise on responses without attributes" do
264
- response = OneLogin::RubySaml::Response.new(response_document_4)
265
- assert_equal OneLogin::RubySaml::Attributes.new, response.attributes
420
+ describe "#attributes" do
421
+ before do
422
+ @response = OneLogin::RubySaml::Response.new(response_document)
423
+ end
424
+
425
+ it "extract the first attribute in a hash accessed via its symbol" do
426
+ assert_equal "demo", @response.attributes[:uid]
427
+ end
428
+
429
+ it "extract the first attribute in a hash accessed via its name" do
430
+ assert_equal "demo", @response.attributes["uid"]
431
+ end
432
+
433
+ it "extract all attributes" do
434
+ assert_equal "demo", @response.attributes[:uid]
435
+ assert_equal "value", @response.attributes[:another_value]
436
+ end
437
+
438
+ it "work for implicit namespaces" do
439
+ response_3 = OneLogin::RubySaml::Response.new(response_document_3)
440
+ assert_equal "someone@example.com", response_3.attributes["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"]
441
+ end
442
+
443
+ it "not raise on responses without attributes" do
444
+ response_4 = OneLogin::RubySaml::Response.new(response_document_4)
445
+ assert_equal OneLogin::RubySaml::Attributes.new, response_4.attributes
266
446
  end
267
447
 
268
- should "extract attributes from all AttributeStatement tags" do
448
+ it "extract attributes from all AttributeStatement tags" do
269
449
  assert_equal "smith", response_with_multiple_attribute_statements.attributes[:surname]
270
450
  assert_equal "bob", response_with_multiple_attribute_statements.attributes[:firstname]
271
451
  end
272
452
 
273
- should "be manipulable by hash methods such as #merge and not raise an exception" do
274
- response = OneLogin::RubySaml::Response.new(response_document)
275
- response.attributes.merge({ :testing_attribute => "test" })
453
+ it "be manipulable by hash methods such as #merge and not raise an exception" do
454
+ @response.attributes.merge({ :testing_attribute => "test" })
276
455
  end
277
456
 
278
- should "be manipulable by hash methods such as #shift and not raise an exception" do
279
- response = OneLogin::RubySaml::Response.new(response_document)
280
- response.attributes.shift
457
+ it "be manipulable by hash methods such as #shift and not raise an exception" do
458
+ @response.attributes.shift
281
459
  end
282
460
 
283
- should "be manipulable by hash methods such as #merge! and actually contain the value" do
284
- response = OneLogin::RubySaml::Response.new(response_document)
285
- response.attributes.merge!({ :testing_attribute => "test" })
286
- assert response.attributes[:testing_attribute]
461
+ it "be manipulable by hash methods such as #merge! and actually contain the value" do
462
+ @response.attributes.merge!({ :testing_attribute => "test" })
463
+ assert @response.attributes[:testing_attribute]
287
464
  end
288
465
 
289
- should "be manipulable by hash methods such as #shift and actually remove the value" do
290
- response = OneLogin::RubySaml::Response.new(response_document)
291
- removed_value = response.attributes.shift
292
- assert_nil response.attributes[removed_value[0]]
466
+ it "be manipulable by hash methods such as #shift and actually remove the value" do
467
+ removed_value = @response.attributes.shift
468
+ assert_nil @response.attributes[removed_value[0]]
293
469
  end
294
470
  end
295
471
 
296
- context "#session_expires_at" do
297
- should "extract the value of the SessionNotOnOrAfter attribute" do
472
+ describe "#session_expires_at" do
473
+ it "extract the value of the SessionNotOnOrAfter attribute" do
298
474
  response = OneLogin::RubySaml::Response.new(response_document)
299
475
  assert response.session_expires_at.is_a?(Time)
300
476
 
@@ -303,124 +479,167 @@ class ResponseTest < Test::Unit::TestCase
303
479
  end
304
480
  end
305
481
 
306
- context "#issuer" do
307
- should "return the issuer inside the response assertion" do
482
+ describe "#issuer" do
483
+ it "return the issuer inside the response assertion" do
308
484
  response = OneLogin::RubySaml::Response.new(response_document)
309
485
  assert_equal "https://app.onelogin.com/saml/metadata/13590", response.issuer
310
486
  end
311
487
 
312
- should "return the issuer inside the response" do
488
+ it "return the issuer inside the response" do
313
489
  response = OneLogin::RubySaml::Response.new(response_document_2)
314
490
  assert_equal "wibble", response.issuer
315
491
  end
316
492
  end
317
493
 
318
- context "#success" do
319
- should "find a status code that says success" do
494
+ describe "#success" do
495
+ it "find a status code that says success" do
320
496
  response = OneLogin::RubySaml::Response.new(response_document)
321
- response.success?
497
+ assert response.send(:success?)
322
498
  end
323
499
  end
324
500
 
325
- context '#xpath_first_from_signed_assertion' do
326
- should 'not allow arbitrary code execution' do
501
+ describe '#xpath_first_from_signed_assertion' do
502
+ it 'not allow arbitrary code execution' do
327
503
  malicious_response_document = fixture('response_eval', false)
328
504
  response = OneLogin::RubySaml::Response.new(malicious_response_document)
329
505
  response.send(:xpath_first_from_signed_assertion)
330
- assert_equal($evalled, nil)
506
+ assert_nil $evalled
331
507
  end
332
508
  end
333
509
 
334
- context "#multiple values" do
335
- should "extract single value as string" do
510
+ describe "#multiple values" do
511
+ it "extract single value as string" do
336
512
  assert_equal "demo", response_multiple_attr_values.attributes[:uid]
337
513
  end
338
514
 
339
- should "extract single value as string in compatibility mode off" do
515
+ it "extract single value as string in compatibility mode off" do
340
516
  OneLogin::RubySaml::Attributes.single_value_compatibility = false
341
517
  assert_equal ["demo"], response_multiple_attr_values.attributes[:uid]
342
518
  # classes are not reloaded between tests so restore default
343
519
  OneLogin::RubySaml::Attributes.single_value_compatibility = true
344
520
  end
345
521
 
346
- should "extract first of multiple values as string for b/w compatibility" do
522
+ it "extract first of multiple values as string for b/w compatibility" do
347
523
  assert_equal 'value1', response_multiple_attr_values.attributes[:another_value]
348
524
  end
349
525
 
350
- should "extract first of multiple values as string for b/w compatibility in compatibility mode off" do
526
+ it "extract first of multiple values as string for b/w compatibility in compatibility mode off" do
351
527
  OneLogin::RubySaml::Attributes.single_value_compatibility = false
352
528
  assert_equal ['value1', 'value2'], response_multiple_attr_values.attributes[:another_value]
353
529
  OneLogin::RubySaml::Attributes.single_value_compatibility = true
354
530
  end
355
531
 
356
- should "return array with all attributes when asked in XML order" do
532
+ it "return array with all attributes when asked in XML order" do
357
533
  assert_equal ['value1', 'value2'], response_multiple_attr_values.attributes.multi(:another_value)
358
534
  end
359
535
 
360
- should "return array with all attributes when asked in XML order in compatibility mode off" do
536
+ it "return array with all attributes when asked in XML order in compatibility mode off" do
361
537
  OneLogin::RubySaml::Attributes.single_value_compatibility = false
362
538
  assert_equal ['value1', 'value2'], response_multiple_attr_values.attributes.multi(:another_value)
363
539
  OneLogin::RubySaml::Attributes.single_value_compatibility = true
364
540
  end
365
541
 
366
- should "return first of multiple values when multiple Attribute tags in XML" do
542
+ it "return first of multiple values when multiple Attribute tags in XML" do
367
543
  assert_equal 'role1', response_multiple_attr_values.attributes[:role]
368
544
  end
369
545
 
370
- should "return first of multiple values when multiple Attribute tags in XML in compatibility mode off" do
546
+ it "return first of multiple values when multiple Attribute tags in XML in compatibility mode off" do
371
547
  OneLogin::RubySaml::Attributes.single_value_compatibility = false
372
548
  assert_equal ['role1', 'role2', 'role3'], response_multiple_attr_values.attributes[:role]
373
549
  OneLogin::RubySaml::Attributes.single_value_compatibility = true
374
550
  end
375
551
 
376
- should "return all of multiple values in reverse order when multiple Attribute tags in XML" do
552
+ it "return all of multiple values in reverse order when multiple Attribute tags in XML" do
377
553
  assert_equal ['role1', 'role2', 'role3'], response_multiple_attr_values.attributes.multi(:role)
378
554
  end
379
555
 
380
- should "return all of multiple values in reverse order when multiple Attribute tags in XML in compatibility mode off" do
556
+ it "return all of multiple values in reverse order when multiple Attribute tags in XML in compatibility mode off" do
381
557
  OneLogin::RubySaml::Attributes.single_value_compatibility = false
382
558
  assert_equal ['role1', 'role2', 'role3'], response_multiple_attr_values.attributes.multi(:role)
383
559
  OneLogin::RubySaml::Attributes.single_value_compatibility = true
384
560
  end
385
561
 
386
- should "return all of multiple values when multiple Attribute tags in multiple AttributeStatement tags" do
562
+ it "return all of multiple values when multiple Attribute tags in multiple AttributeStatement tags" do
387
563
  OneLogin::RubySaml::Attributes.single_value_compatibility = false
388
564
  assert_equal ['role1', 'role2', 'role3'], response_with_multiple_attribute_statements.attributes.multi(:role)
389
565
  OneLogin::RubySaml::Attributes.single_value_compatibility = true
390
566
  end
391
567
 
392
- should "return nil value correctly" do
568
+ it "return nil value correctly" do
393
569
  assert_nil response_multiple_attr_values.attributes[:attribute_with_nil_value]
394
570
  end
395
571
 
396
- should "return nil value correctly when not in compatibility mode off" do
572
+ it "return nil value correctly when not in compatibility mode off" do
397
573
  OneLogin::RubySaml::Attributes.single_value_compatibility = false
398
- assert_equal [nil], response_multiple_attr_values.attributes[:attribute_with_nil_value]
574
+ assert [nil] == response_multiple_attr_values.attributes[:attribute_with_nil_value]
399
575
  OneLogin::RubySaml::Attributes.single_value_compatibility = true
400
576
  end
401
577
 
402
- should "return multiple values including nil and empty string" do
578
+ it "return multiple values including nil and empty string" do
403
579
  response = OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values))
404
580
  assert_equal ["", "valuePresent", nil, nil], response.attributes.multi(:attribute_with_nils_and_empty_strings)
405
581
  end
406
582
 
407
- should "return multiple values from [] when not in compatibility mode off" do
583
+ it "return multiple values from [] when not in compatibility mode off" do
408
584
  OneLogin::RubySaml::Attributes.single_value_compatibility = false
409
585
  assert_equal ["", "valuePresent", nil, nil], response_multiple_attr_values.attributes[:attribute_with_nils_and_empty_strings]
410
586
  OneLogin::RubySaml::Attributes.single_value_compatibility = true
411
587
  end
412
588
 
413
- should "check what happens when trying retrieve attribute that does not exists" do
414
- assert_equal nil, response_multiple_attr_values.attributes[:attribute_not_exists]
415
- assert_equal nil, response_multiple_attr_values.attributes.single(:attribute_not_exists)
416
- assert_equal nil, response_multiple_attr_values.attributes.multi(:attribute_not_exists)
589
+ it "check what happens when trying retrieve attribute that does not exists" do
590
+ assert_nil response_multiple_attr_values.attributes[:attribute_not_exists]
591
+ assert_nil response_multiple_attr_values.attributes.single(:attribute_not_exists)
592
+ assert_nil response_multiple_attr_values.attributes.multi(:attribute_not_exists)
417
593
 
418
594
  OneLogin::RubySaml::Attributes.single_value_compatibility = false
419
- assert_equal nil, response_multiple_attr_values.attributes[:attribute_not_exists]
420
- assert_equal nil, response_multiple_attr_values.attributes.single(:attribute_not_exists)
421
- assert_equal nil, response_multiple_attr_values.attributes.multi(:attribute_not_exists)
595
+ assert_nil response_multiple_attr_values.attributes[:attribute_not_exists]
596
+ assert_nil response_multiple_attr_values.attributes.single(:attribute_not_exists)
597
+ assert_nil response_multiple_attr_values.attributes.multi(:attribute_not_exists)
422
598
  OneLogin::RubySaml::Attributes.single_value_compatibility = true
423
599
  end
424
600
  end
601
+
602
+ describe "signature wrapping attack with encrypted assertion" do
603
+ it "should not be valid" do
604
+ settings = OneLogin::RubySaml::Settings.new
605
+ settings.private_key = valid_key
606
+ signature_wrapping_attack = read_response("encrypted_new_attack.xml.base64")
607
+ response_wrapped = OneLogin::RubySaml::Response.new(signature_wrapping_attack, :settings => settings)
608
+ response_wrapped.stubs(:conditions).returns(nil)
609
+ response_wrapped.stubs(:validate_subject_confirmation).returns(true)
610
+ settings.idp_cert_fingerprint = "385b1eec71143f00db6af936e2ea12a28771d72c"
611
+ assert !response_wrapped.is_valid?
612
+ err = assert_raises(OneLogin::RubySaml::ValidationError) do
613
+ response_wrapped.validate!
614
+ end
615
+ assert_equal "Found an invalid Signed Element. SAML Response rejected", err.message
616
+ end
617
+ end
618
+
619
+ describe "signature wrapping attack - concealed SAML response body" do
620
+ it "should not be valid" do
621
+ settings = OneLogin::RubySaml::Settings.new
622
+ signature_wrapping_attack = read_response("response_with_concealed_signed_assertion.xml")
623
+ response_wrapped = OneLogin::RubySaml::Response.new(signature_wrapping_attack, :settings => settings)
624
+ settings.idp_cert_fingerprint = '4b68c453c7d994aad9025c99d5efcf566287fe8d'
625
+ response_wrapped.stubs(:conditions).returns(nil)
626
+ response_wrapped.stubs(:validate_subject_confirmation).returns(true)
627
+ response_wrapped.stubs(:validate_structure).returns(true)
628
+ assert !response_wrapped.is_valid?
629
+ assert !response_wrapped.validate!
630
+ end
631
+ end
632
+
633
+ describe "signature wrapping attack - doubled signed assertion SAML response" do
634
+ it "should not be valid" do
635
+ settings = OneLogin::RubySaml::Settings.new
636
+ signature_wrapping_attack = read_response("response_with_doubled_signed_assertion.xml")
637
+ response_wrapped = OneLogin::RubySaml::Response.new(signature_wrapping_attack, :settings => settings)
638
+ settings.idp_cert_fingerprint = '4b68c453c7d994aad9025c99d5efcf566287fe8d'
639
+ response_wrapped.stubs(:conditions).returns(nil)
640
+ response_wrapped.stubs(:validate_subject_confirmation).returns(true)
641
+ assert !response_wrapped.is_valid?
642
+ end
643
+ end
425
644
  end
426
645
  end