devise_saml_authenticatable 1.6.3 → 1.8.0

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: f648472eaaf23e5e668e51f84fadff2354879045f0ec798a383dd8a2e2ee135a
4
- data.tar.gz: 443a5e883595f8baa2297e2ca173e8f97ae0abf3502538ce1d0f4e0e1c84081e
3
+ metadata.gz: c2b6dd7d4f718cf0df20aff218f90f1eac720279e4ff5afe6aedef20f84a14fd
4
+ data.tar.gz: 5efc5fa9d89ee10eb6328261b6b870ce580dbe7cd48cedbe8dd609786c5c9f84
5
5
  SHA512:
6
- metadata.gz: 4765fe0a60d2a8ffd97d30d5b0d2fdb55d70a56bd9cb91f760ef7862a0d9ec80570da5d4758a784ac552232e1e5893fdd1accead2ff677893b0eb4a3500dcb9c
7
- data.tar.gz: fcd0e6f70b75fdb9b12b3c1954899989e26f8ddb874c6222ab59ca460387a9672ab7767c13855bc4c3cbabd14fdcdb3ddabb2478412dbb452a17194c20ff4c32
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,8 +85,8 @@ 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
92
  # You can support multiple IdPs by setting this value to the name of a class that implements a ::settings method
@@ -97,9 +97,9 @@ In `config/initializers/devise.rb`:
97
97
  # by setting this to the name of a custom reader class, or use the default.
98
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
@@ -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
@@ -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.3"
2
+ VERSION = "1.8.0"
3
3
  end
@@ -67,6 +67,10 @@ 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
76
  @@saml_attribute_map_resolver ||= "::DeviseSamlAuthenticatable::DefaultAttributeMapResolver"