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