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.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +6 -0
  3. data/.github/workflows/ci.yml +62 -0
  4. data/.gitignore +1 -0
  5. data/.ruby-version +1 -0
  6. data/Gemfile +12 -2
  7. data/README.md +62 -26
  8. data/app/controllers/devise/saml_sessions_controller.rb +33 -27
  9. data/devise_saml_authenticatable.gemspec +2 -1
  10. data/lib/devise_saml_authenticatable/logger.rb +2 -2
  11. data/lib/devise_saml_authenticatable/model.rb +17 -3
  12. data/lib/devise_saml_authenticatable/saml_config.rb +28 -6
  13. data/lib/devise_saml_authenticatable/strategy.rb +23 -5
  14. data/lib/devise_saml_authenticatable/version.rb +1 -1
  15. data/lib/devise_saml_authenticatable.rb +16 -2
  16. data/spec/controllers/devise/saml_sessions_controller_spec.rb +205 -147
  17. data/spec/devise_saml_authenticatable/model_spec.rb +137 -19
  18. data/spec/devise_saml_authenticatable/saml_config_spec.rb +69 -22
  19. data/spec/devise_saml_authenticatable/strategy_spec.rb +58 -9
  20. data/spec/features/saml_authentication_spec.rb +19 -6
  21. data/spec/support/Gemfile.rails5.2 +2 -13
  22. data/spec/support/Gemfile.rails6 +18 -0
  23. data/spec/support/Gemfile.rails6.1 +24 -0
  24. data/spec/support/idp_settings_adapter.rb.erb +19 -9
  25. data/spec/support/idp_template.rb +5 -13
  26. data/spec/support/rails_app.rb +6 -7
  27. data/spec/support/ruby_saml_support.rb +10 -0
  28. data/spec/support/saml_idp_controller.rb.erb +1 -6
  29. data/spec/support/sp_template.rb +22 -19
  30. metadata +14 -12
  31. data/.travis.yml +0 -52
  32. data/spec/support/Gemfile.rails4 +0 -41
  33. data/spec/support/Gemfile.rails5 +0 -25
  34. 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(:exists?).with("/railsroot/config/idp.yml").and_return(false)
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
- issuer: "sp_issuer",
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
- issuer: "sp_issuer_other",
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
- expect(saml_config.idp_sso_target_url).to eq ("idp_sso_url")
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
- expect(saml_config.idp_sso_target_url).to eq ("idp_sso_url_other")
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
- before do
84
- allow(Rails).to receive(:env).and_return("environment")
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
- idp_slo_target_url: idp_slo_target_url
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
- expect(saml_config.idp_slo_target_url).to eq('idp_slo_target_url')
140
- expect(saml_config.idp_sso_target_url).to eq('idp_sso_target_url')
141
- expect(saml_config.issuer).to eq('issuer')
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 receive(:after_saml_authentication)
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: "acs_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
- issuer: "sp_issuer",
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 'capybara/poltergeist'
7
- Capybara.default_driver = :poltergeist
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.escape(entity_id)}"
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 'poltergeist'
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