devise_saml_authenticatable 1.8.0 → 1.9.1

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