devise_saml_authenticatable 1.6.1 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d3ca38cfe51cdf71fc8a264de868fc285bfa413307aa4798e097fbf88e85020d
4
- data.tar.gz: 2371ef1371e29547b4eb9681066fa43f0b6d39236b7df8676236bf13678e2d01
3
+ metadata.gz: c2b6dd7d4f718cf0df20aff218f90f1eac720279e4ff5afe6aedef20f84a14fd
4
+ data.tar.gz: 5efc5fa9d89ee10eb6328261b6b870ce580dbe7cd48cedbe8dd609786c5c9f84
5
5
  SHA512:
6
- metadata.gz: 872bfb214a00504735d9183d169e4119ebb9c6ae3129de729be642f39c4675b57040bb1c9d46899e73d534aa82d7ac9291c425109c10eaeb290831480e7976ab
7
- data.tar.gz: 3269ef293937aef443afa9a325acc0addf027c3ad7421865a9ebe1554d7215461767ffe9e34cd4cc2bf8edbae831e7e342dd3c47245d57209d8ddd820a128212
6
+ metadata.gz: 70c0b6c4e5f6ec2b7f4a421c898c493cb34aef837c119e126d1b557640f685c1c35ad7cddaf94de3598601fe691563fa2984297010b4ac96f539609c8fa55f95
7
+ data.tar.gz: ca3d854ab1bd6b84d3a7d2225feb926f9fbc2d6df5c546c975d5773e8bdd8254d5ce544dd08f6c32a0db29e15f9f4aa3bbc38bee1dbdf491d9e92826b00c760b
@@ -0,0 +1,52 @@
1
+ name: ci
2
+ on:
3
+ push:
4
+ branches:
5
+ - master
6
+ pull_request:
7
+ branches:
8
+ - master
9
+ jobs:
10
+ test:
11
+ strategy:
12
+ fail-fast: false
13
+ matrix:
14
+ ruby:
15
+ - "3.1"
16
+ - "3.0"
17
+ - "2.7"
18
+ - "2.6"
19
+ gemfile:
20
+ - Gemfile
21
+ - spec/support/Gemfile.rails6.1
22
+ - spec/support/Gemfile.rails6
23
+ - spec/support/Gemfile.rails5.2
24
+ bundler:
25
+ - "2"
26
+ exclude:
27
+ - ruby: "2.6"
28
+ gemfile: Gemfile
29
+ bundler: "2"
30
+ - ruby: "3.0"
31
+ gemfile: spec/support/Gemfile.rails5.2
32
+ bundler: "2"
33
+ - ruby: "3.0"
34
+ gemfile: spec/support/Gemfile.rails6
35
+ bundler: "2"
36
+ - ruby: "3.1"
37
+ gemfile: spec/support/Gemfile.rails5.2
38
+ bundler: "2"
39
+ - ruby: "3.1"
40
+ gemfile: spec/support/Gemfile.rails6
41
+ bundler: "2"
42
+ runs-on: ubuntu-latest
43
+ env:
44
+ BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}
45
+ steps:
46
+ - uses: actions/checkout@v2
47
+ - uses: ruby/setup-ruby@v1
48
+ with:
49
+ bundler: ${{ matrix.bundler }}
50
+ ruby-version: ${{ matrix.ruby }}
51
+ bundler-cache: true # runs 'bundle install' and caches installed gems automatically
52
+ - run: bundle exec rake
data/.gitignore CHANGED
@@ -13,4 +13,5 @@ lib/bundler/man
13
13
  pkg
14
14
  rdoc
15
15
  spec/reports
16
+ spec/support/bin/*
16
17
  tmp
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.1.0
data/Gemfile CHANGED
@@ -6,9 +6,19 @@ gemspec
6
6
  group :test do
7
7
  gem 'rake'
8
8
  gem 'rspec', '~> 3.0'
9
- gem 'rails', '~> 6.0'
9
+ gem 'rails', '~> 7.0.0'
10
10
  gem 'rspec-rails'
11
11
  gem 'sqlite3', '~> 1.4.0'
12
12
  gem 'capybara'
13
- gem 'poltergeist'
13
+ gem 'selenium-webdriver'
14
+
15
+ if Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new("3.0")
16
+ gem 'webrick'
17
+ end
18
+
19
+ if Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new("3.1")
20
+ gem 'net-smtp', require: false
21
+ gem 'net-imap', require: false
22
+ gem 'net-pop', require: false
23
+ end
14
24
  end
data/README.md CHANGED
@@ -57,8 +57,8 @@ An extra step in SAML SSO setup is adding your application to your identity prov
57
57
  Your IdP should give you some information you need to configure in [ruby-saml](https://github.com/onelogin/ruby-saml), as in the next section:
58
58
 
59
59
  - Issuer (`idp_entity_id`)
60
- - SSO endpoint (`idp_sso_target_url`)
61
- - SLO endpoint (`idp_slo_target_url`)
60
+ - SSO endpoint (`idp_sso_service_url`)
61
+ - SLO endpoint (`idp_slo_service_url`)
62
62
  - Certificate fingerprint (`idp_cert_fingerprint`) and algorithm (`idp_cert_fingerprint_algorithm`)
63
63
  - Or the certificate itself (`idp_cert`)
64
64
 
@@ -85,21 +85,21 @@ In `config/initializers/devise.rb`:
85
85
  # for the user's session to facilitate an IDP initiated logout request.
86
86
  config.saml_session_index_key = :session_index
87
87
 
88
- # You can set this value to use Subject or SAML assertation as info to which email will be compared.
89
- # If you don't set it then email will be extracted from SAML assertation attributes.
88
+ # You can set this value to use Subject or SAML assertion as info to which email will be compared.
89
+ # If you don't set it then email will be extracted from SAML assertion attributes.
90
90
  config.saml_use_subject = true
91
91
 
92
- # You can support multiple IdPs by setting this value to a class that implements a #settings method which takes
93
- # an IdP entity id as an argument and returns a hash of idp settings for the corresponding IdP.
94
- config.idp_settings_adapter = nil
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.
94
+ # config.idp_settings_adapter = "MyIdPSettingsAdapter"
95
95
 
96
96
  # You provide you own method to find the idp_entity_id in a SAML message in the case of multiple IdPs
97
- # by setting this to a custom reader class, or use the default.
98
- # config.idp_entity_id_reader = DeviseSamlAuthenticatable::DefaultIdpEntityIdReader
97
+ # by setting this to the name of a custom reader class, or use the default.
98
+ # config.idp_entity_id_reader = "DeviseSamlAuthenticatable::DefaultIdpEntityIdReader"
99
99
 
100
- # You can set a handler object that takes the response for a failed SAML request and the strategy,
100
+ # You can set the name of a class that takes the response for a failed SAML request and the strategy,
101
101
  # and implements a #handle method. This method can then redirect the user, return error messages, etc.
102
- # config.saml_failed_callback = nil
102
+ # config.saml_failed_callback = "MySamlFailedCallbacksHandler"
103
103
 
104
104
  # You can customize the named routes generated in case of named route collisions with
105
105
  # other Devise modules or libraries. Set the saml_route_helper_prefix to a string that will
@@ -111,16 +111,19 @@ In `config/initializers/devise.rb`:
111
111
  # This is a time in seconds.
112
112
  # config.allowed_clock_drift_in_seconds = 0
113
113
 
114
+ # In SAML responses, validate that the identity provider has included an InResponseTo
115
+ # header that matches the ID of the SAML request. (Default is false)
116
+ # config.saml_validate_in_response_to = false
117
+
114
118
  # Configure with your SAML settings (see ruby-saml's README for more information: https://github.com/onelogin/ruby-saml).
115
119
  config.saml_configure do |settings|
116
- # assertion_consumer_service_url is required starting with ruby-saml 1.4.3: https://github.com/onelogin/ruby-saml#updating-from-142-to-143
117
120
  settings.assertion_consumer_service_url = "http://localhost:3000/users/saml/auth"
118
121
  settings.assertion_consumer_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
119
122
  settings.name_identifier_format = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
120
123
  settings.issuer = "http://localhost:3000/saml/metadata"
121
124
  settings.authn_context = ""
122
- settings.idp_slo_target_url = "http://localhost/simplesaml/www/saml2/idp/SingleLogoutService.php"
123
- settings.idp_sso_target_url = "http://localhost/simplesaml/www/saml2/idp/SSOService.php"
125
+ settings.idp_slo_service_url = "http://localhost/simplesaml/www/saml2/idp/SingleLogoutService.php"
126
+ settings.idp_sso_service_url = "http://localhost/simplesaml/www/saml2/idp/SSOService.php"
124
127
  settings.idp_cert_fingerprint = "00:A1:2B:3C:44:55:6F:A7:88:CC:DD:EE:22:33:44:55:D6:77:8F:99"
125
128
  settings.idp_cert_fingerprint_algorithm = "http://www.w3.org/2000/09/xmldsig#sha1"
126
129
  end
@@ -169,7 +172,7 @@ If you only have one IdP, you can use the config file above, or just return a si
169
172
  ...
170
173
  # ==> Configuration for :saml_authenticatable
171
174
 
172
- config.saml_attribute_map_resolver = MyAttributeMapResolver
175
+ config.saml_attribute_map_resolver = "MyAttributeMapResolver"
173
176
  end
174
177
  ```
175
178
 
@@ -207,8 +210,8 @@ class IdPSettingsAdapter
207
210
  issuer: "http://localhost:3000/saml/metadata",
208
211
  idp_entity_id: "http://www.example_idp_entity_id.com",
209
212
  authn_context: "",
210
- idp_slo_target_url: "http://example_idp_slo_target_url.com",
211
- idp_sso_target_url: "http://example_idp_sso_target_url.com",
213
+ idp_slo_service_url: "http://example_idp_slo_service_url.com",
214
+ idp_sso_service_url: "http://example_idp_sso_service_url.com",
212
215
  idp_cert: "example_idp_cert"
213
216
  }
214
217
  when "http://www.another_idp_entity_id.biz"
@@ -219,8 +222,8 @@ class IdPSettingsAdapter
219
222
  issuer: "http://localhost:3000/saml/metadata",
220
223
  idp_entity_id: "http://www.another_idp_entity_id.biz",
221
224
  authn_context: "",
222
- idp_slo_target_url: "http://another_idp_slo_target_url.com",
223
- idp_sso_target_url: "http://another_idp_sso_target_url.com",
225
+ idp_slo_service_url: "http://another_idp_slo_service_url.com",
226
+ idp_sso_service_url: "http://another_idp_sso_service_url.com",
224
227
  idp_cert: "another_idp_cert"
225
228
  }
226
229
  else
@@ -1,28 +1,24 @@
1
- require "ruby-saml"
1
+ require 'ruby-saml'
2
2
 
3
3
  class Devise::SamlSessionsController < Devise::SessionsController
4
4
  include DeviseSamlAuthenticatable::SamlConfig
5
- unloadable if Rails::VERSION::MAJOR < 4
6
- if Rails::VERSION::MAJOR < 5
7
- skip_before_filter :verify_authenticity_token
8
- prepend_before_filter :store_info_for_sp_initiated_logout, only: :destroy
9
- else
10
- skip_before_action :verify_authenticity_token, raise: false
11
- prepend_before_action :store_info_for_sp_initiated_logout, only: :destroy
12
- end
5
+
6
+ skip_before_action :verify_authenticity_token, raise: false
7
+ prepend_before_action :verify_signed_out_user, :store_info_for_sp_initiated_logout, only: :destroy
13
8
 
14
9
  def new
15
10
  idp_entity_id = get_idp_entity_id(params)
16
11
  request = OneLogin::RubySaml::Authrequest.new
17
12
  auth_params = { RelayState: relay_state } if relay_state
18
13
  action = request.create(saml_config(idp_entity_id), auth_params || {})
19
- redirect_to action
14
+ session[:saml_transaction_id] = request.request_id if request.respond_to?(:request_id)
15
+ redirect_to action, allow_other_host: true
20
16
  end
21
17
 
22
18
  def metadata
23
19
  idp_entity_id = params[:idp_entity_id]
24
20
  meta = OneLogin::RubySaml::Metadata.new
25
- render :xml => meta.generate(saml_config(idp_entity_id))
21
+ render xml: meta.generate(saml_config(idp_entity_id))
26
22
  end
27
23
 
28
24
  def idp_sign_out
@@ -31,7 +27,7 @@ class Devise::SamlSessionsController < Devise::SessionsController
31
27
  logout_request = OneLogin::RubySaml::SloLogoutrequest.new(params[:SAMLRequest], settings: saml_config)
32
28
  resource_class.reset_session_key_for(logout_request.name_id)
33
29
 
34
- redirect_to generate_idp_logout_response(saml_config, logout_request.id)
30
+ redirect_to generate_idp_logout_response(saml_config, logout_request.id), allow_other_host: true
35
31
  elsif params[:SAMLResponse]
36
32
  # Currently Devise handles the session invalidation when the request is made.
37
33
  # To support a true SP initiated logout response, the request ID would have to be tracked and session invalidated
@@ -49,18 +45,19 @@ class Devise::SamlSessionsController < Devise::SessionsController
49
45
  protected
50
46
 
51
47
  def relay_state
52
- @relay_state ||= if Devise.saml_relay_state.present?
53
- Devise.saml_relay_state.call(request)
54
- end
48
+ @relay_state ||= (Devise.saml_relay_state.call(request) if Devise.saml_relay_state.present?)
55
49
  end
56
50
 
57
51
  # For non transient name ID, save info to identify user for logout purpose
58
52
  # before that user's session got destroyed. These info are used in the
59
53
  # `after_sign_out_path_for` method below.
60
54
  def store_info_for_sp_initiated_logout
61
- return if Devise.saml_config.name_identifier_format == "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
55
+ return if Devise.saml_config.name_identifier_format == 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
56
+
62
57
  @name_identifier_value_for_sp_initiated_logout = Devise.saml_name_identifier_retriever.call(current_user)
63
- @sessionindex_for_sp_initiated_logout = current_user.public_send(Devise.saml_session_index_key) if Devise.saml_session_index_key
58
+ if Devise.saml_session_index_key
59
+ @sessionindex_for_sp_initiated_logout = current_user.public_send(Devise.saml_session_index_key)
60
+ end
64
61
  end
65
62
 
66
63
  # Override devise to send user to IdP logout for SLO
@@ -79,12 +76,21 @@ class Devise::SamlSessionsController < Devise::SessionsController
79
76
  request.create(saml_settings)
80
77
  end
81
78
 
82
- def generate_idp_logout_response(saml_config, logout_request_id)
79
+ # Overried devise: if user is signed out, not create the SP initiated logout request,
80
+ # redirect to saml_sign_out_success_url,
81
+ # or devise's after_sign_out_path_for
82
+ def verify_signed_out_user
83
+ if all_signed_out?
84
+ set_flash_message! :notice, :already_signed_out
83
85
 
84
- params = {}
85
- if relay_state
86
- params[:RelayState] = relay_state
86
+ redirect_to (Devise.saml_sign_out_success_url.presence ||
87
+ Devise::SessionsController.new.after_sign_out_path_for(resource_name)), allow_other_host: true
87
88
  end
89
+ end
90
+
91
+ def generate_idp_logout_response(saml_config, logout_request_id)
92
+ params = {}
93
+ params[:RelayState] = relay_state if relay_state
88
94
 
89
95
  OneLogin::RubySaml::SloLogoutresponse.new.create(saml_config, logout_request_id, nil, params)
90
96
  end
@@ -1,9 +1,9 @@
1
1
  module DeviseSamlAuthenticatable
2
2
 
3
3
  class Logger
4
- def self.send(message, logger = Rails.logger)
4
+ def self.send(message, log_level = ::Logger::INFO, logger = Rails.logger)
5
5
  if ::Devise.saml_logger
6
- logger.add 0, " \e[36msaml:\e[0m #{message}"
6
+ logger.add log_level, " \e[36msaml:\e[0m #{message}"
7
7
  end
8
8
  end
9
9
  end
@@ -66,12 +66,7 @@ module Devise
66
66
  end
67
67
 
68
68
  if Devise.saml_update_user || (resource.new_record? && Devise.saml_create_user)
69
- begin
70
- Devise.saml_update_resource_hook.call(resource, decorated_response, auth_value)
71
- rescue
72
- logger.info("User(#{auth_value}) failed to create or update.")
73
- return nil
74
- end
69
+ Devise.saml_update_resource_hook.call(resource, decorated_response, auth_value)
75
70
  end
76
71
 
77
72
  resource
@@ -87,7 +82,15 @@ module Devise
87
82
  end
88
83
 
89
84
  def attribute_map(saml_response = nil)
90
- Devise.saml_attribute_map_resolver.new(saml_response).attribute_map
85
+ attribute_map_resolver.new(saml_response).attribute_map
86
+ end
87
+
88
+ def attribute_map_resolver
89
+ if Devise.saml_attribute_map_resolver.respond_to?(:new)
90
+ Devise.saml_attribute_map_resolver
91
+ else
92
+ Devise.saml_attribute_map_resolver.constantize
93
+ end
91
94
  end
92
95
  end
93
96
  end
@@ -22,7 +22,7 @@ module DeviseSamlAuthenticatable
22
22
  def adapter_based_config(idp_entity_id)
23
23
  config = Marshal.load(Marshal.dump(Devise.saml_config))
24
24
 
25
- Devise.idp_settings_adapter.settings(idp_entity_id).each do |k,v|
25
+ idp_settings_adapter.settings(idp_entity_id).each do |k,v|
26
26
  acc = "#{k.to_s}=".to_sym
27
27
 
28
28
  if config.respond_to? acc
@@ -33,7 +33,23 @@ module DeviseSamlAuthenticatable
33
33
  end
34
34
 
35
35
  def get_idp_entity_id(params)
36
- Devise.idp_entity_id_reader.entity_id(params)
36
+ idp_entity_id_reader.entity_id(params)
37
+ end
38
+
39
+ def idp_entity_id_reader
40
+ if Devise.idp_entity_id_reader.respond_to?(:entity_id)
41
+ Devise.idp_entity_id_reader
42
+ else
43
+ @idp_entity_id_reader ||= Devise.idp_entity_id_reader.constantize
44
+ end
45
+ end
46
+
47
+ def idp_settings_adapter
48
+ if Devise.idp_settings_adapter.respond_to?(:settings)
49
+ Devise.idp_settings_adapter
50
+ else
51
+ @idp_settings_adapter ||= Devise.idp_settings_adapter.constantize
52
+ end
37
53
  end
38
54
  end
39
55
  end
@@ -8,8 +8,7 @@ module Devise
8
8
  if params[:SAMLResponse]
9
9
  OneLogin::RubySaml::Response.new(
10
10
  params[:SAMLResponse],
11
- settings: saml_config(get_idp_entity_id(params)),
12
- allowed_clock_drift: Devise.allowed_clock_drift_in_seconds,
11
+ response_options,
13
12
  )
14
13
  else
15
14
  false
@@ -36,8 +35,7 @@ module Devise
36
35
  def parse_saml_response
37
36
  @response = OneLogin::RubySaml::Response.new(
38
37
  params[:SAMLResponse],
39
- settings: saml_config(get_idp_entity_id(params)),
40
- allowed_clock_drift: Devise.allowed_clock_drift_in_seconds,
38
+ response_options,
41
39
  )
42
40
  unless @response.is_valid?
43
41
  failed_auth("Auth errors: #{@response.errors.join(', ')}")
@@ -54,9 +52,29 @@ module Devise
54
52
  def failed_auth(msg)
55
53
  DeviseSamlAuthenticatable::Logger.send(msg)
56
54
  fail!(:invalid)
57
- Devise.saml_failed_callback.new.handle(@response, self) if Devise.saml_failed_callback
55
+ failed_callback.new.handle(@response, self) if Devise.saml_failed_callback
56
+ end
57
+
58
+ def failed_callback
59
+ if Devise.saml_failed_callback.respond_to?(:new)
60
+ Devise.saml_failed_callback
61
+ else
62
+ Devise.saml_failed_callback.constantize
63
+ end
58
64
  end
59
65
 
66
+ def response_options
67
+ options = {
68
+ settings: saml_config(get_idp_entity_id(params)),
69
+ allowed_clock_drift: Devise.allowed_clock_drift_in_seconds,
70
+ }
71
+
72
+ if Devise.saml_validate_in_response_to
73
+ options[:matches_request_id] = request.session[:saml_transaction_id] || "ID_MISSING"
74
+ end
75
+
76
+ options
77
+ end
60
78
  end
61
79
  end
62
80
  end
@@ -1,3 +1,3 @@
1
1
  module DeviseSamlAuthenticatable
2
- VERSION = "1.6.1"
2
+ VERSION = "1.8.0"
3
3
  end
@@ -56,7 +56,7 @@ module Devise
56
56
 
57
57
  # Reader that can parse entity id from a SAMLMessage
58
58
  mattr_accessor :idp_entity_id_reader
59
- @@idp_entity_id_reader ||= ::DeviseSamlAuthenticatable::DefaultIdpEntityIdReader
59
+ @@idp_entity_id_reader ||= "::DeviseSamlAuthenticatable::DefaultIdpEntityIdReader"
60
60
 
61
61
  # Implements a #handle method that takes the response and strategy as an argument
62
62
  mattr_accessor :saml_failed_callback
@@ -67,9 +67,13 @@ module Devise
67
67
  mattr_accessor :saml_relay_state
68
68
  @@saml_relay_state
69
69
 
70
+ # Validate that the InResponseTo header in SAML responses matches the ID of the request.
71
+ mattr_accessor :saml_validate_in_response_to
72
+ @@saml_validate_in_response_to = false
73
+
70
74
  # Instead of storing the attribute_map in attribute-map.yml, store it in the database, or set it programatically
71
75
  mattr_accessor :saml_attribute_map_resolver
72
- @@saml_attribute_map_resolver ||= ::DeviseSamlAuthenticatable::DefaultAttributeMapResolver
76
+ @@saml_attribute_map_resolver ||= "::DeviseSamlAuthenticatable::DefaultAttributeMapResolver"
73
77
 
74
78
  # Implements a #validate method that takes the retrieved resource and response right after retrieval,
75
79
  # and returns true if it's valid. False will cause authentication to fail.