devise_saml_authenticatable 1.6.2 → 1.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/dependabot.yml +6 -0
- data/.github/workflows/ci.yml +62 -0
- data/.gitignore +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +12 -2
- data/README.md +62 -26
- data/app/controllers/devise/saml_sessions_controller.rb +33 -27
- data/devise_saml_authenticatable.gemspec +2 -1
- data/lib/devise_saml_authenticatable/logger.rb +2 -2
- data/lib/devise_saml_authenticatable/model.rb +17 -3
- data/lib/devise_saml_authenticatable/saml_config.rb +28 -6
- data/lib/devise_saml_authenticatable/strategy.rb +23 -5
- data/lib/devise_saml_authenticatable/version.rb +1 -1
- data/lib/devise_saml_authenticatable.rb +16 -2
- data/spec/controllers/devise/saml_sessions_controller_spec.rb +205 -147
- data/spec/devise_saml_authenticatable/model_spec.rb +137 -19
- data/spec/devise_saml_authenticatable/saml_config_spec.rb +69 -22
- data/spec/devise_saml_authenticatable/strategy_spec.rb +58 -9
- data/spec/features/saml_authentication_spec.rb +19 -6
- data/spec/support/Gemfile.rails5.2 +2 -13
- data/spec/support/Gemfile.rails6 +18 -0
- data/spec/support/Gemfile.rails6.1 +24 -0
- data/spec/support/idp_settings_adapter.rb.erb +19 -9
- data/spec/support/idp_template.rb +5 -13
- data/spec/support/rails_app.rb +6 -7
- data/spec/support/ruby_saml_support.rb +10 -0
- data/spec/support/saml_idp_controller.rb.erb +1 -6
- data/spec/support/sp_template.rb +22 -19
- metadata +14 -12
- data/.travis.yml +0 -52
- data/spec/support/Gemfile.rails4 +0 -41
- data/spec/support/Gemfile.rails5 +0 -25
- data/spec/support/Gemfile.rails5.1 +0 -25
@@ -64,12 +64,12 @@ describe Devise::Models::SamlAuthenticatable do
|
|
64
64
|
|
65
65
|
it "looks up the user by the configured default user key" do
|
66
66
|
user = Model.new(new_record: false)
|
67
|
-
expect(Model).to receive(:where).with(email: 'user@example.com').and_return([user])
|
67
|
+
expect(Model).to receive(:where).with({ email: 'user@example.com' }).and_return([user])
|
68
68
|
expect(Model.authenticate_with_saml(response, nil)).to eq(user)
|
69
69
|
end
|
70
70
|
|
71
71
|
it "returns nil if it cannot find a user" do
|
72
|
-
expect(Model).to receive(:where).with(email: 'user@example.com').and_return([])
|
72
|
+
expect(Model).to receive(:where).with({ email: 'user@example.com' }).and_return([])
|
73
73
|
expect(Model.authenticate_with_saml(response, nil)).to be_nil
|
74
74
|
end
|
75
75
|
|
@@ -83,12 +83,12 @@ describe Devise::Models::SamlAuthenticatable do
|
|
83
83
|
|
84
84
|
it "looks up the user by the configured default user key" do
|
85
85
|
user = Model.new(new_record: false)
|
86
|
-
expect(Model).to receive(:where).with(email: 'user@example.com').and_return([user])
|
86
|
+
expect(Model).to receive(:where).with({ email: 'user@example.com' }).and_return([user])
|
87
87
|
expect(Model.authenticate_with_saml(response, nil)).to eq(user)
|
88
88
|
end
|
89
89
|
|
90
90
|
it "returns nil if it cannot find a user" do
|
91
|
-
expect(Model).to receive(:where).with(email: 'user@example.com').and_return([])
|
91
|
+
expect(Model).to receive(:where).with({ email: 'user@example.com' }).and_return([])
|
92
92
|
expect(Model.authenticate_with_saml(response, nil)).to be_nil
|
93
93
|
end
|
94
94
|
|
@@ -98,7 +98,7 @@ describe Devise::Models::SamlAuthenticatable do
|
|
98
98
|
end
|
99
99
|
|
100
100
|
it "creates and returns a new user with the name identifier and given attributes" do
|
101
|
-
expect(Model).to receive(:where).with(email: 'user@example.com').and_return([])
|
101
|
+
expect(Model).to receive(:where).with({ email: 'user@example.com' }).and_return([])
|
102
102
|
model = Model.authenticate_with_saml(response, nil)
|
103
103
|
expect(model.email).to eq('user@example.com')
|
104
104
|
expect(model.name).to eq('A User')
|
@@ -106,6 +106,32 @@ describe Devise::Models::SamlAuthenticatable do
|
|
106
106
|
end
|
107
107
|
end
|
108
108
|
|
109
|
+
context "when configured to create a user by a proc and the user is not found" do
|
110
|
+
before do
|
111
|
+
create_user_proc = -> (model_class, _saml_response, auth_value) { model_class == Model && auth_value == 'user@example.com' }
|
112
|
+
allow(Devise).to receive(:saml_create_user).and_return(create_user_proc)
|
113
|
+
end
|
114
|
+
|
115
|
+
context "when the proc returns true" do
|
116
|
+
it "creates and returns a new user with the name identifier and given attributes" do
|
117
|
+
expect(Model).to receive(:where).with({ email: name_id }).and_return([])
|
118
|
+
model = Model.authenticate_with_saml(response, nil)
|
119
|
+
expect(model.email).to eq('user@example.com')
|
120
|
+
expect(model.name).to eq('A User')
|
121
|
+
expect(model.saved).to be(true)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
context "when the proc returns false" do
|
126
|
+
let(:name_id) { 'do_not_create@example.com' }
|
127
|
+
|
128
|
+
it "does not creates new user" do
|
129
|
+
expect(Model).to receive(:where).with({ email: name_id }).and_return([])
|
130
|
+
expect(Model.authenticate_with_saml(response, nil)).to be_nil
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
109
135
|
context "when configured to update a user and the user is found" do
|
110
136
|
before do
|
111
137
|
allow(Devise).to receive(:saml_update_user).and_return(true)
|
@@ -113,22 +139,53 @@ describe Devise::Models::SamlAuthenticatable do
|
|
113
139
|
|
114
140
|
it "creates and returns a new user with the name identifier and given attributes" do
|
115
141
|
user = Model.new(email: "old_mail@mail.com", name: "old name", new_record: false)
|
116
|
-
expect(Model).to receive(:where).with(email: 'user@example.com').and_return([user])
|
142
|
+
expect(Model).to receive(:where).with({ email: 'user@example.com' }).and_return([user])
|
117
143
|
model = Model.authenticate_with_saml(response, nil)
|
118
144
|
expect(model.email).to eq('user@example.com')
|
119
145
|
expect(model.name).to eq('A User')
|
120
146
|
expect(model.saved).to be(true)
|
121
147
|
end
|
122
148
|
end
|
149
|
+
|
150
|
+
context "when configured to update a user by a proc and the user is found" do
|
151
|
+
let(:user) { Model.new(email: 'old_mail@mail.com', name: 'old name', new_record: false) }
|
152
|
+
|
153
|
+
before do
|
154
|
+
update_user_proc = -> (model_class, _saml_response, auth_value) { model_class == Model && auth_value == 'user@example.com' }
|
155
|
+
allow(Devise).to receive(:saml_update_user).and_return(update_user_proc)
|
156
|
+
end
|
157
|
+
|
158
|
+
context "when the proc returns true" do
|
159
|
+
it "updates user with given attributes" do
|
160
|
+
expect(Model).to receive(:where).with({ email: name_id }).and_return([user])
|
161
|
+
model = Model.authenticate_with_saml(response, nil)
|
162
|
+
expect(model.email).to eq('user@example.com')
|
163
|
+
expect(model.name).to eq('A User')
|
164
|
+
expect(model.saved).to be(true)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
context "when the proc returns false" do
|
169
|
+
let(:name_id) { 'do_not_update@example.com' }
|
170
|
+
|
171
|
+
it "does not update user" do
|
172
|
+
expect(Model).to receive(:where).with({ email: name_id }).and_return([user])
|
173
|
+
model = Model.authenticate_with_saml(response, nil)
|
174
|
+
expect(model.email).to eq('old_mail@mail.com')
|
175
|
+
expect(model.name).to eq('old name')
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
123
179
|
end
|
124
180
|
|
181
|
+
|
125
182
|
context "when configured to create an user and the user is not found" do
|
126
183
|
before do
|
127
184
|
allow(Devise).to receive(:saml_create_user).and_return(true)
|
128
185
|
end
|
129
186
|
|
130
187
|
it "creates and returns a new user with the given attributes" do
|
131
|
-
expect(Model).to receive(:where).with(email: 'user@example.com').and_return([])
|
188
|
+
expect(Model).to receive(:where).with({ email: 'user@example.com' }).and_return([])
|
132
189
|
model = Model.authenticate_with_saml(response, nil)
|
133
190
|
expect(model.email).to eq('user@example.com')
|
134
191
|
expect(model.name).to eq('A User')
|
@@ -136,19 +193,48 @@ describe Devise::Models::SamlAuthenticatable do
|
|
136
193
|
end
|
137
194
|
end
|
138
195
|
|
196
|
+
context "when configured to create a user by a proc and the user is not found" do
|
197
|
+
let(:create_user_proc) { -> (_model_class, saml_response, _auth_value) { saml_response.raw_response.issuers.first == 'to_create_idp' } }
|
198
|
+
|
199
|
+
before do
|
200
|
+
allow(Devise).to receive(:saml_create_user).and_return(create_user_proc)
|
201
|
+
end
|
202
|
+
|
203
|
+
context "when the proc returns true" do
|
204
|
+
let(:response) { double(:response, issuers: ['to_create_idp'], attributes: attributes, name_id: name_id) }
|
205
|
+
|
206
|
+
it "creates and returns a new user with the name identifier and given attributes" do
|
207
|
+
expect(Model).to receive(:where).with({ email: 'user@example.com' }).and_return([])
|
208
|
+
model = Model.authenticate_with_saml(response, nil)
|
209
|
+
expect(model.email).to eq('user@example.com')
|
210
|
+
expect(model.name).to eq('A User')
|
211
|
+
expect(model.saved).to be(true)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
context "when the proc returns false" do
|
216
|
+
let(:response) { double(:response, issuers: ['do_not_create_idp'], attributes: attributes, name_id: name_id) }
|
217
|
+
|
218
|
+
it "does not creates new user" do
|
219
|
+
expect(Model).to receive(:where).with({ email: 'user@example.com' }).and_return([])
|
220
|
+
expect(Model.authenticate_with_saml(response, nil)).to be_nil
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
139
225
|
context "when configured to update an user" do
|
140
226
|
before do
|
141
227
|
allow(Devise).to receive(:saml_update_user).and_return(true)
|
142
228
|
end
|
143
229
|
|
144
230
|
it "returns nil if the user is not found" do
|
145
|
-
expect(Model).to receive(:where).with(email: 'user@example.com').and_return([])
|
231
|
+
expect(Model).to receive(:where).with({ email: 'user@example.com' }).and_return([])
|
146
232
|
expect(Model.authenticate_with_saml(response, nil)).to be_nil
|
147
233
|
end
|
148
234
|
|
149
235
|
it "updates the attributes if the user is found" do
|
150
236
|
user = Model.new(email: "old_mail@mail.com", name: "old name", new_record: false)
|
151
|
-
expect(Model).to receive(:where).with(email: 'user@example.com').and_return([user])
|
237
|
+
expect(Model).to receive(:where).with({ email: 'user@example.com' }).and_return([user])
|
152
238
|
model = Model.authenticate_with_saml(response, nil)
|
153
239
|
expect(model.email).to eq('user@example.com')
|
154
240
|
expect(model.name).to eq('A User')
|
@@ -156,6 +242,38 @@ describe Devise::Models::SamlAuthenticatable do
|
|
156
242
|
end
|
157
243
|
end
|
158
244
|
|
245
|
+
context "when configured to update a user by a proc and the user is found" do
|
246
|
+
let(:user) { Model.new(email: 'old_mail@mail.com', name: 'old name', new_record: false) }
|
247
|
+
let(:update_user_proc) { -> (_model_class, saml_response, _auth_value) { saml_response.raw_response.issuers.first == 'to_update_idp' } }
|
248
|
+
|
249
|
+
before do
|
250
|
+
allow(Devise).to receive(:saml_update_user).and_return(update_user_proc)
|
251
|
+
end
|
252
|
+
|
253
|
+
context "when the proc returns true" do
|
254
|
+
let(:response) { double(:response, issuers: ['to_update_idp'], attributes: attributes, name_id: name_id) }
|
255
|
+
|
256
|
+
it "updates user with given attributes" do
|
257
|
+
expect(Model).to receive(:where).with({ email: 'user@example.com' }).and_return([user])
|
258
|
+
model = Model.authenticate_with_saml(response, nil)
|
259
|
+
expect(model.email).to eq('user@example.com')
|
260
|
+
expect(model.name).to eq('A User')
|
261
|
+
expect(model.saved).to be(true)
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
context "when the proc returns false" do
|
266
|
+
let(:response) { double(:response, issuers: ['do_not_update_idp'], attributes: attributes, name_id: name_id) }
|
267
|
+
|
268
|
+
it "does not update user" do
|
269
|
+
expect(Model).to receive(:where).with({ email: 'user@example.com' }).and_return([user])
|
270
|
+
model = Model.authenticate_with_saml(response, nil)
|
271
|
+
expect(model.email).to eq('old_mail@mail.com')
|
272
|
+
expect(model.name).to eq('old name')
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
159
277
|
context "when configured with a case-insensitive key" do
|
160
278
|
shared_examples "correct downcasing" do
|
161
279
|
before do
|
@@ -164,7 +282,7 @@ describe Devise::Models::SamlAuthenticatable do
|
|
164
282
|
|
165
283
|
it "looks up the user with a downcased value" do
|
166
284
|
user = Model.new(new_record: false)
|
167
|
-
expect(Model).to receive(:where).with(email: 'upper@example.com').and_return([user])
|
285
|
+
expect(Model).to receive(:where).with({ email: 'upper@example.com' }).and_return([user])
|
168
286
|
expect(Model.authenticate_with_saml(response, nil)).to eq(user)
|
169
287
|
end
|
170
288
|
end
|
@@ -202,7 +320,7 @@ describe Devise::Models::SamlAuthenticatable do
|
|
202
320
|
end
|
203
321
|
|
204
322
|
it "returns the user" do
|
205
|
-
expect(Model).to receive(:where).with(email: 'user@example.com').and_return([user])
|
323
|
+
expect(Model).to receive(:where).with({ email: 'user@example.com' }).and_return([user])
|
206
324
|
expect(Model.authenticate_with_saml(response, nil)).to eq(user)
|
207
325
|
end
|
208
326
|
end
|
@@ -213,7 +331,7 @@ describe Devise::Models::SamlAuthenticatable do
|
|
213
331
|
end
|
214
332
|
|
215
333
|
it "returns nil" do
|
216
|
-
expect(Model).to receive(:where).with(email: 'user@example.com').and_return([user])
|
334
|
+
expect(Model).to receive(:where).with({ email: 'user@example.com' }).and_return([user])
|
217
335
|
expect(Model.authenticate_with_saml(response, nil)).to be_nil
|
218
336
|
end
|
219
337
|
end
|
@@ -236,7 +354,7 @@ describe Devise::Models::SamlAuthenticatable do
|
|
236
354
|
end
|
237
355
|
|
238
356
|
it "returns the user" do
|
239
|
-
expect(Model).to receive(:where).with(email: 'user@example.com').and_return([user])
|
357
|
+
expect(Model).to receive(:where).with({ email: 'user@example.com' }).and_return([user])
|
240
358
|
expect(Model.authenticate_with_saml(response, nil)).to eq(user)
|
241
359
|
end
|
242
360
|
end
|
@@ -247,7 +365,7 @@ describe Devise::Models::SamlAuthenticatable do
|
|
247
365
|
end
|
248
366
|
|
249
367
|
it "returns nil" do
|
250
|
-
expect(Model).to receive(:where).with(email: 'user@example.com').and_return([user])
|
368
|
+
expect(Model).to receive(:where).with({ email: 'user@example.com' }).and_return([user])
|
251
369
|
expect(Model.authenticate_with_saml(response, nil)).to be_nil
|
252
370
|
end
|
253
371
|
end
|
@@ -294,7 +412,7 @@ describe Devise::Models::SamlAuthenticatable do
|
|
294
412
|
end
|
295
413
|
|
296
414
|
def configure_hook(&block)
|
297
|
-
allow(Model).to receive(:where).with(email: 'user@example.com').and_return([])
|
415
|
+
allow(Model).to receive(:where).with({ email: 'user@example.com' }).and_return([])
|
298
416
|
allow(Devise).to receive(:saml_default_user_key).and_return(:email)
|
299
417
|
allow(Devise).to receive(:saml_create_user).and_return(true)
|
300
418
|
allow(Devise).to receive(:saml_update_resource_hook).and_return(block)
|
@@ -305,7 +423,7 @@ describe Devise::Models::SamlAuthenticatable do
|
|
305
423
|
let(:name_id) { 'SomeUsername' }
|
306
424
|
|
307
425
|
it "can replicate the default behaviour for a new user in a custom locator" do
|
308
|
-
allow(Model).to receive(:where).with(email: attributes['saml-email-format']).and_return([])
|
426
|
+
allow(Model).to receive(:where).with({ email: attributes['saml-email-format'] }).and_return([])
|
309
427
|
|
310
428
|
configure_hook do |model, saml_response, auth_value|
|
311
429
|
Devise.saml_default_resource_locator.call(model, saml_response, auth_value)
|
@@ -321,7 +439,7 @@ describe Devise::Models::SamlAuthenticatable do
|
|
321
439
|
user = Model.new(email: attributes['saml-email-format'], name: attributes['saml-name-format'])
|
322
440
|
user.save!
|
323
441
|
|
324
|
-
allow(Model).to receive(:where).with(email: attributes['saml-email-format']).and_return([user])
|
442
|
+
allow(Model).to receive(:where).with({ email: attributes['saml-email-format'] }).and_return([user])
|
325
443
|
|
326
444
|
configure_hook do |model, saml_response, auth_value|
|
327
445
|
Devise.saml_default_resource_locator.call(model, saml_response, auth_value)
|
@@ -335,7 +453,7 @@ describe Devise::Models::SamlAuthenticatable do
|
|
335
453
|
end
|
336
454
|
|
337
455
|
it "can change the default behaviour for a new user from the saml response" do
|
338
|
-
allow(Model).to receive(:where).with(foo: attributes['saml-email-format'], bar: name_id).and_return([])
|
456
|
+
allow(Model).to receive(:where).with({ foo: attributes['saml-email-format'], bar: name_id }).and_return([])
|
339
457
|
|
340
458
|
configure_hook do |model, saml_response, auth_value|
|
341
459
|
name_id = saml_response.raw_response.name_id
|
@@ -352,7 +470,7 @@ describe Devise::Models::SamlAuthenticatable do
|
|
352
470
|
user = Model.new(email: attributes['saml-email-format'], name: attributes['saml-name-format'])
|
353
471
|
user.save!
|
354
472
|
|
355
|
-
allow(Model).to receive(:where).with(foo: attributes['saml-email-format'], bar: name_id).and_return([user])
|
473
|
+
allow(Model).to receive(:where).with({ foo: attributes['saml-email-format'], bar: name_id }).and_return([user])
|
356
474
|
|
357
475
|
configure_hook do |model, saml_response, auth_value|
|
358
476
|
name_id = saml_response.raw_response.name_id
|
@@ -1,13 +1,16 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
require 'support/ruby_saml_support'
|
2
3
|
|
3
4
|
describe DeviseSamlAuthenticatable::SamlConfig do
|
5
|
+
include RubySamlSupport
|
6
|
+
|
4
7
|
let(:saml_config) { controller.saml_config }
|
5
8
|
let(:controller) { Class.new { include DeviseSamlAuthenticatable::SamlConfig }.new }
|
6
9
|
|
7
10
|
context "when config/idp.yml does not exist" do
|
8
11
|
before do
|
9
12
|
allow(Rails).to receive(:root).and_return("/railsroot")
|
10
|
-
allow(File).to receive(:
|
13
|
+
allow(File).to receive(:exist?).with("/railsroot/config/idp.yml").and_return(false)
|
11
14
|
end
|
12
15
|
|
13
16
|
it "is the global devise SAML config" do
|
@@ -26,32 +29,54 @@ describe DeviseSamlAuthenticatable::SamlConfig do
|
|
26
29
|
let(:saml_config) { controller.saml_config(idp_entity_id) }
|
27
30
|
let(:idp_providers_adapter) {
|
28
31
|
Class.new {
|
32
|
+
extend RubySamlSupport
|
33
|
+
|
29
34
|
def self.settings(idp_entity_id)
|
30
35
|
#some hash of stuff (by doing a fetch, in our case, but could also be a giant hash keyed by idp_entity_id)
|
31
36
|
if idp_entity_id == "http://www.example.com"
|
32
|
-
{
|
37
|
+
base = {
|
33
38
|
assertion_consumer_service_url: "acs_url",
|
34
39
|
assertion_consumer_service_binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
|
35
40
|
name_identifier_format: "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
|
36
|
-
|
41
|
+
sp_entity_id: "sp_issuer",
|
37
42
|
idp_entity_id: "http://www.example.com",
|
38
43
|
authn_context: "",
|
39
|
-
idp_slo_target_url: "idp_slo_url",
|
40
|
-
idp_sso_target_url: "idp_sso_url",
|
41
44
|
idp_cert: "idp_cert"
|
42
45
|
}
|
46
|
+
with_ruby_saml_1_12_or_greater(proc {
|
47
|
+
base.merge!(
|
48
|
+
idp_slo_service_url: "idp_slo_url",
|
49
|
+
idp_sso_service_url: "idp_sso_url",
|
50
|
+
)
|
51
|
+
}, else_do: proc {
|
52
|
+
base.merge!(
|
53
|
+
idp_slo_target_url: "idp_slo_url",
|
54
|
+
idp_sso_target_url: "idp_sso_url",
|
55
|
+
)
|
56
|
+
})
|
57
|
+
base
|
43
58
|
elsif idp_entity_id == "http://www.example.com_other"
|
44
|
-
{
|
59
|
+
base = {
|
45
60
|
assertion_consumer_service_url: "acs_url_other",
|
46
61
|
assertion_consumer_service_binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST_other",
|
47
62
|
name_identifier_format: "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress_other",
|
48
|
-
|
63
|
+
sp_entity_id: "sp_issuer_other",
|
49
64
|
idp_entity_id: "http://www.example.com_other",
|
50
65
|
authn_context: "_other",
|
51
|
-
idp_slo_target_url: "idp_slo_url_other",
|
52
|
-
idp_sso_target_url: "idp_sso_url_other",
|
53
66
|
idp_cert: "idp_cert_other"
|
54
67
|
}
|
68
|
+
with_ruby_saml_1_12_or_greater(proc {
|
69
|
+
base.merge!(
|
70
|
+
idp_slo_service_url: "idp_slo_url_other",
|
71
|
+
idp_sso_service_url: "idp_sso_url_other",
|
72
|
+
)
|
73
|
+
}, else_do: proc {
|
74
|
+
base.merge!(
|
75
|
+
idp_slo_target_url: "idp_slo_url_other",
|
76
|
+
idp_sso_target_url: "idp_sso_url_other",
|
77
|
+
)
|
78
|
+
})
|
79
|
+
base
|
55
80
|
else
|
56
81
|
{}
|
57
82
|
end
|
@@ -63,7 +88,11 @@ describe DeviseSamlAuthenticatable::SamlConfig do
|
|
63
88
|
let(:idp_entity_id) { "http://www.example.com" }
|
64
89
|
it "uses the settings from the adapter for that idp" do
|
65
90
|
expect(saml_config.idp_entity_id).to eq (idp_entity_id)
|
66
|
-
|
91
|
+
with_ruby_saml_1_12_or_greater(proc {
|
92
|
+
expect(saml_config.idp_sso_service_url).to eq('idp_sso_url')
|
93
|
+
}, else_do: proc {
|
94
|
+
expect(saml_config.idp_sso_target_url).to eq('idp_sso_url')
|
95
|
+
})
|
67
96
|
expect(saml_config.class).to eq OneLogin::RubySaml::Settings
|
68
97
|
end
|
69
98
|
end
|
@@ -72,7 +101,11 @@ describe DeviseSamlAuthenticatable::SamlConfig do
|
|
72
101
|
let(:idp_entity_id) { "http://www.example.com_other" }
|
73
102
|
it "returns the other idp settings" do
|
74
103
|
expect(saml_config.idp_entity_id).to eq (idp_entity_id)
|
75
|
-
|
104
|
+
with_ruby_saml_1_12_or_greater(proc {
|
105
|
+
expect(saml_config.idp_sso_service_url).to eq('idp_sso_url_other')
|
106
|
+
}, else_do: proc {
|
107
|
+
expect(saml_config.idp_sso_target_url).to eq('idp_sso_url_other')
|
108
|
+
})
|
76
109
|
expect(saml_config.class).to eq OneLogin::RubySaml::Settings
|
77
110
|
end
|
78
111
|
end
|
@@ -80,11 +113,8 @@ describe DeviseSamlAuthenticatable::SamlConfig do
|
|
80
113
|
end
|
81
114
|
|
82
115
|
context "when config/idp.yml exists" do
|
83
|
-
|
84
|
-
|
85
|
-
allow(Rails).to receive(:root).and_return("/railsroot")
|
86
|
-
allow(File).to receive(:exists?).with("/railsroot/config/idp.yml").and_return(true)
|
87
|
-
allow(File).to receive(:read).with("/railsroot/config/idp.yml").and_return(<<-IDP)
|
116
|
+
let(:idp_yaml) {
|
117
|
+
yaml = <<-IDP
|
88
118
|
---
|
89
119
|
environment:
|
90
120
|
assertion_consumer_logout_service_binding: assertion_consumer_logout_service_binding
|
@@ -104,9 +134,7 @@ environment:
|
|
104
134
|
idp_cert_fingerprint: idp_cert_fingerprint
|
105
135
|
idp_cert_fingerprint_algorithm: idp_cert_fingerprint_algorithm
|
106
136
|
idp_entity_id: idp_entity_id
|
107
|
-
|
108
|
-
idp_sso_target_url: idp_sso_target_url
|
109
|
-
issuer: issuer
|
137
|
+
sp_entity_id: issuer
|
110
138
|
name_identifier_format: name_identifier_format
|
111
139
|
name_identifier_value: name_identifier_value
|
112
140
|
passive: passive
|
@@ -116,6 +144,20 @@ environment:
|
|
116
144
|
sessionindex: sessionindex
|
117
145
|
sp_name_qualifier: sp_name_qualifier
|
118
146
|
IDP
|
147
|
+
with_ruby_saml_1_12_or_greater(proc { yaml << <<SERVICE_URLS }, else_do: proc { yaml << <<TARGET_URLS })
|
148
|
+
idp_slo_service_url: idp_slo_service_url
|
149
|
+
idp_sso_service_url: idp_sso_service_url
|
150
|
+
SERVICE_URLS
|
151
|
+
idp_slo_target_url: idp_slo_service_url
|
152
|
+
idp_sso_target_url: idp_sso_service_url
|
153
|
+
TARGET_URLS
|
154
|
+
yaml
|
155
|
+
}
|
156
|
+
before do
|
157
|
+
allow(Rails).to receive(:env).and_return("environment")
|
158
|
+
allow(Rails).to receive(:root).and_return("/railsroot")
|
159
|
+
allow(File).to receive(:exist?).with("/railsroot/config/idp.yml").and_return(true)
|
160
|
+
allow(File).to receive(:read).with("/railsroot/config/idp.yml").and_return(idp_yaml)
|
119
161
|
end
|
120
162
|
|
121
163
|
it "uses that file's contents" do
|
@@ -136,9 +178,14 @@ environment:
|
|
136
178
|
expect(saml_config.idp_cert_fingerprint).to eq('idp_cert_fingerprint')
|
137
179
|
expect(saml_config.idp_cert_fingerprint_algorithm).to eq('idp_cert_fingerprint_algorithm')
|
138
180
|
expect(saml_config.idp_entity_id).to eq('idp_entity_id')
|
139
|
-
|
140
|
-
|
141
|
-
|
181
|
+
with_ruby_saml_1_12_or_greater(proc {
|
182
|
+
expect(saml_config.idp_slo_service_url).to eq('idp_slo_service_url')
|
183
|
+
expect(saml_config.idp_sso_service_url).to eq('idp_sso_service_url')
|
184
|
+
}, else_do: proc {
|
185
|
+
expect(saml_config.idp_slo_target_url).to eq('idp_slo_service_url')
|
186
|
+
expect(saml_config.idp_sso_target_url).to eq('idp_sso_service_url')
|
187
|
+
})
|
188
|
+
expect(saml_config.sp_entity_id).to eq('issuer')
|
142
189
|
expect(saml_config.name_identifier_format).to eq('name_identifier_format')
|
143
190
|
expect(saml_config.name_identifier_value).to eq('name_identifier_value')
|
144
191
|
expect(saml_config.passive).to eq('passive')
|
@@ -1,6 +1,9 @@
|
|
1
1
|
require 'rails_helper'
|
2
|
+
require 'support/ruby_saml_support'
|
2
3
|
|
3
4
|
describe Devise::Strategies::SamlAuthenticatable do
|
5
|
+
include RubySamlSupport
|
6
|
+
|
4
7
|
subject(:strategy) { described_class.new(env, :user) }
|
5
8
|
let(:env) { {} }
|
6
9
|
let(:errors) { ["Test1", "Test2"] }
|
@@ -16,7 +19,7 @@ describe Devise::Strategies::SamlAuthenticatable do
|
|
16
19
|
let(:user) { double(:user) }
|
17
20
|
before do
|
18
21
|
allow(strategy).to receive(:mapping).and_return(mapping)
|
19
|
-
allow(user).to
|
22
|
+
allow(user).to(receive(:after_saml_authentication)) if user
|
20
23
|
end
|
21
24
|
|
22
25
|
let(:params) { {} }
|
@@ -53,18 +56,28 @@ describe Devise::Strategies::SamlAuthenticatable do
|
|
53
56
|
context "when saml config uses an idp_adapter" do
|
54
57
|
let(:idp_providers_adapter) {
|
55
58
|
Class.new {
|
56
|
-
def self.settings(idp_entity_id)
|
57
|
-
{
|
58
|
-
assertion_consumer_service_url: "
|
59
|
+
def self.settings(idp_entity_id, request)
|
60
|
+
base = {
|
61
|
+
assertion_consumer_service_url: "acs url",
|
59
62
|
assertion_consumer_service_binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
|
60
63
|
name_identifier_format: "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
|
61
|
-
|
64
|
+
sp_entity_id: "sp_issuer",
|
62
65
|
idp_entity_id: "http://www.example.com",
|
63
66
|
authn_context: "",
|
64
|
-
idp_slo_target_url: "idp_slo_url",
|
65
|
-
idp_sso_target_url: "http://idp_sso_url",
|
66
67
|
idp_cert: "idp_cert"
|
67
68
|
}
|
69
|
+
with_ruby_saml_1_12_or_greater(proc {
|
70
|
+
base.merge!(
|
71
|
+
idp_slo_service_url: "idp_slo_url",
|
72
|
+
idp_sso_service_url: "http://idp_sso_url",
|
73
|
+
)
|
74
|
+
}, else_do: proc {
|
75
|
+
base.merge!(
|
76
|
+
idp_slo_target_url: "idp_slo_url",
|
77
|
+
idp_sso_target_url: "http://idp_sso_url",
|
78
|
+
)
|
79
|
+
})
|
80
|
+
base
|
68
81
|
end
|
69
82
|
}
|
70
83
|
}
|
@@ -80,7 +93,7 @@ describe Devise::Strategies::SamlAuthenticatable do
|
|
80
93
|
|
81
94
|
it "authenticates with the response for the corresponding idp" do
|
82
95
|
expect(OneLogin::RubySaml::Response).to receive(:new).with(params[:SAMLResponse], anything)
|
83
|
-
expect(idp_providers_adapter).to receive(:settings).with(idp_entity_id)
|
96
|
+
expect(idp_providers_adapter).to receive(:settings).with(idp_entity_id, anything)
|
84
97
|
expect(user_class).to receive(:authenticate_with_saml).with(response, params[:RelayState])
|
85
98
|
expect(user).to receive(:after_saml_authentication).with(response.sessionindex)
|
86
99
|
|
@@ -93,8 +106,10 @@ describe Devise::Strategies::SamlAuthenticatable do
|
|
93
106
|
let(:user) { nil }
|
94
107
|
|
95
108
|
it "fails to authenticate" do
|
96
|
-
expect(strategy).to receive(:fail!).with(:invalid)
|
97
109
|
strategy.authenticate!
|
110
|
+
expect(strategy).to be_halted
|
111
|
+
expect(strategy.message).to be(:invalid)
|
112
|
+
expect(strategy.result).to be(:failure)
|
98
113
|
end
|
99
114
|
|
100
115
|
it 'logs the error' do
|
@@ -152,6 +167,40 @@ describe Devise::Strategies::SamlAuthenticatable do
|
|
152
167
|
strategy.authenticate!
|
153
168
|
end
|
154
169
|
end
|
170
|
+
|
171
|
+
context "when saml_validate_in_response_to is opted-in to" do
|
172
|
+
let(:transaction_id) { "abc123" }
|
173
|
+
|
174
|
+
before do
|
175
|
+
allow(Devise).to receive(:saml_validate_in_response_to).and_return(true)
|
176
|
+
allow_any_instance_of(ActionDispatch::Request).to receive(:session).and_return(session)
|
177
|
+
end
|
178
|
+
|
179
|
+
context "when the session has a saml_transaction_id" do
|
180
|
+
let(:session) { { saml_transaction_id: transaction_id }}
|
181
|
+
|
182
|
+
it "is valid with the matches_request_id parameter" do
|
183
|
+
expect(OneLogin::RubySaml::Response).to receive(:new).with(params[:SAMLResponse], hash_including(matches_request_id: transaction_id))
|
184
|
+
expect(strategy).to be_valid
|
185
|
+
end
|
186
|
+
|
187
|
+
it "authenticates with the matches_request_id parameter" do
|
188
|
+
expect(OneLogin::RubySaml::Response).to receive(:new).with(params[:SAMLResponse], hash_including(matches_request_id: transaction_id))
|
189
|
+
|
190
|
+
expect(strategy).to receive(:success!).with(user)
|
191
|
+
strategy.authenticate!
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
context "when the session is missing a saml_transaction_id" do
|
196
|
+
let(:session) { { } }
|
197
|
+
|
198
|
+
it "uses 'ID_MISSING' for matches_request_id so validation will fail" do
|
199
|
+
expect(OneLogin::RubySaml::Response).to receive(:new).with(params[:SAMLResponse], hash_including(matches_request_id: "ID_MISSING"))
|
200
|
+
strategy.authenticate!
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
155
204
|
end
|
156
205
|
|
157
206
|
it "is not valid without a SAMLResponse parameter" do
|
@@ -3,8 +3,21 @@ require 'net/http'
|
|
3
3
|
require 'timeout'
|
4
4
|
require 'uri'
|
5
5
|
require 'capybara/rspec'
|
6
|
-
require '
|
7
|
-
|
6
|
+
require 'selenium-webdriver'
|
7
|
+
|
8
|
+
Capybara.register_driver :chrome do |app|
|
9
|
+
options = Selenium::WebDriver::Chrome::Options.new
|
10
|
+
options.add_argument('--headless')
|
11
|
+
options.add_argument('--allow-insecure-localhost')
|
12
|
+
options.add_argument('--ignore-certificate-errors')
|
13
|
+
|
14
|
+
Capybara::Selenium::Driver.new(
|
15
|
+
app,
|
16
|
+
browser: :chrome,
|
17
|
+
capabilities: [options]
|
18
|
+
)
|
19
|
+
end
|
20
|
+
Capybara.default_driver = :chrome
|
8
21
|
Capybara.server = :webrick
|
9
22
|
|
10
23
|
describe "SAML Authentication", type: :feature do
|
@@ -141,7 +154,7 @@ describe "SAML Authentication", type: :feature do
|
|
141
154
|
context "when the idp_settings_adapter key is set" do
|
142
155
|
before(:each) do
|
143
156
|
create_app('idp', 'INCLUDE_SUBJECT_IN_ATTRIBUTES' => "false")
|
144
|
-
create_app('sp', 'USE_SUBJECT_TO_AUTHENTICATE' => "true", 'IDP_SETTINGS_ADAPTER' => "IdpSettingsAdapter", 'IDP_ENTITY_ID_READER' => "OurEntityIdReader")
|
157
|
+
create_app('sp', 'USE_SUBJECT_TO_AUTHENTICATE' => "true", 'IDP_SETTINGS_ADAPTER' => '"IdpSettingsAdapter"', 'IDP_ENTITY_ID_READER' => '"OurEntityIdReader"')
|
145
158
|
|
146
159
|
# use a different port for this entity ID; configured in spec/support/idp_settings_adapter.rb.erb
|
147
160
|
@idp_pid = start_app('idp', 8010)
|
@@ -165,7 +178,7 @@ describe "SAML Authentication", type: :feature do
|
|
165
178
|
let(:valid_destination) { "true" }
|
166
179
|
before(:each) do
|
167
180
|
create_app('idp', 'INCLUDE_SUBJECT_IN_ATTRIBUTES' => "false", 'VALID_DESTINATION' => valid_destination)
|
168
|
-
create_app('sp', 'USE_SUBJECT_TO_AUTHENTICATE' => "true", 'SAML_FAILED_CALLBACK' => "OurSamlFailedCallbackHandler")
|
181
|
+
create_app('sp', 'USE_SUBJECT_TO_AUTHENTICATE' => "true", 'SAML_FAILED_CALLBACK' => '"OurSamlFailedCallbackHandler"')
|
169
182
|
|
170
183
|
@idp_pid = start_app('idp', idp_port)
|
171
184
|
@sp_pid = start_app('sp', sp_port)
|
@@ -204,7 +217,7 @@ describe "SAML Authentication", type: :feature do
|
|
204
217
|
)
|
205
218
|
create_app(
|
206
219
|
"sp",
|
207
|
-
"ATTRIBUTE_MAP_RESOLVER" => "AttributeMapResolver",
|
220
|
+
"ATTRIBUTE_MAP_RESOLVER" => '"AttributeMapResolver"',
|
208
221
|
"USE_SUBJECT_TO_AUTHENTICATE" => "true",
|
209
222
|
)
|
210
223
|
@idp_pid = start_app("idp", idp_port)
|
@@ -224,7 +237,7 @@ describe "SAML Authentication", type: :feature do
|
|
224
237
|
end
|
225
238
|
|
226
239
|
def sign_in(entity_id: "")
|
227
|
-
visit "http://localhost:8020/users/saml/sign_in/?entity_id=#{URI.
|
240
|
+
visit "http://localhost:8020/users/saml/sign_in/?entity_id=#{URI.encode_www_form_component(entity_id)}"
|
228
241
|
fill_in "Email", with: "you@example.com"
|
229
242
|
fill_in "Password", with: "asdf"
|
230
243
|
click_on "Sign in"
|
@@ -6,20 +6,9 @@ gemspec path: '../..'
|
|
6
6
|
group :test do
|
7
7
|
gem 'rake'
|
8
8
|
gem 'rspec', '~> 3.0'
|
9
|
-
gem 'rails', '~> 5.2'
|
9
|
+
gem 'rails', '~> 5.2.0'
|
10
10
|
gem 'rspec-rails', '~> 3.9'
|
11
11
|
gem 'sqlite3', '~> 1.3.6'
|
12
12
|
gem 'capybara'
|
13
|
-
gem '
|
14
|
-
|
15
|
-
# Lock down versions of gems for older versions of Ruby
|
16
|
-
if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.4")
|
17
|
-
gem 'responders', '~> 2.4'
|
18
|
-
end
|
19
|
-
|
20
|
-
if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.3")
|
21
|
-
gem 'byebug', '~> 10.0'
|
22
|
-
elsif Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.4")
|
23
|
-
gem 'byebug', '~> 11.0.0'
|
24
|
-
end
|
13
|
+
gem 'selenium-webdriver'
|
25
14
|
end
|