devise_saml_authenticatable 1.6.3 → 1.9.0
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/workflows/ci.yml +52 -0
- data/.gitignore +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +12 -2
- data/README.md +56 -20
- data/app/controllers/devise/saml_sessions_controller.rb +33 -27
- data/lib/devise_saml_authenticatable/logger.rb +2 -2
- data/lib/devise_saml_authenticatable/model.rb +8 -2
- data/lib/devise_saml_authenticatable/saml_config.rb +10 -4
- data/lib/devise_saml_authenticatable/strategy.rb +23 -5
- data/lib/devise_saml_authenticatable/version.rb +1 -1
- data/lib/devise_saml_authenticatable.rb +14 -0
- data/spec/controllers/devise/saml_sessions_controller_spec.rb +205 -147
- data/spec/devise_saml_authenticatable/model_spec.rb +118 -0
- data/spec/devise_saml_authenticatable/saml_config_spec.rb +64 -17
- data/spec/devise_saml_authenticatable/strategy_spec.rb +57 -8
- data/spec/features/saml_authentication_spec.rb +17 -4
- 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 +20 -17
- metadata +11 -10
- 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
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'rails_helper'
|
2
|
+
require 'support/ruby_saml_support'
|
2
3
|
|
3
4
|
# The important parts from devise
|
4
5
|
class DeviseController < ApplicationController
|
@@ -8,112 +9,122 @@ class DeviseController < ApplicationController
|
|
8
9
|
User
|
9
10
|
end
|
10
11
|
|
11
|
-
def
|
12
|
+
def resource_name
|
13
|
+
'users'
|
14
|
+
end
|
15
|
+
|
16
|
+
def require_no_authentication; end
|
17
|
+
|
18
|
+
def set_flash_message!(key, kind, _options = {})
|
19
|
+
flash[key] = I18n.t("devise.sessions.#{kind}")
|
12
20
|
end
|
13
21
|
end
|
22
|
+
|
14
23
|
class Devise::SessionsController < DeviseController
|
15
24
|
def destroy
|
16
25
|
sign_out
|
17
|
-
redirect_to after_sign_out_path_for(:user)
|
18
|
-
end
|
19
|
-
|
20
|
-
def verify_signed_out_user
|
21
|
-
# no-op for these tests
|
26
|
+
redirect_to after_sign_out_path_for(:user), allow_other_host: true
|
22
27
|
end
|
23
28
|
end
|
24
29
|
|
25
30
|
require_relative '../../../app/controllers/devise/saml_sessions_controller'
|
26
31
|
|
27
32
|
describe Devise::SamlSessionsController, type: :controller do
|
28
|
-
|
33
|
+
include RubySamlSupport
|
34
|
+
|
35
|
+
let(:idp_providers_adapter) { spy('Stub IDPSettings Adaptor') }
|
29
36
|
|
30
37
|
before do
|
31
|
-
@request.env[
|
32
|
-
|
33
|
-
assertion_consumer_service_url:
|
34
|
-
assertion_consumer_service_binding:
|
35
|
-
name_identifier_format:
|
36
|
-
issuer:
|
37
|
-
idp_entity_id:
|
38
|
-
authn_context:
|
39
|
-
|
40
|
-
|
41
|
-
|
38
|
+
@request.env['devise.mapping'] = Devise.mappings[:user]
|
39
|
+
settings = {
|
40
|
+
assertion_consumer_service_url: 'acs_url',
|
41
|
+
assertion_consumer_service_binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
|
42
|
+
name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient',
|
43
|
+
issuer: 'sp_issuer',
|
44
|
+
idp_entity_id: 'http://www.example.com',
|
45
|
+
authn_context: '',
|
46
|
+
idp_cert: 'idp_cert'
|
47
|
+
}
|
48
|
+
with_ruby_saml_1_12_or_greater(proc {
|
49
|
+
settings.merge!(
|
50
|
+
idp_slo_service_url: 'http://idp_slo_url',
|
51
|
+
idp_sso_service_url: 'http://idp_sso_url'
|
52
|
+
)
|
53
|
+
}, else_do: proc {
|
54
|
+
settings.merge!(
|
55
|
+
idp_slo_target_url: 'http://idp_slo_url',
|
56
|
+
idp_sso_target_url: 'http://idp_sso_url'
|
57
|
+
)
|
42
58
|
})
|
59
|
+
allow(idp_providers_adapter).to receive(:settings).and_return(settings)
|
43
60
|
end
|
44
61
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
# patch tests using snippet from https://github.com/rails/rails/issues/34790#issuecomment-483607370
|
49
|
-
class ActionController::TestResponse < ActionDispatch::TestResponse
|
50
|
-
def recycle!
|
51
|
-
@mon_mutex_owner_object_id = nil
|
52
|
-
@mon_mutex = nil
|
53
|
-
initialize
|
54
|
-
end
|
55
|
-
end
|
62
|
+
describe '#new' do
|
63
|
+
let(:saml_response) do
|
64
|
+
File.read(File.join(File.dirname(__FILE__), '../../support', 'response_encrypted_nameid.xml.base64'))
|
56
65
|
end
|
57
|
-
end
|
58
66
|
|
59
|
-
|
60
|
-
|
67
|
+
subject(:do_get) do
|
68
|
+
get :new, params: { 'SAMLResponse' => saml_response }
|
69
|
+
end
|
61
70
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
get :new, "SAMLResponse" => saml_response
|
71
|
+
context 'when using the default saml config' do
|
72
|
+
it 'redirects to the IdP SSO target url' do
|
73
|
+
do_get
|
74
|
+
expect(response).to redirect_to(%r{\Ahttp://localhost:8009/saml/auth\?SAMLRequest=})
|
67
75
|
end
|
68
|
-
}
|
69
76
|
|
70
|
-
|
71
|
-
it "redirects to the IdP SSO target url" do
|
77
|
+
it 'stores saml_transaction_id in the session' do
|
72
78
|
do_get
|
73
|
-
|
79
|
+
if OneLogin::RubySaml::Authrequest.public_instance_methods.include?(:request_id)
|
80
|
+
expect(session[:saml_transaction_id]).to be_present
|
81
|
+
end
|
74
82
|
end
|
75
83
|
end
|
76
84
|
|
77
|
-
context
|
85
|
+
context 'with a specified idp' do
|
78
86
|
before do
|
79
87
|
Devise.idp_settings_adapter = idp_providers_adapter
|
80
88
|
end
|
81
89
|
|
82
|
-
it
|
90
|
+
it 'redirects to the associated IdP SSO target url' do
|
83
91
|
do_get
|
84
|
-
expect(response).to redirect_to(%r
|
92
|
+
expect(response).to redirect_to(%r{\Ahttp://idp_sso_url\?SAMLRequest=})
|
85
93
|
end
|
86
94
|
|
87
|
-
it
|
95
|
+
it 'stores saml_transaction_id in the session' do
|
96
|
+
do_get
|
97
|
+
if OneLogin::RubySaml::Authrequest.public_instance_methods.include?(:request_id)
|
98
|
+
expect(session[:saml_transaction_id]).to be_present
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'uses the DefaultIdpEntityIdReader' do
|
88
103
|
expect(DeviseSamlAuthenticatable::DefaultIdpEntityIdReader).to receive(:entity_id)
|
89
104
|
do_get
|
90
|
-
expect(idp_providers_adapter).to have_received(:settings).with(nil)
|
105
|
+
expect(idp_providers_adapter).to have_received(:settings).with(nil, request)
|
91
106
|
end
|
92
107
|
|
93
|
-
context
|
94
|
-
let(:relay_state) { ->(
|
108
|
+
context 'with a relay_state lambda defined' do
|
109
|
+
let(:relay_state) { ->(_request) { '123' } }
|
95
110
|
|
96
|
-
it
|
111
|
+
it 'includes the RelayState param in the request to the IdP' do
|
97
112
|
expect(Devise).to receive(:saml_relay_state).at_least(:once).and_return(relay_state)
|
98
113
|
do_get
|
99
|
-
expect(response).to redirect_to(%r
|
114
|
+
expect(response).to redirect_to(%r{\Ahttp://idp_sso_url\?SAMLRequest=.*&RelayState=123})
|
100
115
|
end
|
101
116
|
end
|
102
117
|
|
103
|
-
context
|
118
|
+
context 'with a specified idp entity id reader' do
|
104
119
|
class OurIdpEntityIdReader
|
105
120
|
def self.entity_id(params)
|
106
121
|
params[:entity_id]
|
107
122
|
end
|
108
123
|
end
|
109
124
|
|
110
|
-
subject(:do_get)
|
111
|
-
|
112
|
-
|
113
|
-
else
|
114
|
-
get :new, entity_id: "http://www.example.com"
|
115
|
-
end
|
116
|
-
}
|
125
|
+
subject(:do_get) do
|
126
|
+
get :new, params: { entity_id: 'http://www.example.com' }
|
127
|
+
end
|
117
128
|
|
118
129
|
before do
|
119
130
|
@default_reader = Devise.idp_entity_id_reader
|
@@ -124,10 +135,10 @@ describe Devise::SamlSessionsController, type: :controller do
|
|
124
135
|
Devise.idp_entity_id_reader = @default_reader
|
125
136
|
end
|
126
137
|
|
127
|
-
it
|
138
|
+
it 'redirects to the associated IdP SSO target url' do
|
128
139
|
do_get
|
129
|
-
expect(idp_providers_adapter).to have_received(:settings).with(
|
130
|
-
expect(response).to redirect_to(%r
|
140
|
+
expect(idp_providers_adapter).to have_received(:settings).with('http://www.example.com', request)
|
141
|
+
expect(response).to redirect_to(%r{\Ahttp://idp_sso_url\?SAMLRequest=})
|
131
142
|
end
|
132
143
|
end
|
133
144
|
end
|
@@ -136,7 +147,7 @@ describe Devise::SamlSessionsController, type: :controller do
|
|
136
147
|
describe '#metadata' do
|
137
148
|
let(:saml_config) { Devise.saml_config.dup }
|
138
149
|
|
139
|
-
context
|
150
|
+
context 'with the default configuration' do
|
140
151
|
it 'generates metadata' do
|
141
152
|
get :metadata
|
142
153
|
|
@@ -147,20 +158,20 @@ describe Devise::SamlSessionsController, type: :controller do
|
|
147
158
|
end
|
148
159
|
end
|
149
160
|
|
150
|
-
context
|
151
|
-
let(:saml_config) { controller.saml_config(
|
161
|
+
context 'with a specified IDP' do
|
162
|
+
let(:saml_config) { controller.saml_config('anything') }
|
152
163
|
|
153
164
|
before do
|
154
165
|
Devise.idp_settings_adapter = idp_providers_adapter
|
155
166
|
Devise.saml_configure do |settings|
|
156
|
-
settings.assertion_consumer_service_url =
|
157
|
-
settings.assertion_consumer_service_binding =
|
158
|
-
settings.name_identifier_format =
|
159
|
-
settings.issuer =
|
167
|
+
settings.assertion_consumer_service_url = 'http://localhost:3000/users/saml/auth'
|
168
|
+
settings.assertion_consumer_service_binding = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'
|
169
|
+
settings.name_identifier_format = 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
|
170
|
+
settings.issuer = 'http://localhost:3000'
|
160
171
|
end
|
161
172
|
end
|
162
173
|
|
163
|
-
it
|
174
|
+
it 'generates the same service metadata' do
|
164
175
|
get :metadata
|
165
176
|
|
166
177
|
# Remove ID that can vary across requests
|
@@ -172,79 +183,131 @@ describe Devise::SamlSessionsController, type: :controller do
|
|
172
183
|
end
|
173
184
|
|
174
185
|
describe '#destroy' do
|
175
|
-
|
176
|
-
allow(controller).to receive(:sign_out)
|
177
|
-
end
|
186
|
+
subject { delete :destroy }
|
178
187
|
|
179
|
-
context
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
188
|
+
context 'when user is signed out' do
|
189
|
+
before do
|
190
|
+
class Devise::SessionsController < DeviseController
|
191
|
+
def all_signed_out?
|
192
|
+
true
|
193
|
+
end
|
194
|
+
end
|
184
195
|
end
|
185
|
-
end
|
186
196
|
|
187
|
-
|
188
|
-
|
189
|
-
|
197
|
+
shared_examples 'not create SP initiated logout request' do
|
198
|
+
it do
|
199
|
+
expect(OneLogin::RubySaml::Logoutrequest).not_to receive(:new)
|
200
|
+
subject
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
context 'when Devise.saml_sign_out_success_url is set' do
|
205
|
+
before do
|
206
|
+
allow(Devise).to receive(:saml_sign_out_success_url).and_return('http://localhost:8009/logged_out')
|
207
|
+
end
|
208
|
+
|
209
|
+
it 'redirect to saml_sign_out_success_url' do
|
210
|
+
is_expected.to redirect_to 'http://localhost:8009/logged_out'
|
211
|
+
expect(flash[:notice]).to eq I18n.t('devise.sessions.already_signed_out')
|
212
|
+
end
|
213
|
+
|
214
|
+
it_behaves_like 'not create SP initiated logout request'
|
190
215
|
end
|
191
216
|
|
192
|
-
|
193
|
-
|
217
|
+
context 'when Devise.saml_sign_out_success_url is not set' do
|
218
|
+
before do
|
219
|
+
class Devise::SessionsController < DeviseController
|
220
|
+
def after_sign_out_path_for(_)
|
221
|
+
'http://localhost:8009/logged_out'
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
194
225
|
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
"http://localhost:8009/saml/logout"
|
226
|
+
it "redirect to devise's after sign out path" do
|
227
|
+
is_expected.to redirect_to 'http://localhost:8009/logged_out'
|
228
|
+
expect(flash[:notice]).to eq I18n.t('devise.sessions.already_signed_out')
|
199
229
|
end
|
200
230
|
|
201
|
-
|
202
|
-
expect(actual_settings.name_identifier_value).to eq("user@example.com")
|
203
|
-
expect(actual_settings.sessionindex).to eq("sessionindex")
|
231
|
+
it_behaves_like 'not create SP initiated logout request'
|
204
232
|
end
|
205
233
|
end
|
206
234
|
|
207
|
-
context
|
235
|
+
context 'when user is not signed out' do
|
208
236
|
before do
|
209
|
-
Devise
|
237
|
+
class Devise::SessionsController < DeviseController
|
238
|
+
def all_signed_out?
|
239
|
+
false
|
240
|
+
end
|
241
|
+
end
|
242
|
+
allow(controller).to receive(:sign_out)
|
210
243
|
end
|
211
244
|
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
245
|
+
context 'when using the default saml config' do
|
246
|
+
it 'signs out and redirects to the IdP' do
|
247
|
+
delete :destroy
|
248
|
+
expect(controller).to have_received(:sign_out)
|
249
|
+
expect(response).to redirect_to(%r{\Ahttp://localhost:8009/saml/logout\?SAMLRequest=})
|
250
|
+
end
|
217
251
|
end
|
218
252
|
|
219
|
-
context
|
220
|
-
|
221
|
-
|
222
|
-
params[:entity_id]
|
223
|
-
end
|
253
|
+
context 'when configured to use a non-transient name identifier' do
|
254
|
+
before do
|
255
|
+
allow(Devise.saml_config).to receive(:name_identifier_format).and_return('urn:oasis:names:tc:SAML:2.0:nameid-format:persistent')
|
224
256
|
end
|
225
257
|
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
258
|
+
it 'includes a LogoutRequest with the name identifier and session index', :aggregate_failures do
|
259
|
+
controller.current_user = Struct.new(:email, :session_index).new('user@example.com', 'sessionindex')
|
260
|
+
|
261
|
+
actual_settings = nil
|
262
|
+
expect_any_instance_of(OneLogin::RubySaml::Logoutrequest).to receive(:create) do |_, settings|
|
263
|
+
actual_settings = settings
|
264
|
+
'http://localhost:8009/saml/logout'
|
231
265
|
end
|
232
|
-
}
|
233
266
|
|
234
|
-
|
235
|
-
@
|
236
|
-
|
267
|
+
delete :destroy
|
268
|
+
expect(actual_settings.name_identifier_value).to eq('user@example.com')
|
269
|
+
expect(actual_settings.sessionindex).to eq('sessionindex')
|
237
270
|
end
|
271
|
+
end
|
238
272
|
|
239
|
-
|
240
|
-
|
273
|
+
context 'with a specified idp' do
|
274
|
+
before do
|
275
|
+
Devise.idp_settings_adapter = idp_providers_adapter
|
241
276
|
end
|
242
277
|
|
243
|
-
it
|
244
|
-
|
278
|
+
it 'redirects to the associated IdP SSO target url' do
|
279
|
+
expect(DeviseSamlAuthenticatable::DefaultIdpEntityIdReader).to receive(:entity_id)
|
280
|
+
delete :destroy
|
245
281
|
expect(controller).to have_received(:sign_out)
|
246
|
-
expect(
|
247
|
-
|
282
|
+
expect(response).to redirect_to(%r{\Ahttp://idp_slo_url\?SAMLRequest=})
|
283
|
+
end
|
284
|
+
|
285
|
+
context 'with a specified idp entity id reader' do
|
286
|
+
class OurIdpEntityIdReader
|
287
|
+
def self.entity_id(params)
|
288
|
+
params[:entity_id]
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
subject(:do_delete) do
|
293
|
+
delete :destroy, params: { entity_id: 'http://www.example.com' }
|
294
|
+
end
|
295
|
+
|
296
|
+
before do
|
297
|
+
@default_reader = Devise.idp_entity_id_reader
|
298
|
+
Devise.idp_entity_id_reader = OurIdpEntityIdReader # which will have some different behavior
|
299
|
+
end
|
300
|
+
|
301
|
+
after do
|
302
|
+
Devise.idp_entity_id_reader = @default_reader
|
303
|
+
end
|
304
|
+
|
305
|
+
it 'redirects to the associated IdP SLO target url' do
|
306
|
+
do_delete
|
307
|
+
expect(controller).to have_received(:sign_out)
|
308
|
+
expect(idp_providers_adapter).to have_received(:settings).with('http://www.example.com', request)
|
309
|
+
expect(response).to redirect_to(%r{\Ahttp://idp_slo_url\?SAMLRequest=})
|
310
|
+
end
|
248
311
|
end
|
249
312
|
end
|
250
313
|
end
|
@@ -264,14 +327,10 @@ describe Devise::SamlSessionsController, type: :controller do
|
|
264
327
|
expect(response.status).to eq 500
|
265
328
|
end
|
266
329
|
|
267
|
-
context
|
268
|
-
subject(:do_post)
|
269
|
-
|
270
|
-
|
271
|
-
else
|
272
|
-
post :idp_sign_out, SAMLResponse: "stubbed_response"
|
273
|
-
end
|
274
|
-
}
|
330
|
+
context 'when receiving a logout response from the IdP after redirecting an SP logout request' do
|
331
|
+
subject(:do_post) do
|
332
|
+
post :idp_sign_out, params: { SAMLResponse: 'stubbed_response' }
|
333
|
+
end
|
275
334
|
|
276
335
|
it 'accepts a LogoutResponse and redirects sign_in' do
|
277
336
|
do_post
|
@@ -293,20 +352,18 @@ describe Devise::SamlSessionsController, type: :controller do
|
|
293
352
|
end
|
294
353
|
end
|
295
354
|
|
296
|
-
context
|
297
|
-
subject(:do_post)
|
298
|
-
|
299
|
-
|
300
|
-
else
|
301
|
-
post :idp_sign_out, SAMLRequest: "stubbed_logout_request"
|
302
|
-
end
|
303
|
-
}
|
355
|
+
context 'when receiving an IdP logout request' do
|
356
|
+
subject(:do_post) do
|
357
|
+
post :idp_sign_out, params: { SAMLRequest: 'stubbed_logout_request' }
|
358
|
+
end
|
304
359
|
|
305
|
-
let(:saml_request)
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
360
|
+
let(:saml_request) do
|
361
|
+
double(:slo_logoutrequest, {
|
362
|
+
id: 42,
|
363
|
+
name_id: name_id,
|
364
|
+
issuer: 'http://www.example.com'
|
365
|
+
})
|
366
|
+
end
|
310
367
|
let(:name_id) { '12312312' }
|
311
368
|
before do
|
312
369
|
allow(OneLogin::RubySaml::SloLogoutrequest).to receive(:new).and_return(saml_request)
|
@@ -319,27 +376,28 @@ describe Devise::SamlSessionsController, type: :controller do
|
|
319
376
|
expect(User).to have_received(:reset_session_key_for).with(name_id)
|
320
377
|
end
|
321
378
|
|
322
|
-
context
|
323
|
-
let(:idp_entity_id) {
|
379
|
+
context 'with a specified idp' do
|
380
|
+
let(:idp_entity_id) { 'http://www.example.com' }
|
324
381
|
before do
|
325
382
|
Devise.idp_settings_adapter = idp_providers_adapter
|
326
383
|
end
|
327
384
|
|
328
|
-
it
|
385
|
+
it 'accepts a LogoutResponse for the associated slo_target_url and redirects to sign_in' do
|
329
386
|
do_post
|
330
387
|
expect(response.status).to eq 302
|
331
|
-
expect(idp_providers_adapter).to have_received(:settings).with(idp_entity_id)
|
332
|
-
expect(response).to redirect_to
|
388
|
+
expect(idp_providers_adapter).to have_received(:settings).with(idp_entity_id, request)
|
389
|
+
expect(response).to redirect_to 'http://localhost/logout_response'
|
333
390
|
end
|
334
391
|
end
|
335
392
|
|
336
|
-
context
|
337
|
-
let(:relay_state) { ->(
|
393
|
+
context 'with a relay_state lambda defined' do
|
394
|
+
let(:relay_state) { ->(_request) { '123' } }
|
338
395
|
|
339
|
-
it
|
396
|
+
it 'includes the RelayState param in the request to the IdP' do
|
340
397
|
expect(Devise).to receive(:saml_relay_state).at_least(:once).and_return(relay_state)
|
341
398
|
do_post
|
342
|
-
expect(saml_response).to have_received(:create).with(Devise.saml_config, saml_request.id, nil,
|
399
|
+
expect(saml_response).to have_received(:create).with(Devise.saml_config, saml_request.id, nil,
|
400
|
+
{ RelayState: '123' })
|
343
401
|
end
|
344
402
|
end
|
345
403
|
|
@@ -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)
|
@@ -120,8 +146,39 @@ describe Devise::Models::SamlAuthenticatable do
|
|
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)
|
@@ -136,6 +193,35 @@ 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)
|
@@ -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
|