devise_saml_authenticatable 1.8.0 → 1.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c2b6dd7d4f718cf0df20aff218f90f1eac720279e4ff5afe6aedef20f84a14fd
4
- data.tar.gz: 5efc5fa9d89ee10eb6328261b6b870ce580dbe7cd48cedbe8dd609786c5c9f84
3
+ metadata.gz: d503c9931a5af5182f1f6910dcfc548d692fcc3e45ad2fb464b3931c4791ac59
4
+ data.tar.gz: 6da638f28754c2a8f9d44d38a8a61f0796b04e023e0dba8d845fb60bc004bebe
5
5
  SHA512:
6
- metadata.gz: 70c0b6c4e5f6ec2b7f4a421c898c493cb34aef837c119e126d1b557640f685c1c35ad7cddaf94de3598601fe691563fa2984297010b4ac96f539609c8fa55f95
7
- data.tar.gz: ca3d854ab1bd6b84d3a7d2225feb926f9fbc2d6df5c546c975d5773e8bdd8254d5ce544dd08f6c32a0db29e15f9f4aa3bbc38bee1dbdf491d9e92826b00c760b
6
+ metadata.gz: 1d44c5c95a396f22008c33b4636fd201c6d6ec71a5193909bb6cc1aa59f660301831cada23e589b05ed52260d8a1a2e42a7442a72757f821ef6ede752a26554e
7
+ data.tar.gz: '0603740818f257bc90e63f4732c59c6d8a686e0d28c9dedb6cf8ce877a06234e51d39376602d105660e3e25744dfe0163dec931b436ec3bfbe9aabbf06167e36'
@@ -0,0 +1,6 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "github-actions"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "weekly"
@@ -12,6 +12,7 @@ jobs:
12
12
  fail-fast: false
13
13
  matrix:
14
14
  ruby:
15
+ - "3.2"
15
16
  - "3.1"
16
17
  - "3.0"
17
18
  - "2.7"
@@ -39,11 +40,20 @@ jobs:
39
40
  - ruby: "3.1"
40
41
  gemfile: spec/support/Gemfile.rails6
41
42
  bundler: "2"
43
+ - ruby: "3.2"
44
+ gemfile: spec/support/Gemfile.rails5.2
45
+ bundler: "2"
46
+ - ruby: "3.2"
47
+ gemfile: spec/support/Gemfile.rails6
48
+ bundler: "2"
49
+ - ruby: "3.2"
50
+ gemfile: spec/support/Gemfile.rails6.1
51
+ bundler: "2"
42
52
  runs-on: ubuntu-latest
43
53
  env:
44
54
  BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}
45
55
  steps:
46
- - uses: actions/checkout@v2
56
+ - uses: actions/checkout@v3
47
57
  - uses: ruby/setup-ruby@v1
48
58
  with:
49
59
  bundler: ${{ matrix.bundler }}
data/README.md CHANGED
@@ -72,11 +72,42 @@ In `config/initializers/devise.rb`:
72
72
  # ==> Configuration for :saml_authenticatable
73
73
 
74
74
  # Create user if the user does not exist. (Default is false)
75
+ # Can also accept a proc, for ex:
76
+ # Devise.saml_create_user = Proc.new do |model_class, saml_response, auth_value|
77
+ # model_class == Admin
78
+ # end
75
79
  config.saml_create_user = true
76
80
 
77
81
  # Update the attributes of the user after a successful login. (Default is false)
82
+ # Can also accept a proc, for ex:
83
+ # Devise.saml_update_user = Proc.new do |model_class, saml_response, auth_value|
84
+ # model_class == Admin
85
+ # end
78
86
  config.saml_update_user = true
79
87
 
88
+ # Lambda that is called if Devise.saml_update_user and/or Devise.saml_create_user are true.
89
+ # Receives the model object, saml_response and auth_value, and defines how the object's values are
90
+ # updated with regards to the SAML response.
91
+ # config.saml_update_resource_hook = -> (user, saml_response, auth_value) {
92
+ # saml_response.attributes.resource_keys.each do |key|
93
+ # user.send "#{key}=", saml_response.attribute_value_by_resource_key(key)
94
+ # end
95
+ #
96
+ # if (Devise.saml_use_subject)
97
+ # user.send "#{Devise.saml_default_user_key}=", auth_value
98
+ # end
99
+ #
100
+ # user.save!
101
+ # }
102
+
103
+ # Lambda that is called to resolve the saml_response and auth_value into the correct user object.
104
+ # Receives a copy of the ActiveRecord::Model, saml_response and auth_value. Is expected to return
105
+ # one instance of the provided model that is the matched account, or nil if none exists.
106
+ # config.saml_resource_locator = -> (model, saml_response, auth_value) {
107
+ # model.where(Devise.saml_default_user_key => auth_value).first
108
+ # }
109
+
110
+
80
111
  # Set the default user key. The user will be looked up by this key. Make
81
112
  # sure that the Authentication Response includes the attribute.
82
113
  config.saml_default_user_key = :email
@@ -89,8 +120,8 @@ In `config/initializers/devise.rb`:
89
120
  # If you don't set it then email will be extracted from SAML assertion attributes.
90
121
  config.saml_use_subject = true
91
122
 
92
- # You can support multiple IdPs by setting this value to the name of a class that implements a ::settings method
93
- # which takes an IdP entity id as an argument and returns a hash of idp settings for the corresponding IdP.
123
+ # You can implement IdP settings with the options to support multiple IdPs and use the request object by setting this value to the name of a class that implements a ::settings method
124
+ # which takes an IdP entity id and a request object as arguments and returns a hash of idp settings for the corresponding IdP.
94
125
  # config.idp_settings_adapter = "MyIdPSettingsAdapter"
95
126
 
96
127
  # You provide you own method to find the idp_entity_id in a SAML message in the case of multiple IdPs
@@ -120,7 +151,7 @@ In `config/initializers/devise.rb`:
120
151
  settings.assertion_consumer_service_url = "http://localhost:3000/users/saml/auth"
121
152
  settings.assertion_consumer_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
122
153
  settings.name_identifier_format = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
123
- settings.issuer = "http://localhost:3000/saml/metadata"
154
+ settings.sp_entity_id = "http://localhost:3000/saml/metadata"
124
155
  settings.authn_context = ""
125
156
  settings.idp_slo_service_url = "http://localhost/simplesaml/www/saml2/idp/SingleLogoutService.php"
126
157
  settings.idp_sso_service_url = "http://localhost/simplesaml/www/saml2/idp/SSOService.php"
@@ -194,20 +225,22 @@ If you only have one IdP, you can use the config file above, or just return a si
194
225
  end
195
226
  ```
196
227
 
197
- ## Supporting Multiple IdPs
228
+ ## IdP Settings Adapter
229
+
230
+ Implementing a custom settings adapter allows you to support multiple Identity Providers, and dynamic application domains with the request object.
198
231
 
199
- If you must support multiple Identity Providers you can implement an adapter class with a `#settings` method that takes an IdP entity id and returns a hash of settings for the corresponding IdP. The `config.idp_settings_adapter` then must be set to point to your adapter in `config/initializers/devise.rb`. The implementation of the adapter is up to you. A simple example may look like this:
232
+ You can implement an adapter class with a `#settings` method. It must take two arguments (idp_entity_id, request) and return a hash of settings for the corresponding IdP. The `config.idp_settings_adapter` then must be set to point to your adapter in `config/initializers/devise.rb`. The implementation of the adapter is up to you. A simple example may look like this:
200
233
 
201
234
  ```ruby
202
235
  class IdPSettingsAdapter
203
- def self.settings(idp_entity_id)
236
+ def self.settings(idp_entity_id, request)
204
237
  case idp_entity_id
205
238
  when "http://www.example_idp_entity_id.com"
206
239
  {
207
- assertion_consumer_service_url: "http://localhost:3000/users/saml/auth",
240
+ assertion_consumer_service_url: "#{request.protocol}#{request.host_with_port}/users/saml/auth",
208
241
  assertion_consumer_service_binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
209
242
  name_identifier_format: "urn:oasis:names:tc:SAML:2.0:nameid-format:transient",
210
- issuer: "http://localhost:3000/saml/metadata",
243
+ sp_entity_id: "#{request.protocol}#{request.host_with_port}/saml/metadata",
211
244
  idp_entity_id: "http://www.example_idp_entity_id.com",
212
245
  authn_context: "",
213
246
  idp_slo_service_url: "http://example_idp_slo_service_url.com",
@@ -219,7 +252,7 @@ class IdPSettingsAdapter
219
252
  assertion_consumer_service_url: "http://localhost:3000/users/saml/auth",
220
253
  assertion_consumer_service_binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
221
254
  name_identifier_format: "urn:oasis:names:tc:SAML:2.0:nameid-format:transient",
222
- issuer: "http://localhost:3000/saml/metadata",
255
+ sp_entity_id: "http://localhost:3000/saml/metadata",
223
256
  idp_entity_id: "http://www.another_idp_entity_id.biz",
224
257
  authn_context: "",
225
258
  idp_slo_service_url: "http://another_idp_slo_service_url.com",
@@ -8,22 +8,22 @@ class Devise::SamlSessionsController < Devise::SessionsController
8
8
 
9
9
  def new
10
10
  idp_entity_id = get_idp_entity_id(params)
11
- request = OneLogin::RubySaml::Authrequest.new
11
+ auth_request = OneLogin::RubySaml::Authrequest.new
12
12
  auth_params = { RelayState: relay_state } if relay_state
13
- action = request.create(saml_config(idp_entity_id), auth_params || {})
14
- session[:saml_transaction_id] = request.request_id if request.respond_to?(:request_id)
13
+ action = auth_request.create(saml_config(idp_entity_id, request), auth_params || {})
14
+ session[:saml_transaction_id] = auth_request.request_id if auth_request.respond_to?(:request_id)
15
15
  redirect_to action, allow_other_host: true
16
16
  end
17
17
 
18
18
  def metadata
19
19
  idp_entity_id = params[:idp_entity_id]
20
20
  meta = OneLogin::RubySaml::Metadata.new
21
- render xml: meta.generate(saml_config(idp_entity_id))
21
+ render xml: meta.generate(saml_config(idp_entity_id, request))
22
22
  end
23
23
 
24
24
  def idp_sign_out
25
25
  if params[:SAMLRequest] && Devise.saml_session_index_key
26
- saml_config = saml_config(get_idp_entity_id(params))
26
+ saml_config = saml_config(get_idp_entity_id(params), request)
27
27
  logout_request = OneLogin::RubySaml::SloLogoutrequest.new(params[:SAMLRequest], settings: saml_config)
28
28
  resource_class.reset_session_key_for(logout_request.name_id)
29
29
 
@@ -63,8 +63,8 @@ class Devise::SamlSessionsController < Devise::SessionsController
63
63
  # Override devise to send user to IdP logout for SLO
64
64
  def after_sign_out_path_for(_)
65
65
  idp_entity_id = get_idp_entity_id(params)
66
- request = OneLogin::RubySaml::Logoutrequest.new
67
- saml_settings = saml_config(idp_entity_id).dup
66
+ logout_request = OneLogin::RubySaml::Logoutrequest.new
67
+ saml_settings = saml_config(idp_entity_id, request).dup
68
68
 
69
69
  # Add attributes to saml_settings which will later be used to create the SP
70
70
  # initiated logout request
@@ -73,7 +73,7 @@ class Devise::SamlSessionsController < Devise::SessionsController
73
73
  saml_settings.sessionindex = @sessionindex_for_sp_initiated_logout
74
74
  end
75
75
 
76
- request.create(saml_settings)
76
+ logout_request.create(saml_settings)
77
77
  end
78
78
 
79
79
  # Overried devise: if user is signed out, not create the SP initiated logout request,
@@ -6,7 +6,7 @@ Gem::Specification.new do |gem|
6
6
  gem.email = ["Josef.Sauter@gmail.com"]
7
7
  gem.description = %q{SAML Authentication for devise}
8
8
  gem.summary = %q{SAML Authentication for devise }
9
- gem.homepage = ""
9
+ gem.homepage = "https://github.com/apokalipto/devise_saml_authenticatable"
10
10
  gem.license = "MIT"
11
11
 
12
12
  gem.files = `git ls-files`.split($\)
@@ -16,6 +16,7 @@ Gem::Specification.new do |gem|
16
16
  gem.require_paths = ["lib"]
17
17
  gem.version = DeviseSamlAuthenticatable::VERSION
18
18
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
19
+ gem.required_ruby_version = ">= 2.6.0"
19
20
 
20
21
  gem.add_dependency("devise","> 2.0.0")
21
22
  gem.add_dependency("ruby-saml","~> 1.7")
@@ -55,8 +55,11 @@ module Devise
55
55
  end
56
56
  end
57
57
 
58
+ create_user = if Devise.saml_create_user.respond_to?(:call) then Devise.saml_create_user.call(self, decorated_response, auth_value)
59
+ else Devise.saml_create_user
60
+ end
58
61
  if resource.nil?
59
- if Devise.saml_create_user
62
+ if create_user
60
63
  logger.info("Creating user(#{auth_value}).")
61
64
  resource = new
62
65
  else
@@ -65,7 +68,10 @@ module Devise
65
68
  end
66
69
  end
67
70
 
68
- if Devise.saml_update_user || (resource.new_record? && Devise.saml_create_user)
71
+ update_user = if Devise.saml_update_user.respond_to?(:call) then Devise.saml_update_user.call(self, decorated_response, auth_value)
72
+ else Devise.saml_update_user
73
+ end
74
+ if update_user || (resource.new_record? && create_user)
69
75
  Devise.saml_update_resource_hook.call(resource, decorated_response, auth_value)
70
76
  end
71
77
 
@@ -1,9 +1,9 @@
1
1
  require 'ruby-saml'
2
2
  module DeviseSamlAuthenticatable
3
3
  module SamlConfig
4
- def saml_config(idp_entity_id = nil)
4
+ def saml_config(idp_entity_id = nil, request = nil)
5
5
  return file_based_config if file_based_config
6
- return adapter_based_config(idp_entity_id) if Devise.idp_settings_adapter
6
+ return adapter_based_config(idp_entity_id, request) if Devise.idp_settings_adapter
7
7
 
8
8
  Devise.saml_config
9
9
  end
@@ -14,15 +14,21 @@ module DeviseSamlAuthenticatable
14
14
  return @file_based_config if @file_based_config
15
15
  idp_config_path = "#{Rails.root}/config/idp.yml"
16
16
 
17
- if File.exists?(idp_config_path)
17
+ if File.exist?(idp_config_path)
18
18
  @file_based_config ||= OneLogin::RubySaml::Settings.new(YAML.load(File.read(idp_config_path))[Rails.env])
19
19
  end
20
20
  end
21
21
 
22
- def adapter_based_config(idp_entity_id)
22
+ def adapter_based_config(idp_entity_id, request)
23
23
  config = Marshal.load(Marshal.dump(Devise.saml_config))
24
24
 
25
- idp_settings_adapter.settings(idp_entity_id).each do |k,v|
25
+ if idp_settings_adapter.method(:settings).parameters.length == 1
26
+ settings = idp_settings_adapter.settings(idp_entity_id)
27
+ else
28
+ settings = idp_settings_adapter.settings(idp_entity_id, request)
29
+ end
30
+
31
+ settings.each do |k,v|
26
32
  acc = "#{k.to_s}=".to_sym
27
33
 
28
34
  if config.respond_to? acc
@@ -65,7 +65,7 @@ module Devise
65
65
 
66
66
  def response_options
67
67
  options = {
68
- settings: saml_config(get_idp_entity_id(params)),
68
+ settings: saml_config(get_idp_entity_id(params), request),
69
69
  allowed_clock_drift: Devise.allowed_clock_drift_in_seconds,
70
70
  }
71
71
 
@@ -1,3 +1,3 @@
1
1
  module DeviseSamlAuthenticatable
2
- VERSION = "1.8.0"
2
+ VERSION = "1.9.1"
3
3
  end
@@ -29,10 +29,20 @@ module Devise
29
29
  @@saml_logger = true
30
30
 
31
31
  # Add valid users to database
32
+ # Can accept a Boolean value or a Proc that is called with the model class, the saml_response and auth_value
33
+ # Ex:
34
+ # Devise.saml_create_user = Proc.new do |model_class, saml_response, auth_value|
35
+ # model_class == Admin
36
+ # end
32
37
  mattr_accessor :saml_create_user
33
38
  @@saml_create_user = false
34
39
 
35
40
  # Update user attributes after login
41
+ # Can accept a Boolean value or a Proc that is called with the model class, the saml_response and auth_value
42
+ # Ex:
43
+ # Devise.saml_update_user = Proc.new do |model_class, saml_response, auth_value|
44
+ # model_class == User
45
+ # end
36
46
  mattr_accessor :saml_update_user
37
47
  @@saml_update_user = false
38
48
 
@@ -40,7 +40,7 @@ describe Devise::SamlSessionsController, type: :controller do
40
40
  assertion_consumer_service_url: 'acs_url',
41
41
  assertion_consumer_service_binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
42
42
  name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient',
43
- issuer: 'sp_issuer',
43
+ sp_entity_id: 'sp_issuer',
44
44
  idp_entity_id: 'http://www.example.com',
45
45
  authn_context: '',
46
46
  idp_cert: 'idp_cert'
@@ -102,7 +102,7 @@ describe Devise::SamlSessionsController, type: :controller do
102
102
  it 'uses the DefaultIdpEntityIdReader' do
103
103
  expect(DeviseSamlAuthenticatable::DefaultIdpEntityIdReader).to receive(:entity_id)
104
104
  do_get
105
- expect(idp_providers_adapter).to have_received(:settings).with(nil)
105
+ expect(idp_providers_adapter).to have_received(:settings).with(nil, request)
106
106
  end
107
107
 
108
108
  context 'with a relay_state lambda defined' do
@@ -137,7 +137,7 @@ describe Devise::SamlSessionsController, type: :controller do
137
137
 
138
138
  it 'redirects to the associated IdP SSO target url' do
139
139
  do_get
140
- expect(idp_providers_adapter).to have_received(:settings).with('http://www.example.com')
140
+ expect(idp_providers_adapter).to have_received(:settings).with('http://www.example.com', request)
141
141
  expect(response).to redirect_to(%r{\Ahttp://idp_sso_url\?SAMLRequest=})
142
142
  end
143
143
  end
@@ -167,7 +167,7 @@ describe Devise::SamlSessionsController, type: :controller do
167
167
  settings.assertion_consumer_service_url = 'http://localhost:3000/users/saml/auth'
168
168
  settings.assertion_consumer_service_binding = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'
169
169
  settings.name_identifier_format = 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
170
- settings.issuer = 'http://localhost:3000'
170
+ settings.sp_entity_id = 'http://localhost:3000'
171
171
  end
172
172
  end
173
173
 
@@ -305,7 +305,7 @@ describe Devise::SamlSessionsController, type: :controller do
305
305
  it 'redirects to the associated IdP SLO target url' do
306
306
  do_delete
307
307
  expect(controller).to have_received(:sign_out)
308
- expect(idp_providers_adapter).to have_received(:settings).with('http://www.example.com')
308
+ expect(idp_providers_adapter).to have_received(:settings).with('http://www.example.com', request)
309
309
  expect(response).to redirect_to(%r{\Ahttp://idp_slo_url\?SAMLRequest=})
310
310
  end
311
311
  end
@@ -385,7 +385,7 @@ describe Devise::SamlSessionsController, type: :controller do
385
385
  it 'accepts a LogoutResponse for the associated slo_target_url and redirects to sign_in' do
386
386
  do_post
387
387
  expect(response.status).to eq 302
388
- expect(idp_providers_adapter).to have_received(:settings).with(idp_entity_id)
388
+ expect(idp_providers_adapter).to have_received(:settings).with(idp_entity_id, request)
389
389
  expect(response).to redirect_to 'http://localhost/logout_response'
390
390
  end
391
391
  end
@@ -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
@@ -10,7 +10,7 @@ describe DeviseSamlAuthenticatable::SamlConfig do
10
10
  context "when config/idp.yml does not exist" do
11
11
  before do
12
12
  allow(Rails).to receive(:root).and_return("/railsroot")
13
- 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)
14
14
  end
15
15
 
16
16
  it "is the global devise SAML config" do
@@ -38,7 +38,7 @@ describe DeviseSamlAuthenticatable::SamlConfig do
38
38
  assertion_consumer_service_url: "acs_url",
39
39
  assertion_consumer_service_binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
40
40
  name_identifier_format: "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
41
- issuer: "sp_issuer",
41
+ sp_entity_id: "sp_issuer",
42
42
  idp_entity_id: "http://www.example.com",
43
43
  authn_context: "",
44
44
  idp_cert: "idp_cert"
@@ -60,7 +60,7 @@ describe DeviseSamlAuthenticatable::SamlConfig do
60
60
  assertion_consumer_service_url: "acs_url_other",
61
61
  assertion_consumer_service_binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST_other",
62
62
  name_identifier_format: "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress_other",
63
- issuer: "sp_issuer_other",
63
+ sp_entity_id: "sp_issuer_other",
64
64
  idp_entity_id: "http://www.example.com_other",
65
65
  authn_context: "_other",
66
66
  idp_cert: "idp_cert_other"
@@ -134,7 +134,7 @@ environment:
134
134
  idp_cert_fingerprint: idp_cert_fingerprint
135
135
  idp_cert_fingerprint_algorithm: idp_cert_fingerprint_algorithm
136
136
  idp_entity_id: idp_entity_id
137
- issuer: issuer
137
+ sp_entity_id: issuer
138
138
  name_identifier_format: name_identifier_format
139
139
  name_identifier_value: name_identifier_value
140
140
  passive: passive
@@ -156,7 +156,7 @@ TARGET_URLS
156
156
  before do
157
157
  allow(Rails).to receive(:env).and_return("environment")
158
158
  allow(Rails).to receive(:root).and_return("/railsroot")
159
- allow(File).to receive(:exists?).with("/railsroot/config/idp.yml").and_return(true)
159
+ allow(File).to receive(:exist?).with("/railsroot/config/idp.yml").and_return(true)
160
160
  allow(File).to receive(:read).with("/railsroot/config/idp.yml").and_return(idp_yaml)
161
161
  end
162
162
 
@@ -185,7 +185,7 @@ TARGET_URLS
185
185
  expect(saml_config.idp_slo_target_url).to eq('idp_slo_service_url')
186
186
  expect(saml_config.idp_sso_target_url).to eq('idp_sso_service_url')
187
187
  })
188
- expect(saml_config.issuer).to eq('issuer')
188
+ expect(saml_config.sp_entity_id).to eq('issuer')
189
189
  expect(saml_config.name_identifier_format).to eq('name_identifier_format')
190
190
  expect(saml_config.name_identifier_value).to eq('name_identifier_value')
191
191
  expect(saml_config.passive).to eq('passive')
@@ -56,12 +56,12 @@ describe Devise::Strategies::SamlAuthenticatable do
56
56
  context "when saml config uses an idp_adapter" do
57
57
  let(:idp_providers_adapter) {
58
58
  Class.new {
59
- def self.settings(idp_entity_id)
59
+ def self.settings(idp_entity_id, request)
60
60
  base = {
61
- assertion_consumer_service_url: "acs_url",
61
+ assertion_consumer_service_url: "acs url",
62
62
  assertion_consumer_service_binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
63
63
  name_identifier_format: "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
64
- issuer: "sp_issuer",
64
+ sp_entity_id: "sp_issuer",
65
65
  idp_entity_id: "http://www.example.com",
66
66
  authn_context: "",
67
67
  idp_cert: "idp_cert"
@@ -93,7 +93,7 @@ describe Devise::Strategies::SamlAuthenticatable do
93
93
 
94
94
  it "authenticates with the response for the corresponding idp" do
95
95
  expect(OneLogin::RubySaml::Response).to receive(:new).with(params[:SAMLResponse], anything)
96
- expect(idp_providers_adapter).to receive(:settings).with(idp_entity_id)
96
+ expect(idp_providers_adapter).to receive(:settings).with(idp_entity_id, anything)
97
97
  expect(user_class).to receive(:authenticate_with_saml).with(response, params[:RelayState])
98
98
  expect(user).to receive(:after_saml_authentication).with(response.sessionindex)
99
99
 
@@ -5,7 +5,7 @@ class IdpSettingsAdapter
5
5
  assertion_consumer_service_url: "http://localhost:8020/users/saml/auth",
6
6
  assertion_consumer_service_binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
7
7
  name_identifier_format: "urn:oasis:names:tc:SAML:2.0:nameid-format:transient",
8
- issuer: "sp_issuer",
8
+ sp_entity_id: "sp_issuer",
9
9
  idp_entity_id: "http://localhost:8020/saml/metadata",
10
10
  authn_context: "",
11
11
  idp_cert_fingerprint: "9E:65:2E:03:06:8D:80:F2:86:C7:6C:77:A1:D9:14:97:0A:4D:F4:4D"
@@ -83,7 +83,7 @@ after_bundle do
83
83
 
84
84
  config.saml_configure do |settings|
85
85
  settings.assertion_consumer_service_url = "http://localhost:8020/users/saml/auth"
86
- settings.issuer = "http://localhost:8020/saml/metadata"
86
+ settings.sp_entity_id = "http://localhost:8020/saml/metadata"
87
87
  settings.idp_cert_fingerprint = "9E:65:2E:03:06:8D:80:F2:86:C7:6C:77:A1:D9:14:97:0A:4D:F4:4D"
88
88
  settings.name_identifier_format = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
89
89
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: devise_saml_authenticatable
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.8.0
4
+ version: 1.9.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Josef Sauter
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-01-19 00:00:00.000000000 Z
11
+ date: 2023-01-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: devise
@@ -45,6 +45,7 @@ executables: []
45
45
  extensions: []
46
46
  extra_rdoc_files: []
47
47
  files:
48
+ - ".github/dependabot.yml"
48
49
  - ".github/workflows/ci.yml"
49
50
  - ".gitignore"
50
51
  - ".rspec"
@@ -92,7 +93,7 @@ files:
92
93
  - spec/support/saml_idp-saml_slo_post.html.erb
93
94
  - spec/support/saml_idp_controller.rb.erb
94
95
  - spec/support/sp_template.rb
95
- homepage: ''
96
+ homepage: https://github.com/apokalipto/devise_saml_authenticatable
96
97
  licenses:
97
98
  - MIT
98
99
  metadata: {}
@@ -104,14 +105,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
104
105
  requirements:
105
106
  - - ">="
106
107
  - !ruby/object:Gem::Version
107
- version: '0'
108
+ version: 2.6.0
108
109
  required_rubygems_version: !ruby/object:Gem::Requirement
109
110
  requirements:
110
111
  - - ">="
111
112
  - !ruby/object:Gem::Version
112
113
  version: '0'
113
114
  requirements: []
114
- rubygems_version: 3.3.3
115
+ rubygems_version: 3.4.1
115
116
  signing_key:
116
117
  specification_version: 4
117
118
  summary: SAML Authentication for devise