sorcery 0.1.4 → 0.2.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.
- data/Gemfile +4 -2
- data/Gemfile.lock +16 -13
- data/README.rdoc +30 -27
- data/Rakefile +5 -0
- data/VERSION +1 -1
- data/lib/sorcery/controller/submodules/activity_logging.rb +20 -7
- data/lib/sorcery/controller/submodules/brute_force_protection.rb +9 -2
- data/lib/sorcery/controller/submodules/http_basic_auth.rb +8 -3
- data/lib/sorcery/controller/submodules/oauth/oauth1.rb +26 -0
- data/lib/sorcery/controller/submodules/oauth/oauth2.rb +24 -0
- data/lib/sorcery/controller/submodules/oauth/providers/facebook.rb +64 -0
- data/lib/sorcery/controller/submodules/oauth/providers/twitter.rb +61 -0
- data/lib/sorcery/controller/submodules/oauth.rb +92 -0
- data/lib/sorcery/controller/submodules/remember_me.rb +14 -5
- data/lib/sorcery/controller/submodules/session_timeout.rb +6 -1
- data/lib/sorcery/controller.rb +29 -17
- data/lib/sorcery/engine.rb +9 -2
- data/lib/sorcery/model/submodules/activity_logging.rb +12 -7
- data/lib/sorcery/model/submodules/brute_force_protection.rb +11 -4
- data/lib/sorcery/model/submodules/oauth.rb +53 -0
- data/lib/sorcery/model/submodules/remember_me.rb +5 -3
- data/lib/sorcery/model/submodules/reset_password.rb +16 -13
- data/lib/sorcery/model/submodules/user_activation.rb +38 -19
- data/lib/sorcery/model/temporary_token.rb +22 -0
- data/lib/sorcery/model.rb +10 -3
- data/lib/sorcery/test_helpers.rb +84 -0
- data/lib/sorcery.rb +12 -0
- data/sorcery.gemspec +69 -40
- data/spec/Gemfile +3 -2
- data/spec/Gemfile.lock +15 -2
- data/spec/rails3/app_root/.rspec +1 -0
- data/spec/rails3/{Gemfile → app_root/Gemfile} +5 -3
- data/spec/rails3/{Gemfile.lock → app_root/Gemfile.lock} +25 -2
- data/spec/rails3/app_root/app/controllers/application_controller.rb +42 -1
- data/spec/rails3/app_root/app/models/authentication.rb +3 -0
- data/spec/rails3/app_root/app/models/user.rb +4 -1
- data/spec/rails3/app_root/config/application.rb +1 -3
- data/spec/rails3/app_root/config/routes.rb +1 -10
- data/spec/rails3/app_root/db/migrate/activation/20101224223622_add_activation_to_users.rb +6 -4
- data/spec/rails3/app_root/db/migrate/core/20101224223620_create_users.rb +4 -4
- data/spec/rails3/app_root/db/migrate/oauth/20101224223628_create_authentications.rb +14 -0
- data/spec/rails3/{controller_activity_logging_spec.rb → app_root/spec/controller_activity_logging_spec.rb} +13 -13
- data/spec/rails3/{controller_brute_force_protection_spec.rb → app_root/spec/controller_brute_force_protection_spec.rb} +16 -6
- data/spec/rails3/{controller_http_basic_auth_spec.rb → app_root/spec/controller_http_basic_auth_spec.rb} +3 -3
- data/spec/rails3/app_root/spec/controller_oauth2_spec.rb +117 -0
- data/spec/rails3/app_root/spec/controller_oauth_spec.rb +117 -0
- data/spec/rails3/{controller_remember_me_spec.rb → app_root/spec/controller_remember_me_spec.rb} +4 -4
- data/spec/rails3/{controller_session_timeout_spec.rb → app_root/spec/controller_session_timeout_spec.rb} +4 -4
- data/spec/rails3/{controller_spec.rb → app_root/spec/controller_spec.rb} +20 -13
- data/spec/rails3/app_root/spec/spec_helper.orig.rb +27 -0
- data/spec/rails3/app_root/spec/spec_helper.rb +61 -0
- data/spec/rails3/{user_activation_spec.rb → app_root/spec/user_activation_spec.rb} +60 -20
- data/spec/rails3/{user_activity_logging_spec.rb → app_root/spec/user_activity_logging_spec.rb} +4 -4
- data/spec/rails3/{user_brute_force_protection_spec.rb → app_root/spec/user_brute_force_protection_spec.rb} +7 -7
- data/spec/rails3/app_root/spec/user_oauth_spec.rb +39 -0
- data/spec/rails3/{user_remember_me_spec.rb → app_root/spec/user_remember_me_spec.rb} +4 -4
- data/spec/rails3/{user_reset_password_spec.rb → app_root/spec/user_reset_password_spec.rb} +21 -41
- data/spec/rails3/{user_spec.rb → app_root/spec/user_spec.rb} +68 -38
- metadata +127 -58
- data/spec/rails3/app_root/test/fixtures/users.yml +0 -9
- data/spec/rails3/app_root/test/performance/browsing_test.rb +0 -9
- data/spec/rails3/app_root/test/test_helper.rb +0 -13
- data/spec/rails3/app_root/test/unit/user_test.rb +0 -8
- data/spec/rails3/spec_helper.rb +0 -135
- /data/spec/rails3/{Rakefile → app_root/Rakefile} +0 -0
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
module Sorcery
|
|
2
2
|
module Controller
|
|
3
3
|
module Submodules
|
|
4
|
+
# This submodule helps you set a timeout to all user sessions.
|
|
5
|
+
# The timeout can be configured and also you can choose to reset it on every user action.
|
|
4
6
|
module SessionTimeout
|
|
5
7
|
def self.included(base)
|
|
6
8
|
base.send(:include, InstanceMethods)
|
|
@@ -23,16 +25,19 @@ module Sorcery
|
|
|
23
25
|
module InstanceMethods
|
|
24
26
|
protected
|
|
25
27
|
|
|
28
|
+
# Registers last login to be used as the timeout starting point.
|
|
29
|
+
# Runs as a hook after a successful login.
|
|
26
30
|
def register_login_time(user, credentials)
|
|
27
31
|
session[:login_time] = session[:last_action_time] = Time.now.utc
|
|
28
32
|
end
|
|
29
33
|
|
|
34
|
+
# Checks if session timeout was reached and expires the current session if so.
|
|
30
35
|
# To be used as a before_filter, before require_login
|
|
31
36
|
def validate_session
|
|
32
37
|
session_to_use = Config.session_timeout_from_last_action ? session[:last_action_time] : session[:login_time]
|
|
33
38
|
if session_to_use && (Time.now.utc - session_to_use > Config.session_timeout)
|
|
34
39
|
reset_session
|
|
35
|
-
@
|
|
40
|
+
@current_user = false
|
|
36
41
|
else
|
|
37
42
|
session[:last_action_time] = Time.now.utc
|
|
38
43
|
end
|
data/lib/sorcery/controller.rb
CHANGED
|
@@ -5,7 +5,7 @@ module Sorcery
|
|
|
5
5
|
extend ClassMethods
|
|
6
6
|
include InstanceMethods
|
|
7
7
|
Config.submodules.each do |mod|
|
|
8
|
-
begin
|
|
8
|
+
begin # FIXME: is this protection needed?
|
|
9
9
|
include Submodules.const_get(mod.to_s.split("_").map {|p| p.capitalize}.join(""))
|
|
10
10
|
rescue NameError
|
|
11
11
|
# don't stop on a missing submodule.
|
|
@@ -32,62 +32,74 @@ module Sorcery
|
|
|
32
32
|
# If all attempts to auto-login fail, the failure callback will be called.
|
|
33
33
|
def require_login
|
|
34
34
|
if !logged_in?
|
|
35
|
-
session[:
|
|
35
|
+
session[:return_to_url] = request.url if Config.save_return_to_url
|
|
36
36
|
self.send(Config.not_authenticated_action)
|
|
37
37
|
end
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
+
# Takes credentials and returns a user on successful authentication.
|
|
41
|
+
# Runs hooks after login or failed login.
|
|
40
42
|
def login(*credentials)
|
|
41
43
|
user = Config.user_class.authenticate(*credentials)
|
|
42
44
|
if user
|
|
45
|
+
return_to_url = session[:return_to_url]
|
|
43
46
|
reset_session # protect from session fixation attacks
|
|
47
|
+
session[:return_to_url] = return_to_url
|
|
44
48
|
login_user(user)
|
|
45
49
|
after_login!(user, credentials)
|
|
46
|
-
|
|
50
|
+
current_user
|
|
47
51
|
else
|
|
48
52
|
after_failed_login!(credentials)
|
|
49
53
|
nil
|
|
50
54
|
end
|
|
51
55
|
end
|
|
52
56
|
|
|
57
|
+
# Resets the session and runs hooks before and after.
|
|
53
58
|
def logout
|
|
54
59
|
if logged_in?
|
|
55
|
-
before_logout!(
|
|
60
|
+
before_logout!(current_user)
|
|
56
61
|
reset_session
|
|
57
62
|
after_logout!
|
|
58
63
|
end
|
|
59
64
|
end
|
|
60
65
|
|
|
61
66
|
def logged_in?
|
|
62
|
-
!!
|
|
67
|
+
!!current_user
|
|
63
68
|
end
|
|
64
69
|
|
|
65
70
|
# attempts to auto-login from the sources defined (session, basic_auth, cookie, etc.)
|
|
66
71
|
# returns the logged in user if found, false if not (using old restful-authentication trick, nil != false).
|
|
67
|
-
def
|
|
68
|
-
@
|
|
72
|
+
def current_user
|
|
73
|
+
@current_user ||= login_from_session || login_from_other_sources unless @current_user == false
|
|
69
74
|
end
|
|
70
75
|
|
|
71
|
-
def
|
|
72
|
-
|
|
73
|
-
Config.login_sources.find do |source|
|
|
74
|
-
result = send(source)
|
|
75
|
-
end
|
|
76
|
-
result || false
|
|
76
|
+
def return_or_redirect_to(url, flash_hash = {})
|
|
77
|
+
redirect_to(session[:return_to_url] || url, :flash => flash_hash)
|
|
77
78
|
end
|
|
78
79
|
|
|
80
|
+
# The default action for denying non-authenticated users.
|
|
81
|
+
# You can override this method in your controllers.
|
|
79
82
|
def not_authenticated
|
|
80
83
|
redirect_to root_path
|
|
81
84
|
end
|
|
82
85
|
|
|
83
86
|
protected
|
|
84
87
|
|
|
88
|
+
# Tries all available sources (methods) until one doesn't return false.
|
|
89
|
+
def login_from_other_sources
|
|
90
|
+
result = nil
|
|
91
|
+
Config.login_sources.find do |source|
|
|
92
|
+
result = send(source)
|
|
93
|
+
end
|
|
94
|
+
result || false
|
|
95
|
+
end
|
|
96
|
+
|
|
85
97
|
def login_user(user)
|
|
86
98
|
session[:user_id] = user.id
|
|
87
99
|
end
|
|
88
100
|
|
|
89
101
|
def login_from_session
|
|
90
|
-
@
|
|
102
|
+
@current_user = (Config.user_class.find_by_id(session[:user_id]) if session[:user_id]) || false
|
|
91
103
|
end
|
|
92
104
|
|
|
93
105
|
def after_login!(user, credentials)
|
|
@@ -112,11 +124,11 @@ module Sorcery
|
|
|
112
124
|
class << self
|
|
113
125
|
attr_accessor :submodules,
|
|
114
126
|
|
|
115
|
-
:user_class, # what class to use as the user class.
|
|
127
|
+
:user_class, # what class to use as the user class. Set automatically when you call activate_sorcery! in the User class.
|
|
116
128
|
|
|
117
129
|
:not_authenticated_action, # what controller action to call for non-authenticated users.
|
|
118
130
|
|
|
119
|
-
:
|
|
131
|
+
:save_return_to_url, # when a non logged in user tries to enter a page that requires login, save the URL he wanted to reach,
|
|
120
132
|
# and send him there after login.
|
|
121
133
|
|
|
122
134
|
:login_sources,
|
|
@@ -138,7 +150,7 @@ module Sorcery
|
|
|
138
150
|
:@before_logout => [],
|
|
139
151
|
:@after_logout => [],
|
|
140
152
|
:@after_config => [],
|
|
141
|
-
:@
|
|
153
|
+
:@save_return_to_url => true
|
|
142
154
|
}
|
|
143
155
|
end
|
|
144
156
|
|
data/lib/sorcery/engine.rb
CHANGED
|
@@ -13,8 +13,15 @@ module Sorcery
|
|
|
13
13
|
|
|
14
14
|
initializer "extend Controller with sorcery" do |app|
|
|
15
15
|
ActionController::Base.send(:include, Sorcery::Controller)
|
|
16
|
-
ActionController::Base.helper_method :
|
|
16
|
+
ActionController::Base.helper_method :current_user
|
|
17
17
|
end
|
|
18
|
-
|
|
18
|
+
|
|
19
|
+
initializer "attempt to preload user model" do |app|
|
|
20
|
+
begin
|
|
21
|
+
require Rails.root + "app/models/user.rb"
|
|
22
|
+
rescue LoadError
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
19
26
|
end
|
|
20
27
|
end
|
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
module Sorcery
|
|
2
2
|
module Model
|
|
3
3
|
module Submodules
|
|
4
|
+
# This submodule keeps track of events such as login, logout, and last activity time, per user.
|
|
5
|
+
# It helps in estimating which users are active now in the site.
|
|
6
|
+
# This cannot be determined absolutely because a user might be reading a page without clicking anything for a while.
|
|
7
|
+
|
|
8
|
+
# This is the model part of the submodule, which provides configuration options.
|
|
4
9
|
module ActivityLogging
|
|
5
10
|
def self.included(base)
|
|
6
11
|
base.extend(ClassMethods)
|
|
7
12
|
base.sorcery_config.class_eval do
|
|
8
|
-
attr_accessor :last_login_at_attribute_name,
|
|
9
|
-
:last_logout_at_attribute_name,
|
|
10
|
-
:last_activity_at_attribute_name,
|
|
13
|
+
attr_accessor :last_login_at_attribute_name, # last login attribute name.
|
|
14
|
+
:last_logout_at_attribute_name, # last logout attribute name.
|
|
15
|
+
:last_activity_at_attribute_name, # last activity attribute name.
|
|
11
16
|
:activity_timeout # how long since last activity is the user defined logged out?
|
|
12
17
|
end
|
|
13
18
|
|
|
14
19
|
base.sorcery_config.instance_eval do
|
|
15
|
-
@defaults.merge!(:@last_login_at_attribute_name
|
|
16
|
-
:@last_logout_at_attribute_name
|
|
17
|
-
:@last_activity_at_attribute_name
|
|
20
|
+
@defaults.merge!(:@last_login_at_attribute_name => :last_login_at,
|
|
21
|
+
:@last_logout_at_attribute_name => :last_logout_at,
|
|
22
|
+
:@last_activity_at_attribute_name => :last_activity_at,
|
|
18
23
|
:@activity_timeout => 10.minutes)
|
|
19
24
|
reset!
|
|
20
25
|
end
|
|
@@ -22,7 +27,7 @@ module Sorcery
|
|
|
22
27
|
|
|
23
28
|
module ClassMethods
|
|
24
29
|
# get all users with last_activity within timeout
|
|
25
|
-
def
|
|
30
|
+
def current_users
|
|
26
31
|
config = sorcery_config
|
|
27
32
|
where("#{config.last_activity_at_attribute_name} IS NOT NULL") \
|
|
28
33
|
.where("#{config.last_logout_at_attribute_name} IS NULL OR #{config.last_activity_at_attribute_name} > #{config.last_logout_at_attribute_name}") \
|
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
module Sorcery
|
|
2
2
|
module Model
|
|
3
3
|
module Submodules
|
|
4
|
+
# This module helps protect user accounts by locking them down after too many failed attemps to login were detected.
|
|
5
|
+
# This is the model part of the submodule which provides configuration options and methods for locking and unlocking the user.
|
|
4
6
|
module BruteForceProtection
|
|
5
7
|
def self.included(base)
|
|
6
8
|
base.sorcery_config.class_eval do
|
|
7
9
|
attr_accessor :failed_logins_count_attribute_name, # failed logins attribute name.
|
|
8
10
|
:lock_expires_at_attribute_name, # this field indicates whether user is banned and when it will be active again.
|
|
9
|
-
:
|
|
11
|
+
:consecutive_login_retries_amount_limit, # how many failed logins allowed.
|
|
10
12
|
:login_lock_time_period # how long the user should be banned. in seconds. 0 for permanent.
|
|
11
13
|
end
|
|
12
14
|
|
|
13
15
|
base.sorcery_config.instance_eval do
|
|
14
16
|
@defaults.merge!(:@failed_logins_count_attribute_name => :failed_logins_count,
|
|
15
17
|
:@lock_expires_at_attribute_name => :lock_expires_at,
|
|
16
|
-
:@
|
|
18
|
+
:@consecutive_login_retries_amount_limit => 50,
|
|
17
19
|
:@login_lock_time_period => 3600)
|
|
18
20
|
reset!
|
|
19
21
|
end
|
|
@@ -33,11 +35,14 @@ module Sorcery
|
|
|
33
35
|
end
|
|
34
36
|
|
|
35
37
|
module InstanceMethods
|
|
38
|
+
# Called by the controller to increment the failed logins counter.
|
|
39
|
+
# Calls 'lock!' if login retries limit was reached.
|
|
36
40
|
def register_failed_login!
|
|
37
41
|
config = sorcery_config
|
|
42
|
+
return if !unlocked?
|
|
38
43
|
self.increment(config.failed_logins_count_attribute_name)
|
|
39
44
|
save!
|
|
40
|
-
self.lock! if self.send(config.failed_logins_count_attribute_name) >= config.
|
|
45
|
+
self.lock! if self.send(config.failed_logins_count_attribute_name) >= config.consecutive_login_retries_amount_limit
|
|
41
46
|
end
|
|
42
47
|
|
|
43
48
|
protected
|
|
@@ -58,9 +63,11 @@ module Sorcery
|
|
|
58
63
|
self.send(config.lock_expires_at_attribute_name).nil?
|
|
59
64
|
end
|
|
60
65
|
|
|
66
|
+
# Prevents a locked user from logging in, and unlocks users that expired their lock time.
|
|
67
|
+
# Runs as a hook before authenticate.
|
|
61
68
|
def prevent_locked_user_login
|
|
62
69
|
config = sorcery_config
|
|
63
|
-
if !self.unlocked?
|
|
70
|
+
if !self.unlocked? && config.login_lock_time_period != 0
|
|
64
71
|
self.unlock! if self.send(config.lock_expires_at_attribute_name) <= Time.now.utc
|
|
65
72
|
end
|
|
66
73
|
unlocked?
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
module Sorcery
|
|
2
|
+
module Model
|
|
3
|
+
module Submodules
|
|
4
|
+
# This submodule helps you login users from OAuth providers such as Twitter.
|
|
5
|
+
# This is the model part which handles finding the user using access tokens.
|
|
6
|
+
# For the controller options see Sorcery::Controller::Oauth.
|
|
7
|
+
#
|
|
8
|
+
# Socery assumes (read: requires) you will create external users in the same table where you keep your regular users,
|
|
9
|
+
# but that you will have a separate table for keeping their external authentication data,
|
|
10
|
+
# and that that separate table has a few rows for each user, facebook and twitter for example (a one-to-many relationship).
|
|
11
|
+
#
|
|
12
|
+
# External users will have a null crypted_password field, since we do not hold their password.
|
|
13
|
+
# They will not be sent activation emails on creation.
|
|
14
|
+
module Oauth
|
|
15
|
+
def self.included(base)
|
|
16
|
+
base.sorcery_config.class_eval do
|
|
17
|
+
attr_accessor :authentications_class,
|
|
18
|
+
:authentications_user_id_attribute_name,
|
|
19
|
+
:provider_attribute_name,
|
|
20
|
+
:provider_uid_attribute_name
|
|
21
|
+
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
base.sorcery_config.instance_eval do
|
|
25
|
+
@defaults.merge!(:@authentications_class => nil,
|
|
26
|
+
:@authentications_user_id_attribute_name => :user_id,
|
|
27
|
+
:@provider_attribute_name => :provider,
|
|
28
|
+
:@provider_uid_attribute_name => :uid)
|
|
29
|
+
|
|
30
|
+
reset!
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
base.send(:include, InstanceMethods)
|
|
34
|
+
base.extend(ClassMethods)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
module ClassMethods
|
|
38
|
+
# takes a provider and uid and finds a user by them.
|
|
39
|
+
def load_from_provider(provider,uid)
|
|
40
|
+
config = sorcery_config
|
|
41
|
+
authentication = config.authentications_class.find_by_provider_and_uid(provider, uid)
|
|
42
|
+
user = find(authentication.send(config.authentications_user_id_attribute_name)) if authentication
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
module InstanceMethods
|
|
47
|
+
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
module Sorcery
|
|
2
2
|
module Model
|
|
3
3
|
module Submodules
|
|
4
|
+
# The Remember Me submodule takes care of setting the user's cookie so that he will be automatically logged in to the site on every visit,
|
|
5
|
+
# until the cookie expires.
|
|
4
6
|
module RememberMe
|
|
5
7
|
def self.included(base)
|
|
6
8
|
base.sorcery_config.class_eval do
|
|
@@ -22,15 +24,15 @@ module Sorcery
|
|
|
22
24
|
end
|
|
23
25
|
|
|
24
26
|
module InstanceMethods
|
|
25
|
-
# You shouldn't really use this one - it's called by the controller's 'remember_me!' method.
|
|
27
|
+
# You shouldn't really use this one yourself - it's called by the controller's 'remember_me!' method.
|
|
26
28
|
def remember_me!
|
|
27
29
|
config = sorcery_config
|
|
28
|
-
self.send(:"#{config.remember_me_token_attribute_name}=",
|
|
30
|
+
self.send(:"#{config.remember_me_token_attribute_name}=", generate_random_token)
|
|
29
31
|
self.send(:"#{config.remember_me_token_expires_at_attribute_name}=", Time.now + config.remember_me_for)
|
|
30
32
|
self.save!(:validate => false)
|
|
31
33
|
end
|
|
32
34
|
|
|
33
|
-
# You shouldn't really use this one - it's called by the controller's 'forget_me!' method.
|
|
35
|
+
# You shouldn't really use this one yourself - it's called by the controller's 'forget_me!' method.
|
|
34
36
|
def forget_me!
|
|
35
37
|
config = sorcery_config
|
|
36
38
|
self.send(:"#{config.remember_me_token_attribute_name}=", nil)
|
|
@@ -2,6 +2,12 @@ module Sorcery
|
|
|
2
2
|
module Model
|
|
3
3
|
module Submodules
|
|
4
4
|
# This submodule adds the ability to reset password via email confirmation.
|
|
5
|
+
# When the user requests an email is sent to him with a url.
|
|
6
|
+
# The url includes a token, which is also saved with the user's record in the db.
|
|
7
|
+
# The token has configurable expiration.
|
|
8
|
+
# When the user clicks the url in the email, providing the token has not yet expired, he will be able to reset his password via a form.
|
|
9
|
+
#
|
|
10
|
+
# When using this submodule, supplying a mailer is mandatory.
|
|
5
11
|
module ResetPassword
|
|
6
12
|
def self.included(base)
|
|
7
13
|
base.sorcery_config.class_eval do
|
|
@@ -30,22 +36,22 @@ module Sorcery
|
|
|
30
36
|
base.sorcery_config.after_config << :validate_mailer_defined
|
|
31
37
|
|
|
32
38
|
base.extend(ClassMethods)
|
|
39
|
+
base.send(:include, TemporaryToken)
|
|
33
40
|
base.send(:include, InstanceMethods)
|
|
34
41
|
end
|
|
35
42
|
|
|
36
43
|
module ClassMethods
|
|
37
|
-
|
|
44
|
+
# Find user by token, also checks for expiration.
|
|
45
|
+
# Returns the user if token found and is valid.
|
|
38
46
|
def load_from_reset_password_token(token)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return user.reset_password_token_valid? ? user : nil
|
|
43
|
-
end
|
|
44
|
-
user
|
|
47
|
+
token_attr_name = @sorcery_config.reset_password_token_attribute_name
|
|
48
|
+
token_expiration_date_attr = @sorcery_config.reset_password_token_expires_at_attribute_name
|
|
49
|
+
load_from_token(token, token_attr_name, token_expiration_date_attr)
|
|
45
50
|
end
|
|
46
51
|
|
|
47
52
|
protected
|
|
48
53
|
|
|
54
|
+
# This submodule requires the developer to define his own mailer class to be used by it.
|
|
49
55
|
def validate_mailer_defined
|
|
50
56
|
msg = "To use reset_password submodule, you must define a mailer (config.reset_password_mailer = YourMailerClass)."
|
|
51
57
|
raise ArgumentError, msg if @sorcery_config.reset_password_mailer == nil
|
|
@@ -59,7 +65,7 @@ module Sorcery
|
|
|
59
65
|
config = sorcery_config
|
|
60
66
|
# hammering protection
|
|
61
67
|
return if config.reset_password_time_between_emails && self.send(config.reset_password_email_sent_at_attribute_name) && self.send(config.reset_password_email_sent_at_attribute_name) > config.reset_password_time_between_emails.ago.utc
|
|
62
|
-
self.send(:"#{config.reset_password_token_attribute_name}=",
|
|
68
|
+
self.send(:"#{config.reset_password_token_attribute_name}=", generate_random_token)
|
|
63
69
|
self.send(:"#{config.reset_password_token_expires_at_attribute_name}=", Time.now.utc + config.reset_password_expiration_period) if config.reset_password_expiration_period
|
|
64
70
|
self.send(:"#{config.reset_password_email_sent_at_attribute_name}=", Time.now.utc)
|
|
65
71
|
self.class.transaction do
|
|
@@ -68,18 +74,15 @@ module Sorcery
|
|
|
68
74
|
end
|
|
69
75
|
end
|
|
70
76
|
|
|
77
|
+
# Clears token and tries to update the new password for the user.
|
|
71
78
|
def reset_password!(params)
|
|
72
79
|
clear_reset_password_token
|
|
73
80
|
update_attributes(params)
|
|
74
81
|
end
|
|
75
|
-
|
|
76
|
-
def reset_password_token_valid?
|
|
77
|
-
config = sorcery_config
|
|
78
|
-
config.reset_password_expiration_period ? Time.now.utc < self.send(config.reset_password_token_expires_at_attribute_name) : true
|
|
79
|
-
end
|
|
80
82
|
|
|
81
83
|
protected
|
|
82
84
|
|
|
85
|
+
# Clears the token.
|
|
83
86
|
def clear_reset_password_token
|
|
84
87
|
config = sorcery_config
|
|
85
88
|
self.send(:"#{config.reset_password_token_attribute_name}=", nil)
|
|
@@ -8,27 +8,33 @@ module Sorcery
|
|
|
8
8
|
module UserActivation
|
|
9
9
|
def self.included(base)
|
|
10
10
|
base.sorcery_config.class_eval do
|
|
11
|
-
attr_accessor :activation_state_attribute_name,
|
|
12
|
-
:
|
|
13
|
-
:
|
|
14
|
-
:
|
|
15
|
-
:
|
|
16
|
-
:
|
|
11
|
+
attr_accessor :activation_state_attribute_name, # the attribute name to hold activation state (active/pending).
|
|
12
|
+
:activation_token_attribute_name, # the attribute name to hold activation code (sent by email).
|
|
13
|
+
:activation_token_expires_at_attribute_name, # the attribute name to hold activation code expiration date.
|
|
14
|
+
:activation_token_expiration_period, # how many seconds before the activation code expires. nil for never expires.
|
|
15
|
+
:user_activation_mailer, # your mailer class. Required.
|
|
16
|
+
:activation_needed_email_method_name, # activation needed email method on your mailer class.
|
|
17
|
+
:activation_success_email_method_name, # activation success email method on your mailer class.
|
|
18
|
+
:prevent_non_active_users_to_login # do you want to prevent or allow users that did not activate by email to login?
|
|
17
19
|
end
|
|
18
20
|
|
|
19
21
|
base.sorcery_config.instance_eval do
|
|
20
|
-
@defaults.merge!(:@activation_state_attribute_name
|
|
21
|
-
:@
|
|
22
|
-
:@
|
|
23
|
-
:@
|
|
24
|
-
:@
|
|
25
|
-
:@
|
|
22
|
+
@defaults.merge!(:@activation_state_attribute_name => :activation_state,
|
|
23
|
+
:@activation_token_attribute_name => :activation_token,
|
|
24
|
+
:@activation_token_expires_at_attribute_name => :activation_token_expires_at,
|
|
25
|
+
:@activation_token_expiration_period => nil,
|
|
26
|
+
:@user_activation_mailer => nil,
|
|
27
|
+
:@activation_needed_email_method_name => :activation_needed_email,
|
|
28
|
+
:@activation_success_email_method_name => :activation_success_email,
|
|
29
|
+
:@prevent_non_active_users_to_login => true)
|
|
26
30
|
reset!
|
|
27
31
|
end
|
|
28
32
|
|
|
29
33
|
base.class_eval do
|
|
30
|
-
|
|
31
|
-
|
|
34
|
+
# don't setup activation if no password supplied - this user is created automatically
|
|
35
|
+
before_create :setup_activation, :if => Proc.new { |user| user.send(sorcery_config.password_attribute_name).present? }
|
|
36
|
+
# don't send activation needed email if no crypted password created - this user is external (OAuth etc.)
|
|
37
|
+
after_create :send_activation_needed_email!, :if => Proc.new { |user| !user.external?}
|
|
32
38
|
end
|
|
33
39
|
|
|
34
40
|
base.sorcery_config.after_config << :validate_mailer_defined
|
|
@@ -36,12 +42,22 @@ module Sorcery
|
|
|
36
42
|
base.sorcery_config.before_authenticate << :prevent_non_active_login
|
|
37
43
|
|
|
38
44
|
base.extend(ClassMethods)
|
|
45
|
+
base.send(:include, TemporaryToken)
|
|
39
46
|
base.send(:include, InstanceMethods)
|
|
40
47
|
end
|
|
41
48
|
|
|
42
49
|
module ClassMethods
|
|
50
|
+
# Find user by token, also checks for expiration.
|
|
51
|
+
# Returns the user if token found and is valid.
|
|
52
|
+
def load_from_activation_token(token)
|
|
53
|
+
token_attr_name = @sorcery_config.activation_token_attribute_name
|
|
54
|
+
token_expiration_date_attr = @sorcery_config.activation_token_expires_at_attribute_name
|
|
55
|
+
load_from_token(token, token_attr_name, token_expiration_date_attr)
|
|
56
|
+
end
|
|
57
|
+
|
|
43
58
|
protected
|
|
44
59
|
|
|
60
|
+
# This submodule requires the developer to define his own mailer class to be used by it.
|
|
45
61
|
def validate_mailer_defined
|
|
46
62
|
msg = "To use user_activation submodule, you must define a mailer (config.user_activation_mailer = YourMailerClass)."
|
|
47
63
|
raise ArgumentError, msg if @sorcery_config.user_activation_mailer == nil
|
|
@@ -49,23 +65,26 @@ module Sorcery
|
|
|
49
65
|
end
|
|
50
66
|
|
|
51
67
|
module InstanceMethods
|
|
68
|
+
# clears activation code, sets the user as 'active' and optionaly sends a success email.
|
|
52
69
|
def activate!
|
|
53
70
|
config = sorcery_config
|
|
54
|
-
self.send(:"#{config.
|
|
71
|
+
self.send(:"#{config.activation_token_attribute_name}=", nil)
|
|
55
72
|
self.send(:"#{config.activation_state_attribute_name}=", "active")
|
|
56
|
-
send_activation_success_email!
|
|
73
|
+
send_activation_success_email! unless self.external?
|
|
57
74
|
save!(:validate => false) # don't run validations
|
|
58
75
|
end
|
|
59
|
-
|
|
76
|
+
|
|
60
77
|
protected
|
|
61
78
|
|
|
62
79
|
def setup_activation
|
|
63
80
|
config = sorcery_config
|
|
64
|
-
|
|
65
|
-
self.send(:"#{config.
|
|
81
|
+
generated_activation_token = generate_random_token
|
|
82
|
+
self.send(:"#{config.activation_token_attribute_name}=", generated_activation_token)
|
|
66
83
|
self.send(:"#{config.activation_state_attribute_name}=", "pending")
|
|
84
|
+
self.send(:"#{config.activation_token_expires_at_attribute_name}=", Time.now.utc + config.activation_token_expiration_period) if config.activation_token_expiration_period
|
|
67
85
|
end
|
|
68
86
|
|
|
87
|
+
# called automatically after user initial creation.
|
|
69
88
|
def send_activation_needed_email!
|
|
70
89
|
generic_send_email(:activation_needed_email_method_name, :user_activation_mailer) unless sorcery_config.activation_needed_email_method_name.nil?
|
|
71
90
|
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module Sorcery
|
|
2
|
+
module Model
|
|
3
|
+
# This module encapsulates the logic for temporary token.
|
|
4
|
+
# A temporary token is created to identify a user in scenarios such as reseting password and activating the user by email.
|
|
5
|
+
module TemporaryToken
|
|
6
|
+
def self.included(base)
|
|
7
|
+
base.extend(ClassMethods)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
module ClassMethods
|
|
11
|
+
def load_from_token(token, token_attr_name, token_expiration_date_attr)
|
|
12
|
+
return nil if token.blank?
|
|
13
|
+
user = where("#{token_attr_name} = ?", token).first
|
|
14
|
+
if !user.blank? && !user.send(token_expiration_date_attr).nil?
|
|
15
|
+
return Time.now.utc < user.send(token_expiration_date_attr) ? user : nil
|
|
16
|
+
end
|
|
17
|
+
user
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
data/lib/sorcery/model.rb
CHANGED
|
@@ -55,7 +55,8 @@ module Sorcery
|
|
|
55
55
|
_salt = user.send(@sorcery_config.salt_attribute_name) if user && !@sorcery_config.salt_attribute_name.nil? && !@sorcery_config.encryption_provider.nil?
|
|
56
56
|
user if user && @sorcery_config.before_authenticate.all? {|c| user.send(c)} && credentials_match?(user.send(@sorcery_config.crypted_password_attribute_name),credentials[1],_salt)
|
|
57
57
|
end
|
|
58
|
-
|
|
58
|
+
|
|
59
|
+
# Calls the configured encryption provider to compare the supplied password with the encrypted one.
|
|
59
60
|
def credentials_match?(crypted, *tokens)
|
|
60
61
|
return crypted == tokens.join if @sorcery_config.encryption_provider.nil?
|
|
61
62
|
@sorcery_config.encryption_provider.matches?(crypted, *tokens)
|
|
@@ -78,13 +79,19 @@ module Sorcery
|
|
|
78
79
|
self.class.sorcery_config
|
|
79
80
|
end
|
|
80
81
|
|
|
82
|
+
# identifies whether this user is regular, i.e. we hold his credentials in our db,
|
|
83
|
+
# or that he is external, and his credentials are saved elsewhere (twitter/facebook etc.).
|
|
84
|
+
def external?
|
|
85
|
+
send(sorcery_config.crypted_password_attribute_name).nil?
|
|
86
|
+
end
|
|
87
|
+
|
|
81
88
|
protected
|
|
82
89
|
|
|
83
90
|
# creates new salt and saves it.
|
|
84
91
|
# encrypts password with salt and save it.
|
|
85
92
|
def encrypt_password
|
|
86
93
|
config = sorcery_config
|
|
87
|
-
new_salt = self.send(:"#{config.salt_attribute_name}=",
|
|
94
|
+
new_salt = self.send(:"#{config.salt_attribute_name}=", generate_random_token) if !config.salt_attribute_name.nil?
|
|
88
95
|
self.send(:"#{config.crypted_password_attribute_name}=", self.class.encrypt(self.send(config.password_attribute_name),new_salt))
|
|
89
96
|
end
|
|
90
97
|
|
|
@@ -104,7 +111,7 @@ module Sorcery
|
|
|
104
111
|
end
|
|
105
112
|
|
|
106
113
|
# Random code, used for salt and temporary tokens.
|
|
107
|
-
def
|
|
114
|
+
def generate_random_token
|
|
108
115
|
return Digest::SHA1.hexdigest( Time.now.to_s.split(//).sort_by {rand}.join )
|
|
109
116
|
end
|
|
110
117
|
end
|