devise_saml_authenticatable 1.6.2 → 1.9.1

Sign up to get free protection for your applications and to get access to all the features.
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