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.
Files changed (65) hide show
  1. data/Gemfile +4 -2
  2. data/Gemfile.lock +16 -13
  3. data/README.rdoc +30 -27
  4. data/Rakefile +5 -0
  5. data/VERSION +1 -1
  6. data/lib/sorcery/controller/submodules/activity_logging.rb +20 -7
  7. data/lib/sorcery/controller/submodules/brute_force_protection.rb +9 -2
  8. data/lib/sorcery/controller/submodules/http_basic_auth.rb +8 -3
  9. data/lib/sorcery/controller/submodules/oauth/oauth1.rb +26 -0
  10. data/lib/sorcery/controller/submodules/oauth/oauth2.rb +24 -0
  11. data/lib/sorcery/controller/submodules/oauth/providers/facebook.rb +64 -0
  12. data/lib/sorcery/controller/submodules/oauth/providers/twitter.rb +61 -0
  13. data/lib/sorcery/controller/submodules/oauth.rb +92 -0
  14. data/lib/sorcery/controller/submodules/remember_me.rb +14 -5
  15. data/lib/sorcery/controller/submodules/session_timeout.rb +6 -1
  16. data/lib/sorcery/controller.rb +29 -17
  17. data/lib/sorcery/engine.rb +9 -2
  18. data/lib/sorcery/model/submodules/activity_logging.rb +12 -7
  19. data/lib/sorcery/model/submodules/brute_force_protection.rb +11 -4
  20. data/lib/sorcery/model/submodules/oauth.rb +53 -0
  21. data/lib/sorcery/model/submodules/remember_me.rb +5 -3
  22. data/lib/sorcery/model/submodules/reset_password.rb +16 -13
  23. data/lib/sorcery/model/submodules/user_activation.rb +38 -19
  24. data/lib/sorcery/model/temporary_token.rb +22 -0
  25. data/lib/sorcery/model.rb +10 -3
  26. data/lib/sorcery/test_helpers.rb +84 -0
  27. data/lib/sorcery.rb +12 -0
  28. data/sorcery.gemspec +69 -40
  29. data/spec/Gemfile +3 -2
  30. data/spec/Gemfile.lock +15 -2
  31. data/spec/rails3/app_root/.rspec +1 -0
  32. data/spec/rails3/{Gemfile → app_root/Gemfile} +5 -3
  33. data/spec/rails3/{Gemfile.lock → app_root/Gemfile.lock} +25 -2
  34. data/spec/rails3/app_root/app/controllers/application_controller.rb +42 -1
  35. data/spec/rails3/app_root/app/models/authentication.rb +3 -0
  36. data/spec/rails3/app_root/app/models/user.rb +4 -1
  37. data/spec/rails3/app_root/config/application.rb +1 -3
  38. data/spec/rails3/app_root/config/routes.rb +1 -10
  39. data/spec/rails3/app_root/db/migrate/activation/20101224223622_add_activation_to_users.rb +6 -4
  40. data/spec/rails3/app_root/db/migrate/core/20101224223620_create_users.rb +4 -4
  41. data/spec/rails3/app_root/db/migrate/oauth/20101224223628_create_authentications.rb +14 -0
  42. data/spec/rails3/{controller_activity_logging_spec.rb → app_root/spec/controller_activity_logging_spec.rb} +13 -13
  43. data/spec/rails3/{controller_brute_force_protection_spec.rb → app_root/spec/controller_brute_force_protection_spec.rb} +16 -6
  44. data/spec/rails3/{controller_http_basic_auth_spec.rb → app_root/spec/controller_http_basic_auth_spec.rb} +3 -3
  45. data/spec/rails3/app_root/spec/controller_oauth2_spec.rb +117 -0
  46. data/spec/rails3/app_root/spec/controller_oauth_spec.rb +117 -0
  47. data/spec/rails3/{controller_remember_me_spec.rb → app_root/spec/controller_remember_me_spec.rb} +4 -4
  48. data/spec/rails3/{controller_session_timeout_spec.rb → app_root/spec/controller_session_timeout_spec.rb} +4 -4
  49. data/spec/rails3/{controller_spec.rb → app_root/spec/controller_spec.rb} +20 -13
  50. data/spec/rails3/app_root/spec/spec_helper.orig.rb +27 -0
  51. data/spec/rails3/app_root/spec/spec_helper.rb +61 -0
  52. data/spec/rails3/{user_activation_spec.rb → app_root/spec/user_activation_spec.rb} +60 -20
  53. data/spec/rails3/{user_activity_logging_spec.rb → app_root/spec/user_activity_logging_spec.rb} +4 -4
  54. data/spec/rails3/{user_brute_force_protection_spec.rb → app_root/spec/user_brute_force_protection_spec.rb} +7 -7
  55. data/spec/rails3/app_root/spec/user_oauth_spec.rb +39 -0
  56. data/spec/rails3/{user_remember_me_spec.rb → app_root/spec/user_remember_me_spec.rb} +4 -4
  57. data/spec/rails3/{user_reset_password_spec.rb → app_root/spec/user_reset_password_spec.rb} +21 -41
  58. data/spec/rails3/{user_spec.rb → app_root/spec/user_spec.rb} +68 -38
  59. metadata +127 -58
  60. data/spec/rails3/app_root/test/fixtures/users.yml +0 -9
  61. data/spec/rails3/app_root/test/performance/browsing_test.rb +0 -9
  62. data/spec/rails3/app_root/test/test_helper.rb +0 -13
  63. data/spec/rails3/app_root/test/unit/user_test.rb +0 -8
  64. data/spec/rails3/spec_helper.rb +0 -135
  65. /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
- @logged_in_user = false
40
+ @current_user = false
36
41
  else
37
42
  session[:last_action_time] = Time.now.utc
38
43
  end
@@ -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[:user_wanted_url] = request.url if Config.save_user_wanted_url
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
- logged_in_user
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!(logged_in_user)
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
- !!logged_in_user
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 logged_in_user
68
- @logged_in_user ||= login_from_session || login_from_other_sources unless @logged_in_user == false
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 login_from_other_sources
72
- result = nil
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
- @logged_in_user = (Config.user_class.find_by_id(session[:user_id]) if session[:user_id]) || false
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
- :save_user_wanted_url, # when a non logged in user tries to enter a page that requires login, save the URL he wanted to reach,
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
- :@save_user_wanted_url => true
153
+ :@save_return_to_url => true
142
154
  }
143
155
  end
144
156
 
@@ -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 :logged_in_user
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, # last login attribute name.
9
- :last_logout_at_attribute_name, # last logout attribute name.
10
- :last_activity_at_attribute_name, # last activity 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 => :last_login_at,
16
- :@last_logout_at_attribute_name => :last_logout_at,
17
- :@last_activity_at_attribute_name => :last_activity_at,
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 logged_in_users
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
- :consecutive_login_retries_amount_allowed, # how many failed logins allowed.
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
- :@consecutive_login_retries_amount_allowed => 50,
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.consecutive_login_retries_amount_allowed
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}=", generate_random_code)
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
- return nil if token.blank?
40
- user = where("#{@sorcery_config.reset_password_token_attribute_name} = ?", token).first
41
- if !user.blank? && !@sorcery_config.reset_password_expiration_period.nil?
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}=", generate_random_code)
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, # the attribute name to hold activation state (active/pending).
12
- :activation_code_attribute_name, # the attribute name to hold activation code (sent by email).
13
- :user_activation_mailer, # your mailer class. Needed.
14
- :activation_needed_email_method_name, # activation needed email method on your mailer class.
15
- :activation_success_email_method_name, # activation success email method on your mailer class.
16
- :prevent_non_active_users_to_login # do you want to prevent or allow users that did not activate by email to login?
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 => :activation_state,
21
- :@activation_code_attribute_name => :activation_code,
22
- :@user_activation_mailer => nil,
23
- :@activation_needed_email_method_name => :activation_needed_email,
24
- :@activation_success_email_method_name => :activation_success_email,
25
- :@prevent_non_active_users_to_login => true)
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
- before_create :setup_activation
31
- after_create :send_activation_needed_email!
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.activation_code_attribute_name}=", nil)
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
- generated_activation_code = CryptoProviders::SHA1.encrypt( Time.now.to_s.split(//).sort_by {rand}.join )
65
- self.send(:"#{config.activation_code_attribute_name}=", generated_activation_code)
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}=", generate_random_code) if !config.salt_attribute_name.nil?
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 generate_random_code
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