ruby-saml 0.8.10 → 0.8.15

Sign up to get free protection for your applications and to get access to all the features.

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