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