Skip to content

Commit 248d8b6

Browse files
authored
Merge pull request #255 from codez/multiple-user-sessions
Store IDP session index in session to allow multiple sessions per user
2 parents c674a77 + f33506e commit 248d8b6

File tree

8 files changed

+42
-46
lines changed

8 files changed

+42
-46
lines changed

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,9 @@ In `config/initializers/devise.rb`:
115115
# sure that the Authentication Response includes the attribute.
116116
config.saml_default_user_key = :email
117117

118-
# Optional. This stores the session index defined by the IDP during login. If provided it will be used as a salt
119-
# for the user's session to facilitate an IDP initiated logout request.
120-
config.saml_session_index_key = :session_index
118+
# Optional. This stores the session index defined by the IDP during login.
119+
# If provided it will be used to facilitate an IDP initiated logout request.
120+
config.saml_session_index_key = :saml_session_index
121121

122122
# You can set this value to use Subject or SAML assertion as info to which email will be compared.
123123
# If you don't set it then email will be extracted from SAML assertion attributes.
@@ -303,7 +303,7 @@ Logout support is included by immediately terminating the local session and then
303303

304304
Logout requests from the IDP are supported by the `idp_sign_out` endpoint. Directing logout requests to `users/saml/idp_sign_out` will log out the respective user by invalidating their current sessions.
305305

306-
`saml_session_index_key` must be configured to support this feature.
306+
To disable this feature, set `saml_session_index_key` to `nil`.
307307

308308
## Signing and Encrypting Authentication Requests and Assertions
309309

app/controllers/devise/saml_sessions_controller.rb

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,11 @@ def metadata
2323

2424
def idp_sign_out
2525
if params[:SAMLRequest] && Devise.saml_session_index_key
26+
Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name)
27+
session[Devise.saml_session_index_key] = nil
28+
2629
saml_config = saml_config(get_idp_entity_id(params), request)
2730
logout_request = OneLogin::RubySaml::SloLogoutrequest.new(params[:SAMLRequest], settings: saml_config)
28-
resource_class.reset_session_key_for(logout_request.name_id)
29-
3031
redirect_to generate_idp_logout_response(saml_config, logout_request.id), allow_other_host: true
3132
elsif params[:SAMLResponse]
3233
# Currently Devise handles the session invalidation when the request is made.
@@ -56,7 +57,7 @@ def store_info_for_sp_initiated_logout
5657

5758
@name_identifier_value_for_sp_initiated_logout = Devise.saml_name_identifier_retriever.call(current_user)
5859
if Devise.saml_session_index_key
59-
@sessionindex_for_sp_initiated_logout = current_user.public_send(Devise.saml_session_index_key)
60+
@sessionindex_for_sp_initiated_logout = session[Devise.saml_session_index_key]
6061
end
6162
end
6263

lib/devise_saml_authenticatable.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ module Devise
3030

3131
# Add valid users to database
3232
# Can accept a Boolean value or a Proc that is called with the model class, the saml_response and auth_value
33-
# Ex:
33+
# Ex:
3434
# Devise.saml_create_user = Proc.new do |model_class, saml_response, auth_value|
3535
# model_class == Admin
3636
# end
@@ -39,7 +39,7 @@ module Devise
3939

4040
# Update user attributes after login
4141
# Can accept a Boolean value or a Proc that is called with the model class, the saml_response and auth_value
42-
# Ex:
42+
# Ex:
4343
# Devise.saml_update_user = Proc.new do |model_class, saml_response, auth_value|
4444
# model_class == User
4545
# end
@@ -54,7 +54,7 @@ module Devise
5454

5555
# Key used to index sessions for later retrieval
5656
mattr_accessor :saml_session_index_key
57-
@@saml_session_index_key
57+
@@saml_session_index_key ||= :saml_session_index
5858

5959
# Redirect after signout (redirects to 'users/saml/sign_in' by default)
6060
mattr_accessor :saml_sign_out_success_url

lib/devise_saml_authenticatable/model.rb

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,6 @@ module SamlAuthenticatable
1212
attr_accessor :password_confirmation
1313
end
1414

15-
def after_saml_authentication(session_index)
16-
if Devise.saml_session_index_key && self.respond_to?(Devise.saml_session_index_key)
17-
self.update_attribute(Devise.saml_session_index_key, session_index)
18-
end
19-
end
20-
21-
def authenticatable_salt
22-
if Devise.saml_session_index_key &&
23-
self.respond_to?(Devise.saml_session_index_key) &&
24-
self.send(Devise.saml_session_index_key).present?
25-
self.send(Devise.saml_session_index_key)
26-
else
27-
super
28-
end
29-
end
30-
3115
module ClassMethods
3216
def authenticate_with_saml(saml_response, relay_state)
3317
key = Devise.saml_default_user_key
@@ -78,11 +62,6 @@ def authenticate_with_saml(saml_response, relay_state)
7862
resource
7963
end
8064

81-
def reset_session_key_for(name_id)
82-
resource = find_by(Devise.saml_default_user_key => name_id)
83-
resource.update_attribute(Devise.saml_session_index_key, nil) unless resource.nil?
84-
end
85-
8665
def find_for_shibb_authentication(conditions)
8766
find_for_authentication(conditions)
8867
end

lib/devise_saml_authenticatable/strategy.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ def authenticate!
1919
parse_saml_response
2020
retrieve_resource unless self.halted?
2121
unless self.halted?
22-
@resource.after_saml_authentication(@response.sessionindex)
22+
if Devise.saml_session_index_key
23+
request.session[Devise.saml_session_index_key] = @response.sessionindex
24+
end
2325
success!(@resource)
2426
end
2527
end

spec/controllers/devise/saml_sessions_controller_spec.rb

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ def destroy
3131

3232
describe Devise::SamlSessionsController, type: :controller do
3333
include RubySamlSupport
34+
include Devise::Test::ControllerHelpers
3435

3536
let(:idp_providers_adapter) { spy('Stub IDPSettings Adaptor') }
3637

@@ -256,7 +257,8 @@ def all_signed_out?
256257
end
257258

258259
it 'includes a LogoutRequest with the name identifier and session index', :aggregate_failures do
259-
controller.current_user = Struct.new(:email, :session_index).new('[email protected]', 'sessionindex')
260+
controller.current_user = Struct.new(:email).new('[email protected]')
261+
session[Devise.saml_session_index_key] = 'sessionindex'
260262

261263
actual_settings = nil
262264
expect_any_instance_of(OneLogin::RubySaml::Logoutrequest).to receive(:create) do |_, settings|
@@ -322,9 +324,10 @@ def self.entity_id(params)
322324
end
323325

324326
it 'returns invalid request if SAMLRequest or SAMLResponse is not passed' do
325-
expect(User).not_to receive(:reset_session_key_for)
327+
session[Devise.saml_session_index_key] = 'sessionindex'
326328
post :idp_sign_out
327329
expect(response.status).to eq 500
330+
expect(session[Devise.saml_session_index_key]).to eq('sessionindex')
328331
end
329332

330333
context 'when receiving a logout response from the IdP after redirecting an SP logout request' do
@@ -367,13 +370,13 @@ def self.entity_id(params)
367370
let(:name_id) { '12312312' }
368371
before do
369372
allow(OneLogin::RubySaml::SloLogoutrequest).to receive(:new).and_return(saml_request)
370-
allow(User).to receive(:reset_session_key_for)
373+
session[Devise.saml_session_index_key] = 'sessionindex'
371374
end
372375

373376
it 'direct the resource to reset the session key' do
374377
do_post
375378
expect(response).to redirect_to response_url
376-
expect(User).to have_received(:reset_session_key_for).with(name_id)
379+
expect(session[Devise.saml_session_index_key]).to be_nil
377380
end
378381

379382
context 'with a specified idp' do
@@ -387,6 +390,7 @@ def self.entity_id(params)
387390
expect(response.status).to eq 302
388391
expect(idp_providers_adapter).to have_received(:settings).with(idp_entity_id, request)
389392
expect(response).to redirect_to 'http://localhost/logout_response'
393+
expect(session[Devise.saml_session_index_key]).to be_nil
390394
end
391395
end
392396

@@ -407,7 +411,6 @@ def self.entity_id(params)
407411
end
408412

409413
it 'returns invalid request' do
410-
expect(User).not_to receive(:reset_session_key_for).with(name_id)
411414
do_post
412415
expect(response.status).to eq 500
413416
end

spec/devise_saml_authenticatable/strategy_spec.rb

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@
2323
end
2424

2525
let(:params) { {} }
26+
let(:session) { {} }
2627
before do
2728
allow(strategy).to receive(:params).and_return(params)
29+
allow_any_instance_of(ActionDispatch::Request).to receive(:session).and_return(session)
2830
end
2931

3032
context "with a login SAMLResponse parameter" do
@@ -37,10 +39,25 @@
3739
it "authenticates with the response" do
3840
expect(OneLogin::RubySaml::Response).to receive(:new).with(params[:SAMLResponse], anything)
3941
expect(user_class).to receive(:authenticate_with_saml).with(response, nil)
40-
expect(user).to receive(:after_saml_authentication).with(response.sessionindex)
4142

4243
expect(strategy).to receive(:success!).with(user)
4344
strategy.authenticate!
45+
expect(session).to eq(Devise.saml_session_index_key => response.sessionindex)
46+
end
47+
48+
context "when saml_session_index_key is not configured" do
49+
before do
50+
Devise.saml_session_index_key = nil
51+
end
52+
53+
it "authenticates with the response" do
54+
expect(OneLogin::RubySaml::Response).to receive(:new).with(params[:SAMLResponse], anything)
55+
expect(user_class).to receive(:authenticate_with_saml).with(response, nil)
56+
57+
expect(strategy).to receive(:success!).with(user)
58+
strategy.authenticate!
59+
expect(session).to eq({})
60+
end
4461
end
4562

4663
context "and a RelayState parameter" do
@@ -95,10 +112,10 @@ def self.settings(idp_entity_id, request)
95112
expect(OneLogin::RubySaml::Response).to receive(:new).with(params[:SAMLResponse], anything)
96113
expect(idp_providers_adapter).to receive(:settings).with(idp_entity_id, anything)
97114
expect(user_class).to receive(:authenticate_with_saml).with(response, params[:RelayState])
98-
expect(user).to receive(:after_saml_authentication).with(response.sessionindex)
99115

100116
expect(strategy).to receive(:success!).with(user)
101117
strategy.authenticate!
118+
expect(session).to eq(Devise.saml_session_index_key => response.sessionindex)
102119
end
103120
end
104121

@@ -173,7 +190,6 @@ def handle(response, strategy)
173190

174191
before do
175192
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)
177193
end
178194

179195
context "when the session has a saml_transaction_id" do
@@ -193,8 +209,6 @@ def handle(response, strategy)
193209
end
194210

195211
context "when the session is missing a saml_transaction_id" do
196-
let(:session) { { } }
197-
198212
it "uses 'ID_MISSING' for matches_request_id so validation will fail" do
199213
expect(OneLogin::RubySaml::Response).to receive(:new).with(params[:SAMLResponse], hash_including(matches_request_id: "ID_MISSING"))
200214
strategy.authenticate!

spec/support/saml_idp_controller.rb.erb

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,10 +103,7 @@ class SamlIdpController < StubSamlIdp::IdpController
103103
def sp_sign_out
104104
idp_slo_authenticate(params[:name_id])
105105
saml_slo_request = encode_SAML_SLO_Request("[email protected]")
106-
uri = URI.parse("http://localhost:8020/users/saml/idp_sign_out")
107-
require 'net/http'
108-
Net::HTTP.post_form(uri, {"SAMLRequest" => saml_slo_request})
109-
head :no_content
106+
redirect_to "http://localhost:8020/users/saml/idp_sign_out?SAMLRequest=#{URI.encode_www_form_component(saml_slo_request)}"
110107
end
111108

112109
def idp_slo_authenticate(email)

0 commit comments

Comments
 (0)