ruby-saml 0.8.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of ruby-saml might be problematic. Click here for more details.

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