kl-ruby-saml 0.0.1

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 (137) hide show
  1. checksums.yaml +7 -0
  2. data/.document +5 -0
  3. data/.gitignore +14 -0
  4. data/.travis.yml +17 -0
  5. data/Gemfile +9 -0
  6. data/LICENSE +19 -0
  7. data/README.md +575 -0
  8. data/Rakefile +41 -0
  9. data/changelog.md +75 -0
  10. data/gemfiles/nokogiri-1.5.gemfile +5 -0
  11. data/lib/onelogin/ruby-saml.rb +17 -0
  12. data/lib/onelogin/ruby-saml/attribute_service.rb +57 -0
  13. data/lib/onelogin/ruby-saml/attributes.rb +128 -0
  14. data/lib/onelogin/ruby-saml/authrequest.rb +156 -0
  15. data/lib/onelogin/ruby-saml/http_error.rb +7 -0
  16. data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +161 -0
  17. data/lib/onelogin/ruby-saml/logging.rb +30 -0
  18. data/lib/onelogin/ruby-saml/logoutrequest.rb +131 -0
  19. data/lib/onelogin/ruby-saml/logoutresponse.rb +241 -0
  20. data/lib/onelogin/ruby-saml/metadata.rb +123 -0
  21. data/lib/onelogin/ruby-saml/response.rb +722 -0
  22. data/lib/onelogin/ruby-saml/saml_message.rb +158 -0
  23. data/lib/onelogin/ruby-saml/settings.rb +165 -0
  24. data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +258 -0
  25. data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +136 -0
  26. data/lib/onelogin/ruby-saml/utils.rb +172 -0
  27. data/lib/onelogin/ruby-saml/validation_error.rb +7 -0
  28. data/lib/onelogin/ruby-saml/version.rb +5 -0
  29. data/lib/ruby-saml.rb +1 -0
  30. data/lib/schemas/saml-schema-assertion-2.0.xsd +283 -0
  31. data/lib/schemas/saml-schema-authn-context-2.0.xsd +23 -0
  32. data/lib/schemas/saml-schema-authn-context-types-2.0.xsd +821 -0
  33. data/lib/schemas/saml-schema-metadata-2.0.xsd +337 -0
  34. data/lib/schemas/saml-schema-protocol-2.0.xsd +302 -0
  35. data/lib/schemas/sstc-metadata-attr.xsd +35 -0
  36. data/lib/schemas/sstc-saml-attribute-ext.xsd +25 -0
  37. data/lib/schemas/sstc-saml-metadata-algsupport-v1.0.xsd +41 -0
  38. data/lib/schemas/sstc-saml-metadata-ui-v1.0.xsd +89 -0
  39. data/lib/schemas/xenc-schema.xsd +136 -0
  40. data/lib/schemas/xml.xsd +287 -0
  41. data/lib/schemas/xmldsig-core-schema.xsd +309 -0
  42. data/lib/xml_security.rb +358 -0
  43. data/ruby-saml.gemspec +57 -0
  44. data/test/certificates/certificate1 +12 -0
  45. data/test/certificates/certificate_without_head_foot +1 -0
  46. data/test/certificates/formatted_certificate +14 -0
  47. data/test/certificates/formatted_private_key +12 -0
  48. data/test/certificates/formatted_rsa_private_key +12 -0
  49. data/test/certificates/invalid_certificate1 +1 -0
  50. data/test/certificates/invalid_certificate2 +1 -0
  51. data/test/certificates/invalid_certificate3 +12 -0
  52. data/test/certificates/invalid_private_key1 +1 -0
  53. data/test/certificates/invalid_private_key2 +1 -0
  54. data/test/certificates/invalid_private_key3 +10 -0
  55. data/test/certificates/invalid_rsa_private_key1 +1 -0
  56. data/test/certificates/invalid_rsa_private_key2 +1 -0
  57. data/test/certificates/invalid_rsa_private_key3 +10 -0
  58. data/test/certificates/ruby-saml.crt +14 -0
  59. data/test/certificates/ruby-saml.key +15 -0
  60. data/test/idp_metadata_parser_test.rb +95 -0
  61. data/test/logging_test.rb +62 -0
  62. data/test/logout_requests/invalid_slo_request.xml +6 -0
  63. data/test/logout_requests/slo_request.xml +4 -0
  64. data/test/logout_requests/slo_request.xml.base64 +1 -0
  65. data/test/logout_requests/slo_request_deflated.xml.base64 +1 -0
  66. data/test/logout_requests/slo_request_with_session_index.xml +5 -0
  67. data/test/logout_responses/logoutresponse_fixtures.rb +67 -0
  68. data/test/logoutrequest_test.rb +211 -0
  69. data/test/logoutresponse_test.rb +258 -0
  70. data/test/metadata_test.rb +203 -0
  71. data/test/request_test.rb +282 -0
  72. data/test/response_test.rb +1094 -0
  73. data/test/responses/adfs_response_sha1.xml +46 -0
  74. data/test/responses/adfs_response_sha256.xml +46 -0
  75. data/test/responses/adfs_response_sha384.xml +46 -0
  76. data/test/responses/adfs_response_sha512.xml +46 -0
  77. data/test/responses/adfs_response_xmlns.xml +45 -0
  78. data/test/responses/attackxee.xml +13 -0
  79. data/test/responses/idp_descriptor.xml +3 -0
  80. data/test/responses/invalids/invalid_audience.xml.base64 +1 -0
  81. data/test/responses/invalids/invalid_issuer_assertion.xml.base64 +1 -0
  82. data/test/responses/invalids/invalid_issuer_message.xml.base64 +1 -0
  83. data/test/responses/invalids/invalid_signature_position.xml.base64 +1 -0
  84. data/test/responses/invalids/invalid_subjectconfirmation_inresponse.xml.base64 +1 -0
  85. data/test/responses/invalids/invalid_subjectconfirmation_nb.xml.base64 +1 -0
  86. data/test/responses/invalids/invalid_subjectconfirmation_noa.xml.base64 +1 -0
  87. data/test/responses/invalids/invalid_subjectconfirmation_recipient.xml.base64 +1 -0
  88. data/test/responses/invalids/multiple_assertions.xml.base64 +2 -0
  89. data/test/responses/invalids/multiple_signed.xml.base64 +1 -0
  90. data/test/responses/invalids/no_id.xml.base64 +1 -0
  91. data/test/responses/invalids/no_saml2.xml.base64 +1 -0
  92. data/test/responses/invalids/no_signature.xml.base64 +1 -0
  93. data/test/responses/invalids/no_status.xml.base64 +1 -0
  94. data/test/responses/invalids/no_status_code.xml.base64 +1 -0
  95. data/test/responses/invalids/no_subjectconfirmation_data.xml.base64 +1 -0
  96. data/test/responses/invalids/no_subjectconfirmation_method.xml.base64 +1 -0
  97. data/test/responses/invalids/response_encrypted_attrs.xml.base64 +1 -0
  98. data/test/responses/invalids/response_invalid_signed_element.xml.base64 +1 -0
  99. data/test/responses/invalids/status_code_responder.xml.base64 +1 -0
  100. data/test/responses/invalids/status_code_responer_and_msg.xml.base64 +1 -0
  101. data/test/responses/no_signature_ns.xml +48 -0
  102. data/test/responses/open_saml_response.xml +56 -0
  103. data/test/responses/response_assertion_wrapped.xml.base64 +93 -0
  104. data/test/responses/response_encrypted_nameid.xml.base64 +1 -0
  105. data/test/responses/response_eval.xml +7 -0
  106. data/test/responses/response_no_cert_and_encrypted_attrs.xml +29 -0
  107. data/test/responses/response_unsigned_xml_base64 +1 -0
  108. data/test/responses/response_with_ampersands.xml +139 -0
  109. data/test/responses/response_with_ampersands.xml.base64 +93 -0
  110. data/test/responses/response_with_multiple_attribute_values.xml +67 -0
  111. data/test/responses/response_with_saml2_namespace.xml.base64 +102 -0
  112. data/test/responses/response_with_signed_assertion.xml.base64 +66 -0
  113. data/test/responses/response_with_signed_assertion_2.xml.base64 +1 -0
  114. data/test/responses/response_with_undefined_recipient.xml.base64 +1 -0
  115. data/test/responses/response_without_attributes.xml.base64 +79 -0
  116. data/test/responses/response_wrapped.xml.base64 +150 -0
  117. data/test/responses/signed_message_encrypted_signed_assertion.xml.base64 +1 -0
  118. data/test/responses/signed_message_encrypted_unsigned_assertion.xml.base64 +1 -0
  119. data/test/responses/simple_saml_php.xml +71 -0
  120. data/test/responses/starfield_response.xml.base64 +1 -0
  121. data/test/responses/test_sign.xml +43 -0
  122. data/test/responses/unsigned_message_aes128_encrypted_signed_assertion.xml.base64 +1 -0
  123. data/test/responses/unsigned_message_aes192_encrypted_signed_assertion.xml.base64 +1 -0
  124. data/test/responses/unsigned_message_aes256_encrypted_signed_assertion.xml.base64 +1 -0
  125. data/test/responses/unsigned_message_des192_encrypted_signed_assertion.xml.base64 +1 -0
  126. data/test/responses/unsigned_message_encrypted_assertion_without_saml_namespace.xml.base64 +1 -0
  127. data/test/responses/unsigned_message_encrypted_signed_assertion.xml.base64 +1 -0
  128. data/test/responses/unsigned_message_encrypted_unsigned_assertion.xml.base64 +1 -0
  129. data/test/responses/valid_response.xml.base64 +1 -0
  130. data/test/saml_message_test.rb +56 -0
  131. data/test/settings_test.rb +218 -0
  132. data/test/slo_logoutrequest_test.rb +275 -0
  133. data/test/slo_logoutresponse_test.rb +185 -0
  134. data/test/test_helper.rb +252 -0
  135. data/test/utils_test.rb +145 -0
  136. data/test/xml_security_test.rb +329 -0
  137. metadata +415 -0
@@ -0,0 +1,1094 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
2
+
3
+ require 'onelogin/ruby-saml/response'
4
+
5
+ class RubySamlTest < Minitest::Test
6
+
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
+
40
+ it "raise an exception when response is initialized with nil" do
41
+ assert_raises(ArgumentError) { OneLogin::RubySaml::Response.new(nil) }
42
+ end
43
+
44
+ it "be able to parse a document which contains ampersands" do
45
+ XMLSecurity::SignedDocument.any_instance.stubs(:digests_match?).returns(true)
46
+ OneLogin::RubySaml::Response.any_instance.stubs(:validate_conditions).returns(true)
47
+
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
74
+ end
75
+
76
+ it "adapt namespace" do
77
+ refute_nil response.nameid
78
+ refute_nil response_without_attributes.nameid
79
+ refute_nil response_with_signed_assertion.nameid
80
+ end
81
+
82
+ it "default to raw input when a response is not Base64 encoded" do
83
+ decoded = Base64.decode64(response_document_without_attributes)
84
+ response_from_raw = OneLogin::RubySaml::Response.new(decoded)
85
+ assert response_from_raw.document
86
+ end
87
+
88
+ describe "Assertion" do
89
+ it "only retreive an assertion with an ID that matches the signature's reference URI" do
90
+ response_wrapped.stubs(:conditions).returns(nil)
91
+ settings.idp_cert_fingerprint = signature_fingerprint_1
92
+ response_wrapped.settings = settings
93
+ assert_nil response_wrapped.nameid
94
+ end
95
+ end
96
+
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"
386
+ end
387
+ end
388
+ end
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
+
469
+ describe "#validate_structure" do
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
+ }
486
+ end
487
+ end
488
+
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
500
+ end
501
+
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"
506
+ end
507
+ end
508
+
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
514
+ end
515
+
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"
520
+ end
521
+ end
522
+
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
529
+ end
530
+
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
536
+ end
537
+
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"
543
+ end
544
+ end
545
+
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
551
+
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
555
+ end
556
+
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
616
+ end
617
+
618
+ it "return false when the session has expired" do
619
+ response.settings = settings
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
631
+ end
632
+
633
+ it "return false when no fingerprint" do
634
+ settings.idp_cert_fingerprint = nil
635
+ settings.idp_cert = nil
636
+ response.settings = settings
637
+ assert !response.send(:validate_signature)
638
+ assert_includes response.errors, "Invalid Signature on SAML Response"
639
+ end
640
+
641
+ it "return false when the signature is invalid" do
642
+ settings.idp_cert_fingerprint = signature_fingerprint_1
643
+ response.settings = settings
644
+ assert !response.send(:validate_signature)
645
+ assert_includes response.errors, "Invalid Signature on SAML Response"
646
+ end
647
+ end
648
+
649
+ describe "#nameid" do
650
+ it "extract the value of the name id element" do
651
+ assert_equal "support@onelogin.com", response.nameid
652
+ assert_equal "someone@example.com", response_with_signed_assertion.nameid
653
+ end
654
+
655
+ it "be extractable from an OpenSAML response" do
656
+ response_open_saml = OneLogin::RubySaml::Response.new(fixture(:open_saml))
657
+ assert_equal "someone@example.org", response_open_saml.nameid
658
+ end
659
+
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
668
+ response = OneLogin::RubySaml::Response.new(fixture(:simple_saml_php))
669
+ assert_equal "_51be37965feb5579d803141076936dc2e9d1d98ebf", response.sessionindex
670
+ end
671
+ end
672
+
673
+ describe "#check_conditions" do
674
+ it "check time conditions" do
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
685
+ end
686
+
687
+ it "optionally allows for clock drift" do
688
+ # The NotBefore condition in the document is 2011-06-14T18:21:01.516Z
689
+ Timecop.freeze(Time.parse("2011-06-14T18:21:01Z")) do
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
695
+ )
696
+ assert !special_response_with_saml2_namespace.send(:validate_conditions)
697
+ end
698
+
699
+ Timecop.freeze(Time.parse("2011-06-14T18:21:01Z")) do
700
+ special_response_with_saml2_namespace = OneLogin::RubySaml::Response.new(
701
+ response_document_with_saml2_namespace,
702
+ :allowed_clock_drift => 0.516
703
+ )
704
+ assert special_response_with_saml2_namespace.send(:validate_conditions)
705
+ end
706
+ end
707
+ end
708
+
709
+ describe "#attributes" do
710
+ it "extract the first attribute in a hash accessed via its symbol" do
711
+ assert_equal "demo", response.attributes[:uid]
712
+ end
713
+
714
+ it "extract the first attribute in a hash accessed via its name" do
715
+ assert_equal "demo", response.attributes["uid"]
716
+ end
717
+
718
+ it "extract all attributes" do
719
+ assert_equal "demo", response.attributes[:uid]
720
+ assert_equal "value", response.attributes[:another_value]
721
+ end
722
+
723
+ it "work for implicit namespaces" do
724
+ assert_equal "someone@example.com", response_with_signed_assertion.attributes["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"]
725
+ end
726
+
727
+ it "not raise errors about nil/empty attributes for EncryptedAttributes" do
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"]
730
+ end
731
+
732
+ it "not raise on responses without attributes" do
733
+ assert_equal OneLogin::RubySaml::Attributes.new, response_unsigned.attributes
734
+ end
735
+
736
+ describe "#multiple values" do
737
+ it "extract single value as string" do
738
+ assert_equal "demo", response_multiple_attr_values.attributes[:uid]
739
+ end
740
+
741
+ it "extract single value as string in compatibility mode off" do
742
+ OneLogin::RubySaml::Attributes.single_value_compatibility = false
743
+ assert_equal ["demo"], response_multiple_attr_values.attributes[:uid]
744
+ # classes are not reloaded between tests so restore default
745
+ OneLogin::RubySaml::Attributes.single_value_compatibility = true
746
+ end
747
+
748
+ it "extract first of multiple values as string for b/w compatibility" do
749
+ assert_equal 'value1', response_multiple_attr_values.attributes[:another_value]
750
+ end
751
+
752
+ it "extract first of multiple values as string for b/w compatibility in compatibility mode off" do
753
+ OneLogin::RubySaml::Attributes.single_value_compatibility = false
754
+ assert_equal ['value1', 'value2'], response_multiple_attr_values.attributes[:another_value]
755
+ OneLogin::RubySaml::Attributes.single_value_compatibility = true
756
+ end
757
+
758
+ it "return array with all attributes when asked in XML order" do
759
+ assert_equal ['value1', 'value2'], response_multiple_attr_values.attributes.multi(:another_value)
760
+ end
761
+
762
+ it "return array with all attributes when asked in XML order in compatibility mode off" do
763
+ OneLogin::RubySaml::Attributes.single_value_compatibility = false
764
+ assert_equal ['value1', 'value2'], response_multiple_attr_values.attributes.multi(:another_value)
765
+ OneLogin::RubySaml::Attributes.single_value_compatibility = true
766
+ end
767
+
768
+ it "return first of multiple values when multiple Attribute tags in XML" do
769
+ assert_equal 'role1', response_multiple_attr_values.attributes[:role]
770
+ end
771
+
772
+ it "return first of multiple values when multiple Attribute tags in XML in compatibility mode off" do
773
+ OneLogin::RubySaml::Attributes.single_value_compatibility = false
774
+ assert_equal ['role1', 'role2', 'role3'], response_multiple_attr_values.attributes[:role]
775
+ OneLogin::RubySaml::Attributes.single_value_compatibility = true
776
+ end
777
+
778
+ it "return all of multiple values in reverse order when multiple Attribute tags in XML" do
779
+ assert_equal ['role1', 'role2', 'role3'], response_multiple_attr_values.attributes.multi(:role)
780
+ end
781
+
782
+ it "return all of multiple values in reverse order when multiple Attribute tags in XML in compatibility mode off" do
783
+ OneLogin::RubySaml::Attributes.single_value_compatibility = false
784
+ assert_equal ['role1', 'role2', 'role3'], response_multiple_attr_values.attributes.multi(:role)
785
+ OneLogin::RubySaml::Attributes.single_value_compatibility = true
786
+ end
787
+
788
+ it "return nil value correctly" do
789
+ assert_nil response_multiple_attr_values.attributes[:attribute_with_nil_value]
790
+ end
791
+
792
+ it "return nil value correctly when not in compatibility mode off" do
793
+ OneLogin::RubySaml::Attributes.single_value_compatibility = false
794
+ assert_equal [nil], response_multiple_attr_values.attributes[:attribute_with_nil_value]
795
+ OneLogin::RubySaml::Attributes.single_value_compatibility = true
796
+ end
797
+
798
+ it "return multiple values including nil and empty string" do
799
+ response = OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values))
800
+ assert_equal ["", "valuePresent", nil, nil], response.attributes.multi(:attribute_with_nils_and_empty_strings)
801
+ end
802
+
803
+ it "return multiple values from [] when not in compatibility mode off" do
804
+ OneLogin::RubySaml::Attributes.single_value_compatibility = false
805
+ assert_equal ["", "valuePresent", nil, nil], response_multiple_attr_values.attributes[:attribute_with_nils_and_empty_strings]
806
+ OneLogin::RubySaml::Attributes.single_value_compatibility = true
807
+ end
808
+
809
+ it "check what happens when trying retrieve attribute that does not exists" do
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)
813
+
814
+ OneLogin::RubySaml::Attributes.single_value_compatibility = false
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)
818
+ OneLogin::RubySaml::Attributes.single_value_compatibility = true
819
+ end
820
+
821
+ end
822
+ end
823
+
824
+ describe "#session_expires_at" do
825
+ it "extract the value of the SessionNotOnOrAfter attribute" do
826
+ assert response.session_expires_at.is_a?(Time)
827
+ end
828
+
829
+ it "return nil when the value of the SessionNotOnOrAfter is not set" do
830
+ assert_nil response_without_attributes.session_expires_at
831
+ end
832
+ end
833
+
834
+ describe "#issuers" do
835
+ it "return the issuer inside the response assertion" do
836
+ assert_includes response.issuers, "https://app.onelogin.com/saml/metadata/13590"
837
+ end
838
+
839
+ it "return the issuer inside the response" do
840
+ assert_includes response_without_attributes.issuers, "wibble"
841
+ end
842
+ end
843
+
844
+ describe "#success" do
845
+ it "find a status code that says success" do
846
+ response.success?
847
+ end
848
+ end
849
+
850
+ describe '#xpath_first_from_signed_assertion' do
851
+ it 'not allow arbitrary code execution' do
852
+ malicious_response_document = fixture('response_eval', false)
853
+ malicious_response = OneLogin::RubySaml::Response.new(malicious_response_document)
854
+ malicious_response.send(:xpath_first_from_signed_assertion)
855
+ assert_nil $evalled
856
+ end
857
+ end
858
+
859
+ describe '#sign_document' do
860
+ it 'Sign an unsigned SAML Response XML and initiate the SAML object with it' do
861
+ xml = Base64.decode64(fixture("test_sign.xml"))
862
+
863
+ document = XMLSecurity::Document.new(xml)
864
+
865
+ formated_cert = OneLogin::RubySaml::Utils.format_cert(ruby_saml_cert_text)
866
+ cert = OpenSSL::X509::Certificate.new(formated_cert)
867
+
868
+ formated_private_key = OneLogin::RubySaml::Utils.format_private_key(ruby_saml_key_text)
869
+ private_key = OpenSSL::PKey::RSA.new(formated_private_key)
870
+ document.sign_document(private_key, cert)
871
+
872
+ signed_response = OneLogin::RubySaml::Response.new(document.to_s)
873
+ settings.idp_cert = ruby_saml_cert_text
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
1091
+ end
1092
+ end
1093
+ end
1094
+ end