ruby-saml 0.8.8 → 0.8.13

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 (45) hide show
  1. checksums.yaml +7 -7
  2. data/Gemfile +11 -1
  3. data/README.md +5 -2
  4. data/Rakefile +0 -14
  5. data/lib/onelogin/ruby-saml/authrequest.rb +86 -20
  6. data/lib/onelogin/ruby-saml/logoutrequest.rb +95 -20
  7. data/lib/onelogin/ruby-saml/logoutresponse.rb +5 -28
  8. data/lib/onelogin/ruby-saml/metadata.rb +5 -5
  9. data/lib/onelogin/ruby-saml/response.rb +187 -4
  10. data/lib/onelogin/ruby-saml/setting_error.rb +6 -0
  11. data/lib/onelogin/ruby-saml/settings.rb +146 -10
  12. data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +158 -0
  13. data/lib/onelogin/ruby-saml/utils.rb +169 -0
  14. data/lib/onelogin/ruby-saml/version.rb +1 -1
  15. data/lib/ruby-saml.rb +2 -1
  16. data/lib/xml_security.rb +330 -78
  17. data/test/certificates/ruby-saml-2.crt +15 -0
  18. data/test/certificates/ruby-saml.crt +14 -0
  19. data/test/certificates/ruby-saml.key +15 -0
  20. data/test/logoutrequest_test.rb +177 -44
  21. data/test/logoutresponse_test.rb +25 -29
  22. data/test/request_test.rb +100 -37
  23. data/test/response_test.rb +213 -111
  24. data/test/responses/adfs_response_xmlns.xml +45 -0
  25. data/test/responses/encrypted_new_attack.xml.base64 +1 -0
  26. data/test/responses/invalids/multiple_signed.xml.base64 +1 -0
  27. data/test/responses/invalids/no_signature.xml.base64 +1 -0
  28. data/test/responses/invalids/response_with_concealed_signed_assertion.xml +51 -0
  29. data/test/responses/invalids/response_with_doubled_signed_assertion.xml +49 -0
  30. data/test/responses/invalids/signature_wrapping_attack.xml.base64 +1 -0
  31. data/test/responses/logoutresponse_fixtures.rb +6 -6
  32. data/test/responses/response_with_concealed_signed_assertion.xml +51 -0
  33. data/test/responses/response_with_doubled_signed_assertion.xml +49 -0
  34. data/test/responses/response_with_signed_assertion_3.xml +30 -0
  35. data/test/responses/response_with_signed_message_and_assertion.xml +34 -0
  36. data/test/responses/response_with_undefined_recipient.xml.base64 +1 -0
  37. data/test/responses/response_wrapped.xml.base64 +150 -0
  38. data/test/responses/valid_response.xml.base64 +1 -0
  39. data/test/responses/valid_response_without_x509certificate.xml.base64 +1 -0
  40. data/test/settings_test.rb +7 -7
  41. data/test/slo_logoutresponse_test.rb +226 -0
  42. data/test/test_helper.rb +117 -12
  43. data/test/utils_test.rb +10 -10
  44. data/test/xml_security_test.rb +310 -68
  45. metadata +88 -45
@@ -1,11 +1,15 @@
1
1
  require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
2
2
 
3
- class RequestTest < Test::Unit::TestCase
3
+ class RequestTest < Minitest::Test
4
4
 
5
- context "Authrequest" do
6
- should "create the deflated SAMLRequest URL parameter" do
7
- settings = OneLogin::RubySaml::Settings.new
5
+ describe "Authrequest" do
6
+ let(:settings) { OneLogin::RubySaml::Settings.new }
7
+
8
+ before do
8
9
  settings.idp_sso_target_url = "http://example.com"
10
+ end
11
+
12
+ it "create the deflated SAMLRequest URL parameter" do
9
13
  auth_url = OneLogin::RubySaml::Authrequest.new.create(settings)
10
14
  assert auth_url =~ /^http:\/\/example\.com\?SAMLRequest=/
11
15
  payload = CGI.unescape(auth_url.split("=").last)
@@ -19,9 +23,7 @@ class RequestTest < Test::Unit::TestCase
19
23
  assert_match /^<samlp:AuthnRequest/, inflated
20
24
  end
21
25
 
22
- should "create the deflated SAMLRequest URL parameter including the Destination" do
23
- settings = OneLogin::RubySaml::Settings.new
24
- settings.idp_sso_target_url = "http://example.com"
26
+ it "create the deflated SAMLRequest URL parameter including the Destination" do
25
27
  auth_url = OneLogin::RubySaml::Authrequest.new.create(settings)
26
28
  payload = CGI.unescape(auth_url.split("=").last)
27
29
  decoded = Base64.decode64(payload)
@@ -34,10 +36,8 @@ class RequestTest < Test::Unit::TestCase
34
36
  assert_match /<samlp:AuthnRequest[^<]* Destination='http:\/\/example.com'/, inflated
35
37
  end
36
38
 
37
- should "create the SAMLRequest URL parameter without deflating" do
38
- settings = OneLogin::RubySaml::Settings.new
39
+ it "create the SAMLRequest URL parameter without deflating" do
39
40
  settings.compress_request = false
40
- settings.idp_sso_target_url = "http://example.com"
41
41
  auth_url = OneLogin::RubySaml::Authrequest.new.create(settings)
42
42
  assert auth_url =~ /^http:\/\/example\.com\?SAMLRequest=/
43
43
  payload = CGI.unescape(auth_url.split("=").last)
@@ -46,9 +46,7 @@ class RequestTest < Test::Unit::TestCase
46
46
  assert_match /^<samlp:AuthnRequest/, decoded
47
47
  end
48
48
 
49
- should "create the SAMLRequest URL parameter with IsPassive" do
50
- settings = OneLogin::RubySaml::Settings.new
51
- settings.idp_sso_target_url = "http://example.com"
49
+ it "create the SAMLRequest URL parameter with IsPassive" do
52
50
  settings.passive = true
53
51
  auth_url = OneLogin::RubySaml::Authrequest.new.create(settings)
54
52
  assert auth_url =~ /^http:\/\/example\.com\?SAMLRequest=/
@@ -63,9 +61,7 @@ class RequestTest < Test::Unit::TestCase
63
61
  assert_match /<samlp:AuthnRequest[^<]* IsPassive='true'/, inflated
64
62
  end
65
63
 
66
- should "create the SAMLRequest URL parameter with ProtocolBinding" do
67
- settings = OneLogin::RubySaml::Settings.new
68
- settings.idp_sso_target_url = "http://example.com"
64
+ it "create the SAMLRequest URL parameter with ProtocolBinding" do
69
65
  settings.protocol_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
70
66
  auth_url = OneLogin::RubySaml::Authrequest.new.create(settings)
71
67
  assert auth_url =~ /^http:\/\/example\.com\?SAMLRequest=/
@@ -80,9 +76,7 @@ class RequestTest < Test::Unit::TestCase
80
76
  assert_match /<samlp:AuthnRequest[^<]* ProtocolBinding='urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'/, inflated
81
77
  end
82
78
 
83
- should "create the SAMLRequest URL parameter with ForceAuthn" do
84
- settings = OneLogin::RubySaml::Settings.new
85
- settings.idp_sso_target_url = "http://example.com"
79
+ it "create the SAMLRequest URL parameter with ForceAuthn" do
86
80
  settings.force_authn = true
87
81
  auth_url = OneLogin::RubySaml::Authrequest.new.create(settings)
88
82
  assert auth_url =~ /^http:\/\/example\.com\?SAMLRequest=/
@@ -96,9 +90,7 @@ class RequestTest < Test::Unit::TestCase
96
90
  assert_match /<samlp:AuthnRequest[^<]* ForceAuthn='true'/, inflated
97
91
  end
98
92
 
99
- should "create the SAMLRequest URL parameter with NameID Format" do
100
- settings = OneLogin::RubySaml::Settings.new
101
- settings.idp_sso_target_url = "http://example.com"
93
+ it "create the SAMLRequest URL parameter with NameID Format" do
102
94
  settings.name_identifier_format = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
103
95
  auth_url = OneLogin::RubySaml::Authrequest.new.create(settings)
104
96
  assert auth_url =~ /^http:\/\/example\.com\?SAMLRequest=/
@@ -113,9 +105,7 @@ class RequestTest < Test::Unit::TestCase
113
105
  assert_match /<samlp:NameIDPolicy[^<]* Format='urn:oasis:names:tc:SAML:2.0:nameid-format:transient'/, inflated
114
106
  end
115
107
 
116
- should "create the SAMLRequest URL parameter with Subject" do
117
- settings = OneLogin::RubySaml::Settings.new
118
- settings.idp_sso_target_url = "http://example.com"
108
+ it "create the SAMLRequest URL parameter with Subject" do
119
109
  settings.name_identifier_value_requested = "testuser@example.com"
120
110
  settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
121
111
  auth_url = OneLogin::RubySaml::Authrequest.new.create(settings)
@@ -132,10 +122,7 @@ class RequestTest < Test::Unit::TestCase
132
122
  assert inflated.include?("<saml:SubjectConfirmation Method='urn:oasis:names:tc:SAML:2.0:cm:bearer'/>")
133
123
  end
134
124
 
135
- should "accept extra parameters" do
136
- settings = OneLogin::RubySaml::Settings.new
137
- settings.idp_sso_target_url = "http://example.com"
138
-
125
+ it "accept extra parameters" do
139
126
  auth_url = OneLogin::RubySaml::Authrequest.new.create(settings, { :hello => "there" })
140
127
  assert auth_url =~ /&hello=there$/
141
128
 
@@ -143,24 +130,100 @@ class RequestTest < Test::Unit::TestCase
143
130
  assert auth_url =~ /&hello=$/
144
131
  end
145
132
 
146
- context "when the target url doesn't contain a query string" do
147
- should "create the SAMLRequest parameter correctly" do
148
- settings = OneLogin::RubySaml::Settings.new
149
- settings.idp_sso_target_url = "http://example.com"
150
-
133
+ describe "when the target url doesn't contain a query string" do
134
+ it "create the SAMLRequest parameter correctly" do
151
135
  auth_url = OneLogin::RubySaml::Authrequest.new.create(settings)
152
136
  assert auth_url =~ /^http:\/\/example.com\?SAMLRequest/
153
137
  end
154
138
  end
155
139
 
156
- context "when the target url contains a query string" do
157
- should "create the SAMLRequest parameter correctly" do
158
- settings = OneLogin::RubySaml::Settings.new
140
+ describe "when the target url contains a query string" do
141
+ it "create the SAMLRequest parameter correctly" do
159
142
  settings.idp_sso_target_url = "http://example.com?field=value"
160
143
 
161
144
  auth_url = OneLogin::RubySaml::Authrequest.new.create(settings)
162
145
  assert auth_url =~ /^http:\/\/example.com\?field=value&SAMLRequest/
163
146
  end
164
147
  end
148
+
149
+ describe "#create_params when the settings indicate to sign (embebed) the request" do
150
+ before do
151
+ settings.compress_request = false
152
+ settings.idp_sso_target_url = "http://example.com?field=value"
153
+ settings.security[:authn_requests_signed] = true
154
+ settings.security[:embed_sign] = true
155
+ settings.certificate = ruby_saml_cert_text
156
+ settings.private_key = ruby_saml_key_text
157
+ end
158
+
159
+ it "create a signed request" do
160
+ params = OneLogin::RubySaml::Authrequest.new.create_params(settings)
161
+ request_xml = Base64.decode64(params["SAMLRequest"])
162
+ assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], request_xml
163
+ assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], request_xml
164
+ end
165
+
166
+ it "create a signed request with 256 digest and signature methods" do
167
+ settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256
168
+ settings.security[:digest_method] = XMLSecurity::Document::SHA512
169
+
170
+ params = OneLogin::RubySaml::Authrequest.new.create_params(settings)
171
+
172
+ request_xml = Base64.decode64(params["SAMLRequest"])
173
+ assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], request_xml
174
+ assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'/>], request_xml
175
+ assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2001/04/xmlenc#sha512'/>], request_xml
176
+ end
177
+ end
178
+
179
+ describe "#create_params when the settings indicate to sign the request" do
180
+ let(:cert) { OpenSSL::X509::Certificate.new(ruby_saml_cert_text) }
181
+
182
+ before do
183
+ settings.compress_request = false
184
+ settings.idp_sso_target_url = "http://example.com?field=value"
185
+ settings.security[:authn_requests_signed] = true
186
+ settings.security[:embed_sign] = false
187
+ settings.certificate = ruby_saml_cert_text
188
+ settings.private_key = ruby_saml_key_text
189
+ end
190
+
191
+ it "create a signature parameter with RSA_SHA1 and validate it" do
192
+ settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1
193
+
194
+ params = OneLogin::RubySaml::Authrequest.new.create_params(settings, :RelayState => 'http://example.com')
195
+ assert params['SAMLRequest']
196
+ assert params[:RelayState]
197
+ assert params['Signature']
198
+ assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA1
199
+
200
+ query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}"
201
+ query_string << "&RelayState=#{CGI.escape(params[:RelayState])}"
202
+ query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}"
203
+
204
+ signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg'])
205
+ assert_equal signature_algorithm, OpenSSL::Digest::SHA1
206
+
207
+ assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
208
+ end
209
+
210
+ it "create a signature parameter with RSA_SHA256 and validate it" do
211
+ settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256
212
+
213
+ params = OneLogin::RubySaml::Authrequest.new.create_params(settings, :RelayState => 'http://example.com')
214
+ assert params['Signature']
215
+ assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA256
216
+
217
+ query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}"
218
+ query_string << "&RelayState=#{CGI.escape(params[:RelayState])}"
219
+ query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}"
220
+
221
+ signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg'])
222
+ assert_equal signature_algorithm, OpenSSL::Digest::SHA256
223
+ assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
224
+ end
225
+ end
226
+
165
227
  end
228
+
166
229
  end
@@ -1,13 +1,13 @@
1
1
  require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
2
2
 
3
- class RubySamlTest < 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
5
+ describe "Response" do
6
+ it "raise an exception when response is initialized with nil" do
7
7
  assert_raises(ArgumentError) { OneLogin::RubySaml::Response.new(nil) }
8
8
  end
9
9
 
10
- should "be able to parse a document which contains ampersands" do
10
+ it "be able to parse a document which contains ampersands" do
11
11
  XMLSecurity::SignedDocument.any_instance.stubs(:digests_match?).returns(true)
12
12
  OneLogin::RubySaml::Response.any_instance.stubs(:validate_conditions).returns(true)
13
13
 
@@ -18,7 +18,7 @@ class RubySamlTest < Test::Unit::TestCase
18
18
  response.validate!
19
19
  end
20
20
 
21
- should "adapt namespace" do
21
+ it "adapt namespace" do
22
22
  response = OneLogin::RubySaml::Response.new(response_document)
23
23
  assert !response.name_id.nil?
24
24
  response = OneLogin::RubySaml::Response.new(response_document_2)
@@ -27,14 +27,14 @@ class RubySamlTest < Test::Unit::TestCase
27
27
  assert !response.name_id.nil?
28
28
  end
29
29
 
30
- should "default to raw input when a response is not Base64 encoded" do
30
+ it "default to raw input when a response is not Base64 encoded" do
31
31
  decoded = Base64.decode64(response_document_2)
32
32
  response = OneLogin::RubySaml::Response.new(decoded)
33
33
  assert response.document
34
34
  end
35
35
 
36
- context "Assertion" do
37
- should "only retreive an assertion with an ID that matches the signature's reference URI" do
36
+ describe "Assertion" do
37
+ it "only retreive an assertion with an ID that matches the signature's reference URI" do
38
38
  response = OneLogin::RubySaml::Response.new(wrapped_response_2)
39
39
  response.stubs(:conditions).returns(nil)
40
40
  settings = OneLogin::RubySaml::Settings.new
@@ -44,77 +44,109 @@ class RubySamlTest < Test::Unit::TestCase
44
44
  end
45
45
  end
46
46
 
47
- context "#validate!" do
48
- should "raise when encountering a condition that prevents the document from being valid" do
47
+ describe "#validate!" do
48
+ it "raise when encountering a condition that prevents the document from being valid" do
49
49
  response = OneLogin::RubySaml::Response.new(response_document)
50
- assert_raise(OneLogin::RubySaml::ValidationError) do
50
+ assert_raises(OneLogin::RubySaml::ValidationError) do
51
51
  response.validate!
52
52
  end
53
53
  end
54
54
  end
55
55
 
56
- context "#is_valid?" do
57
- should "return false when response is initialized with blank data" do
56
+ describe "#is_valid?" do
57
+ it "return false when response is initialized with blank data" do
58
58
  response = OneLogin::RubySaml::Response.new('')
59
59
  assert !response.is_valid?
60
60
  end
61
61
 
62
- should "return false if settings have not been set" do
62
+ it "return false if settings have not been set" do
63
63
  response = OneLogin::RubySaml::Response.new(response_document)
64
64
  assert !response.is_valid?
65
65
  end
66
66
 
67
- should "return true when the response is initialized with valid data" do
68
- response = OneLogin::RubySaml::Response.new(response_document_4)
67
+ it "return true when the response is initialized with valid data" do
68
+ response = OneLogin::RubySaml::Response.new(response_document_valid_signed)
69
69
  response.stubs(:conditions).returns(nil)
70
70
  assert !response.is_valid?
71
71
  settings = OneLogin::RubySaml::Settings.new
72
72
  assert !response.is_valid?
73
73
  response.settings = settings
74
74
  assert !response.is_valid?
75
- settings.idp_cert_fingerprint = signature_fingerprint_1
76
- assert response.is_valid?
75
+ response.settings.idp_cert_fingerprint = signature_fingerprint_valid_res
76
+ response.validate!
77
77
  end
78
78
 
79
- should "should be idempotent when the response is initialized with invalid data" do
80
- response = OneLogin::RubySaml::Response.new(response_document_4)
79
+ it "should be idempotent when the response is initialized with invalid data" do
80
+ response = OneLogin::RubySaml::Response.new(response_document_valid_signed)
81
81
  response.stubs(:conditions).returns(nil)
82
82
  settings = OneLogin::RubySaml::Settings.new
83
- response.settings = settings
83
+ response.settings = settings
84
84
  assert !response.is_valid?
85
85
  assert !response.is_valid?
86
86
  end
87
87
 
88
- should "should be idempotent when the response is initialized with valid data" do
89
- response = OneLogin::RubySaml::Response.new(response_document_4)
88
+ it "should be idempotent when the response is initialized with valid data" do
89
+ response = OneLogin::RubySaml::Response.new(response_document_valid_signed)
90
90
  response.stubs(:conditions).returns(nil)
91
91
  settings = OneLogin::RubySaml::Settings.new
92
92
  response.settings = settings
93
- settings.idp_cert_fingerprint = signature_fingerprint_1
93
+ response.settings.idp_cert_fingerprint = signature_fingerprint_valid_res
94
94
  assert response.is_valid?
95
95
  assert response.is_valid?
96
96
  end
97
97
 
98
- should "return true when using certificate instead of fingerprint" do
99
- response = OneLogin::RubySaml::Response.new(response_document_4)
98
+ it "return true when using certificate instead of fingerprint" do
99
+ response = OneLogin::RubySaml::Response.new(response_document_valid_signed)
100
100
  response.stubs(:conditions).returns(nil)
101
101
  settings = OneLogin::RubySaml::Settings.new
102
102
  response.settings = settings
103
- settings.idp_cert = signature_1
103
+ settings.idp_cert = valid_cert
104
104
  assert response.is_valid?
105
105
  end
106
106
 
107
- should "not allow signature wrapping attack" do
107
+ it "not allow signature wrapping attack" do
108
108
  response = OneLogin::RubySaml::Response.new(response_document_4)
109
109
  response.stubs(:conditions).returns(nil)
110
110
  settings = OneLogin::RubySaml::Settings.new
111
111
  settings.idp_cert_fingerprint = signature_fingerprint_1
112
112
  response.settings = settings
113
- assert response.is_valid?
113
+ assert !response.is_valid?
114
114
  assert response.name_id == "test@onelogin.com"
115
115
  end
116
116
 
117
- should "support dynamic namespace resolution on signature elements" do
117
+ it "not allow element wrapping attack" do
118
+ response_wrapped = OneLogin::RubySaml::Response.new(response_document_wrapped)
119
+ response_wrapped.stubs(:conditions).returns(nil)
120
+ response_wrapped.stubs(:validate_subject_confirmation).returns(true)
121
+ settings = OneLogin::RubySaml::Settings.new
122
+ response_wrapped.settings = settings
123
+ response_wrapped.settings.idp_cert_fingerprint = signature_fingerprint_1
124
+
125
+ assert !response_wrapped.is_valid?
126
+ assert_nil response_wrapped.name_id
127
+ end
128
+
129
+ it "raise when no signature" do
130
+ response_no_signed_elements = OneLogin::RubySaml::Response.new(read_invalid_response("no_signature.xml.base64"))
131
+ settings.idp_cert_fingerprint = signature_fingerprint_1
132
+ response_no_signed_elements.settings = settings
133
+ error_msg = "Found an unexpected number of Signature Element. SAML Response rejected"
134
+ assert_raises(OneLogin::RubySaml::ValidationError, error_msg) do
135
+ response_no_signed_elements.validate!
136
+ end
137
+ end
138
+
139
+ it "raise when multiple signatures" do
140
+ response_multiple_signed = OneLogin::RubySaml::Response.new(read_invalid_response("multiple_signed.xml.base64"))
141
+ settings.idp_cert_fingerprint = signature_fingerprint_1
142
+ response_multiple_signed.settings = settings
143
+ error_msg = "Duplicated ID. SAML Response rejected"
144
+ assert_raises(OneLogin::RubySaml::ValidationError, error_msg) do
145
+ response_multiple_signed.validate!
146
+ end
147
+ end
148
+
149
+ it "support dynamic namespace resolution on signature elements" do
118
150
  response = OneLogin::RubySaml::Response.new(fixture("no_signature_ns.xml"))
119
151
  response.stubs(:conditions).returns(nil)
120
152
  settings = OneLogin::RubySaml::Settings.new
@@ -124,7 +156,7 @@ class RubySamlTest < Test::Unit::TestCase
124
156
  assert response.validate!
125
157
  end
126
158
 
127
- should "validate ADFS assertions" do
159
+ it "validate ADFS assertions" do
128
160
  response = OneLogin::RubySaml::Response.new(fixture(:adfs_response_sha256))
129
161
  response.stubs(:conditions).returns(nil)
130
162
  settings = OneLogin::RubySaml::Settings.new
@@ -133,7 +165,7 @@ class RubySamlTest < Test::Unit::TestCase
133
165
  assert response.validate!
134
166
  end
135
167
 
136
- should "validate the digest" do
168
+ it "validate the digest" do
137
169
  response = OneLogin::RubySaml::Response.new(r1_response_document_6)
138
170
  response.stubs(:conditions).returns(nil)
139
171
  settings = OneLogin::RubySaml::Settings.new
@@ -142,8 +174,8 @@ class RubySamlTest < Test::Unit::TestCase
142
174
  assert response.validate!
143
175
  end
144
176
 
145
- should "validate SAML 2.0 XML structure" do
146
- resp_xml = Base64.decode64(response_document_4).gsub(/emailAddress/,'test')
177
+ it "validate SAML 2.0 XML structure" do
178
+ resp_xml = Base64.decode64(response_document_valid_signed).gsub(/emailAddress/,'test')
147
179
  response = OneLogin::RubySaml::Response.new(Base64.encode64(resp_xml))
148
180
  response.stubs(:conditions).returns(nil)
149
181
  settings = OneLogin::RubySaml::Settings.new
@@ -152,17 +184,50 @@ class RubySamlTest < Test::Unit::TestCase
152
184
  assert_raises(OneLogin::RubySaml::ValidationError, 'Digest mismatch'){ response.validate! }
153
185
  end
154
186
 
155
- should "Prevent node text with comment (VU#475445) attack" do
187
+ it "Prevent node text with comment (VU#475445) attack" do
156
188
  response_doc = File.read(File.join(File.dirname(__FILE__), "responses", 'response_node_text_attack.xml.base64'))
157
189
  response = OneLogin::RubySaml::Response.new(response_doc)
158
190
 
159
191
  assert_equal "support@onelogin.com", response.name_id
160
192
  assert_equal "smith", response.attributes["surname"]
161
193
  end
194
+
195
+ describe '#validate_audience' do
196
+ it "return true when sp_entity_id not set or empty" do
197
+ response = OneLogin::RubySaml::Response.new(response_document_valid_signed)
198
+ response.stubs(:conditions).returns(nil)
199
+ settings = OneLogin::RubySaml::Settings.new
200
+ response.settings = settings
201
+ settings.idp_cert_fingerprint = signature_fingerprint_valid_res
202
+ assert response.is_valid?
203
+ settings.sp_entity_id = ''
204
+ assert response.is_valid?
205
+ end
206
+
207
+ it "return false when sp_entity_id set to incorrectly" do
208
+ response = OneLogin::RubySaml::Response.new(response_document_valid_signed)
209
+ response.stubs(:conditions).returns(nil)
210
+ settings = OneLogin::RubySaml::Settings.new
211
+ response.settings = settings
212
+ settings.idp_cert_fingerprint = signature_fingerprint_valid_res
213
+ settings.sp_entity_id = 'wrong_audience'
214
+ assert !response.is_valid?
215
+ end
216
+
217
+ it "return true when sp_entity_id set to correctly" do
218
+ response = OneLogin::RubySaml::Response.new(response_document_valid_signed)
219
+ response.stubs(:conditions).returns(nil)
220
+ settings = OneLogin::RubySaml::Settings.new
221
+ response.settings = settings
222
+ settings.idp_cert_fingerprint = signature_fingerprint_valid_res
223
+ settings.sp_entity_id = 'https://someone.example.com/audience'
224
+ assert response.is_valid?
225
+ end
226
+ end
162
227
  end
163
228
 
164
- context "#name_id" do
165
- should "extract the value of the name id element" do
229
+ describe "#name_id" do
230
+ it "extract the value of the name id element" do
166
231
  response = OneLogin::RubySaml::Response.new(response_document)
167
232
  assert_equal "support@onelogin.com", response.name_id
168
233
 
@@ -170,19 +235,19 @@ class RubySamlTest < Test::Unit::TestCase
170
235
  assert_equal "someone@example.com", response.name_id
171
236
  end
172
237
 
173
- should "be extractable from an OpenSAML response" do
238
+ it "be extractable from an OpenSAML response" do
174
239
  response = OneLogin::RubySaml::Response.new(fixture(:open_saml))
175
240
  assert_equal "someone@example.org", response.name_id
176
241
  end
177
242
 
178
- should "be extractable from a Simple SAML PHP response" do
243
+ it "be extractable from a Simple SAML PHP response" do
179
244
  response = OneLogin::RubySaml::Response.new(fixture(:simple_saml_php))
180
245
  assert_equal "someone@example.com", response.name_id
181
246
  end
182
247
  end
183
248
 
184
- context "#check_conditions" do
185
- should "check time conditions" do
249
+ describe "#check_conditions" do
250
+ it "check time conditions" do
186
251
  response = OneLogin::RubySaml::Response.new(response_document)
187
252
  assert !response.send(:validate_conditions, true)
188
253
  response = OneLogin::RubySaml::Response.new(response_document_6)
@@ -193,75 +258,74 @@ class RubySamlTest < Test::Unit::TestCase
193
258
  assert response.send(:validate_conditions, true)
194
259
  end
195
260
 
196
- should "optionally allow for clock drift" do
261
+ it "optionally allow for clock drift" do
197
262
  # The NotBefore condition in the document is 2011-06-14T18:21:01.516Z
198
- Time.stubs(:now).returns(Time.parse("2011-06-14T18:21:01Z"))
263
+ expected_time = Time.parse("2011-06-14T18:21:01Z")
264
+ Time.stubs(:now).returns(expected_time)
199
265
  response = OneLogin::RubySaml::Response.new(response_document_5, :allowed_clock_drift => 0.515)
200
266
  assert !response.send(:validate_conditions, true)
201
267
 
202
- Time.stubs(:now).returns(Time.parse("2011-06-14T18:21:01Z"))
268
+ expected_time = Time.parse("2011-06-14T18:21:01Z")
269
+ Time.stubs(:now).returns(expected_time)
203
270
  response = OneLogin::RubySaml::Response.new(response_document_5, :allowed_clock_drift => 0.516)
204
271
  assert response.send(:validate_conditions, true)
205
272
  end
206
273
  end
207
274
 
208
- context "#attributes" do
209
- should "extract the first attribute in a hash accessed via its symbol" do
210
- response = OneLogin::RubySaml::Response.new(response_document)
211
- assert_equal "demo", response.attributes[:uid]
275
+ describe "#attributes" do
276
+ before do
277
+ @response = OneLogin::RubySaml::Response.new(response_document)
212
278
  end
213
279
 
214
- should "extract the first attribute in a hash accessed via its name" do
215
- response = OneLogin::RubySaml::Response.new(response_document)
216
- assert_equal "demo", response.attributes["uid"]
280
+ it "extract the first attribute in a hash accessed via its symbol" do
281
+ assert_equal "demo", @response.attributes[:uid]
217
282
  end
218
283
 
219
- should "extract all attributes" do
220
- response = OneLogin::RubySaml::Response.new(response_document)
221
- assert_equal "demo", response.attributes[:uid]
222
- assert_equal "value", response.attributes[:another_value]
284
+ it "extract the first attribute in a hash accessed via its name" do
285
+ assert_equal "demo", @response.attributes["uid"]
223
286
  end
224
287
 
225
- should "work for implicit namespaces" do
226
- response = OneLogin::RubySaml::Response.new(response_document_3)
227
- assert_equal "someone@example.com", response.attributes["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"]
288
+ it "extract all attributes" do
289
+ assert_equal "demo", @response.attributes[:uid]
290
+ assert_equal "value", @response.attributes[:another_value]
228
291
  end
229
292
 
230
- should "not raise on responses without attributes" do
231
- response = OneLogin::RubySaml::Response.new(response_document_4)
232
- assert_equal OneLogin::RubySaml::Attributes.new, response.attributes
293
+ it "work for implicit namespaces" do
294
+ response_3 = OneLogin::RubySaml::Response.new(response_document_3)
295
+ assert_equal "someone@example.com", response_3.attributes["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"]
296
+ end
297
+
298
+ it "not raise on responses without attributes" do
299
+ response_4 = OneLogin::RubySaml::Response.new(response_document_4)
300
+ assert_equal OneLogin::RubySaml::Attributes.new, response_4.attributes
233
301
  end
234
302
 
235
- should "extract attributes from all AttributeStatement tags" do
303
+ it "extract attributes from all AttributeStatement tags" do
236
304
  assert_equal "smith", response_with_multiple_attribute_statements.attributes[:surname]
237
305
  assert_equal "bob", response_with_multiple_attribute_statements.attributes[:firstname]
238
306
  end
239
307
 
240
- should "be manipulable by hash methods such as #merge and not raise an exception" do
241
- response = OneLogin::RubySaml::Response.new(response_document)
242
- response.attributes.merge({ :testing_attribute => "test" })
308
+ it "be manipulable by hash methods such as #merge and not raise an exception" do
309
+ @response.attributes.merge({ :testing_attribute => "test" })
243
310
  end
244
311
 
245
- should "be manipulable by hash methods such as #shift and not raise an exception" do
246
- response = OneLogin::RubySaml::Response.new(response_document)
247
- response.attributes.shift
312
+ it "be manipulable by hash methods such as #shift and not raise an exception" do
313
+ @response.attributes.shift
248
314
  end
249
315
 
250
- should "be manipulable by hash methods such as #merge! and actually contain the value" do
251
- response = OneLogin::RubySaml::Response.new(response_document)
252
- response.attributes.merge!({ :testing_attribute => "test" })
253
- assert response.attributes[:testing_attribute]
316
+ it "be manipulable by hash methods such as #merge! and actually contain the value" do
317
+ @response.attributes.merge!({ :testing_attribute => "test" })
318
+ assert @response.attributes[:testing_attribute]
254
319
  end
255
320
 
256
- should "be manipulable by hash methods such as #shift and actually remove the value" do
257
- response = OneLogin::RubySaml::Response.new(response_document)
258
- removed_value = response.attributes.shift
259
- assert_nil response.attributes[removed_value[0]]
321
+ it "be manipulable by hash methods such as #shift and actually remove the value" do
322
+ removed_value = @response.attributes.shift
323
+ assert_nil @response.attributes[removed_value[0]]
260
324
  end
261
325
  end
262
326
 
263
- context "#session_expires_at" do
264
- should "extract the value of the SessionNotOnOrAfter attribute" do
327
+ describe "#session_expires_at" do
328
+ it "extract the value of the SessionNotOnOrAfter attribute" do
265
329
  response = OneLogin::RubySaml::Response.new(response_document)
266
330
  assert response.session_expires_at.is_a?(Time)
267
331
 
@@ -270,124 +334,162 @@ class RubySamlTest < Test::Unit::TestCase
270
334
  end
271
335
  end
272
336
 
273
- context "#issuer" do
274
- should "return the issuer inside the response assertion" do
337
+ describe "#issuer" do
338
+ it "return the issuer inside the response assertion" do
275
339
  response = OneLogin::RubySaml::Response.new(response_document)
276
340
  assert_equal "https://app.onelogin.com/saml/metadata/13590", response.issuer
277
341
  end
278
342
 
279
- should "return the issuer inside the response" do
343
+ it "return the issuer inside the response" do
280
344
  response = OneLogin::RubySaml::Response.new(response_document_2)
281
345
  assert_equal "wibble", response.issuer
282
346
  end
283
347
  end
284
348
 
285
- context "#success" do
286
- should "find a status code that says success" do
349
+ describe "#success" do
350
+ it "find a status code that says success" do
287
351
  response = OneLogin::RubySaml::Response.new(response_document)
288
- response.success?
352
+ assert response.send(:success?)
289
353
  end
290
354
  end
291
355
 
292
- context '#xpath_first_from_signed_assertion' do
293
- should 'not allow arbitrary code execution' do
356
+ describe '#xpath_first_from_signed_assertion' do
357
+ it 'not allow arbitrary code execution' do
294
358
  malicious_response_document = fixture('response_eval', false)
295
359
  response = OneLogin::RubySaml::Response.new(malicious_response_document)
296
360
  response.send(:xpath_first_from_signed_assertion)
297
- assert_equal($evalled, nil)
361
+ assert_nil $evalled
298
362
  end
299
363
  end
300
364
 
301
- context "#multiple values" do
302
- should "extract single value as string" do
365
+ describe "#multiple values" do
366
+ it "extract single value as string" do
303
367
  assert_equal "demo", response_multiple_attr_values.attributes[:uid]
304
368
  end
305
369
 
306
- should "extract single value as string in compatibility mode off" do
370
+ it "extract single value as string in compatibility mode off" do
307
371
  OneLogin::RubySaml::Attributes.single_value_compatibility = false
308
372
  assert_equal ["demo"], response_multiple_attr_values.attributes[:uid]
309
373
  # classes are not reloaded between tests so restore default
310
374
  OneLogin::RubySaml::Attributes.single_value_compatibility = true
311
375
  end
312
376
 
313
- should "extract first of multiple values as string for b/w compatibility" do
377
+ it "extract first of multiple values as string for b/w compatibility" do
314
378
  assert_equal 'value1', response_multiple_attr_values.attributes[:another_value]
315
379
  end
316
380
 
317
- should "extract first of multiple values as string for b/w compatibility in compatibility mode off" do
381
+ it "extract first of multiple values as string for b/w compatibility in compatibility mode off" do
318
382
  OneLogin::RubySaml::Attributes.single_value_compatibility = false
319
383
  assert_equal ['value1', 'value2'], response_multiple_attr_values.attributes[:another_value]
320
384
  OneLogin::RubySaml::Attributes.single_value_compatibility = true
321
385
  end
322
386
 
323
- should "return array with all attributes when asked in XML order" do
387
+ it "return array with all attributes when asked in XML order" do
324
388
  assert_equal ['value1', 'value2'], response_multiple_attr_values.attributes.multi(:another_value)
325
389
  end
326
390
 
327
- should "return array with all attributes when asked in XML order in compatibility mode off" do
391
+ it "return array with all attributes when asked in XML order in compatibility mode off" do
328
392
  OneLogin::RubySaml::Attributes.single_value_compatibility = false
329
393
  assert_equal ['value1', 'value2'], response_multiple_attr_values.attributes.multi(:another_value)
330
394
  OneLogin::RubySaml::Attributes.single_value_compatibility = true
331
395
  end
332
396
 
333
- should "return first of multiple values when multiple Attribute tags in XML" do
397
+ it "return first of multiple values when multiple Attribute tags in XML" do
334
398
  assert_equal 'role1', response_multiple_attr_values.attributes[:role]
335
399
  end
336
400
 
337
- should "return first of multiple values when multiple Attribute tags in XML in compatibility mode off" do
401
+ it "return first of multiple values when multiple Attribute tags in XML in compatibility mode off" do
338
402
  OneLogin::RubySaml::Attributes.single_value_compatibility = false
339
403
  assert_equal ['role1', 'role2', 'role3'], response_multiple_attr_values.attributes[:role]
340
404
  OneLogin::RubySaml::Attributes.single_value_compatibility = true
341
405
  end
342
406
 
343
- should "return all of multiple values in reverse order when multiple Attribute tags in XML" do
407
+ it "return all of multiple values in reverse order when multiple Attribute tags in XML" do
344
408
  assert_equal ['role1', 'role2', 'role3'], response_multiple_attr_values.attributes.multi(:role)
345
409
  end
346
410
 
347
- should "return all of multiple values in reverse order when multiple Attribute tags in XML in compatibility mode off" do
411
+ it "return all of multiple values in reverse order when multiple Attribute tags in XML in compatibility mode off" do
348
412
  OneLogin::RubySaml::Attributes.single_value_compatibility = false
349
413
  assert_equal ['role1', 'role2', 'role3'], response_multiple_attr_values.attributes.multi(:role)
350
414
  OneLogin::RubySaml::Attributes.single_value_compatibility = true
351
415
  end
352
416
 
353
- should "return all of multiple values when multiple Attribute tags in multiple AttributeStatement tags" do
417
+ it "return all of multiple values when multiple Attribute tags in multiple AttributeStatement tags" do
354
418
  OneLogin::RubySaml::Attributes.single_value_compatibility = false
355
419
  assert_equal ['role1', 'role2', 'role3'], response_with_multiple_attribute_statements.attributes.multi(:role)
356
420
  OneLogin::RubySaml::Attributes.single_value_compatibility = true
357
421
  end
358
422
 
359
- should "return nil value correctly" do
423
+ it "return nil value correctly" do
360
424
  assert_nil response_multiple_attr_values.attributes[:attribute_with_nil_value]
361
425
  end
362
426
 
363
- should "return nil value correctly when not in compatibility mode off" do
427
+ it "return nil value correctly when not in compatibility mode off" do
364
428
  OneLogin::RubySaml::Attributes.single_value_compatibility = false
365
- assert_equal [nil], response_multiple_attr_values.attributes[:attribute_with_nil_value]
429
+ assert [nil] == response_multiple_attr_values.attributes[:attribute_with_nil_value]
366
430
  OneLogin::RubySaml::Attributes.single_value_compatibility = true
367
431
  end
368
432
 
369
- should "return multiple values including nil and empty string" do
433
+ it "return multiple values including nil and empty string" do
370
434
  response = OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values))
371
435
  assert_equal ["", "valuePresent", nil, nil], response.attributes.multi(:attribute_with_nils_and_empty_strings)
372
436
  end
373
437
 
374
- should "return multiple values from [] when not in compatibility mode off" do
438
+ it "return multiple values from [] when not in compatibility mode off" do
375
439
  OneLogin::RubySaml::Attributes.single_value_compatibility = false
376
440
  assert_equal ["", "valuePresent", nil, nil], response_multiple_attr_values.attributes[:attribute_with_nils_and_empty_strings]
377
441
  OneLogin::RubySaml::Attributes.single_value_compatibility = true
378
442
  end
379
443
 
380
- should "check what happens when trying retrieve attribute that does not exists" do
381
- assert_equal nil, response_multiple_attr_values.attributes[:attribute_not_exists]
382
- assert_equal nil, response_multiple_attr_values.attributes.single(:attribute_not_exists)
383
- assert_equal nil, response_multiple_attr_values.attributes.multi(:attribute_not_exists)
444
+ it "check what happens when trying retrieve attribute that does not exists" do
445
+ assert_nil response_multiple_attr_values.attributes[:attribute_not_exists]
446
+ assert_nil response_multiple_attr_values.attributes.single(:attribute_not_exists)
447
+ assert_nil response_multiple_attr_values.attributes.multi(:attribute_not_exists)
384
448
 
385
449
  OneLogin::RubySaml::Attributes.single_value_compatibility = false
386
- assert_equal nil, response_multiple_attr_values.attributes[:attribute_not_exists]
387
- assert_equal nil, response_multiple_attr_values.attributes.single(:attribute_not_exists)
388
- assert_equal nil, response_multiple_attr_values.attributes.multi(:attribute_not_exists)
450
+ assert_nil response_multiple_attr_values.attributes[:attribute_not_exists]
451
+ assert_nil response_multiple_attr_values.attributes.single(:attribute_not_exists)
452
+ assert_nil response_multiple_attr_values.attributes.multi(:attribute_not_exists)
389
453
  OneLogin::RubySaml::Attributes.single_value_compatibility = true
390
454
  end
391
455
  end
456
+
457
+ describe "signature wrapping attack with encrypted assertion" do
458
+ it "should not be valid" do
459
+ settings = OneLogin::RubySaml::Settings.new
460
+ settings.private_key = valid_key
461
+ signature_wrapping_attack = read_response("encrypted_new_attack.xml.base64")
462
+ response_wrapped = OneLogin::RubySaml::Response.new(signature_wrapping_attack, :settings => settings)
463
+ response_wrapped.stubs(:conditions).returns(nil)
464
+ response_wrapped.stubs(:validate_subject_confirmation).returns(true)
465
+ settings.idp_cert_fingerprint = "385b1eec71143f00db6af936e2ea12a28771d72c"
466
+ assert !response_wrapped.is_valid?
467
+ end
468
+ end
469
+
470
+ describe "signature wrapping attack - concealed SAML response body" do
471
+ it "should not be valid" do
472
+ settings = OneLogin::RubySaml::Settings.new
473
+ signature_wrapping_attack = read_response("response_with_concealed_signed_assertion.xml")
474
+ response_wrapped = OneLogin::RubySaml::Response.new(signature_wrapping_attack, :settings => settings)
475
+ settings.idp_cert_fingerprint = '4b68c453c7d994aad9025c99d5efcf566287fe8d'
476
+ response_wrapped.stubs(:conditions).returns(nil)
477
+ response_wrapped.stubs(:validate_subject_confirmation).returns(true)
478
+ assert !response_wrapped.is_valid?
479
+ assert_raises(OneLogin::RubySaml::ValidationError, "SAML Response must contain 1 assertion"){ response_wrapped.validate! }
480
+ end
481
+ end
482
+
483
+ describe "signature wrapping attack - doubled signed assertion SAML response" do
484
+ it "should not be valid" do
485
+ settings = OneLogin::RubySaml::Settings.new
486
+ signature_wrapping_attack = read_response("response_with_doubled_signed_assertion.xml")
487
+ response_wrapped = OneLogin::RubySaml::Response.new(signature_wrapping_attack, :settings => settings)
488
+ settings.idp_cert_fingerprint = '4b68c453c7d994aad9025c99d5efcf566287fe8d'
489
+ response_wrapped.stubs(:conditions).returns(nil)
490
+ response_wrapped.stubs(:validate_subject_confirmation).returns(true)
491
+ assert !response_wrapped.is_valid?
492
+ end
493
+ end
392
494
  end
393
495
  end