sorcery 0.9.1 → 0.16.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/FUNDING.yml +1 -0
- data/.github/ISSUE_TEMPLATE.md +24 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +7 -0
- data/.github/workflows/ruby.yml +70 -0
- data/.gitignore +3 -0
- data/.rubocop.yml +55 -0
- data/.rubocop_todo.yml +163 -0
- data/CHANGELOG.md +132 -34
- data/CODE_OF_CONDUCT.md +14 -0
- data/Gemfile +3 -17
- data/{LICENSE.txt → LICENSE.md} +1 -1
- data/MAINTAINING.md +64 -0
- data/README.md +146 -269
- data/Rakefile +4 -2
- data/SECURITY.md +19 -0
- data/gemfiles/rails_52.gemfile +7 -0
- data/gemfiles/rails_60.gemfile +7 -0
- data/gemfiles/rails_61.gemfile +7 -0
- data/gemfiles/rails_70.gemfile +7 -0
- data/lib/generators/sorcery/USAGE +1 -1
- data/lib/generators/sorcery/helpers.rb +8 -4
- data/lib/generators/sorcery/install_generator.rb +41 -35
- data/lib/generators/sorcery/templates/initializer.rb +216 -112
- data/lib/generators/sorcery/templates/migration/activity_logging.rb +7 -7
- data/lib/generators/sorcery/templates/migration/brute_force_protection.rb +5 -5
- data/lib/generators/sorcery/templates/migration/core.rb +5 -7
- data/lib/generators/sorcery/templates/migration/external.rb +4 -4
- data/lib/generators/sorcery/templates/migration/magic_login.rb +9 -0
- data/lib/generators/sorcery/templates/migration/remember_me.rb +5 -5
- data/lib/generators/sorcery/templates/migration/reset_password.rb +7 -6
- data/lib/generators/sorcery/templates/migration/user_activation.rb +6 -6
- data/lib/sorcery/adapters/active_record_adapter.rb +11 -21
- data/lib/sorcery/adapters/mongoid_adapter.rb +23 -11
- data/lib/sorcery/controller/config.rb +27 -23
- data/lib/sorcery/controller/submodules/activity_logging.rb +16 -18
- data/lib/sorcery/controller/submodules/brute_force_protection.rb +1 -2
- data/lib/sorcery/controller/submodules/external.rb +69 -44
- data/lib/sorcery/controller/submodules/http_basic_auth.rb +18 -19
- data/lib/sorcery/controller/submodules/remember_me.rb +16 -16
- data/lib/sorcery/controller/submodules/session_timeout.rb +33 -11
- data/lib/sorcery/controller.rb +50 -35
- data/lib/sorcery/crypto_providers/aes256.rb +17 -16
- data/lib/sorcery/crypto_providers/bcrypt.rb +26 -22
- data/lib/sorcery/crypto_providers/common.rb +1 -1
- data/lib/sorcery/crypto_providers/md5.rb +5 -5
- data/lib/sorcery/crypto_providers/sha1.rb +5 -5
- data/lib/sorcery/crypto_providers/sha256.rb +2 -2
- data/lib/sorcery/crypto_providers/sha512.rb +3 -3
- data/lib/sorcery/engine.rb +19 -11
- data/lib/sorcery/model/config.rb +73 -50
- data/lib/sorcery/model/submodules/activity_logging.rb +31 -12
- data/lib/sorcery/model/submodules/brute_force_protection.rb +38 -31
- data/lib/sorcery/model/submodules/external.rb +22 -10
- data/lib/sorcery/model/submodules/magic_login.rb +130 -0
- data/lib/sorcery/model/submodules/remember_me.rb +19 -7
- data/lib/sorcery/model/submodules/reset_password.rb +64 -42
- data/lib/sorcery/model/submodules/user_activation.rb +52 -54
- data/lib/sorcery/model/temporary_token.rb +30 -7
- data/lib/sorcery/model.rb +65 -40
- data/lib/sorcery/protocols/oauth.rb +4 -9
- data/lib/sorcery/protocols/oauth2.rb +0 -2
- data/lib/sorcery/providers/auth0.rb +46 -0
- data/lib/sorcery/providers/base.rb +4 -4
- data/lib/sorcery/providers/battlenet.rb +51 -0
- data/lib/sorcery/providers/discord.rb +52 -0
- data/lib/sorcery/providers/facebook.rb +8 -11
- data/lib/sorcery/providers/github.rb +5 -7
- data/lib/sorcery/providers/google.rb +3 -5
- data/lib/sorcery/providers/heroku.rb +7 -8
- data/lib/sorcery/providers/instagram.rb +73 -0
- data/lib/sorcery/providers/jira.rb +12 -17
- data/lib/sorcery/providers/line.rb +63 -0
- data/lib/sorcery/providers/linkedin.rb +44 -35
- data/lib/sorcery/providers/liveid.rb +4 -7
- data/lib/sorcery/providers/microsoft.rb +59 -0
- data/lib/sorcery/providers/paypal.rb +60 -0
- data/lib/sorcery/providers/salesforce.rb +3 -5
- data/lib/sorcery/providers/slack.rb +45 -0
- data/lib/sorcery/providers/twitter.rb +4 -6
- data/lib/sorcery/providers/vk.rb +8 -9
- data/lib/sorcery/providers/wechat.rb +81 -0
- data/lib/sorcery/providers/xing.rb +7 -10
- data/lib/sorcery/test_helpers/internal/rails.rb +25 -17
- data/lib/sorcery/test_helpers/internal.rb +15 -14
- data/lib/sorcery/test_helpers/rails/controller.rb +1 -1
- data/lib/sorcery/test_helpers/rails/integration.rb +5 -6
- data/lib/sorcery/test_helpers/rails/request.rb +20 -0
- data/lib/sorcery/version.rb +1 -1
- data/lib/sorcery.rb +4 -17
- data/sorcery.gemspec +43 -28
- data/spec/active_record/user_activation_spec.rb +4 -5
- data/spec/active_record/user_activity_logging_spec.rb +4 -6
- data/spec/active_record/user_brute_force_protection_spec.rb +5 -6
- data/spec/active_record/user_magic_login_spec.rb +15 -0
- data/spec/active_record/user_oauth_spec.rb +5 -6
- data/spec/active_record/user_remember_me_spec.rb +5 -6
- data/spec/active_record/user_reset_password_spec.rb +4 -5
- data/spec/active_record/user_spec.rb +7 -17
- data/spec/controllers/controller_activity_logging_spec.rb +13 -24
- data/spec/controllers/controller_brute_force_protection_spec.rb +8 -10
- data/spec/controllers/controller_http_basic_auth_spec.rb +20 -21
- data/spec/controllers/controller_oauth2_spec.rb +297 -158
- data/spec/controllers/controller_oauth_spec.rb +97 -71
- data/spec/controllers/controller_remember_me_spec.rb +49 -36
- data/spec/controllers/controller_session_timeout_spec.rb +106 -20
- data/spec/controllers/controller_spec.rb +87 -111
- data/spec/orm/active_record.rb +3 -3
- data/spec/providers/example_provider_spec.rb +17 -0
- data/spec/providers/example_spec.rb +17 -0
- data/spec/providers/examples_spec.rb +17 -0
- data/spec/providers/vk_spec.rb +42 -0
- data/spec/rails_app/app/active_record/authentication.rb +1 -1
- data/spec/rails_app/app/active_record/user.rb +2 -2
- data/spec/rails_app/app/assets/config/manifest.js +1 -0
- data/spec/rails_app/app/controllers/application_controller.rb +2 -0
- data/spec/rails_app/app/controllers/sorcery_controller.rb +250 -46
- data/spec/rails_app/app/mailers/sorcery_mailer.rb +23 -17
- data/spec/rails_app/app/views/sorcery_mailer/magic_login_email.html.erb +13 -0
- data/spec/rails_app/app/views/sorcery_mailer/magic_login_email.text.erb +6 -0
- data/spec/rails_app/config/application.rb +14 -9
- data/spec/rails_app/config/boot.rb +2 -2
- data/spec/rails_app/config/environment.rb +1 -1
- data/spec/rails_app/config/environments/test.rb +1 -1
- data/spec/rails_app/config/initializers/compatible_legacy_migration.rb +11 -0
- data/spec/rails_app/config/initializers/session_store.rb +3 -3
- data/spec/rails_app/config/routes.rb +31 -1
- data/spec/rails_app/config/secrets.yml +4 -0
- data/spec/rails_app/config.ru +1 -1
- data/spec/rails_app/db/migrate/activation/20101224223622_add_activation_to_users.rb +4 -4
- data/spec/rails_app/db/migrate/activity_logging/20101224223624_add_activity_logging_to_users.rb +10 -10
- data/spec/rails_app/db/migrate/brute_force_protection/20101224223626_add_brute_force_protection_to_users.rb +5 -5
- data/spec/rails_app/db/migrate/core/20101224223620_create_users.rb +5 -5
- data/spec/rails_app/db/migrate/external/20101224223628_create_authentications_and_user_providers.rb +3 -3
- data/spec/rails_app/db/migrate/invalidate_active_sessions/20180221093235_add_invalidate_active_sessions_before_to_users.rb +9 -0
- data/spec/rails_app/db/migrate/magic_login/20170924151831_add_magic_login_to_users.rb +17 -0
- data/spec/rails_app/db/migrate/remember_me/20101224223623_add_remember_me_token_to_users.rb +6 -6
- data/spec/rails_app/db/migrate/reset_password/20101224223622_add_reset_password_to_users.rb +7 -5
- data/spec/rails_app/db/schema.rb +7 -9
- data/spec/shared_examples/user_activation_shared_examples.rb +177 -58
- data/spec/shared_examples/user_activity_logging_shared_examples.rb +47 -41
- data/spec/shared_examples/user_brute_force_protection_shared_examples.rb +19 -24
- data/spec/shared_examples/user_magic_login_shared_examples.rb +150 -0
- data/spec/shared_examples/user_oauth_shared_examples.rb +7 -10
- data/spec/shared_examples/user_remember_me_shared_examples.rb +91 -22
- data/spec/shared_examples/user_reset_password_shared_examples.rb +153 -58
- data/spec/shared_examples/user_shared_examples.rb +328 -145
- data/spec/sorcery_crypto_providers_spec.rb +122 -75
- data/spec/sorcery_temporary_token_spec.rb +27 -0
- data/spec/spec.opts +1 -1
- data/spec/spec_helper.rb +19 -14
- data/spec/support/migration_helper.rb +29 -0
- data/spec/support/providers/example.rb +11 -0
- data/spec/support/providers/example_provider.rb +11 -0
- data/spec/support/providers/examples.rb +11 -0
- metadata +119 -89
- data/.travis.yml +0 -132
- data/gemfiles/active_record-rails40.gemfile +0 -7
- data/gemfiles/active_record-rails41.gemfile +0 -7
- data/gemfiles/mongo_mapper-rails40.gemfile +0 -9
- data/gemfiles/mongo_mapper-rails41.gemfile +0 -9
- data/gemfiles/mongoid-rails40.gemfile +0 -9
- data/gemfiles/mongoid-rails41.gemfile +0 -9
- data/gemfiles/mongoid3-rails32.gemfile +0 -9
- data/lib/sorcery/adapters/data_mapper_adapter.rb +0 -176
- data/lib/sorcery/adapters/mongo_mapper_adapter.rb +0 -110
- data/lib/sorcery/railties/tasks.rake +0 -6
- data/spec/data_mapper/user_activation_spec.rb +0 -10
- data/spec/data_mapper/user_activity_logging_spec.rb +0 -14
- data/spec/data_mapper/user_brute_force_protection_spec.rb +0 -9
- data/spec/data_mapper/user_oauth_spec.rb +0 -9
- data/spec/data_mapper/user_remember_me_spec.rb +0 -8
- data/spec/data_mapper/user_reset_password_spec.rb +0 -8
- data/spec/data_mapper/user_spec.rb +0 -27
- data/spec/mongo_mapper/user_activation_spec.rb +0 -9
- data/spec/mongo_mapper/user_activity_logging_spec.rb +0 -8
- data/spec/mongo_mapper/user_brute_force_protection_spec.rb +0 -8
- data/spec/mongo_mapper/user_oauth_spec.rb +0 -8
- data/spec/mongo_mapper/user_remember_me_spec.rb +0 -8
- data/spec/mongo_mapper/user_reset_password_spec.rb +0 -8
- data/spec/mongo_mapper/user_spec.rb +0 -37
- data/spec/mongoid/user_activation_spec.rb +0 -9
- data/spec/mongoid/user_activity_logging_spec.rb +0 -8
- data/spec/mongoid/user_brute_force_protection_spec.rb +0 -8
- data/spec/mongoid/user_oauth_spec.rb +0 -8
- data/spec/mongoid/user_remember_me_spec.rb +0 -8
- data/spec/mongoid/user_reset_password_spec.rb +0 -8
- data/spec/mongoid/user_spec.rb +0 -51
- data/spec/orm/data_mapper.rb +0 -48
- data/spec/orm/mongo_mapper.rb +0 -10
- data/spec/orm/mongoid.rb +0 -22
- data/spec/rails_app/app/data_mapper/authentication.rb +0 -8
- data/spec/rails_app/app/data_mapper/user.rb +0 -7
- data/spec/rails_app/app/mongo_mapper/authentication.rb +0 -6
- data/spec/rails_app/app/mongo_mapper/user.rb +0 -7
- data/spec/rails_app/app/mongoid/authentication.rb +0 -7
- data/spec/rails_app/app/mongoid/user.rb +0 -7
- data/spec/rails_app/config/initializers/secret_token.rb +0 -7
- data/spec/rails_app/log/development.log +0 -1791
@@ -2,46 +2,46 @@ module Sorcery
|
|
2
2
|
module Controller
|
3
3
|
module Submodules
|
4
4
|
# This submodule integrates HTTP Basic authentication into sorcery.
|
5
|
-
# You are provided with a before
|
5
|
+
# You are provided with a before action, require_login_from_http_basic,
|
6
6
|
# which requests the browser for authentication.
|
7
|
-
# Then the rest of the submodule takes care of logging the user in
|
7
|
+
# Then the rest of the submodule takes care of logging the user in
|
8
8
|
# into the session, so that the next requests will keep him logged in.
|
9
9
|
module HttpBasicAuth
|
10
10
|
def self.included(base)
|
11
11
|
base.send(:include, InstanceMethods)
|
12
12
|
Config.module_eval do
|
13
13
|
class << self
|
14
|
-
attr_accessor :controller_to_realm_map
|
15
|
-
|
14
|
+
attr_accessor :controller_to_realm_map # What realm to display for which controller name.
|
15
|
+
|
16
16
|
def merge_http_basic_auth_defaults!
|
17
|
-
@defaults.merge!(:@controller_to_realm_map
|
17
|
+
@defaults.merge!(:@controller_to_realm_map => { 'application' => 'Application' })
|
18
18
|
end
|
19
19
|
end
|
20
20
|
merge_http_basic_auth_defaults!
|
21
21
|
end
|
22
|
+
|
22
23
|
Config.login_sources << :login_from_basic_auth
|
23
24
|
end
|
24
|
-
|
25
|
-
module InstanceMethods
|
26
25
|
|
26
|
+
module InstanceMethods
|
27
27
|
protected
|
28
|
-
|
29
|
-
# to be used as a
|
28
|
+
|
29
|
+
# to be used as a before_action.
|
30
30
|
# The method sets a session when requesting the user's credentials.
|
31
31
|
# This is a trick to overcome the way HTTP authentication works (explained below):
|
32
32
|
#
|
33
|
-
# Once the user fills the credentials once, the browser will always send it to the
|
33
|
+
# Once the user fills the credentials once, the browser will always send it to the
|
34
34
|
# server when visiting the website, until the browser is closed.
|
35
|
-
# This causes wierd behaviour if the user logs out. The session is reset, yet the
|
36
|
-
# user is re-logged in by the
|
35
|
+
# This causes wierd behaviour if the user logs out. The session is reset, yet the
|
36
|
+
# user is re-logged in by the before_action calling 'login_from_basic_auth'.
|
37
37
|
# To overcome this, we set a session when requesting the password, which logout will
|
38
38
|
# reset, and that's how we know if we need to request for HTTP auth again.
|
39
39
|
def require_login_from_http_basic
|
40
|
-
(request_http_basic_authentication(realm_name_by_controller)
|
40
|
+
(request_http_basic_authentication(realm_name_by_controller) && (session[:http_authentication_used] = true) && return) if request.authorization.nil? || session[:http_authentication_used].nil?
|
41
41
|
require_login
|
42
42
|
session[:http_authentication_used] = nil unless logged_in?
|
43
43
|
end
|
44
|
-
|
44
|
+
|
45
45
|
# given to main controller module as a login source callback
|
46
46
|
def login_from_basic_auth
|
47
47
|
authenticate_with_http_basic do |username, password|
|
@@ -50,7 +50,7 @@ module Sorcery
|
|
50
50
|
@current_user
|
51
51
|
end
|
52
52
|
end
|
53
|
-
|
53
|
+
|
54
54
|
# Sets the realm name by searching the controller name in the hash given at configuration time.
|
55
55
|
def realm_name_by_controller
|
56
56
|
if defined?(ActionController::Base)
|
@@ -58,17 +58,16 @@ module Sorcery
|
|
58
58
|
while current_controller != ActionController::Base
|
59
59
|
result = Config.controller_to_realm_map[current_controller.controller_name]
|
60
60
|
return result if result
|
61
|
+
|
61
62
|
current_controller = current_controller.superclass
|
62
63
|
end
|
63
64
|
nil
|
64
65
|
else
|
65
|
-
Config.controller_to_realm_map[
|
66
|
+
Config.controller_to_realm_map['application']
|
66
67
|
end
|
67
68
|
end
|
68
|
-
|
69
69
|
end
|
70
|
-
|
71
70
|
end
|
72
71
|
end
|
73
72
|
end
|
74
|
-
end
|
73
|
+
end
|
@@ -17,9 +17,9 @@ module Sorcery
|
|
17
17
|
end
|
18
18
|
merge_remember_me_defaults!
|
19
19
|
end
|
20
|
+
|
20
21
|
Config.login_sources << :login_from_cookie
|
21
|
-
Config.
|
22
|
-
Config.after_logout << :forget_me!
|
22
|
+
Config.before_logout << :forget_me!
|
23
23
|
end
|
24
24
|
|
25
25
|
module InstanceMethods
|
@@ -29,10 +29,16 @@ module Sorcery
|
|
29
29
|
set_remember_me_cookie!(current_user)
|
30
30
|
end
|
31
31
|
|
32
|
-
# Clears the cookie and
|
32
|
+
# Clears the cookie, and depending on the value of remember_me_token_persist_globally, may clear the token value.
|
33
33
|
def forget_me!
|
34
34
|
current_user.forget_me!
|
35
|
-
cookies.delete(:remember_me_token, :
|
35
|
+
cookies.delete(:remember_me_token, domain: Config.cookie_domain)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Clears the cookie, and clears the token value.
|
39
|
+
def force_forget_me!
|
40
|
+
current_user.force_forget_me!
|
41
|
+
cookies.delete(:remember_me_token, domain: Config.cookie_domain)
|
36
42
|
end
|
37
43
|
|
38
44
|
# Override.
|
@@ -45,20 +51,15 @@ module Sorcery
|
|
45
51
|
|
46
52
|
protected
|
47
53
|
|
48
|
-
# calls remember_me! if a third credential was passed to the login method.
|
49
|
-
# Runs as a hook after login.
|
50
|
-
def remember_me_if_asked_to(user, credentials)
|
51
|
-
remember_me! if ( credentials.size == 3 && credentials[2] && credentials[2] != "0" )
|
52
|
-
end
|
53
|
-
|
54
54
|
# Checks the cookie for a remember me token, tried to find a user with that token
|
55
55
|
# and logs the user in if found.
|
56
56
|
# Runs as a login source. See 'current_user' method for how it is used.
|
57
57
|
def login_from_cookie
|
58
|
-
user = cookies.signed[:remember_me_token] && user_class.sorcery_adapter.find_by_remember_me_token(cookies.signed[:remember_me_token])
|
58
|
+
user = cookies.signed[:remember_me_token] && user_class.sorcery_adapter.find_by_remember_me_token(cookies.signed[:remember_me_token]) if defined? cookies
|
59
59
|
if user && user.has_remember_me_token?
|
60
60
|
set_remember_me_cookie!(user)
|
61
61
|
session[:user_id] = user.id.to_s
|
62
|
+
after_remember_me!(user)
|
62
63
|
@current_user = user
|
63
64
|
else
|
64
65
|
@current_user = false
|
@@ -67,14 +68,13 @@ module Sorcery
|
|
67
68
|
|
68
69
|
def set_remember_me_cookie!(user)
|
69
70
|
cookies.signed[:remember_me_token] = {
|
70
|
-
:
|
71
|
-
:
|
72
|
-
:
|
73
|
-
:
|
71
|
+
value: user.send(user.sorcery_config.remember_me_token_attribute_name),
|
72
|
+
expires: user.send(user.sorcery_config.remember_me_token_expires_at_attribute_name),
|
73
|
+
httponly: Config.remember_me_httponly,
|
74
|
+
domain: Config.cookie_domain
|
74
75
|
}
|
75
76
|
end
|
76
77
|
end
|
77
|
-
|
78
78
|
end
|
79
79
|
end
|
80
80
|
end
|
@@ -8,38 +8,52 @@ module Sorcery
|
|
8
8
|
base.send(:include, InstanceMethods)
|
9
9
|
Config.module_eval do
|
10
10
|
class << self
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
# how long in seconds to keep the session alive.
|
12
|
+
attr_accessor :session_timeout
|
13
|
+
# use the last action as the beginning of session timeout.
|
14
|
+
attr_accessor :session_timeout_from_last_action
|
15
|
+
# allow users to invalidate active sessions
|
16
|
+
attr_accessor :session_timeout_invalidate_active_sessions_enabled
|
15
17
|
|
16
18
|
def merge_session_timeout_defaults!
|
17
|
-
@defaults.merge!(:@session_timeout
|
18
|
-
:@session_timeout_from_last_action
|
19
|
+
@defaults.merge!(:@session_timeout => 3600, # 1.hour
|
20
|
+
:@session_timeout_from_last_action => false,
|
21
|
+
:@session_timeout_invalidate_active_sessions_enabled => false)
|
19
22
|
end
|
20
23
|
end
|
21
24
|
merge_session_timeout_defaults!
|
22
25
|
end
|
26
|
+
|
23
27
|
Config.after_login << :register_login_time
|
24
|
-
|
28
|
+
Config.after_remember_me << :register_login_time
|
29
|
+
|
30
|
+
base.prepend_before_action :validate_session
|
25
31
|
end
|
26
32
|
|
27
33
|
module InstanceMethods
|
34
|
+
def invalidate_active_sessions!
|
35
|
+
return unless Config.session_timeout_invalidate_active_sessions_enabled
|
36
|
+
return unless current_user.present?
|
37
|
+
|
38
|
+
current_user.send(:invalidate_sessions_before=, Time.now.in_time_zone)
|
39
|
+
current_user.save
|
40
|
+
end
|
41
|
+
|
28
42
|
protected
|
29
43
|
|
30
44
|
# Registers last login to be used as the timeout starting point.
|
31
45
|
# Runs as a hook after a successful login.
|
32
|
-
def register_login_time(
|
46
|
+
def register_login_time(_user, _credentials = nil)
|
33
47
|
session[:login_time] = session[:last_action_time] = Time.now.in_time_zone
|
34
48
|
end
|
35
49
|
|
36
50
|
# Checks if session timeout was reached and expires the current session if so.
|
37
|
-
# To be used as a
|
51
|
+
# To be used as a before_action, before require_login
|
38
52
|
def validate_session
|
39
53
|
session_to_use = Config.session_timeout_from_last_action ? session[:last_action_time] : session[:login_time]
|
40
|
-
if session_to_use && sorcery_session_expired?(session_to_use.to_time)
|
54
|
+
if (session_to_use && sorcery_session_expired?(session_to_use.to_time)) || sorcery_session_invalidated?
|
41
55
|
reset_sorcery_session
|
42
|
-
|
56
|
+
remove_instance_variable :@current_user if defined? @current_user
|
43
57
|
else
|
44
58
|
session[:last_action_time] = Time.now.in_time_zone
|
45
59
|
end
|
@@ -49,6 +63,14 @@ module Sorcery
|
|
49
63
|
Time.now.in_time_zone - time > Config.session_timeout
|
50
64
|
end
|
51
65
|
|
66
|
+
# Use login time if present, otherwise use last action time.
|
67
|
+
def sorcery_session_invalidated?
|
68
|
+
return false unless Config.session_timeout_invalidate_active_sessions_enabled
|
69
|
+
return false unless current_user.present? && current_user.try(:invalidate_sessions_before).present?
|
70
|
+
|
71
|
+
time = session[:login_time] || session[:last_action_time] || Time.now.in_time_zone
|
72
|
+
time < current_user.invalidate_sessions_before
|
73
|
+
end
|
52
74
|
end
|
53
75
|
end
|
54
76
|
end
|
data/lib/sorcery/controller.rb
CHANGED
@@ -4,11 +4,14 @@ module Sorcery
|
|
4
4
|
klass.class_eval do
|
5
5
|
include InstanceMethods
|
6
6
|
Config.submodules.each do |mod|
|
7
|
+
# FIXME: Is there a cleaner way to handle missing submodules?
|
8
|
+
# rubocop:disable Lint/HandleExceptions
|
7
9
|
begin
|
8
|
-
include Submodules.const_get(mod.to_s.split('_').map
|
10
|
+
include Submodules.const_get(mod.to_s.split('_').map(&:capitalize).join)
|
9
11
|
rescue NameError
|
10
12
|
# don't stop on a missing submodule.
|
11
13
|
end
|
14
|
+
# rubocop:enable Lint/HandleExceptions
|
12
15
|
end
|
13
16
|
end
|
14
17
|
Config.update!
|
@@ -16,55 +19,63 @@ module Sorcery
|
|
16
19
|
end
|
17
20
|
|
18
21
|
module InstanceMethods
|
19
|
-
# To be used as
|
22
|
+
# To be used as before_action.
|
20
23
|
# Will trigger auto-login attempts via the call to logged_in?
|
21
24
|
# If all attempts to auto-login fail, the failure callback will be called.
|
22
25
|
def require_login
|
23
|
-
if
|
24
|
-
|
25
|
-
|
26
|
+
return if logged_in?
|
27
|
+
|
28
|
+
if Config.save_return_to_url && request.get? && !request.xhr? && !request.format.json?
|
29
|
+
session[:return_to_url] = request.url
|
26
30
|
end
|
31
|
+
|
32
|
+
send(Config.not_authenticated_action)
|
27
33
|
end
|
28
34
|
|
29
35
|
# Takes credentials and returns a user on successful authentication.
|
30
36
|
# Runs hooks after login or failed login.
|
31
37
|
def login(*credentials)
|
32
38
|
@current_user = nil
|
33
|
-
|
34
|
-
|
39
|
+
|
40
|
+
user_class.authenticate(*credentials) do |user, failure_reason|
|
41
|
+
if failure_reason
|
42
|
+
after_failed_login!(credentials)
|
43
|
+
|
44
|
+
yield(user, failure_reason) if block_given?
|
45
|
+
|
46
|
+
# FIXME: Does using `break` or `return nil` change functionality?
|
47
|
+
# rubocop:disable Lint/NonLocalExitFromIterator
|
48
|
+
return
|
49
|
+
# rubocop:enable Lint/NonLocalExitFromIterator
|
50
|
+
end
|
51
|
+
|
35
52
|
old_session = session.dup.to_hash
|
36
53
|
reset_sorcery_session
|
37
|
-
old_session.each_pair do |k,v|
|
54
|
+
old_session.each_pair do |k, v|
|
38
55
|
session[k.to_sym] = v
|
39
56
|
end
|
40
57
|
form_authenticity_token
|
41
58
|
|
42
|
-
auto_login(user)
|
59
|
+
auto_login(user, credentials[2])
|
43
60
|
after_login!(user, credentials)
|
44
|
-
|
45
|
-
|
46
|
-
after_failed_login!(credentials)
|
47
|
-
nil
|
61
|
+
|
62
|
+
block_given? ? yield(current_user, nil) : current_user
|
48
63
|
end
|
49
64
|
end
|
50
65
|
|
51
|
-
# put this into the catch block to rescue undefined method `destroy_session'
|
52
|
-
# hotfix for https://github.com/NoamB/sorcery/issues/464
|
53
|
-
# can be removed when Rails 4.1 is out
|
54
66
|
def reset_sorcery_session
|
55
67
|
reset_session # protect from session fixation attacks
|
56
|
-
rescue NoMethodError
|
57
68
|
end
|
58
69
|
|
59
70
|
# Resets the session and runs hooks before and after.
|
60
71
|
def logout
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
72
|
+
return unless logged_in?
|
73
|
+
|
74
|
+
user = current_user
|
75
|
+
before_logout!
|
76
|
+
@current_user = nil
|
77
|
+
reset_sorcery_session
|
78
|
+
after_logout!(user)
|
68
79
|
end
|
69
80
|
|
70
81
|
def logged_in?
|
@@ -87,7 +98,7 @@ module Sorcery
|
|
87
98
|
# used when a user tries to access a page while logged out, is asked to login,
|
88
99
|
# and we want to return him back to the page he originally wanted.
|
89
100
|
def redirect_back_or_to(url, flash_hash = {})
|
90
|
-
redirect_to(session[:return_to_url] || url, :
|
101
|
+
redirect_to(session[:return_to_url] || url, flash: flash_hash)
|
91
102
|
session[:return_to_url] = nil
|
92
103
|
end
|
93
104
|
|
@@ -102,7 +113,7 @@ module Sorcery
|
|
102
113
|
#
|
103
114
|
# @param [<User-Model>] user the user instance.
|
104
115
|
# @return - do not depend on the return value.
|
105
|
-
def auto_login(user,
|
116
|
+
def auto_login(user, _should_remember = false)
|
106
117
|
session[:user_id] = user.id.to_s
|
107
118
|
@current_user = user
|
108
119
|
end
|
@@ -132,26 +143,30 @@ module Sorcery
|
|
132
143
|
end
|
133
144
|
|
134
145
|
def after_login!(user, credentials = [])
|
135
|
-
Config.after_login.each {|c|
|
146
|
+
Config.after_login.each { |c| send(c, user, credentials) }
|
136
147
|
end
|
137
148
|
|
138
149
|
def after_failed_login!(credentials)
|
139
|
-
Config.after_failed_login.each {|c|
|
150
|
+
Config.after_failed_login.each { |c| send(c, credentials) }
|
140
151
|
end
|
141
152
|
|
142
|
-
def before_logout!
|
143
|
-
Config.before_logout.each {|c|
|
153
|
+
def before_logout!
|
154
|
+
Config.before_logout.each { |c| send(c) }
|
144
155
|
end
|
145
156
|
|
146
|
-
def after_logout!
|
147
|
-
Config.after_logout.each {|c|
|
157
|
+
def after_logout!(user)
|
158
|
+
Config.after_logout.each { |c| send(c, user) }
|
159
|
+
end
|
160
|
+
|
161
|
+
def after_remember_me!(user)
|
162
|
+
Config.after_remember_me.each { |c| send(c, user) }
|
148
163
|
end
|
149
164
|
|
150
165
|
def user_class
|
151
166
|
@user_class ||= Config.user_class.to_s.constantize
|
167
|
+
rescue NameError
|
168
|
+
raise ArgumentError, 'You have incorrectly defined user_class or have forgotten to define it in intitializer file (config.user_class = \'User\').'
|
152
169
|
end
|
153
|
-
|
154
170
|
end
|
155
|
-
|
156
|
-
end
|
171
|
+
end
|
157
172
|
end
|
@@ -1,51 +1,52 @@
|
|
1
|
-
require
|
1
|
+
require 'openssl'
|
2
2
|
|
3
3
|
module Sorcery
|
4
4
|
module CryptoProviders
|
5
|
-
# This encryption method is reversible if you have the supplied key.
|
5
|
+
# This encryption method is reversible if you have the supplied key.
|
6
6
|
# So in order to use this encryption method you must supply it with a key first.
|
7
7
|
# In an initializer, or before your application initializes, you should do the following:
|
8
8
|
#
|
9
9
|
# Sorcery::Model::ConfigAES256.key = "my 32 bytes long key"
|
10
10
|
#
|
11
|
-
# My final comment is that this is a strong encryption method,
|
11
|
+
# My final comment is that this is a strong encryption method,
|
12
12
|
# but its main weakness is that its reversible. If you do not need to reverse the hash
|
13
13
|
# then you should consider Sha512 or BCrypt instead.
|
14
14
|
#
|
15
15
|
# Keep your key in a safe place, some even say the key should be stored on a separate server.
|
16
|
-
# This won't hurt performance because the only time it will try and access the key on the
|
16
|
+
# This won't hurt performance because the only time it will try and access the key on the
|
17
17
|
# separate server is during initialization, which only
|
18
|
-
# happens once. The reasoning behind this is if someone does compromise your server they
|
18
|
+
# happens once. The reasoning behind this is if someone does compromise your server they
|
19
19
|
# won't have the key also. Basically, you don't want to store the key with the lock.
|
20
20
|
class AES256
|
21
21
|
class << self
|
22
22
|
attr_writer :key
|
23
|
-
|
23
|
+
|
24
24
|
def encrypt(*tokens)
|
25
25
|
aes.encrypt
|
26
26
|
aes.key = @key
|
27
|
-
[aes.update(tokens.join) + aes.final].pack(
|
27
|
+
[aes.update(tokens.join) + aes.final].pack('m').chomp
|
28
28
|
end
|
29
|
-
|
29
|
+
|
30
30
|
def matches?(crypted, *tokens)
|
31
31
|
decrypt(crypted) == tokens.join
|
32
|
-
rescue OpenSSL::CipherError
|
32
|
+
rescue OpenSSL::Cipher::CipherError
|
33
33
|
false
|
34
34
|
end
|
35
|
-
|
35
|
+
|
36
36
|
def decrypt(crypted)
|
37
37
|
aes.decrypt
|
38
38
|
aes.key = @key
|
39
|
-
(aes.update(crypted.unpack(
|
39
|
+
(aes.update(crypted.unpack('m').first) + aes.final)
|
40
40
|
end
|
41
|
-
|
41
|
+
|
42
42
|
private
|
43
|
-
|
43
|
+
|
44
44
|
def aes
|
45
|
-
raise ArgumentError
|
46
|
-
|
45
|
+
raise ArgumentError, "#{name} expects a 32 bytes long key. Please use Sorcery::Model::Config.encryption_key to set it." if @key.nil? || @key == ''
|
46
|
+
|
47
|
+
@aes ||= OpenSSL::Cipher.new('AES-256-ECB')
|
47
48
|
end
|
48
49
|
end
|
49
50
|
end
|
50
51
|
end
|
51
|
-
end
|
52
|
+
end
|
@@ -2,9 +2,9 @@ require 'bcrypt'
|
|
2
2
|
|
3
3
|
module Sorcery
|
4
4
|
module CryptoProviders
|
5
|
-
# For most apps Sha512 is plenty secure, but if you are building an app that stores nuclear
|
5
|
+
# For most apps Sha512 is plenty secure, but if you are building an app that stores nuclear
|
6
6
|
# launch codes you might want to consier BCrypt. This is an extremely
|
7
|
-
# secure hashing algorithm, mainly because it is slow.
|
7
|
+
# secure hashing algorithm, mainly because it is slow.
|
8
8
|
# A brute force attack on a BCrypt encrypted password would take much longer than a brute force attack on a
|
9
9
|
# password encrypted with a Sha algorithm. Keep in mind you are sacrificing performance by using this,
|
10
10
|
# generating a password takes exponentially longer than any
|
@@ -40,30 +40,35 @@ module Sorcery
|
|
40
40
|
# You are good to go!
|
41
41
|
class BCrypt
|
42
42
|
class << self
|
43
|
-
#
|
43
|
+
# Setting the option :pepper allows users to append an app-specific secret token.
|
44
|
+
# Basically it's equivalent to :salt_join_token option, but have a different name to ensure
|
45
|
+
# backward compatibility in generating/matching passwords.
|
46
|
+
attr_accessor :pepper
|
47
|
+
# This is the :cost option for the BCrpyt library.
|
44
48
|
# The higher the cost the more secure it is and the longer is take the generate a hash. By default this is 10.
|
45
|
-
# Set this to whatever you want, play around with it to get that perfect balance between
|
49
|
+
# Set this to whatever you want, play around with it to get that perfect balance between
|
46
50
|
# security and performance.
|
47
51
|
def cost
|
48
52
|
@cost ||= 10
|
49
53
|
end
|
50
54
|
attr_writer :cost
|
51
|
-
alias
|
52
|
-
alias
|
53
|
-
|
55
|
+
alias stretches cost
|
56
|
+
alias stretches= cost=
|
57
|
+
|
54
58
|
# Creates a BCrypt hash for the password passed.
|
55
59
|
def encrypt(*tokens)
|
56
|
-
::BCrypt::Password.create(join_tokens(tokens), :
|
60
|
+
::BCrypt::Password.create(join_tokens(tokens), cost: cost)
|
57
61
|
end
|
58
|
-
|
62
|
+
|
59
63
|
# Does the hash match the tokens? Uses the same tokens that were used to encrypt.
|
60
64
|
def matches?(hash, *tokens)
|
61
65
|
hash = new_from_hash(hash)
|
62
66
|
return false if hash.nil? || hash == {}
|
67
|
+
|
63
68
|
hash == join_tokens(tokens)
|
64
69
|
end
|
65
|
-
|
66
|
-
# This method is used as a flag to tell Sorcery to "resave" the password
|
70
|
+
|
71
|
+
# This method is used as a flag to tell Sorcery to "resave" the password
|
67
72
|
# upon a successful login, using the new cost
|
68
73
|
def cost_matches?(hash)
|
69
74
|
hash = new_from_hash(hash)
|
@@ -73,25 +78,24 @@ module Sorcery
|
|
73
78
|
hash.cost == cost
|
74
79
|
end
|
75
80
|
end
|
76
|
-
|
81
|
+
|
77
82
|
def reset!
|
78
83
|
@cost = 10
|
84
|
+
@pepper = ''
|
79
85
|
end
|
80
|
-
|
86
|
+
|
81
87
|
private
|
82
|
-
|
88
|
+
|
83
89
|
def join_tokens(tokens)
|
84
|
-
tokens.flatten.join
|
90
|
+
tokens.flatten.join.concat(pepper.to_s) # make sure to add pepper in case tokens have only one element
|
85
91
|
end
|
86
|
-
|
92
|
+
|
87
93
|
def new_from_hash(hash)
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
return nil
|
92
|
-
end
|
94
|
+
::BCrypt::Password.new(hash)
|
95
|
+
rescue ::BCrypt::Errors::InvalidHash
|
96
|
+
nil
|
93
97
|
end
|
94
98
|
end
|
95
99
|
end
|
96
100
|
end
|
97
|
-
end
|
101
|
+
end
|
@@ -1,9 +1,9 @@
|
|
1
|
-
require
|
2
|
-
|
1
|
+
require 'digest/md5'
|
2
|
+
|
3
3
|
module Sorcery
|
4
4
|
module CryptoProviders
|
5
|
-
# This class was made for the users transitioning from md5 based systems.
|
6
|
-
# I highly discourage using this crypto provider as it superbly inferior
|
5
|
+
# This class was made for the users transitioning from md5 based systems.
|
6
|
+
# I highly discourage using this crypto provider as it superbly inferior
|
7
7
|
# to your other options.
|
8
8
|
#
|
9
9
|
# Please use any other provider offered by Sorcery.
|
@@ -16,4 +16,4 @@ module Sorcery
|
|
16
16
|
end
|
17
17
|
end
|
18
18
|
end
|
19
|
-
end
|
19
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require 'digest/sha1'
|
2
2
|
|
3
3
|
module Sorcery
|
4
4
|
module CryptoProviders
|
@@ -8,9 +8,9 @@ module Sorcery
|
|
8
8
|
include Common
|
9
9
|
class << self
|
10
10
|
def join_token
|
11
|
-
@join_token ||=
|
11
|
+
@join_token ||= '--'
|
12
12
|
end
|
13
|
-
|
13
|
+
|
14
14
|
# Turns your raw password into a Sha1 hash.
|
15
15
|
def encrypt(*tokens)
|
16
16
|
tokens = tokens.flatten
|
@@ -18,11 +18,11 @@ module Sorcery
|
|
18
18
|
stretches.times { digest = secure_digest([digest, *tokens].join(join_token)) }
|
19
19
|
digest
|
20
20
|
end
|
21
|
-
|
21
|
+
|
22
22
|
def secure_digest(digest)
|
23
23
|
Digest::SHA1.hexdigest(digest)
|
24
24
|
end
|
25
25
|
end
|
26
26
|
end
|
27
27
|
end
|
28
|
-
end
|
28
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
|
-
require
|
1
|
+
require 'digest/sha2'
|
2
2
|
|
3
3
|
module Sorcery
|
4
|
-
# The activate_sorcery method has a custom_crypto_provider configuration option.
|
4
|
+
# The activate_sorcery method has a custom_crypto_provider configuration option.
|
5
5
|
# This allows you to use any type of encryption you like.
|
6
6
|
# Just create a class with a class level encrypt and matches? method. See example below.
|
7
7
|
#
|
@@ -1,7 +1,7 @@
|
|
1
|
-
require
|
1
|
+
require 'digest/sha2'
|
2
2
|
|
3
3
|
module Sorcery
|
4
|
-
# The activate_sorcery method has a custom_crypto_provider configuration option.
|
4
|
+
# The activate_sorcery method has a custom_crypto_provider configuration option.
|
5
5
|
# This allows you to use any type of encryption you like.
|
6
6
|
# Just create a class with a class level encrypt and matches? method. See example below.
|
7
7
|
#
|
@@ -33,4 +33,4 @@ module Sorcery
|
|
33
33
|
end
|
34
34
|
end
|
35
35
|
end
|
36
|
-
end
|
36
|
+
end
|