ruby-saml 0.8.8 → 0.8.13

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 (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