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 +4 -4
- data/.github/dependabot.yml +6 -0
- data/.github/workflows/ci.yml +11 -1
- data/README.md +42 -9
- data/app/controllers/devise/saml_sessions_controller.rb +8 -8
- data/devise_saml_authenticatable.gemspec +2 -1
- data/lib/devise_saml_authenticatable/model.rb +8 -2
- data/lib/devise_saml_authenticatable/saml_config.rb +11 -5
- data/lib/devise_saml_authenticatable/strategy.rb +1 -1
- data/lib/devise_saml_authenticatable/version.rb +1 -1
- data/lib/devise_saml_authenticatable.rb +10 -0
- data/spec/controllers/devise/saml_sessions_controller_spec.rb +6 -6
- data/spec/devise_saml_authenticatable/model_spec.rb +137 -19
- data/spec/devise_saml_authenticatable/saml_config_spec.rb +6 -6
- data/spec/devise_saml_authenticatable/strategy_spec.rb +4 -4
- data/spec/support/idp_settings_adapter.rb.erb +1 -1
- data/spec/support/sp_template.rb +1 -1
- metadata +6 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d503c9931a5af5182f1f6910dcfc548d692fcc3e45ad2fb464b3931c4791ac59
|
4
|
+
data.tar.gz: 6da638f28754c2a8f9d44d38a8a61f0796b04e023e0dba8d845fb60bc004bebe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1d44c5c95a396f22008c33b4636fd201c6d6ec71a5193909bb6cc1aa59f660301831cada23e589b05ed52260d8a1a2e42a7442a72757f821ef6ede752a26554e
|
7
|
+
data.tar.gz: '0603740818f257bc90e63f4732c59c6d8a686e0d28c9dedb6cf8ce877a06234e51d39376602d105660e3e25744dfe0163dec931b436ec3bfbe9aabbf06167e36'
|
data/.github/workflows/ci.yml
CHANGED
@@ -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@
|
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
|
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.
|
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
|
-
##
|
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
|
-
|
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: "
|
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
|
-
|
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
|
-
|
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
|
-
|
11
|
+
auth_request = OneLogin::RubySaml::Authrequest.new
|
12
12
|
auth_params = { RelayState: relay_state } if relay_state
|
13
|
-
action =
|
14
|
-
session[:saml_transaction_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
|
-
|
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
|
-
|
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
|
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
|
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.
|
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
|
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
|
@@ -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
|
-
|
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.
|
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(:
|
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
|
-
|
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
|
-
|
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
|
-
|
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(:
|
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.
|
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: "
|
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
|
-
|
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
|
-
|
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"
|
data/spec/support/sp_template.rb
CHANGED
@@ -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.
|
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.
|
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:
|
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:
|
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.
|
115
|
+
rubygems_version: 3.4.1
|
115
116
|
signing_key:
|
116
117
|
specification_version: 4
|
117
118
|
summary: SAML Authentication for devise
|