ruby-saml 0.8.12

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


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

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