ruby-saml 0.9.4 → 1.0.0

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.
Files changed (101) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/LICENSE +1 -1
  4. data/README.md +71 -15
  5. data/changelog.md +15 -6
  6. data/lib/onelogin/ruby-saml.rb +1 -0
  7. data/lib/onelogin/ruby-saml/attribute_service.rb +25 -2
  8. data/lib/onelogin/ruby-saml/attributes.rb +42 -23
  9. data/lib/onelogin/ruby-saml/authrequest.rb +33 -8
  10. data/lib/onelogin/ruby-saml/http_error.rb +7 -0
  11. data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +65 -10
  12. data/lib/onelogin/ruby-saml/logging.rb +14 -10
  13. data/lib/onelogin/ruby-saml/logoutrequest.rb +39 -14
  14. data/lib/onelogin/ruby-saml/logoutresponse.rb +166 -39
  15. data/lib/onelogin/ruby-saml/metadata.rb +40 -23
  16. data/lib/onelogin/ruby-saml/response.rb +562 -88
  17. data/lib/onelogin/ruby-saml/saml_message.rb +80 -14
  18. data/lib/onelogin/ruby-saml/settings.rb +62 -23
  19. data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +210 -20
  20. data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +44 -13
  21. data/lib/onelogin/ruby-saml/utils.rb +163 -40
  22. data/lib/onelogin/ruby-saml/version.rb +1 -1
  23. data/lib/schemas/saml-schema-metadata-2.0.xsd +0 -2
  24. data/lib/xml_security.rb +87 -29
  25. data/ruby-saml.gemspec +1 -0
  26. data/test/certificates/{r1_certificate2_base64 → certificate_without_head_foot} +0 -0
  27. data/test/certificates/formatted_certificate +14 -0
  28. data/test/certificates/formatted_private_key +12 -0
  29. data/test/certificates/formatted_rsa_private_key +12 -0
  30. data/test/certificates/invalid_certificate1 +1 -0
  31. data/test/certificates/invalid_certificate2 +1 -0
  32. data/test/certificates/invalid_certificate3 +12 -0
  33. data/test/certificates/invalid_private_key1 +1 -0
  34. data/test/certificates/invalid_private_key2 +1 -0
  35. data/test/certificates/invalid_private_key3 +10 -0
  36. data/test/certificates/invalid_rsa_private_key1 +1 -0
  37. data/test/certificates/invalid_rsa_private_key2 +1 -0
  38. data/test/certificates/invalid_rsa_private_key3 +10 -0
  39. data/test/idp_metadata_parser_test.rb +41 -4
  40. data/test/logging_test.rb +62 -0
  41. data/test/logout_requests/invalid_slo_request.xml +6 -0
  42. data/test/{responses → logout_requests}/slo_request.xml +0 -0
  43. data/test/logout_requests/slo_request.xml.base64 +1 -0
  44. data/test/logout_requests/slo_request_deflated.xml.base64 +1 -0
  45. data/test/logout_requests/slo_request_with_session_index.xml +5 -0
  46. data/test/{responses → logout_responses}/logoutresponse_fixtures.rb +6 -6
  47. data/test/logoutrequest_test.rb +79 -52
  48. data/test/logoutresponse_test.rb +206 -59
  49. data/test/metadata_test.rb +77 -7
  50. data/test/request_test.rb +80 -65
  51. data/test/response_test.rb +862 -189
  52. data/test/responses/attackxee.xml +13 -0
  53. data/test/responses/invalids/invalid_audience.xml.base64 +1 -0
  54. data/test/responses/invalids/invalid_issuer_assertion.xml.base64 +1 -0
  55. data/test/responses/invalids/invalid_issuer_message.xml.base64 +1 -0
  56. data/test/responses/invalids/invalid_signature_position.xml.base64 +1 -0
  57. data/test/responses/invalids/invalid_subjectconfirmation_inresponse.xml.base64 +1 -0
  58. data/test/responses/invalids/invalid_subjectconfirmation_nb.xml.base64 +1 -0
  59. data/test/responses/invalids/invalid_subjectconfirmation_noa.xml.base64 +1 -0
  60. data/test/responses/invalids/invalid_subjectconfirmation_recipient.xml.base64 +1 -0
  61. data/test/responses/invalids/multiple_assertions.xml.base64 +2 -0
  62. data/test/responses/invalids/multiple_signed.xml.base64 +1 -0
  63. data/test/responses/invalids/no_id.xml.base64 +1 -0
  64. data/test/responses/invalids/no_saml2.xml.base64 +1 -0
  65. data/test/responses/invalids/no_signature.xml.base64 +1 -0
  66. data/test/responses/invalids/no_status.xml.base64 +1 -0
  67. data/test/responses/invalids/no_status_code.xml.base64 +1 -0
  68. data/test/responses/invalids/no_subjectconfirmation_data.xml.base64 +1 -0
  69. data/test/responses/invalids/no_subjectconfirmation_method.xml.base64 +1 -0
  70. data/test/responses/invalids/response_encrypted_attrs.xml.base64 +1 -0
  71. data/test/responses/invalids/response_invalid_signed_element.xml.base64 +1 -0
  72. data/test/responses/invalids/status_code_responder.xml.base64 +1 -0
  73. data/test/responses/invalids/status_code_responer_and_msg.xml.base64 +1 -0
  74. data/test/responses/{response4.xml.base64 → response_assertion_wrapped.xml.base64} +0 -0
  75. data/test/responses/response_encrypted_nameid.xml.base64 +1 -0
  76. data/test/responses/response_unsigned_xml_base64 +1 -0
  77. data/test/responses/{response5.xml.base64 → response_with_saml2_namespace.xml.base64} +0 -0
  78. data/test/responses/{response3.xml.base64 → response_with_signed_assertion.xml.base64} +0 -0
  79. data/test/responses/{r1_response6.xml.base64 → response_with_signed_assertion_2.xml.base64} +0 -0
  80. data/test/responses/{response1.xml.base64 → response_with_undefined_recipient.xml.base64} +0 -0
  81. data/test/responses/{response2.xml.base64 → response_without_attributes.xml.base64} +0 -0
  82. data/test/responses/{wrapped_response_2.xml.base64 → response_wrapped.xml.base64} +0 -0
  83. data/test/responses/signed_message_encrypted_signed_assertion.xml.base64 +1 -0
  84. data/test/responses/signed_message_encrypted_unsigned_assertion.xml.base64 +1 -0
  85. data/test/responses/unsigned_message_aes128_encrypted_signed_assertion.xml.base64 +1 -0
  86. data/test/responses/unsigned_message_aes192_encrypted_signed_assertion.xml.base64 +1 -0
  87. data/test/responses/unsigned_message_aes256_encrypted_signed_assertion.xml.base64 +1 -0
  88. data/test/responses/unsigned_message_des192_encrypted_signed_assertion.xml.base64 +1 -0
  89. data/test/responses/unsigned_message_encrypted_assertion_without_saml_namespace.xml.base64 +1 -0
  90. data/test/responses/unsigned_message_encrypted_signed_assertion.xml.base64 +1 -0
  91. data/test/responses/unsigned_message_encrypted_unsigned_assertion.xml.base64 +1 -0
  92. data/test/responses/valid_response.xml.base64 +1 -0
  93. data/test/saml_message_test.rb +56 -0
  94. data/test/settings_test.rb +138 -1
  95. data/test/slo_logoutrequest_test.rb +239 -28
  96. data/test/slo_logoutresponse_test.rb +93 -71
  97. data/test/test_helper.rb +138 -31
  98. data/test/utils_test.rb +129 -25
  99. data/test/xml_security_test.rb +140 -71
  100. metadata +142 -25
  101. data/test/responses/response_node_text_attack.xml.base64 +0 -1
@@ -5,11 +5,15 @@ require 'onelogin/ruby-saml/authrequest'
5
5
  class RequestTest < Minitest::Test
6
6
 
7
7
  describe "Authrequest" do
8
- it "create the deflated SAMLRequest URL parameter" do
9
- settings = OneLogin::RubySaml::Settings.new
8
+ let(:settings) { OneLogin::RubySaml::Settings.new }
9
+
10
+ before do
10
11
  settings.idp_sso_target_url = "http://example.com"
12
+ end
13
+
14
+ it "create the deflated SAMLRequest URL parameter" do
11
15
  auth_url = OneLogin::RubySaml::Authrequest.new.create(settings)
12
- assert auth_url =~ /^http:\/\/example\.com\?SAMLRequest=/
16
+ assert_match /^http:\/\/example\.com\?SAMLRequest=/, auth_url
13
17
  payload = CGI.unescape(auth_url.split("=").last)
14
18
  decoded = Base64.decode64(payload)
15
19
 
@@ -22,8 +26,6 @@ class RequestTest < Minitest::Test
22
26
  end
23
27
 
24
28
  it "create the deflated SAMLRequest URL parameter including the Destination" do
25
- settings = OneLogin::RubySaml::Settings.new
26
- settings.idp_sso_target_url = "http://example.com"
27
29
  auth_url = OneLogin::RubySaml::Authrequest.new.create(settings)
28
30
  payload = CGI.unescape(auth_url.split("=").last)
29
31
  decoded = Base64.decode64(payload)
@@ -37,11 +39,9 @@ class RequestTest < Minitest::Test
37
39
  end
38
40
 
39
41
  it "create the SAMLRequest URL parameter without deflating" do
40
- settings = OneLogin::RubySaml::Settings.new
41
42
  settings.compress_request = false
42
- settings.idp_sso_target_url = "http://example.com"
43
43
  auth_url = OneLogin::RubySaml::Authrequest.new.create(settings)
44
- assert auth_url =~ /^http:\/\/example\.com\?SAMLRequest=/
44
+ assert_match /^http:\/\/example\.com\?SAMLRequest=/, auth_url
45
45
  payload = CGI.unescape(auth_url.split("=").last)
46
46
  decoded = Base64.decode64(payload)
47
47
 
@@ -49,11 +49,9 @@ class RequestTest < Minitest::Test
49
49
  end
50
50
 
51
51
  it "create the SAMLRequest URL parameter with IsPassive" do
52
- settings = OneLogin::RubySaml::Settings.new
53
- settings.idp_sso_target_url = "http://example.com"
54
52
  settings.passive = true
55
53
  auth_url = OneLogin::RubySaml::Authrequest.new.create(settings)
56
- assert auth_url =~ /^http:\/\/example\.com\?SAMLRequest=/
54
+ assert_match /^http:\/\/example\.com\?SAMLRequest=/, auth_url
57
55
  payload = CGI.unescape(auth_url.split("=").last)
58
56
  decoded = Base64.decode64(payload)
59
57
 
@@ -66,11 +64,9 @@ class RequestTest < Minitest::Test
66
64
  end
67
65
 
68
66
  it "create the SAMLRequest URL parameter with ProtocolBinding" do
69
- settings = OneLogin::RubySaml::Settings.new
70
- settings.idp_sso_target_url = "http://example.com"
71
67
  settings.protocol_binding = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'
72
68
  auth_url = OneLogin::RubySaml::Authrequest.new.create(settings)
73
- assert auth_url =~ /^http:\/\/example\.com\?SAMLRequest=/
69
+ assert_match /^http:\/\/example\.com\?SAMLRequest=/, auth_url
74
70
  payload = CGI.unescape(auth_url.split("=").last)
75
71
  decoded = Base64.decode64(payload)
76
72
 
@@ -83,11 +79,9 @@ class RequestTest < Minitest::Test
83
79
  end
84
80
 
85
81
  it "create the SAMLRequest URL parameter with AttributeConsumingServiceIndex" do
86
- settings = OneLogin::RubySaml::Settings.new
87
- settings.idp_sso_target_url = "http://example.com"
88
82
  settings.attributes_index = 30
89
83
  auth_url = OneLogin::RubySaml::Authrequest.new.create(settings)
90
- assert auth_url =~ /^http:\/\/example\.com\?SAMLRequest=/
84
+ assert_match /^http:\/\/example\.com\?SAMLRequest=/, auth_url
91
85
  payload = CGI.unescape(auth_url.split("=").last)
92
86
  decoded = Base64.decode64(payload)
93
87
 
@@ -99,11 +93,9 @@ class RequestTest < Minitest::Test
99
93
  end
100
94
 
101
95
  it "create the SAMLRequest URL parameter with ForceAuthn" do
102
- settings = OneLogin::RubySaml::Settings.new
103
- settings.idp_sso_target_url = "http://example.com"
104
96
  settings.force_authn = true
105
97
  auth_url = OneLogin::RubySaml::Authrequest.new.create(settings)
106
- assert auth_url =~ /^http:\/\/example\.com\?SAMLRequest=/
98
+ assert_match /^http:\/\/example\.com\?SAMLRequest=/, auth_url
107
99
  payload = CGI.unescape(auth_url.split("=").last)
108
100
  decoded = Base64.decode64(payload)
109
101
 
@@ -114,66 +106,96 @@ class RequestTest < Minitest::Test
114
106
  assert_match /<samlp:AuthnRequest[^<]* ForceAuthn='true'/, inflated
115
107
  end
116
108
 
117
- it "accept extra parameters" do
118
- settings = OneLogin::RubySaml::Settings.new
119
- settings.idp_sso_target_url = "http://example.com"
109
+ it "create the SAMLRequest URL parameter with NameID Format" do
110
+ settings.name_identifier_format = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
111
+ auth_url = OneLogin::RubySaml::Authrequest.new.create(settings)
112
+ assert_match /^http:\/\/example\.com\?SAMLRequest=/, auth_url
113
+ payload = CGI.unescape(auth_url.split("=").last)
114
+ decoded = Base64.decode64(payload)
115
+ zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS)
116
+ inflated = zstream.inflate(decoded)
117
+ zstream.finish
118
+ zstream.close
119
+
120
+ assert_match /<samlp:NameIDPolicy[^<]* AllowCreate='true'/, inflated
121
+ assert_match /<samlp:NameIDPolicy[^<]* Format='urn:oasis:names:tc:SAML:2.0:nameid-format:transient'/, inflated
122
+ end
120
123
 
124
+ it "accept extra parameters" do
121
125
  auth_url = OneLogin::RubySaml::Authrequest.new.create(settings, { :hello => "there" })
122
- assert auth_url =~ /&hello=there$/
126
+ assert_match /&hello=there$/, auth_url
123
127
 
124
128
  auth_url = OneLogin::RubySaml::Authrequest.new.create(settings, { :hello => nil })
125
- assert auth_url =~ /&hello=$/
129
+ assert_match /&hello=$/, auth_url
126
130
  end
127
131
 
128
132
  describe "when the target url doesn't contain a query string" do
129
133
  it "create the SAMLRequest parameter correctly" do
130
- settings = OneLogin::RubySaml::Settings.new
131
- settings.idp_sso_target_url = "http://example.com"
132
134
 
133
135
  auth_url = OneLogin::RubySaml::Authrequest.new.create(settings)
134
- assert auth_url =~ /^http:\/\/example.com\?SAMLRequest/
136
+ assert_match /^http:\/\/example.com\?SAMLRequest/, auth_url
135
137
  end
136
138
  end
137
139
 
138
140
  describe "when the target url contains a query string" do
139
141
  it "create the SAMLRequest parameter correctly" do
140
- settings = OneLogin::RubySaml::Settings.new
141
142
  settings.idp_sso_target_url = "http://example.com?field=value"
142
143
 
143
144
  auth_url = OneLogin::RubySaml::Authrequest.new.create(settings)
144
- assert auth_url =~ /^http:\/\/example.com\?field=value&SAMLRequest/
145
+ assert_match /^http:\/\/example.com\?field=value&SAMLRequest/, auth_url
145
146
  end
146
147
  end
147
148
 
149
+ it "create the saml:AuthnContextClassRef element correctly" do
150
+ settings.authn_context = 'secure/name/password/uri'
151
+ auth_doc = OneLogin::RubySaml::Authrequest.new.create_authentication_xml_doc(settings)
152
+ assert_match /<saml:AuthnContextClassRef>secure\/name\/password\/uri<\/saml:AuthnContextClassRef>/, auth_doc.to_s
153
+ end
154
+
155
+ it "create the saml:AuthnContextClassRef with comparison exact" do
156
+ settings.authn_context = 'secure/name/password/uri'
157
+ auth_doc = OneLogin::RubySaml::Authrequest.new.create_authentication_xml_doc(settings)
158
+ assert_match /<samlp:RequestedAuthnContext[\S ]+Comparison='exact'/, auth_doc.to_s
159
+ assert_match /<saml:AuthnContextClassRef>secure\/name\/password\/uri<\/saml:AuthnContextClassRef>/, auth_doc.to_s
160
+ end
161
+
162
+ it "create the saml:AuthnContextClassRef with comparison minimun" do
163
+ settings.authn_context = 'secure/name/password/uri'
164
+ settings.authn_context_comparison = 'minimun'
165
+ auth_doc = OneLogin::RubySaml::Authrequest.new.create_authentication_xml_doc(settings)
166
+ assert_match /<samlp:RequestedAuthnContext[\S ]+Comparison='minimun'/, auth_doc.to_s
167
+ assert_match /<saml:AuthnContextClassRef>secure\/name\/password\/uri<\/saml:AuthnContextClassRef>/, auth_doc.to_s
168
+ end
169
+
170
+ it "create the saml:AuthnContextDeclRef element correctly" do
171
+ settings.authn_context_decl_ref = 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport'
172
+ auth_doc = OneLogin::RubySaml::Authrequest.new.create_authentication_xml_doc(settings)
173
+ assert_match /<saml:AuthnContextDeclRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport<\/saml:AuthnContextDeclRef>/, auth_doc.to_s
174
+ end
175
+
148
176
  describe "#create_params when the settings indicate to sign (embebed) the request" do
149
- it "create a signed request" do
150
- settings = OneLogin::RubySaml::Settings.new
177
+ before do
151
178
  settings.compress_request = false
152
179
  settings.idp_sso_target_url = "http://example.com?field=value"
153
180
  settings.security[:authn_requests_signed] = true
154
181
  settings.security[:embed_sign] = true
155
- settings.certificate = ruby_saml_cert_text
182
+ settings.certificate = ruby_saml_cert_text
156
183
  settings.private_key = ruby_saml_key_text
184
+ end
157
185
 
186
+ it "create a signed request" do
158
187
  params = OneLogin::RubySaml::Authrequest.new.create_params(settings)
159
188
  request_xml = Base64.decode64(params["SAMLRequest"])
160
189
  assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], request_xml
161
190
  assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], request_xml
162
- assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/>], request_xml
163
191
  end
164
192
 
165
193
  it "create a signed request with 256 digest and signature methods" do
166
- settings = OneLogin::RubySaml::Settings.new
167
- settings.compress_request = false
168
- settings.idp_sso_target_url = "http://example.com?field=value"
169
- settings.security[:authn_requests_signed] = true
170
- settings.security[:embed_sign] = true
171
194
  settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256
172
195
  settings.security[:digest_method] = XMLSecurity::Document::SHA512
173
- settings.certificate = ruby_saml_cert_text
174
- settings.private_key = ruby_saml_key_text
175
196
 
176
197
  params = OneLogin::RubySaml::Authrequest.new.create_params(settings)
198
+
177
199
  request_xml = Base64.decode64(params["SAMLRequest"])
178
200
  assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], request_xml
179
201
  assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'/>], request_xml
@@ -182,22 +204,22 @@ class RequestTest < Minitest::Test
182
204
  end
183
205
 
184
206
  describe "#create_params when the settings indicate to sign the request" do
185
- def setup
186
- @settings = OneLogin::RubySaml::Settings.new
187
- @settings.compress_request = false
188
- @settings.idp_sso_target_url = "http://example.com?field=value"
189
- @settings.assertion_consumer_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign"
190
- @settings.security[:authn_requests_signed] = true
191
- @settings.security[:embed_sign] = false
192
- @settings.certificate = ruby_saml_cert_text
193
- @settings.private_key = ruby_saml_key_text
194
- @cert = OpenSSL::X509::Certificate.new(ruby_saml_cert_text)
207
+ let(:cert) { OpenSSL::X509::Certificate.new(ruby_saml_cert_text) }
208
+
209
+ before do
210
+ settings.compress_request = false
211
+ settings.idp_sso_target_url = "http://example.com?field=value"
212
+ settings.assertion_consumer_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign"
213
+ settings.security[:authn_requests_signed] = true
214
+ settings.security[:embed_sign] = false
215
+ settings.certificate = ruby_saml_cert_text
216
+ settings.private_key = ruby_saml_key_text
195
217
  end
196
218
 
197
219
  it "create a signature parameter with RSA_SHA1 and validate it" do
198
- @settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1
220
+ settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1
199
221
 
200
- params = OneLogin::RubySaml::Authrequest.new.create_params(@settings, :RelayState => 'http://example.com')
222
+ params = OneLogin::RubySaml::Authrequest.new.create_params(settings, :RelayState => 'http://example.com')
201
223
  assert params['SAMLRequest']
202
224
  assert params[:RelayState]
203
225
  assert params['Signature']
@@ -209,13 +231,14 @@ class RequestTest < Minitest::Test
209
231
 
210
232
  signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg'])
211
233
  assert_equal signature_algorithm, OpenSSL::Digest::SHA1
212
- assert @cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
234
+
235
+ assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
213
236
  end
214
237
 
215
238
  it "create a signature parameter with RSA_SHA256 and validate it" do
216
- @settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256
239
+ settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256
217
240
 
218
- params = OneLogin::RubySaml::Authrequest.new.create_params(@settings, :RelayState => 'http://example.com')
241
+ params = OneLogin::RubySaml::Authrequest.new.create_params(settings, :RelayState => 'http://example.com')
219
242
  assert params['Signature']
220
243
  assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA256
221
244
 
@@ -225,21 +248,17 @@ class RequestTest < Minitest::Test
225
248
 
226
249
  signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg'])
227
250
  assert_equal signature_algorithm, OpenSSL::Digest::SHA256
228
- assert @cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
251
+ assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
229
252
  end
230
253
  end
231
254
 
232
255
  it "create the saml:AuthnContextClassRef element correctly" do
233
- settings = OneLogin::RubySaml::Settings.new
234
- settings.idp_sso_target_url = "http://example.com"
235
256
  settings.authn_context = 'secure/name/password/uri'
236
257
  auth_doc = OneLogin::RubySaml::Authrequest.new.create_authentication_xml_doc(settings)
237
258
  assert auth_doc.to_s =~ /<saml:AuthnContextClassRef>secure\/name\/password\/uri<\/saml:AuthnContextClassRef>/
238
259
  end
239
260
 
240
261
  it "create the saml:AuthnContextClassRef with comparison exact" do
241
- settings = OneLogin::RubySaml::Settings.new
242
- settings.idp_sso_target_url = "http://example.com"
243
262
  settings.authn_context = 'secure/name/password/uri'
244
263
  auth_doc = OneLogin::RubySaml::Authrequest.new.create_authentication_xml_doc(settings)
245
264
  assert auth_doc.to_s =~ /<samlp:RequestedAuthnContext[\S ]+Comparison='exact'/
@@ -247,8 +266,6 @@ class RequestTest < Minitest::Test
247
266
  end
248
267
 
249
268
  it "create the saml:AuthnContextClassRef with comparison minimun" do
250
- settings = OneLogin::RubySaml::Settings.new
251
- settings.idp_sso_target_url = "http://example.com"
252
269
  settings.authn_context = 'secure/name/password/uri'
253
270
  settings.authn_context_comparison = 'minimun'
254
271
  auth_doc = OneLogin::RubySaml::Authrequest.new.create_authentication_xml_doc(settings)
@@ -257,8 +274,6 @@ class RequestTest < Minitest::Test
257
274
  end
258
275
 
259
276
  it "create the saml:AuthnContextDeclRef element correctly" do
260
- settings = OneLogin::RubySaml::Settings.new
261
- settings.idp_sso_target_url = "http://example.com"
262
277
  settings.authn_context_decl_ref = 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport'
263
278
  auth_doc = OneLogin::RubySaml::Authrequest.new.create_authentication_xml_doc(settings)
264
279
  assert auth_doc.to_s =~ /<saml:AuthnContextDeclRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport<\/saml:AuthnContextDeclRef>/
@@ -5,6 +5,38 @@ require 'onelogin/ruby-saml/response'
5
5
  class RubySamlTest < Minitest::Test
6
6
 
7
7
  describe "Response" do
8
+
9
+ let(:settings) { OneLogin::RubySaml::Settings.new }
10
+ let(:response) { OneLogin::RubySaml::Response.new(response_document_without_recipient) }
11
+ let(:response_without_attributes) { OneLogin::RubySaml::Response.new(response_document_without_attributes) }
12
+ let(:response_with_signed_assertion) { OneLogin::RubySaml::Response.new(response_document_with_signed_assertion) }
13
+ let(:response_unsigned) { OneLogin::RubySaml::Response.new(response_document_unsigned) }
14
+ let(:response_wrapped) { OneLogin::RubySaml::Response.new(response_document_wrapped) }
15
+ let(:response_multiple_attr_values) { OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values)) }
16
+ let(:response_valid_signed) { OneLogin::RubySaml::Response.new(response_document_valid_signed) }
17
+ let(:response_no_id) { OneLogin::RubySaml::Response.new(read_invalid_response("no_id.xml.base64")) }
18
+ let(:response_no_version) { OneLogin::RubySaml::Response.new(read_invalid_response("no_saml2.xml.base64")) }
19
+ let(:response_multi_assertion) { OneLogin::RubySaml::Response.new(read_invalid_response("multiple_assertions.xml.base64")) }
20
+ let(:response_no_status) { OneLogin::RubySaml::Response.new(read_invalid_response("no_status.xml.base64")) }
21
+ let(:response_no_statuscode) { OneLogin::RubySaml::Response.new(read_invalid_response("no_status_code.xml.base64")) }
22
+ let(:response_statuscode_responder) { OneLogin::RubySaml::Response.new(read_invalid_response("status_code_responder.xml.base64")) }
23
+ let(:response_statuscode_responder_and_msg) { OneLogin::RubySaml::Response.new(read_invalid_response("status_code_responer_and_msg.xml.base64")) }
24
+ let(:response_encrypted_attrs) { OneLogin::RubySaml::Response.new(read_invalid_response("response_encrypted_attrs.xml.base64")) }
25
+ let(:response_no_signed_elements) { OneLogin::RubySaml::Response.new(read_invalid_response("no_signature.xml.base64")) }
26
+ let(:response_multiple_signed) { OneLogin::RubySaml::Response.new(read_invalid_response("multiple_signed.xml.base64")) }
27
+ let(:response_invalid_audience) { OneLogin::RubySaml::Response.new(read_invalid_response("invalid_audience.xml.base64")) }
28
+ let(:response_invalid_signed_element) { OneLogin::RubySaml::Response.new(read_invalid_response("response_invalid_signed_element.xml.base64")) }
29
+ let(:response_invalid_issuer_assertion) { OneLogin::RubySaml::Response.new(read_invalid_response("invalid_issuer_assertion.xml.base64")) }
30
+ let(:response_invalid_issuer_message) { OneLogin::RubySaml::Response.new(read_invalid_response("invalid_issuer_message.xml.base64")) }
31
+ let(:response_no_subjectconfirmation_data) { OneLogin::RubySaml::Response.new(read_invalid_response("no_subjectconfirmation_data.xml.base64")) }
32
+ let(:response_no_subjectconfirmation_method) { OneLogin::RubySaml::Response.new(read_invalid_response("no_subjectconfirmation_method.xml.base64")) }
33
+ let(:response_invalid_subjectconfirmation_inresponse) { OneLogin::RubySaml::Response.new(read_invalid_response("invalid_subjectconfirmation_inresponse.xml.base64")) }
34
+ let(:response_invalid_subjectconfirmation_recipient) { OneLogin::RubySaml::Response.new(read_invalid_response("invalid_subjectconfirmation_recipient.xml.base64")) }
35
+ let(:response_invalid_subjectconfirmation_nb) { OneLogin::RubySaml::Response.new(read_invalid_response("invalid_subjectconfirmation_nb.xml.base64")) }
36
+ let(:response_invalid_subjectconfirmation_noa) { OneLogin::RubySaml::Response.new(read_invalid_response("invalid_subjectconfirmation_noa.xml.base64")) }
37
+ let(:response_invalid_signature_position) { OneLogin::RubySaml::Response.new(read_invalid_response("invalid_signature_position.xml.base64")) }
38
+ let(:response_encrypted_nameid) { OneLogin::RubySaml::Response.new(response_document_encrypted_nameid) }
39
+
8
40
  it "raise an exception when response is initialized with nil" do
9
41
  assert_raises(ArgumentError) { OneLogin::RubySaml::Response.new(nil) }
10
42
  end
@@ -13,319 +45,753 @@ class RubySamlTest < Minitest::Test
13
45
  XMLSecurity::SignedDocument.any_instance.stubs(:digests_match?).returns(true)
14
46
  OneLogin::RubySaml::Response.any_instance.stubs(:validate_conditions).returns(true)
15
47
 
16
- response = OneLogin::RubySaml::Response.new(ampersands_response)
17
- settings = OneLogin::RubySaml::Settings.new
18
- settings.idp_cert_fingerprint = 'c51985d947f1be57082025050846eb27f6cab783'
19
- response.settings = settings
20
- response.validate!
48
+ ampersands_response = OneLogin::RubySaml::Response.new(ampersands_document)
49
+ ampersands_response.settings = settings
50
+ ampersands_response.settings.idp_cert_fingerprint = 'c51985d947f1be57082025050846eb27f6cab783'
51
+
52
+ assert !ampersands_response.is_valid?
53
+ assert_includes ampersands_response.errors, "SAML Response must contain 1 assertion"
54
+ end
55
+
56
+ describe "Prevent XEE attack" do
57
+ before do
58
+ @response = OneLogin::RubySaml::Response.new(fixture(:attackxee))
59
+ end
60
+
61
+ it "false when evil attack vector is present, soft = true" do
62
+ @response.soft = true
63
+ assert !@response.send(:validate_structure)
64
+ assert_includes @response.errors, "Invalid SAML Response. Not match the saml-schema-protocol-2.0.xsd"
65
+ end
66
+
67
+ it "raise when evil attack vector is present, soft = false " do
68
+ @response.soft = false
69
+
70
+ assert_raises(OneLogin::RubySaml::ValidationError) do
71
+ @response.send(:validate_structure)
72
+ end
73
+ end
21
74
  end
22
75
 
23
76
  it "adapt namespace" do
24
- response = OneLogin::RubySaml::Response.new(response_document)
25
- refute_nil response.name_id
26
- response = OneLogin::RubySaml::Response.new(response_document_2)
27
- refute_nil response.name_id
28
- response = OneLogin::RubySaml::Response.new(response_document_3)
29
- refute_nil response.name_id
77
+ refute_nil response.nameid
78
+ refute_nil response_without_attributes.nameid
79
+ refute_nil response_with_signed_assertion.nameid
30
80
  end
31
81
 
32
82
  it "default to raw input when a response is not Base64 encoded" do
33
- decoded = Base64.decode64(response_document_2)
34
- response = OneLogin::RubySaml::Response.new(decoded)
35
- assert response.document
83
+ decoded = Base64.decode64(response_document_without_attributes)
84
+ response_from_raw = OneLogin::RubySaml::Response.new(decoded)
85
+ assert response_from_raw.document
36
86
  end
37
87
 
38
88
  describe "Assertion" do
39
89
  it "only retreive an assertion with an ID that matches the signature's reference URI" do
40
- response = OneLogin::RubySaml::Response.new(wrapped_response_2)
41
- response.stubs(:conditions).returns(nil)
42
- settings = OneLogin::RubySaml::Settings.new
90
+ response_wrapped.stubs(:conditions).returns(nil)
43
91
  settings.idp_cert_fingerprint = signature_fingerprint_1
44
- response.settings = settings
45
- assert_nil response.name_id
92
+ response_wrapped.settings = settings
93
+ assert_nil response_wrapped.nameid
46
94
  end
47
95
  end
48
96
 
49
- describe "#validate!" do
50
- it "raise when encountering a condition that prevents the document from being valid" do
51
- response = OneLogin::RubySaml::Response.new(response_document)
52
- assert_raises(OneLogin::RubySaml::ValidationError) do
53
- response.validate!
97
+ describe "#is_valid?" do
98
+ describe "soft = false" do
99
+
100
+ before do
101
+ response.soft = false
102
+ response_valid_signed.soft = false
103
+ end
104
+
105
+ it "raise when response is initialized with blank data" do
106
+ blank_response = OneLogin::RubySaml::Response.new('')
107
+ blank_response.soft = false
108
+ error_msg = "Blank response"
109
+ assert_raises(OneLogin::RubySaml::ValidationError, error_msg) do
110
+ blank_response.is_valid?
111
+ end
112
+ assert_includes blank_response.errors, error_msg
113
+ end
114
+
115
+ it "raise when settings have not been set" do
116
+ error_msg = "No settings on response"
117
+ assert_raises(OneLogin::RubySaml::ValidationError, error_msg) do
118
+ response.is_valid?
119
+ end
120
+ assert_includes response.errors, error_msg
121
+ end
122
+
123
+ it "raise when No fingerprint or certificate on settings" do
124
+ settings.idp_cert_fingerprint = nil
125
+ settings.idp_cert = nil
126
+ response.settings = settings
127
+ error_msg = "No fingerprint or certificate on settings"
128
+ assert_raises(OneLogin::RubySaml::ValidationError, error_msg) do
129
+ response.is_valid?
130
+ end
131
+ assert_includes response.errors, error_msg
132
+ end
133
+
134
+ it "raise when signature wrapping attack" do
135
+ response_wrapped.stubs(:conditions).returns(nil)
136
+ response_wrapped.stubs(:validate_subject_confirmation).returns(true)
137
+ settings.idp_cert_fingerprint = signature_fingerprint_1
138
+ response_wrapped.settings = settings
139
+ assert !response_wrapped.is_valid?
140
+ end
141
+
142
+ it "validate SAML 2.0 XML structure" do
143
+ resp_xml = Base64.decode64(response_document_unsigned).gsub(/emailAddress/,'test')
144
+ response_unsigned_mod = OneLogin::RubySaml::Response.new(Base64.encode64(resp_xml))
145
+ response_unsigned_mod.stubs(:conditions).returns(nil)
146
+ settings.idp_cert_fingerprint = signature_fingerprint_1
147
+ response_unsigned_mod.settings = settings
148
+ response_unsigned_mod.soft = false
149
+ assert_raises(OneLogin::RubySaml::ValidationError, 'Digest mismatch') do
150
+ response_unsigned_mod.is_valid?
151
+ end
152
+ end
153
+
154
+ it "raise when encountering a condition that prevents the document from being valid" do
155
+ settings.idp_cert_fingerprint = ruby_saml_cert_fingerprint
156
+ response.settings = settings
157
+ response.soft = false
158
+ error_msg = "Current time is on or after NotOnOrAfter condition"
159
+ assert_raises(OneLogin::RubySaml::ValidationError, error_msg) do
160
+ response.is_valid?
161
+ end
162
+ assert_includes response.errors[0], error_msg
163
+ end
164
+
165
+ it "raise when encountering a SAML Response with bad formatted" do
166
+ settings.idp_cert_fingerprint = signature_fingerprint_1
167
+ response_without_attributes.settings = settings
168
+ response_without_attributes.soft = false
169
+ assert_raises(OneLogin::RubySaml::ValidationError) do
170
+ response_without_attributes.is_valid?
171
+ end
172
+ end
173
+
174
+ it "raise when the inResponseTo value does not match the Request ID" do
175
+ settings.soft = false
176
+ settings.idp_cert_fingerprint = signature_fingerprint_1
177
+ opts = {}
178
+ opts[:settings] = settings
179
+ opts[:matches_request_id] = "invalid_request_id"
180
+ response_valid_signed = OneLogin::RubySaml::Response.new(response_document_valid_signed, opts)
181
+ error_msg = "The InResponseTo of the Response: _fc4a34b0-7efb-012e-caae-782bcb13bb38, does not match the ID of the AuthNRequest sent by the SP: invalid_request_id"
182
+ assert_raises(OneLogin::RubySaml::ValidationError, error_msg) do
183
+ response_valid_signed.is_valid?
184
+ end
185
+ assert_includes response_valid_signed.errors, error_msg
186
+ end
187
+
188
+ it "raise when the assertion contains encrypted attributes" do
189
+ settings.idp_cert_fingerprint = signature_fingerprint_1
190
+ response_encrypted_attrs.settings = settings
191
+ response_encrypted_attrs.soft = false
192
+ error_msg = "There is an EncryptedAttribute in the Response and this SP not support them"
193
+ assert_raises(OneLogin::RubySaml::ValidationError, error_msg) do
194
+ response_encrypted_attrs.is_valid?
195
+ end
196
+ assert_includes response_encrypted_attrs.errors, error_msg
197
+ end
198
+
199
+ it "raise when there is no valid audience" do
200
+ settings.idp_cert_fingerprint = signature_fingerprint_1
201
+ settings.issuer = 'invalid'
202
+ response_valid_signed.settings = settings
203
+ response_valid_signed.soft = false
204
+ error_msg = "#{response_valid_signed.settings.issuer} is not a valid audience for this Response"
205
+ assert_raises(OneLogin::RubySaml::ValidationError, error_msg) do
206
+ response_valid_signed.is_valid?
207
+ end
208
+ assert_includes response_valid_signed.errors, error_msg
209
+ end
210
+
211
+ it "raise when no ID present in the SAML Response" do
212
+ settings.idp_cert_fingerprint = signature_fingerprint_1
213
+ response_no_id.settings = settings
214
+ response_no_id.soft = false
215
+ error_msg = "Missing ID attribute on SAML Response"
216
+ assert_raises(OneLogin::RubySaml::ValidationError, error_msg) do
217
+ response_no_id.is_valid?
218
+ end
219
+ assert_includes response_no_id.errors, error_msg
220
+ end
221
+
222
+ it "raise when no 2.0 Version present in the SAML Response" do
223
+ settings.idp_cert_fingerprint = signature_fingerprint_1
224
+ response_no_version.settings = settings
225
+ response_no_version.soft = false
226
+ error_msg = "Unsupported SAML version"
227
+ assert_raises(OneLogin::RubySaml::ValidationError, error_msg) do
228
+ response_no_version.is_valid?
229
+ end
230
+ assert_includes response_no_version.errors, error_msg
231
+ end
232
+ end
233
+
234
+ describe "soft = true" do
235
+ before do
236
+ response.soft = true
237
+ response_valid_signed.soft = true
238
+ end
239
+
240
+ it "return true when the response is initialized with valid data" do
241
+ response_valid_signed.stubs(:conditions).returns(nil)
242
+ response_valid_signed.settings = settings
243
+ response_valid_signed.settings.idp_cert_fingerprint = ruby_saml_cert_fingerprint
244
+ assert response_valid_signed.is_valid?
245
+ assert_empty response_valid_signed.errors
246
+ end
247
+
248
+ it "return true when the response is initialized with valid data and using certificate instead of fingerprint" do
249
+ response_valid_signed.stubs(:conditions).returns(nil)
250
+ response_valid_signed.settings = settings
251
+ response_valid_signed.settings.idp_cert = ruby_saml_cert_text
252
+ assert response_valid_signed.is_valid?
253
+ assert_empty response_valid_signed.errors
254
+ end
255
+
256
+ it "return false when response is initialized with blank data" do
257
+ blank_response = OneLogin::RubySaml::Response.new('')
258
+ blank_response.soft = true
259
+ assert !blank_response.is_valid?
260
+ assert_includes blank_response.errors, "Blank response"
261
+ end
262
+
263
+ it "return false if settings have not been set" do
264
+ assert !response.is_valid?
265
+ assert_includes response.errors, "No settings on response"
266
+ end
267
+
268
+ it "return false if fingerprint or certificate not been set on settings" do
269
+ response.settings = settings
270
+ assert !response.is_valid?
271
+ assert_includes response.errors, "No fingerprint or certificate on settings"
272
+ end
273
+
274
+ it "should be idempotent when the response is initialized with invalid data" do
275
+ response_unsigned.stubs(:conditions).returns(nil)
276
+ response_unsigned.settings = settings
277
+ assert !response_unsigned.is_valid?
278
+ assert !response_unsigned.is_valid?
279
+ end
280
+
281
+ it "should be idempotent when the response is initialized with valid data" do
282
+ response_valid_signed.stubs(:conditions).returns(nil)
283
+ response_valid_signed.settings = settings
284
+ response_valid_signed.settings.idp_cert_fingerprint = ruby_saml_cert_fingerprint
285
+ assert response_valid_signed.is_valid?
286
+ assert response_valid_signed.is_valid?
287
+ end
288
+
289
+ it "not allow signature wrapping attack" do
290
+ response_wrapped.stubs(:conditions).returns(nil)
291
+ response_wrapped.stubs(:validate_subject_confirmation).returns(true)
292
+ settings.idp_cert_fingerprint = signature_fingerprint_1
293
+ response_wrapped.settings = settings
294
+ assert !response_wrapped.is_valid?
295
+ end
296
+
297
+ it "support dynamic namespace resolution on signature elements" do
298
+ no_signature_response = OneLogin::RubySaml::Response.new(fixture("no_signature_ns.xml"))
299
+ no_signature_response.stubs(:conditions).returns(nil)
300
+ no_signature_response.stubs(:validate_subject_confirmation).returns(true)
301
+ no_signature_response.settings = settings
302
+ no_signature_response.settings.idp_cert_fingerprint = "28:74:9B:E8:1F:E8:10:9C:A8:7C:A9:C3:E3:C5:01:6C:92:1C:B4:BA"
303
+ XMLSecurity::SignedDocument.any_instance.expects(:validate_signature).returns(true)
304
+ assert no_signature_response.is_valid?
305
+ end
306
+
307
+ it "validate ADFS assertions" do
308
+ adfs_response = OneLogin::RubySaml::Response.new(fixture(:adfs_response_sha256))
309
+ adfs_response.stubs(:conditions).returns(nil)
310
+ adfs_response.stubs(:validate_subject_confirmation).returns(true)
311
+ settings.idp_cert_fingerprint = "28:74:9B:E8:1F:E8:10:9C:A8:7C:A9:C3:E3:C5:01:6C:92:1C:B4:BA"
312
+ adfs_response.settings = settings
313
+ adfs_response.soft = true
314
+ assert adfs_response.is_valid?
315
+ end
316
+
317
+ it "validate SAML 2.0 XML structure" do
318
+ resp_xml = Base64.decode64(response_document_unsigned).gsub(/emailAddress/,'test')
319
+ response_unsigned_mod = OneLogin::RubySaml::Response.new(Base64.encode64(resp_xml))
320
+ response_unsigned_mod.stubs(:conditions).returns(nil)
321
+ settings.idp_cert_fingerprint = signature_fingerprint_1
322
+ response_unsigned_mod.settings = settings
323
+ response_unsigned_mod.soft = true
324
+ assert !response_unsigned_mod.is_valid?
325
+ end
326
+
327
+ it "return false when encountering a condition that prevents the document from being valid" do
328
+ settings.idp_cert_fingerprint = ruby_saml_cert_fingerprint
329
+ response.settings = settings
330
+ error_msg = "Current time is on or after NotOnOrAfter condition"
331
+ assert !response.is_valid?
332
+ assert_includes response.errors[0], "Current time is on or after NotOnOrAfter condition"
333
+ end
334
+
335
+ it "return false when encountering a SAML Response with bad formatted" do
336
+ settings.idp_cert_fingerprint = signature_fingerprint_1
337
+ response_without_attributes.settings = settings
338
+ response_without_attributes.soft = true
339
+ error_msg = "Invalid SAML Response. Not match the saml-schema-protocol-2.0.xsd"
340
+ response_without_attributes.is_valid?
341
+ assert_includes response_without_attributes.errors, error_msg
342
+ end
343
+
344
+ it "return false when the inResponseTo value does not match the Request ID" do
345
+ settings.soft = true
346
+ settings.idp_cert_fingerprint = signature_fingerprint_1
347
+ opts = {}
348
+ opts[:settings] = settings
349
+ opts[:matches_request_id] = "invalid_request_id"
350
+ response_valid_signed = OneLogin::RubySaml::Response.new(response_document_valid_signed, opts)
351
+ response_valid_signed.is_valid?
352
+ assert_includes response_valid_signed.errors, "The InResponseTo of the Response: _fc4a34b0-7efb-012e-caae-782bcb13bb38, does not match the ID of the AuthNRequest sent by the SP: invalid_request_id"
353
+ end
354
+
355
+ it "return false when the assertion contains encrypted attributes" do
356
+ settings.idp_cert_fingerprint = signature_fingerprint_1
357
+ response_encrypted_attrs.settings = settings
358
+ response_encrypted_attrs.soft = true
359
+ response_encrypted_attrs.is_valid?
360
+ assert_includes response_encrypted_attrs.errors, "There is an EncryptedAttribute in the Response and this SP not support them"
361
+ end
362
+
363
+ it "return false when there is no valid audience" do
364
+ settings.idp_cert_fingerprint = signature_fingerprint_1
365
+ settings.issuer = 'invalid'
366
+ response_valid_signed.settings = settings
367
+ response_valid_signed.is_valid?
368
+ assert_includes response_valid_signed.errors, "#{response_valid_signed.settings.issuer} is not a valid audience for this Response"
369
+ end
370
+
371
+ it "return false when no ID present in the SAML Response" do
372
+ settings.idp_cert_fingerprint = signature_fingerprint_1
373
+ response_no_id.settings = settings
374
+ response_no_id.soft = true
375
+ response_no_id.is_valid?
376
+ assert_includes response_no_id.errors, "Missing ID attribute on SAML Response"
377
+ end
378
+
379
+ it "return false when no 2.0 Version present in the SAML Response" do
380
+ settings.idp_cert_fingerprint = signature_fingerprint_1
381
+ response_no_version.settings = settings
382
+ response_no_version.soft = true
383
+ error_msg = "Unsupported SAML version"
384
+ response_no_version.is_valid?
385
+ assert_includes response_no_version.errors, "Unsupported SAML version"
54
386
  end
55
387
  end
56
388
  end
57
389
 
390
+ describe "#validate_audience" do
391
+ it "return true when the audience is valid" do
392
+ response.settings = settings
393
+ response.settings.issuer = '{audience}'
394
+ assert response.send(:validate_audience)
395
+ assert_empty response.errors
396
+ end
397
+
398
+ it "return false when the audience is valid" do
399
+ response.settings = settings
400
+ response.settings.issuer = 'invalid_audience'
401
+ assert !response.send(:validate_audience)
402
+ assert_includes response.errors, "#{response.settings.issuer} is not a valid audience for this Response"
403
+ end
404
+ end
405
+
406
+ describe "#validate_issuer" do
407
+ it "return true when the issuer of the Message/Assertion matches the IdP entityId" do
408
+ response_valid_signed.settings = settings
409
+ assert response_valid_signed.send(:validate_issuer)
410
+
411
+ response_valid_signed.settings.idp_entity_id = 'https://app.onelogin.com/saml2'
412
+ assert response_valid_signed.send(:validate_issuer)
413
+ end
414
+
415
+ it "return false when the issuer of the Message does not match the IdP entityId" do
416
+ response_invalid_issuer_message.settings = settings
417
+ response_invalid_issuer_message.settings.idp_entity_id = 'http://idp.example.com/'
418
+ assert !response_invalid_issuer_message.send(:validate_issuer)
419
+ assert_includes response_invalid_issuer_message.errors, "Doesn't match the issuer, expected: <#{response_invalid_issuer_message.settings.idp_entity_id}>, but was: <http://invalid.issuer.example.com/>"
420
+ end
421
+
422
+ it "return false when the issuer of the Assertion does not match the IdP entityId" do
423
+ response_invalid_issuer_assertion.settings = settings
424
+ response_invalid_issuer_assertion.settings.idp_entity_id = 'http://idp.example.com/'
425
+ assert !response_invalid_issuer_assertion.send(:validate_issuer)
426
+ assert_includes response_invalid_issuer_assertion.errors, "Doesn't match the issuer, expected: <#{response_invalid_issuer_assertion.settings.idp_entity_id}>, but was: <http://invalid.issuer.example.com/>"
427
+ end
428
+ end
429
+
430
+ describe "#validate_num_assertion" do
431
+ it "return true when SAML Response contains 1 assertion" do
432
+ assert response.send(:validate_num_assertion)
433
+ assert_empty response.errors
434
+ end
435
+
436
+ it "return false when no 2.0 Version present in the SAML Response" do
437
+ assert !response_multi_assertion.send(:validate_num_assertion)
438
+ assert_includes response_multi_assertion.errors, "SAML Response must contain 1 assertion"
439
+ end
440
+ end
441
+
442
+ describe "validate_success_status" do
443
+ it "return true when the status is 'Success'" do
444
+ assert response.send(:validate_success_status)
445
+ assert_empty response.errors
446
+ end
447
+
448
+ it "return false when the status if no Status provided" do
449
+ assert !response_no_status.send(:validate_success_status)
450
+ assert_includes response_no_status.errors, "The status code of the Response was not Success"
451
+ end
452
+
453
+ it "return false when the status if no StatusCode provided" do
454
+ assert !response_no_statuscode.send(:validate_success_status)
455
+ assert_includes response_no_statuscode.errors, "The status code of the Response was not Success"
456
+ end
457
+
458
+ it "return false when the status is not 'Success'" do
459
+ assert !response_statuscode_responder.send(:validate_success_status)
460
+ assert_includes response_statuscode_responder.errors, "The status code of the Response was not Success, was Responder"
461
+ end
462
+
463
+ it "return false when the status is not 'Success', and shows the StatusMessage" do
464
+ assert !response_statuscode_responder_and_msg.send(:validate_success_status)
465
+ assert_includes response_statuscode_responder_and_msg.errors, "The status code of the Response was not Success, was Responder -> something_is_wrong"
466
+ end
467
+ end
468
+
58
469
  describe "#validate_structure" do
59
- it "raise when encountering a condition that prevents the document from being valid" do
60
- response = OneLogin::RubySaml::Response.new(response_document_2)
61
- response.send(:validate_structure)
62
- assert response.errors.include? "Schema validation failed"
470
+ it "return true when encountering a wellformed SAML Response" do
471
+ assert response.send(:validate_structure)
472
+ assert_empty response.errors
473
+ end
474
+
475
+ it "return false when encountering a mailformed element that prevents the document from being valid" do
476
+ response_without_attributes.soft = true
477
+ response_without_attributes.send(:validate_structure)
478
+ assert response_without_attributes.errors.include? "Invalid SAML Response. Not match the saml-schema-protocol-2.0.xsd"
479
+ end
480
+
481
+ it "raise when encountering a mailformed element that prevents the document from being valid" do
482
+ response_without_attributes.soft = false
483
+ assert_raises(OneLogin::RubySaml::ValidationError) {
484
+ response_without_attributes.send(:validate_structure)
485
+ }
63
486
  end
64
487
  end
65
488
 
66
- describe "#is_valid?" do
67
- it "return false when response is initialized with blank data" do
68
- response = OneLogin::RubySaml::Response.new('')
69
- assert !response.is_valid?
489
+ describe "#validate_in_response_to" do
490
+ it "return true when the inResponseTo value matches the Request ID" do
491
+ response = OneLogin::RubySaml::Response.new(response_document_valid_signed, :settings => settings, :matches_request_id => "_fc4a34b0-7efb-012e-caae-782bcb13bb38")
492
+ assert response.send(:validate_in_response_to)
493
+ assert_empty response.errors
494
+ end
495
+
496
+ it "return true when no Request ID is provided for checking" do
497
+ response = OneLogin::RubySaml::Response.new(response_document_valid_signed, :settings => settings)
498
+ assert response.send(:validate_in_response_to)
499
+ assert_empty response.errors
70
500
  end
71
501
 
72
- it "return false if settings have not been set" do
73
- response = OneLogin::RubySaml::Response.new(response_document)
74
- assert !response.is_valid?
502
+ it "return false when the inResponseTo value does not match the Request ID" do
503
+ response = OneLogin::RubySaml::Response.new(response_document_valid_signed, :settings => settings, :matches_request_id => "invalid_request_id")
504
+ assert !response.send(:validate_in_response_to)
505
+ assert_includes response.errors, "The InResponseTo of the Response: _fc4a34b0-7efb-012e-caae-782bcb13bb38, does not match the ID of the AuthNRequest sent by the SP: invalid_request_id"
75
506
  end
507
+ end
76
508
 
77
- it "return true when the response is initialized with valid data" do
78
- response = OneLogin::RubySaml::Response.new(response_document_4)
79
- response.stubs(:conditions).returns(nil)
80
- assert !response.is_valid?
81
- settings = OneLogin::RubySaml::Settings.new
82
- assert !response.is_valid?
83
- response.settings = settings
84
- assert !response.is_valid?
85
- settings.idp_cert_fingerprint = signature_fingerprint_1
86
- assert response.is_valid?
509
+ describe "#validate_no_encrypted_attributes" do
510
+ it "return true when the assertion does not contain encrypted attributes" do
511
+ response_valid_signed.settings = settings
512
+ assert response_valid_signed.send(:validate_no_encrypted_attributes)
513
+ assert_empty response_valid_signed.errors
87
514
  end
88
515
 
89
- it "should be idempotent when the response is initialized with invalid data" do
90
- response = OneLogin::RubySaml::Response.new(response_document_4)
91
- response.stubs(:conditions).returns(nil)
92
- settings = OneLogin::RubySaml::Settings.new
93
- response.settings = settings
94
- assert !response.is_valid?
95
- assert !response.is_valid?
516
+ it "return false when the assertion contains encrypted attributes" do
517
+ response_encrypted_attrs.settings = settings
518
+ assert !response_encrypted_attrs.send(:validate_no_encrypted_attributes)
519
+ assert_includes response_encrypted_attrs.errors, "There is an EncryptedAttribute in the Response and this SP not support them"
96
520
  end
521
+ end
97
522
 
98
- it "should be idempotent when the response is initialized with valid data" do
99
- response = OneLogin::RubySaml::Response.new(response_document_4)
100
- response.stubs(:conditions).returns(nil)
101
- settings = OneLogin::RubySaml::Settings.new
102
- response.settings = settings
103
- settings.idp_cert_fingerprint = signature_fingerprint_1
104
- assert response.is_valid?
105
- assert response.is_valid?
523
+ describe "#validate_audience" do
524
+ it "return true when the audience is valid" do
525
+ response_valid_signed.settings = settings
526
+ response_valid_signed.settings.issuer = "https://someone.example.com/audience"
527
+ assert response_valid_signed.send(:validate_audience)
528
+ assert_empty response_valid_signed.errors
106
529
  end
107
530
 
108
- it "return true when using certificate instead of fingerprint" do
109
- response = OneLogin::RubySaml::Response.new(response_document_4)
110
- response.stubs(:conditions).returns(nil)
111
- settings = OneLogin::RubySaml::Settings.new
112
- response.settings = settings
113
- settings.idp_cert = signature_1
114
- assert response.is_valid?
531
+ it "return true when there is not issuer defined" do
532
+ response_valid_signed.settings = settings
533
+ response_valid_signed.settings.issuer = nil
534
+ assert response_valid_signed.send(:validate_audience)
535
+ assert_empty response_valid_signed.errors
115
536
  end
116
537
 
117
- it "not allow signature wrapping attack" do
118
- response = OneLogin::RubySaml::Response.new(response_document_4)
119
- response.stubs(:conditions).returns(nil)
120
- settings = OneLogin::RubySaml::Settings.new
121
- settings.idp_cert_fingerprint = signature_fingerprint_1
122
- response.settings = settings
123
- assert response.is_valid?
124
- assert_equal response.name_id, "test@onelogin.com"
538
+ it "return false when there is no valid audience" do
539
+ response_invalid_audience.settings = settings
540
+ response_invalid_audience.settings.issuer = "https://invalid.example.com/audience"
541
+ assert !response_invalid_audience.send(:validate_audience)
542
+ assert_includes response_invalid_audience.errors, "#{response_invalid_audience.settings.issuer} is not a valid audience for this Response"
125
543
  end
544
+ end
126
545
 
127
- it "Prevent node text with comment (VU#475445) attack" do
128
- response_doc = File.read(File.join(File.dirname(__FILE__), "responses", 'response_node_text_attack.xml.base64'))
129
- response = OneLogin::RubySaml::Response.new(response_doc)
546
+ describe "#validate_issuer" do
547
+ it "return true when the issuer of the Message/Assertion matches the IdP entityId or it was empty" do
548
+ response_valid_signed.settings = settings
549
+ assert response_valid_signed.send(:validate_issuer)
550
+ assert_empty response_valid_signed.errors
130
551
 
131
- assert_equal "support@onelogin.com", response.name_id
132
- assert_equal "smith", response.attributes["surname"]
552
+ response_valid_signed.settings.idp_entity_id = 'https://app.onelogin.com/saml2'
553
+ assert response_valid_signed.send(:validate_issuer)
554
+ assert_empty response_valid_signed.errors
133
555
  end
134
556
 
135
- it "support dynamic namespace resolution on signature elements" do
136
- response = OneLogin::RubySaml::Response.new(fixture("no_signature_ns.xml"))
137
- response.stubs(:conditions).returns(nil)
138
- settings = OneLogin::RubySaml::Settings.new
139
- response.settings = settings
140
- settings.idp_cert_fingerprint = "28:74:9B:E8:1F:E8:10:9C:A8:7C:A9:C3:E3:C5:01:6C:92:1C:B4:BA"
141
- XMLSecurity::SignedDocument.any_instance.expects(:validate_signature).returns(true)
142
- assert response.validate!
557
+ it "return false when the issuer of the Message does not match the IdP entityId" do
558
+ response_invalid_issuer_message.settings = settings
559
+ response_invalid_issuer_message.settings.idp_entity_id = 'http://idp.example.com/'
560
+ assert !response_invalid_issuer_message.send(:validate_issuer)
561
+ assert_includes response_invalid_issuer_message.errors, "Doesn't match the issuer, expected: <#{response_invalid_issuer_message.settings.idp_entity_id}>, but was: <http://invalid.issuer.example.com/>"
562
+ end
563
+
564
+ it "return false when the issuer of the Assertion does not match the IdP entityId" do
565
+ response_invalid_issuer_assertion.settings = settings
566
+ response_invalid_issuer_assertion.settings.idp_entity_id = 'http://idp.example.com/'
567
+ assert !response_invalid_issuer_assertion.send(:validate_issuer)
568
+ assert_includes response_invalid_issuer_assertion.errors, "Doesn't match the issuer, expected: <#{response_invalid_issuer_assertion.settings.idp_entity_id}>, but was: <http://invalid.issuer.example.com/>"
569
+ end
570
+ end
571
+
572
+ describe "#validate_subject_confirmation" do
573
+ it "return true when valid subject confirmation" do
574
+ response_valid_signed.settings = settings
575
+ response_valid_signed.settings.assertion_consumer_service_url = 'recipient'
576
+ assert response_valid_signed.send(:validate_subject_confirmation)
577
+ assert_empty response_valid_signed.errors
578
+ end
579
+
580
+ it "return false when no subject confirmation data" do
581
+ response_no_subjectconfirmation_data.settings = settings
582
+ assert !response_no_subjectconfirmation_data.send(:validate_subject_confirmation)
583
+ assert_includes response_no_subjectconfirmation_data.errors, "A valid SubjectConfirmation was not found on this Response"
584
+ end
585
+
586
+ it "return false when no valid subject confirmation method" do
587
+ response_no_subjectconfirmation_method.settings = settings
588
+ assert !response_no_subjectconfirmation_method.send(:validate_subject_confirmation)
589
+ assert_includes response_no_subjectconfirmation_method.errors, "A valid SubjectConfirmation was not found on this Response"
590
+ end
591
+
592
+ it "return false when invalid inresponse" do
593
+ response_invalid_subjectconfirmation_inresponse.settings = settings
594
+ assert !response_invalid_subjectconfirmation_inresponse.send(:validate_subject_confirmation)
595
+ assert_includes response_invalid_subjectconfirmation_inresponse.errors, "A valid SubjectConfirmation was not found on this Response"
596
+ end
597
+
598
+ it "return false when invalid NotBefore" do
599
+ response_invalid_subjectconfirmation_nb.settings = settings
600
+ assert !response_invalid_subjectconfirmation_nb.send(:validate_subject_confirmation)
601
+ assert_includes response_invalid_subjectconfirmation_nb.errors, "A valid SubjectConfirmation was not found on this Response"
602
+ end
603
+
604
+ it "return false when invalid NotOnOrAfter" do
605
+ response_invalid_subjectconfirmation_noa.settings = settings
606
+ assert !response_invalid_subjectconfirmation_noa.send(:validate_subject_confirmation)
607
+ assert_includes response_invalid_subjectconfirmation_noa.errors, "A valid SubjectConfirmation was not found on this Response"
608
+ end
609
+ end
610
+
611
+ describe "#validate_session_expiration" do
612
+ it "return true when the session has not expired" do
613
+ response_valid_signed.settings = settings
614
+ assert response_valid_signed.send(:validate_session_expiration)
615
+ assert_empty response_valid_signed.errors
143
616
  end
144
617
 
145
- it "validate ADFS assertions" do
146
- response = OneLogin::RubySaml::Response.new(fixture(:adfs_response_sha256))
147
- response.stubs(:conditions).returns(nil)
148
- settings = OneLogin::RubySaml::Settings.new
149
- settings.idp_cert_fingerprint = "28:74:9B:E8:1F:E8:10:9C:A8:7C:A9:C3:E3:C5:01:6C:92:1C:B4:BA"
618
+ it "return false when the session has expired" do
150
619
  response.settings = settings
151
- assert response.validate!
620
+ assert !response.send(:validate_session_expiration)
621
+ assert_includes response.errors, "The attributes have expired, based on the SessionNotOnOrAfter of the AttributeStatement of this Response"
622
+ end
623
+ end
624
+
625
+ describe "#validate_signature" do
626
+ it "return true when the signature is valid" do
627
+ settings.idp_cert_fingerprint = ruby_saml_cert_fingerprint
628
+ response_valid_signed.settings = settings
629
+ assert response_valid_signed.send(:validate_signature)
630
+ assert_empty response_valid_signed.errors
152
631
  end
153
632
 
154
- it "validate the digest" do
155
- response = OneLogin::RubySaml::Response.new(r1_response_document_6)
156
- response.stubs(:conditions).returns(nil)
157
- settings = OneLogin::RubySaml::Settings.new
158
- settings.idp_cert = Base64.decode64(r1_signature_2)
633
+ it "return false when no fingerprint" do
634
+ settings.idp_cert_fingerprint = nil
635
+ settings.idp_cert = nil
159
636
  response.settings = settings
160
- assert response.validate!
637
+ assert !response.send(:validate_signature)
638
+ assert_includes response.errors, "Invalid Signature on SAML Response"
161
639
  end
162
640
 
163
- it "validate SAML 2.0 XML structure" do
164
- resp_xml = Base64.decode64(response_document_4).gsub(/emailAddress/,'test')
165
- response = OneLogin::RubySaml::Response.new(Base64.encode64(resp_xml))
166
- response.stubs(:conditions).returns(nil)
167
- settings = OneLogin::RubySaml::Settings.new
641
+ it "return false when the signature is invalid" do
168
642
  settings.idp_cert_fingerprint = signature_fingerprint_1
169
643
  response.settings = settings
170
- assert_raises(OneLogin::RubySaml::ValidationError, 'Digest mismatch'){ response.validate! }
644
+ assert !response.send(:validate_signature)
645
+ assert_includes response.errors, "Invalid Signature on SAML Response"
171
646
  end
172
647
  end
173
648
 
174
- describe "#name_id" do
649
+ describe "#nameid" do
175
650
  it "extract the value of the name id element" do
176
- response = OneLogin::RubySaml::Response.new(response_document)
177
- assert_equal "support@onelogin.com", response.name_id
178
-
179
- response = OneLogin::RubySaml::Response.new(response_document_3)
180
- assert_equal "someone@example.com", response.name_id
651
+ assert_equal "support@onelogin.com", response.nameid
652
+ assert_equal "someone@example.com", response_with_signed_assertion.nameid
181
653
  end
182
654
 
183
655
  it "be extractable from an OpenSAML response" do
184
- response = OneLogin::RubySaml::Response.new(fixture(:open_saml))
185
- assert_equal "someone@example.org", response.name_id
656
+ response_open_saml = OneLogin::RubySaml::Response.new(fixture(:open_saml))
657
+ assert_equal "someone@example.org", response_open_saml.nameid
186
658
  end
187
659
 
188
660
  it "be extractable from a Simple SAML PHP response" do
661
+ response_ssp = OneLogin::RubySaml::Response.new(fixture(:simple_saml_php))
662
+ assert_equal "someone@example.com", response_ssp.nameid
663
+ end
664
+ end
665
+
666
+ describe "#sessionindex" do
667
+ it "extract the value of the sessionindex element" do
189
668
  response = OneLogin::RubySaml::Response.new(fixture(:simple_saml_php))
190
- assert_equal "someone@example.com", response.name_id
669
+ assert_equal "_51be37965feb5579d803141076936dc2e9d1d98ebf", response.sessionindex
191
670
  end
192
671
  end
193
672
 
194
673
  describe "#check_conditions" do
195
674
  it "check time conditions" do
196
- response = OneLogin::RubySaml::Response.new(response_document)
197
- assert !response.send(:validate_conditions, true)
198
- response = OneLogin::RubySaml::Response.new(response_document_6)
199
- assert response.send(:validate_conditions, true)
200
- time = Time.parse("2011-06-14T18:25:01.516Z")
201
- Time.stubs(:now).returns(time)
202
- response = OneLogin::RubySaml::Response.new(response_document_5)
203
- assert response.send(:validate_conditions, true)
675
+ response.soft = true
676
+ assert !response.send(:validate_conditions)
677
+ response_time_updated = OneLogin::RubySaml::Response.new(response_document_without_recipient_with_time_updated)
678
+ response_time_updated.soft = true
679
+ assert response_time_updated.send(:validate_conditions)
680
+ Timecop.freeze(Time.parse("2011-06-14T18:25:01.516Z")) do
681
+ response_with_saml2_namespace = OneLogin::RubySaml::Response.new(response_document_with_saml2_namespace)
682
+ response_with_saml2_namespace.soft = true
683
+ assert response_with_saml2_namespace.send(:validate_conditions)
684
+ end
204
685
  end
205
686
 
206
687
  it "optionally allows for clock drift" do
207
688
  # The NotBefore condition in the document is 2011-06-14T18:21:01.516Z
208
689
  Timecop.freeze(Time.parse("2011-06-14T18:21:01Z")) do
209
- response = OneLogin::RubySaml::Response.new(
210
- response_document_5,
211
- :allowed_clock_drift => 0.515
690
+ settings.soft = true
691
+ special_response_with_saml2_namespace = OneLogin::RubySaml::Response.new(
692
+ response_document_with_saml2_namespace,
693
+ :allowed_clock_drift => 0.515,
694
+ :settings => settings
212
695
  )
213
- assert !response.send(:validate_conditions, true)
696
+ assert !special_response_with_saml2_namespace.send(:validate_conditions)
214
697
  end
215
698
 
216
699
  Timecop.freeze(Time.parse("2011-06-14T18:21:01Z")) do
217
- response = OneLogin::RubySaml::Response.new(
218
- response_document_5,
700
+ special_response_with_saml2_namespace = OneLogin::RubySaml::Response.new(
701
+ response_document_with_saml2_namespace,
219
702
  :allowed_clock_drift => 0.516
220
703
  )
221
- assert response.send(:validate_conditions, true)
704
+ assert special_response_with_saml2_namespace.send(:validate_conditions)
222
705
  end
223
706
  end
224
707
  end
225
708
 
226
709
  describe "#attributes" do
227
710
  it "extract the first attribute in a hash accessed via its symbol" do
228
- response = OneLogin::RubySaml::Response.new(response_document)
229
711
  assert_equal "demo", response.attributes[:uid]
230
712
  end
231
713
 
232
714
  it "extract the first attribute in a hash accessed via its name" do
233
- response = OneLogin::RubySaml::Response.new(response_document)
234
715
  assert_equal "demo", response.attributes["uid"]
235
716
  end
236
717
 
237
718
  it "extract all attributes" do
238
- response = OneLogin::RubySaml::Response.new(response_document)
239
719
  assert_equal "demo", response.attributes[:uid]
240
720
  assert_equal "value", response.attributes[:another_value]
241
721
  end
242
722
 
243
723
  it "work for implicit namespaces" do
244
- response = OneLogin::RubySaml::Response.new(response_document_3)
245
- assert_equal "someone@example.com", response.attributes["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"]
724
+ assert_equal "someone@example.com", response_with_signed_assertion.attributes["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"]
246
725
  end
247
726
 
248
727
  it "not raise errors about nil/empty attributes for EncryptedAttributes" do
249
- response = OneLogin::RubySaml::Response.new(response_document_7)
250
- assert_equal 'Demo', response.attributes["first_name"]
728
+ response_no_cert_and_encrypted_attrs = OneLogin::RubySaml::Response.new(response_document_no_cert_and_encrypted_attrs)
729
+ assert_equal 'Demo', response_no_cert_and_encrypted_attrs.attributes["first_name"]
251
730
  end
252
731
 
253
732
  it "not raise on responses without attributes" do
254
- response = OneLogin::RubySaml::Response.new(response_document_4)
255
- assert_equal OneLogin::RubySaml::Attributes.new, response.attributes
733
+ assert_equal OneLogin::RubySaml::Attributes.new, response_unsigned.attributes
256
734
  end
257
735
 
258
736
  describe "#multiple values" do
259
737
  it "extract single value as string" do
260
- response = OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values))
261
- assert_equal "demo", response.attributes[:uid]
738
+ assert_equal "demo", response_multiple_attr_values.attributes[:uid]
262
739
  end
263
740
 
264
741
  it "extract single value as string in compatibility mode off" do
265
- response = OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values))
266
742
  OneLogin::RubySaml::Attributes.single_value_compatibility = false
267
- assert_equal ["demo"], response.attributes[:uid]
743
+ assert_equal ["demo"], response_multiple_attr_values.attributes[:uid]
268
744
  # classes are not reloaded between tests so restore default
269
745
  OneLogin::RubySaml::Attributes.single_value_compatibility = true
270
746
  end
271
747
 
272
748
  it "extract first of multiple values as string for b/w compatibility" do
273
- response = OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values))
274
- assert_equal 'value1', response.attributes[:another_value]
749
+ assert_equal 'value1', response_multiple_attr_values.attributes[:another_value]
275
750
  end
276
751
 
277
752
  it "extract first of multiple values as string for b/w compatibility in compatibility mode off" do
278
- response = OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values))
279
753
  OneLogin::RubySaml::Attributes.single_value_compatibility = false
280
- assert_equal ['value1', 'value2'], response.attributes[:another_value]
754
+ assert_equal ['value1', 'value2'], response_multiple_attr_values.attributes[:another_value]
281
755
  OneLogin::RubySaml::Attributes.single_value_compatibility = true
282
756
  end
283
757
 
284
758
  it "return array with all attributes when asked in XML order" do
285
- response = OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values))
286
- assert_equal ['value1', 'value2'], response.attributes.multi(:another_value)
759
+ assert_equal ['value1', 'value2'], response_multiple_attr_values.attributes.multi(:another_value)
287
760
  end
288
761
 
289
762
  it "return array with all attributes when asked in XML order in compatibility mode off" do
290
- response = OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values))
291
763
  OneLogin::RubySaml::Attributes.single_value_compatibility = false
292
- assert_equal ['value1', 'value2'], response.attributes.multi(:another_value)
764
+ assert_equal ['value1', 'value2'], response_multiple_attr_values.attributes.multi(:another_value)
293
765
  OneLogin::RubySaml::Attributes.single_value_compatibility = true
294
766
  end
295
767
 
296
768
  it "return first of multiple values when multiple Attribute tags in XML" do
297
- response = OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values))
298
- assert_equal 'role1', response.attributes[:role]
769
+ assert_equal 'role1', response_multiple_attr_values.attributes[:role]
299
770
  end
300
771
 
301
772
  it "return first of multiple values when multiple Attribute tags in XML in compatibility mode off" do
302
- response = OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values))
303
773
  OneLogin::RubySaml::Attributes.single_value_compatibility = false
304
- assert_equal ['role1', 'role2', 'role3'], response.attributes[:role]
774
+ assert_equal ['role1', 'role2', 'role3'], response_multiple_attr_values.attributes[:role]
305
775
  OneLogin::RubySaml::Attributes.single_value_compatibility = true
306
776
  end
307
777
 
308
778
  it "return all of multiple values in reverse order when multiple Attribute tags in XML" do
309
- response = OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values))
310
- assert_equal ['role1', 'role2', 'role3'], response.attributes.multi(:role)
779
+ assert_equal ['role1', 'role2', 'role3'], response_multiple_attr_values.attributes.multi(:role)
311
780
  end
312
781
 
313
782
  it "return all of multiple values in reverse order when multiple Attribute tags in XML in compatibility mode off" do
314
- response = OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values))
315
783
  OneLogin::RubySaml::Attributes.single_value_compatibility = false
316
- assert_equal ['role1', 'role2', 'role3'], response.attributes.multi(:role)
784
+ assert_equal ['role1', 'role2', 'role3'], response_multiple_attr_values.attributes.multi(:role)
317
785
  OneLogin::RubySaml::Attributes.single_value_compatibility = true
318
786
  end
319
787
 
320
788
  it "return nil value correctly" do
321
- response = OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values))
322
- assert_nil response.attributes[:attribute_with_nil_value]
789
+ assert_nil response_multiple_attr_values.attributes[:attribute_with_nil_value]
323
790
  end
324
791
 
325
792
  it "return nil value correctly when not in compatibility mode off" do
326
- response = OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values))
327
793
  OneLogin::RubySaml::Attributes.single_value_compatibility = false
328
- assert_equal [nil], response.attributes[:attribute_with_nil_value]
794
+ assert_equal [nil], response_multiple_attr_values.attributes[:attribute_with_nil_value]
329
795
  OneLogin::RubySaml::Attributes.single_value_compatibility = true
330
796
  end
331
797
 
@@ -335,22 +801,20 @@ class RubySamlTest < Minitest::Test
335
801
  end
336
802
 
337
803
  it "return multiple values from [] when not in compatibility mode off" do
338
- response = OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values))
339
804
  OneLogin::RubySaml::Attributes.single_value_compatibility = false
340
- assert_equal ["", "valuePresent", nil, nil], response.attributes[:attribute_with_nils_and_empty_strings]
805
+ assert_equal ["", "valuePresent", nil, nil], response_multiple_attr_values.attributes[:attribute_with_nils_and_empty_strings]
341
806
  OneLogin::RubySaml::Attributes.single_value_compatibility = true
342
807
  end
343
808
 
344
809
  it "check what happens when trying retrieve attribute that does not exists" do
345
- response = OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values))
346
- assert_nil response.attributes[:attribute_not_exists]
347
- assert_nil response.attributes.single(:attribute_not_exists)
348
- assert_nil response.attributes.multi(:attribute_not_exists)
810
+ assert_equal nil, response_multiple_attr_values.attributes[:attribute_not_exists]
811
+ assert_equal nil, response_multiple_attr_values.attributes.single(:attribute_not_exists)
812
+ assert_equal nil, response_multiple_attr_values.attributes.multi(:attribute_not_exists)
349
813
 
350
814
  OneLogin::RubySaml::Attributes.single_value_compatibility = false
351
- assert_nil response.attributes[:attribute_not_exists]
352
- assert_nil response.attributes.single(:attribute_not_exists)
353
- assert_nil response.attributes.multi(:attribute_not_exists)
815
+ assert_equal nil, response_multiple_attr_values.attributes[:attribute_not_exists]
816
+ assert_equal nil, response_multiple_attr_values.attributes.single(:attribute_not_exists)
817
+ assert_equal nil, response_multiple_attr_values.attributes.multi(:attribute_not_exists)
354
818
  OneLogin::RubySaml::Attributes.single_value_compatibility = true
355
819
  end
356
820
 
@@ -359,29 +823,26 @@ class RubySamlTest < Minitest::Test
359
823
 
360
824
  describe "#session_expires_at" do
361
825
  it "extract the value of the SessionNotOnOrAfter attribute" do
362
- response = OneLogin::RubySaml::Response.new(response_document)
363
826
  assert response.session_expires_at.is_a?(Time)
827
+ end
364
828
 
365
- response = OneLogin::RubySaml::Response.new(response_document_2)
366
- assert_nil response.session_expires_at
829
+ it "return nil when the value of the SessionNotOnOrAfter is not set" do
830
+ assert_nil response_without_attributes.session_expires_at
367
831
  end
368
832
  end
369
833
 
370
- describe "#issuer" do
834
+ describe "#issuers" do
371
835
  it "return the issuer inside the response assertion" do
372
- response = OneLogin::RubySaml::Response.new(response_document)
373
- assert_equal "https://app.onelogin.com/saml/metadata/13590", response.issuer
836
+ assert_includes response.issuers, "https://app.onelogin.com/saml/metadata/13590"
374
837
  end
375
838
 
376
839
  it "return the issuer inside the response" do
377
- response = OneLogin::RubySaml::Response.new(response_document_2)
378
- assert_equal "wibble", response.issuer
840
+ assert_includes response_without_attributes.issuers, "wibble"
379
841
  end
380
842
  end
381
843
 
382
844
  describe "#success" do
383
845
  it "find a status code that says success" do
384
- response = OneLogin::RubySaml::Response.new(response_document)
385
846
  response.success?
386
847
  end
387
848
  end
@@ -389,8 +850,8 @@ class RubySamlTest < Minitest::Test
389
850
  describe '#xpath_first_from_signed_assertion' do
390
851
  it 'not allow arbitrary code execution' do
391
852
  malicious_response_document = fixture('response_eval', false)
392
- response = OneLogin::RubySaml::Response.new(malicious_response_document)
393
- response.send(:xpath_first_from_signed_assertion)
853
+ malicious_response = OneLogin::RubySaml::Response.new(malicious_response_document)
854
+ malicious_response.send(:xpath_first_from_signed_assertion)
394
855
  assert_nil $evalled
395
856
  end
396
857
  end
@@ -408,13 +869,225 @@ class RubySamlTest < Minitest::Test
408
869
  private_key = OpenSSL::PKey::RSA.new(formated_private_key)
409
870
  document.sign_document(private_key, cert)
410
871
 
411
- saml_response = OneLogin::RubySaml::Response.new(document.to_s)
412
- settings = OneLogin::RubySaml::Settings.new
872
+ signed_response = OneLogin::RubySaml::Response.new(document.to_s)
413
873
  settings.idp_cert = ruby_saml_cert_text
414
- saml_response.settings = settings
415
- time = Time.parse("2015-03-18T04:50:24Z")
416
- Time.stubs(:now).returns(time)
417
- saml_response.validate!
874
+ signed_response.settings = settings
875
+ Timecop.freeze(Time.parse("2015-03-18T04:50:24Z")) do
876
+ assert signed_response.is_valid?
877
+ end
878
+ assert_empty signed_response.errors
879
+ end
880
+ end
881
+
882
+ describe "retrieve nameID" do
883
+ it 'is possible when nameID inside the assertion' do
884
+ response_valid_signed.settings = settings
885
+ assert_equal "test@onelogin.com", response_valid_signed.nameid
886
+ end
887
+
888
+ it 'is not possible when encryptID inside the assertion but no private key' do
889
+ response_encrypted_nameid.settings = settings
890
+ assert_raises(OneLogin::RubySaml::ValidationError, "An EncryptedID found and no SP private key found on the settings to decrypt it") do
891
+ assert_equal "test@onelogin.com", response_encrypted_nameid.nameid
892
+ end
893
+ end
894
+
895
+ it 'is possible when encryptID inside the assertion and settings has the private key' do
896
+ settings.private_key = ruby_saml_key_text
897
+ response_encrypted_nameid.settings = settings
898
+ assert_equal "test@onelogin.com", response_encrypted_nameid.nameid
899
+ end
900
+
901
+ end
902
+
903
+ end
904
+
905
+ describe 'try to initialize an encrypted response' do
906
+ it 'raise if an encrypted assertion is found and no sp private key to decrypt it' do
907
+ error_msg = "An EncryptedAssertion found and no SP private key found on the settings to decrypt it. Be sure you provided the :settings parameter at the initialize method"
908
+
909
+ assert_raises(OneLogin::RubySaml::ValidationError, error_msg) do
910
+ response = OneLogin::RubySaml::Response.new(signed_message_encrypted_unsigned_assertion)
911
+ end
912
+
913
+ assert_raises(OneLogin::RubySaml::ValidationError, error_msg) do
914
+ response2 = OneLogin::RubySaml::Response.new(signed_message_encrypted_unsigned_assertion, :settings => settings)
915
+ end
916
+
917
+ settings.certificate = ruby_saml_cert_text
918
+ settings.private_key = ruby_saml_key_text
919
+ assert_raises(OneLogin::RubySaml::ValidationError, error_msg) do
920
+ response3 = OneLogin::RubySaml::Response.new(signed_message_encrypted_unsigned_assertion)
921
+ response3.settings
922
+ end
923
+ end
924
+
925
+ it 'raise if an encrypted assertion is found and the sp private key is wrong' do
926
+ settings.certificate = ruby_saml_cert_text
927
+ wrong_private_key = ruby_saml_key_text.sub!('A', 'B')
928
+ settings.private_key = wrong_private_key
929
+
930
+ error_msg = "Neither PUB key nor PRIV key: nested asn1 error"
931
+ assert_raises(OpenSSL::PKey::RSAError, error_msg) do
932
+ response = OneLogin::RubySaml::Response.new(signed_message_encrypted_unsigned_assertion, :settings => settings)
933
+ end
934
+ end
935
+
936
+ it 'return true if an encrypted assertion is found and settings initialized with private_key' do
937
+ settings.certificate = ruby_saml_cert_text
938
+ settings.private_key = ruby_saml_key_text
939
+ response = OneLogin::RubySaml::Response.new(signed_message_encrypted_unsigned_assertion, :settings => settings)
940
+ assert response.decrypted_document
941
+
942
+ response2 = OneLogin::RubySaml::Response.new(signed_message_encrypted_signed_assertion, :settings => settings)
943
+ assert response2.decrypted_document
944
+
945
+ response3 = OneLogin::RubySaml::Response.new(unsigned_message_encrypted_signed_assertion, :settings => settings)
946
+ assert response3.decrypted_document
947
+
948
+ response4 = OneLogin::RubySaml::Response.new(unsigned_message_encrypted_unsigned_assertion, :settings => settings)
949
+ assert response4.decrypted_document
950
+ end
951
+ end
952
+
953
+ describe "retrieve nameID and attributes from encrypted assertion" do
954
+
955
+ before do
956
+ settings.idp_cert_fingerprint = 'EE:17:4E:FB:A8:81:71:12:0D:2A:78:43:BC:E7:0C:07:58:79:F4:F4'
957
+ settings.issuer = 'http://rubysaml.com:3000/saml/metadata'
958
+ settings.assertion_consumer_service_url = 'http://rubysaml.com:3000/saml/acs'
959
+ settings.certificate = ruby_saml_cert_text
960
+ settings.private_key = ruby_saml_key_text
961
+ end
962
+
963
+ it 'is possible when signed_message_encrypted_unsigned_assertion' do
964
+ response = OneLogin::RubySaml::Response.new(signed_message_encrypted_unsigned_assertion, :settings => settings)
965
+ Timecop.freeze(Time.parse("2015-03-19T14:30:31Z")) do
966
+ assert response.is_valid?
967
+ assert_empty response.errors
968
+ assert_equal "test", response.attributes[:uid]
969
+ assert_equal "98e2bb61075e951b37d6b3be6954a54b340d86c7", response.nameid
970
+ end
971
+ end
972
+
973
+ it 'is possible when signed_message_encrypted_signed_assertion' do
974
+ response = OneLogin::RubySaml::Response.new(signed_message_encrypted_signed_assertion, :settings => settings)
975
+ Timecop.freeze(Time.parse("2015-03-19T14:30:31Z")) do
976
+ assert response.is_valid?
977
+ assert_empty response.errors
978
+ assert_equal "test", response.attributes[:uid]
979
+ assert_equal "98e2bb61075e951b37d6b3be6954a54b340d86c7", response.nameid
980
+ end
981
+ end
982
+
983
+ it 'is possible when unsigned_message_encrypted_signed_assertion' do
984
+ response = OneLogin::RubySaml::Response.new(unsigned_message_encrypted_signed_assertion, :settings => settings)
985
+ Timecop.freeze(Time.parse("2015-03-19T14:30:31Z")) do
986
+ assert response.is_valid?
987
+ assert_empty response.errors
988
+ assert_equal "test", response.attributes[:uid]
989
+ assert_equal "98e2bb61075e951b37d6b3be6954a54b340d86c7", response.nameid
990
+ end
991
+ end
992
+
993
+ it 'is not possible when unsigned_message_encrypted_unsigned_assertion' do
994
+ response = OneLogin::RubySaml::Response.new(unsigned_message_encrypted_unsigned_assertion, :settings => settings)
995
+ Timecop.freeze(Time.parse("2015-03-19T14:30:31Z")) do
996
+ assert !response.is_valid?
997
+ assert_includes response.errors, "Found an unexpected number of Signature Element. SAML Response rejected"
998
+ end
999
+ end
1000
+ end
1001
+
1002
+ describe "#decrypt_assertion" do
1003
+ before do
1004
+ settings.private_key = ruby_saml_key_text
1005
+ end
1006
+
1007
+ describe "check right settings" do
1008
+
1009
+ it "is not possible to decrypt the assertion if no private key" do
1010
+ response = OneLogin::RubySaml::Response.new(signed_message_encrypted_unsigned_assertion, :settings => settings)
1011
+
1012
+ encrypted_assertion_node = REXML::XPath.first(
1013
+ response.document,
1014
+ "(/p:Response/EncryptedAssertion/)|(/p:Response/a:EncryptedAssertion/)",
1015
+ { "p" => "urn:oasis:names:tc:SAML:2.0:protocol", "a" => "urn:oasis:names:tc:SAML:2.0:assertion" }
1016
+ )
1017
+ response.settings.private_key = nil
1018
+
1019
+ error_msg = "An EncryptedAssertion found and no SP private key found on the settings to decrypt it"
1020
+ assert_raises(OneLogin::RubySaml::ValidationError, error_msg) do
1021
+ decrypted = response.send(:decrypt_assertion, encrypted_assertion_node)
1022
+ end
1023
+ end
1024
+
1025
+ it "is possible to decrypt the assertion if private key" do
1026
+ response = OneLogin::RubySaml::Response.new(signed_message_encrypted_unsigned_assertion, :settings => settings)
1027
+
1028
+ encrypted_assertion_node = REXML::XPath.first(
1029
+ response.document,
1030
+ "(/p:Response/EncryptedAssertion/)|(/p:Response/a:EncryptedAssertion/)",
1031
+ { "p" => "urn:oasis:names:tc:SAML:2.0:protocol", "a" => "urn:oasis:names:tc:SAML:2.0:assertion" }
1032
+ )
1033
+ decrypted = response.send(:decrypt_assertion, encrypted_assertion_node)
1034
+
1035
+ encrypted_assertion_node2 = REXML::XPath.first(
1036
+ decrypted,
1037
+ "(/p:Response/EncryptedAssertion/)|(/p:Response/a:EncryptedAssertion/)",
1038
+ { "p" => "urn:oasis:names:tc:SAML:2.0:protocol", "a" => "urn:oasis:names:tc:SAML:2.0:assertion" }
1039
+ )
1040
+ assert_nil encrypted_assertion_node2
1041
+ assert decrypted.name, "Assertion"
1042
+ end
1043
+
1044
+ it "is possible to decrypt the assertion if private key but no saml namespace on the Assertion Element that is inside the EncryptedAssertion" do
1045
+ unsigned_message_encrypted_assertion_without_saml_namespace = read_response('unsigned_message_encrypted_assertion_without_saml_namespace.xml.base64')
1046
+ response = OneLogin::RubySaml::Response.new(unsigned_message_encrypted_assertion_without_saml_namespace, :settings => settings)
1047
+ encrypted_assertion_node = REXML::XPath.first(
1048
+ response.document,
1049
+ "(/p:Response/EncryptedAssertion/)|(/p:Response/a:EncryptedAssertion/)",
1050
+ { "p" => "urn:oasis:names:tc:SAML:2.0:protocol", "a" => "urn:oasis:names:tc:SAML:2.0:assertion" }
1051
+ )
1052
+ decrypted = response.send(:decrypt_assertion, encrypted_assertion_node)
1053
+
1054
+ encrypted_assertion_node2 = REXML::XPath.first(
1055
+ decrypted,
1056
+ "(/p:Response/EncryptedAssertion/)|(/p:Response/a:EncryptedAssertion/)",
1057
+ { "p" => "urn:oasis:names:tc:SAML:2.0:protocol", "a" => "urn:oasis:names:tc:SAML:2.0:assertion" }
1058
+ )
1059
+ assert_nil encrypted_assertion_node2
1060
+ assert decrypted.name, "Assertion"
1061
+ end
1062
+ end
1063
+
1064
+ describe "check different encrypt methods supported" do
1065
+ it "EncryptionMethod DES-192 && Key Encryption Algorithm RSA-1_5" do
1066
+ unsigned_message_des192_encrypted_signed_assertion = read_response('unsigned_message_des192_encrypted_signed_assertion.xml.base64')
1067
+ response = OneLogin::RubySaml::Response.new(unsigned_message_des192_encrypted_signed_assertion, :settings => settings)
1068
+ assert_equal "test", response.attributes[:uid]
1069
+ assert_equal "_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7", response.nameid
1070
+ end
1071
+
1072
+ it "EncryptionMethod AES-128 && Key Encryption Algorithm RSA-OAEP-MGF1P" do
1073
+ unsigned_message_aes128_encrypted_signed_assertion = read_response('unsigned_message_aes128_encrypted_signed_assertion.xml.base64')
1074
+ response = OneLogin::RubySaml::Response.new(unsigned_message_aes128_encrypted_signed_assertion, :settings => settings)
1075
+ assert_equal "test", response.attributes[:uid]
1076
+ assert_equal "_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7", response.nameid
1077
+ end
1078
+
1079
+ it "EncryptionMethod AES-192 && Key Encryption Algorithm RSA-OAEP-MGF1P" do
1080
+ unsigned_message_aes192_encrypted_signed_assertion = read_response('unsigned_message_aes192_encrypted_signed_assertion.xml.base64')
1081
+ response = OneLogin::RubySaml::Response.new(unsigned_message_aes192_encrypted_signed_assertion, :settings => settings)
1082
+ assert_equal "test", response.attributes[:uid]
1083
+ assert_equal "_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7", response.nameid
1084
+ end
1085
+
1086
+ it "EncryptionMethod AES-256 && Key Encryption Algorithm RSA-OAEP-MGF1P" do
1087
+ unsigned_message_aes256_encrypted_signed_assertion = read_response('unsigned_message_aes256_encrypted_signed_assertion.xml.base64')
1088
+ response = OneLogin::RubySaml::Response.new(unsigned_message_aes256_encrypted_signed_assertion, :settings => settings)
1089
+ assert_equal "test", response.attributes[:uid]
1090
+ assert_equal "_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7", response.nameid
418
1091
  end
419
1092
  end
420
1093
  end