upstream-devise 2.1.0.rc

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 (215) hide show
  1. data/.gitignore +10 -0
  2. data/.travis.yml +15 -0
  3. data/CHANGELOG.rdoc +846 -0
  4. data/Gemfile +35 -0
  5. data/Gemfile.lock +165 -0
  6. data/MIT-LICENSE +20 -0
  7. data/README.md +384 -0
  8. data/Rakefile +34 -0
  9. data/app/controllers/devise/confirmations_controller.rb +43 -0
  10. data/app/controllers/devise/omniauth_callbacks_controller.rb +24 -0
  11. data/app/controllers/devise/passwords_controller.rb +47 -0
  12. data/app/controllers/devise/registrations_controller.rb +107 -0
  13. data/app/controllers/devise/sessions_controller.rb +49 -0
  14. data/app/controllers/devise/unlocks_controller.rb +44 -0
  15. data/app/controllers/devise_controller.rb +177 -0
  16. data/app/helpers/devise_helper.rb +25 -0
  17. data/app/mailers/devise/mailer.rb +15 -0
  18. data/app/views/devise/_links.erb +3 -0
  19. data/app/views/devise/confirmations/new.html.erb +12 -0
  20. data/app/views/devise/mailer/confirmation_instructions.html.erb +5 -0
  21. data/app/views/devise/mailer/reset_password_instructions.html.erb +8 -0
  22. data/app/views/devise/mailer/unlock_instructions.html.erb +7 -0
  23. data/app/views/devise/passwords/edit.html.erb +16 -0
  24. data/app/views/devise/passwords/new.html.erb +12 -0
  25. data/app/views/devise/registrations/edit.html.erb +25 -0
  26. data/app/views/devise/registrations/new.html.erb +18 -0
  27. data/app/views/devise/sessions/new.html.erb +17 -0
  28. data/app/views/devise/shared/_links.erb +25 -0
  29. data/app/views/devise/unlocks/new.html.erb +12 -0
  30. data/config/locales/en.yml +57 -0
  31. data/devise.gemspec +25 -0
  32. data/gemfiles/Gemfile.rails-3.1.x +35 -0
  33. data/gemfiles/Gemfile.rails-3.1.x.lock +167 -0
  34. data/lib/devise.rb +455 -0
  35. data/lib/devise/controllers/helpers.rb +269 -0
  36. data/lib/devise/controllers/rememberable.rb +52 -0
  37. data/lib/devise/controllers/scoped_views.rb +17 -0
  38. data/lib/devise/controllers/url_helpers.rb +67 -0
  39. data/lib/devise/delegator.rb +17 -0
  40. data/lib/devise/encryptors/authlogic_sha512.rb +19 -0
  41. data/lib/devise/encryptors/base.rb +24 -0
  42. data/lib/devise/encryptors/clearance_sha1.rb +17 -0
  43. data/lib/devise/encryptors/restful_authentication_sha1.rb +22 -0
  44. data/lib/devise/encryptors/sha1.rb +25 -0
  45. data/lib/devise/encryptors/sha512.rb +25 -0
  46. data/lib/devise/failure_app.rb +185 -0
  47. data/lib/devise/hooks/activatable.rb +11 -0
  48. data/lib/devise/hooks/forgetable.rb +9 -0
  49. data/lib/devise/hooks/lockable.rb +7 -0
  50. data/lib/devise/hooks/rememberable.rb +6 -0
  51. data/lib/devise/hooks/timeoutable.rb +22 -0
  52. data/lib/devise/hooks/trackable.rb +9 -0
  53. data/lib/devise/mailers/helpers.rb +86 -0
  54. data/lib/devise/mapping.rb +172 -0
  55. data/lib/devise/models.rb +123 -0
  56. data/lib/devise/models/authenticatable.rb +231 -0
  57. data/lib/devise/models/confirmable.rb +242 -0
  58. data/lib/devise/models/database_authenticatable.rb +126 -0
  59. data/lib/devise/models/encryptable.rb +86 -0
  60. data/lib/devise/models/lockable.rb +185 -0
  61. data/lib/devise/models/omniauthable.rb +27 -0
  62. data/lib/devise/models/recoverable.rb +140 -0
  63. data/lib/devise/models/registerable.rb +25 -0
  64. data/lib/devise/models/rememberable.rb +125 -0
  65. data/lib/devise/models/timeoutable.rb +49 -0
  66. data/lib/devise/models/token_authenticatable.rb +77 -0
  67. data/lib/devise/models/trackable.rb +35 -0
  68. data/lib/devise/models/validatable.rb +66 -0
  69. data/lib/devise/modules.rb +30 -0
  70. data/lib/devise/omniauth.rb +28 -0
  71. data/lib/devise/omniauth/config.rb +45 -0
  72. data/lib/devise/omniauth/url_helpers.rb +33 -0
  73. data/lib/devise/orm/active_record.rb +3 -0
  74. data/lib/devise/orm/mongoid.rb +3 -0
  75. data/lib/devise/param_filter.rb +41 -0
  76. data/lib/devise/rails.rb +54 -0
  77. data/lib/devise/rails/routes.rb +412 -0
  78. data/lib/devise/rails/warden_compat.rb +43 -0
  79. data/lib/devise/strategies/authenticatable.rb +165 -0
  80. data/lib/devise/strategies/base.rb +15 -0
  81. data/lib/devise/strategies/database_authenticatable.rb +21 -0
  82. data/lib/devise/strategies/rememberable.rb +53 -0
  83. data/lib/devise/strategies/token_authenticatable.rb +57 -0
  84. data/lib/devise/test_helpers.rb +130 -0
  85. data/lib/devise/version.rb +3 -0
  86. data/lib/generators/active_record/devise_generator.rb +78 -0
  87. data/lib/generators/active_record/templates/migration.rb +19 -0
  88. data/lib/generators/active_record/templates/migration_existing.rb +26 -0
  89. data/lib/generators/devise/devise_generator.rb +24 -0
  90. data/lib/generators/devise/install_generator.rb +24 -0
  91. data/lib/generators/devise/orm_helpers.rb +32 -0
  92. data/lib/generators/devise/views_generator.rb +110 -0
  93. data/lib/generators/mongoid/devise_generator.rb +60 -0
  94. data/lib/generators/templates/README +31 -0
  95. data/lib/generators/templates/devise.rb +216 -0
  96. data/lib/generators/templates/markerb/confirmation_instructions.markerb +5 -0
  97. data/lib/generators/templates/markerb/reset_password_instructions.markerb +8 -0
  98. data/lib/generators/templates/markerb/unlock_instructions.markerb +7 -0
  99. data/lib/generators/templates/simple_form_for/confirmations/new.html.erb +15 -0
  100. data/lib/generators/templates/simple_form_for/passwords/edit.html.erb +19 -0
  101. data/lib/generators/templates/simple_form_for/passwords/new.html.erb +15 -0
  102. data/lib/generators/templates/simple_form_for/registrations/edit.html.erb +22 -0
  103. data/lib/generators/templates/simple_form_for/registrations/new.html.erb +17 -0
  104. data/lib/generators/templates/simple_form_for/sessions/new.html.erb +15 -0
  105. data/lib/generators/templates/simple_form_for/unlocks/new.html.erb +15 -0
  106. data/test/controllers/custom_strategy_test.rb +62 -0
  107. data/test/controllers/helpers_test.rb +254 -0
  108. data/test/controllers/internal_helpers_test.rb +97 -0
  109. data/test/controllers/sessions_controller_test.rb +36 -0
  110. data/test/controllers/url_helpers_test.rb +59 -0
  111. data/test/delegator_test.rb +19 -0
  112. data/test/devise_test.rb +72 -0
  113. data/test/encryptors_test.rb +30 -0
  114. data/test/failure_app_test.rb +211 -0
  115. data/test/generators/active_record_generator_test.rb +69 -0
  116. data/test/generators/devise_generator_test.rb +39 -0
  117. data/test/generators/install_generator_test.rb +13 -0
  118. data/test/generators/mongoid_generator_test.rb +23 -0
  119. data/test/generators/views_generator_test.rb +52 -0
  120. data/test/helpers/devise_helper_test.rb +51 -0
  121. data/test/indifferent_hash.rb +33 -0
  122. data/test/integration/authenticatable_test.rb +587 -0
  123. data/test/integration/confirmable_test.rb +255 -0
  124. data/test/integration/database_authenticatable_test.rb +82 -0
  125. data/test/integration/http_authenticatable_test.rb +97 -0
  126. data/test/integration/lockable_test.rb +224 -0
  127. data/test/integration/omniauthable_test.rb +133 -0
  128. data/test/integration/recoverable_test.rb +300 -0
  129. data/test/integration/registerable_test.rb +324 -0
  130. data/test/integration/rememberable_test.rb +158 -0
  131. data/test/integration/timeoutable_test.rb +114 -0
  132. data/test/integration/token_authenticatable_test.rb +161 -0
  133. data/test/integration/trackable_test.rb +92 -0
  134. data/test/mailers/confirmation_instructions_test.rb +95 -0
  135. data/test/mailers/reset_password_instructions_test.rb +83 -0
  136. data/test/mailers/unlock_instructions_test.rb +77 -0
  137. data/test/mapping_test.rb +127 -0
  138. data/test/models/authenticatable_test.rb +7 -0
  139. data/test/models/confirmable_test.rb +357 -0
  140. data/test/models/database_authenticatable_test.rb +189 -0
  141. data/test/models/encryptable_test.rb +73 -0
  142. data/test/models/lockable_test.rb +263 -0
  143. data/test/models/omniauthable_test.rb +7 -0
  144. data/test/models/recoverable_test.rb +205 -0
  145. data/test/models/registerable_test.rb +7 -0
  146. data/test/models/rememberable_test.rb +174 -0
  147. data/test/models/serializable_test.rb +48 -0
  148. data/test/models/timeoutable_test.rb +46 -0
  149. data/test/models/token_authenticatable_test.rb +55 -0
  150. data/test/models/trackable_test.rb +13 -0
  151. data/test/models/validatable_test.rb +117 -0
  152. data/test/models_test.rb +179 -0
  153. data/test/omniauth/config_test.rb +57 -0
  154. data/test/omniauth/url_helpers_test.rb +58 -0
  155. data/test/orm/active_record.rb +9 -0
  156. data/test/orm/mongoid.rb +14 -0
  157. data/test/rails_app/Rakefile +10 -0
  158. data/test/rails_app/app/active_record/admin.rb +6 -0
  159. data/test/rails_app/app/active_record/shim.rb +2 -0
  160. data/test/rails_app/app/active_record/user.rb +6 -0
  161. data/test/rails_app/app/controllers/admins/sessions_controller.rb +6 -0
  162. data/test/rails_app/app/controllers/admins_controller.rb +6 -0
  163. data/test/rails_app/app/controllers/application_controller.rb +8 -0
  164. data/test/rails_app/app/controllers/home_controller.rb +25 -0
  165. data/test/rails_app/app/controllers/publisher/registrations_controller.rb +2 -0
  166. data/test/rails_app/app/controllers/publisher/sessions_controller.rb +2 -0
  167. data/test/rails_app/app/controllers/users/omniauth_callbacks_controller.rb +14 -0
  168. data/test/rails_app/app/controllers/users_controller.rb +23 -0
  169. data/test/rails_app/app/helpers/application_helper.rb +3 -0
  170. data/test/rails_app/app/mailers/users/mailer.rb +3 -0
  171. data/test/rails_app/app/mongoid/admin.rb +30 -0
  172. data/test/rails_app/app/mongoid/shim.rb +24 -0
  173. data/test/rails_app/app/mongoid/user.rb +45 -0
  174. data/test/rails_app/app/views/admins/index.html.erb +1 -0
  175. data/test/rails_app/app/views/admins/sessions/new.html.erb +2 -0
  176. data/test/rails_app/app/views/home/admin_dashboard.html.erb +1 -0
  177. data/test/rails_app/app/views/home/index.html.erb +1 -0
  178. data/test/rails_app/app/views/home/join.html.erb +1 -0
  179. data/test/rails_app/app/views/home/private.html.erb +1 -0
  180. data/test/rails_app/app/views/home/user_dashboard.html.erb +1 -0
  181. data/test/rails_app/app/views/layouts/application.html.erb +24 -0
  182. data/test/rails_app/app/views/users/index.html.erb +1 -0
  183. data/test/rails_app/app/views/users/mailer/confirmation_instructions.erb +1 -0
  184. data/test/rails_app/app/views/users/sessions/new.html.erb +1 -0
  185. data/test/rails_app/config.ru +4 -0
  186. data/test/rails_app/config/application.rb +41 -0
  187. data/test/rails_app/config/boot.rb +8 -0
  188. data/test/rails_app/config/database.yml +18 -0
  189. data/test/rails_app/config/environment.rb +5 -0
  190. data/test/rails_app/config/environments/development.rb +18 -0
  191. data/test/rails_app/config/environments/production.rb +33 -0
  192. data/test/rails_app/config/environments/test.rb +33 -0
  193. data/test/rails_app/config/initializers/backtrace_silencers.rb +7 -0
  194. data/test/rails_app/config/initializers/devise.rb +186 -0
  195. data/test/rails_app/config/initializers/inflections.rb +2 -0
  196. data/test/rails_app/config/initializers/secret_token.rb +2 -0
  197. data/test/rails_app/config/routes.rb +90 -0
  198. data/test/rails_app/db/migrate/20100401102949_create_tables.rb +77 -0
  199. data/test/rails_app/db/schema.rb +52 -0
  200. data/test/rails_app/lib/shared_admin.rb +14 -0
  201. data/test/rails_app/lib/shared_user.rb +26 -0
  202. data/test/rails_app/public/404.html +26 -0
  203. data/test/rails_app/public/422.html +26 -0
  204. data/test/rails_app/public/500.html +26 -0
  205. data/test/rails_app/public/favicon.ico +0 -0
  206. data/test/rails_app/script/rails +10 -0
  207. data/test/routes_test.rb +248 -0
  208. data/test/support/assertions.rb +42 -0
  209. data/test/support/helpers.rb +91 -0
  210. data/test/support/integration.rb +90 -0
  211. data/test/support/locale/en.yml +4 -0
  212. data/test/support/webrat/integrations/rails.rb +24 -0
  213. data/test/test_helper.rb +27 -0
  214. data/test/test_helpers_test.rb +134 -0
  215. metadata +451 -0
@@ -0,0 +1,126 @@
1
+ require 'devise/strategies/database_authenticatable'
2
+ require 'bcrypt'
3
+
4
+ module Devise
5
+ module Models
6
+ # Authenticatable Module, responsible for encrypting password and validating
7
+ # authenticity of a user while signing in.
8
+ #
9
+ # == Options
10
+ #
11
+ # DatabaseAuthenticable adds the following options to devise_for:
12
+ #
13
+ # * +pepper+: a random string used to provide a more secure hash. Use
14
+ # `rake secret` to generate new keys.
15
+ #
16
+ # * +stretches+: the cost given to bcrypt.
17
+ #
18
+ # == Examples
19
+ #
20
+ # User.find(1).valid_password?('password123') # returns true/false
21
+ #
22
+ module DatabaseAuthenticatable
23
+ extend ActiveSupport::Concern
24
+
25
+ included do
26
+ attr_reader :password, :current_password
27
+ attr_accessor :password_confirmation
28
+ end
29
+
30
+ def self.required_fields(klass)
31
+ [:encrypted_password] + klass.authentication_keys
32
+ end
33
+
34
+ # Generates password encryption based on the given value.
35
+ def password=(new_password)
36
+ @password = new_password
37
+ self.encrypted_password = password_digest(@password) if @password.present?
38
+ end
39
+
40
+ # Verifies whether an password (ie from sign in) is the user password.
41
+ def valid_password?(password)
42
+ return false if encrypted_password.blank?
43
+ bcrypt = ::BCrypt::Password.new(encrypted_password)
44
+ password = ::BCrypt::Engine.hash_secret("#{password}#{self.class.pepper}", bcrypt.salt)
45
+ Devise.secure_compare(password, encrypted_password)
46
+ end
47
+
48
+ # Set password and password confirmation to nil
49
+ def clean_up_passwords
50
+ self.password = self.password_confirmation = nil
51
+ end
52
+
53
+ # Update record attributes when :current_password matches, otherwise returns
54
+ # error on :current_password. It also automatically rejects :password and
55
+ # :password_confirmation if they are blank.
56
+ def update_with_password(params, *options)
57
+ current_password = params.delete(:current_password)
58
+
59
+ if params[:password].blank?
60
+ params.delete(:password)
61
+ params.delete(:password_confirmation) if params[:password_confirmation].blank?
62
+ end
63
+
64
+ result = if valid_password?(current_password)
65
+ update_attributes(params, *options)
66
+ else
67
+ self.attributes = params
68
+ self.valid?
69
+ self.errors.add(:current_password, current_password.blank? ? :blank : :invalid)
70
+ false
71
+ end
72
+
73
+ clean_up_passwords
74
+ result
75
+ end
76
+
77
+ # Updates record attributes without asking for the current password.
78
+ # Never allows to change the current password. If you are using this
79
+ # method, you should probably override this method to protect other
80
+ # attributes you would not like to be updated without a password.
81
+ #
82
+ # Example:
83
+ #
84
+ # def update_without_password(params={})
85
+ # params.delete(:email)
86
+ # super(params)
87
+ # end
88
+ #
89
+ def update_without_password(params, *options)
90
+ params.delete(:password)
91
+ params.delete(:password_confirmation)
92
+
93
+ result = update_attributes(params, *options)
94
+ clean_up_passwords
95
+ result
96
+ end
97
+
98
+ def after_database_authentication
99
+ end
100
+
101
+ # A reliable way to expose the salt regardless of the implementation.
102
+ def authenticatable_salt
103
+ encrypted_password[0,29] if encrypted_password
104
+ end
105
+
106
+ protected
107
+
108
+ # Digests the password using bcrypt.
109
+ def password_digest(password)
110
+ ::BCrypt::Password.create("#{password}#{self.class.pepper}", :cost => self.class.stretches).to_s
111
+ end
112
+
113
+ module ClassMethods
114
+ Devise::Models.config(self, :pepper, :stretches)
115
+
116
+ # We assume this method already gets the sanitized values from the
117
+ # DatabaseAuthenticatable strategy. If you are using this method on
118
+ # your own, be sure to sanitize the conditions hash to only include
119
+ # the proper fields.
120
+ def find_for_database_authentication(conditions)
121
+ find_for_authentication(conditions)
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,86 @@
1
+ require 'devise/strategies/database_authenticatable'
2
+
3
+ module Devise
4
+ module Models
5
+ # Encryptable module adds support to several encryptors wrapping
6
+ # them in a salt and pepper mechanism to increase security.
7
+ #
8
+ # == Options
9
+ #
10
+ # Encryptable adds the following options to devise_for:
11
+ #
12
+ # * +pepper+: a random string used to provide a more secure hash.
13
+ #
14
+ # * +encryptor+: the encryptor going to be used. By default is nil.
15
+ #
16
+ # == Examples
17
+ #
18
+ # User.find(1).valid_password?('password123') # returns true/false
19
+ #
20
+ module Encryptable
21
+ extend ActiveSupport::Concern
22
+
23
+ included do
24
+ attr_reader :password, :current_password
25
+ attr_accessor :password_confirmation
26
+ end
27
+
28
+ def self.required_fields(klass)
29
+ [:password_salt]
30
+ end
31
+
32
+ # Generates password salt when setting the password.
33
+ def password=(new_password)
34
+ self.password_salt = self.class.password_salt if new_password.present?
35
+ super
36
+ end
37
+
38
+ # Validates the password considering the salt.
39
+ def valid_password?(password)
40
+ return false if encrypted_password.blank?
41
+ encryptor_class.compare(encrypted_password, password, self.class.stretches, authenticatable_salt, self.class.pepper)
42
+ end
43
+
44
+ # Overrides authenticatable salt to use the new password_salt
45
+ # column. authenticatable_salt is used by `valid_password?`
46
+ # and by other modules whenever there is a need for a random
47
+ # token based on the user password.
48
+ def authenticatable_salt
49
+ self.password_salt
50
+ end
51
+
52
+ protected
53
+
54
+ # Digests the password using the configured encryptor.
55
+ def password_digest(password)
56
+ if password_salt.present?
57
+ encryptor_class.digest(password, self.class.stretches, authenticatable_salt, self.class.pepper)
58
+ end
59
+ end
60
+
61
+ def encryptor_class
62
+ self.class.encryptor_class
63
+ end
64
+
65
+ module ClassMethods
66
+ Devise::Models.config(self, :encryptor)
67
+
68
+ # Returns the class for the configured encryptor.
69
+ def encryptor_class
70
+ @encryptor_class ||= case encryptor
71
+ when :bcrypt
72
+ raise "In order to use bcrypt as encryptor, simply remove :encryptable from your devise model"
73
+ when nil
74
+ raise "You need to give an :encryptor as option in order to use :encryptable"
75
+ else
76
+ Devise::Encryptors.const_get(encryptor.to_s.classify)
77
+ end
78
+ end
79
+
80
+ def password_salt
81
+ self.encryptor_class.salt(self.stretches)
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,185 @@
1
+ require "devise/hooks/lockable"
2
+
3
+ module Devise
4
+ module Models
5
+ # Handles blocking a user access after a certain number of attempts.
6
+ # Lockable accepts two different strategies to unlock a user after it's
7
+ # blocked: email and time. The former will send an email to the user when
8
+ # the lock happens, containing a link to unlock its account. The second
9
+ # will unlock the user automatically after some configured time (ie 2.hours).
10
+ # It's also possible to setup lockable to use both email and time strategies.
11
+ #
12
+ # == Options
13
+ #
14
+ # Lockable adds the following options to +devise+:
15
+ #
16
+ # * +maximum_attempts+: how many attempts should be accepted before blocking the user.
17
+ # * +lock_strategy+: lock the user account by :failed_attempts or :none.
18
+ # * +unlock_strategy+: unlock the user account by :time, :email, :both or :none.
19
+ # * +unlock_in+: the time you want to lock the user after to lock happens. Only available when unlock_strategy is :time or :both.
20
+ # * +unlock_keys+: the keys you want to use when locking and unlocking an account
21
+ #
22
+ module Lockable
23
+ extend ActiveSupport::Concern
24
+
25
+ delegate :lock_strategy_enabled?, :unlock_strategy_enabled?, :to => "self.class"
26
+
27
+ def self.required_fields(klass)
28
+ attributes = []
29
+ attributes << :failed_attempts if klass.lock_strategy_enabled?(:failed_attempts)
30
+ attributes << :unlock_at if klass.unlock_strategy_enabled?(:time)
31
+ attributes << :unlock_token if klass.unlock_strategy_enabled?(:email)
32
+
33
+ attributes
34
+ end
35
+
36
+ # Lock a user setting its locked_at to actual time.
37
+ def lock_access!
38
+ self.locked_at = Time.now.utc
39
+
40
+ if unlock_strategy_enabled?(:email)
41
+ generate_unlock_token
42
+ send_unlock_instructions
43
+ end
44
+
45
+ save(:validate => false)
46
+ end
47
+
48
+ # Unlock a user by cleaning locked_at and failed_attempts.
49
+ def unlock_access!
50
+ self.locked_at = nil
51
+ self.failed_attempts = 0 if respond_to?(:failed_attempts=)
52
+ self.unlock_token = nil if respond_to?(:unlock_token=)
53
+ save(:validate => false)
54
+ end
55
+
56
+ # Verifies whether a user is locked or not.
57
+ def access_locked?
58
+ locked_at && !lock_expired?
59
+ end
60
+
61
+ # Send unlock instructions by email
62
+ def send_unlock_instructions
63
+ self.devise_mailer.unlock_instructions(self).deliver
64
+ end
65
+
66
+ # Resend the unlock instructions if the user is locked.
67
+ def resend_unlock_token
68
+ if_access_locked { send_unlock_instructions }
69
+ end
70
+
71
+ # Overwrites active_for_authentication? from Devise::Models::Activatable for locking purposes
72
+ # by verifying whether a user is active to sign in or not based on locked?
73
+ def active_for_authentication?
74
+ super && !access_locked?
75
+ end
76
+
77
+ # Overwrites invalid_message from Devise::Models::Authenticatable to define
78
+ # the correct reason for blocking the sign in.
79
+ def inactive_message
80
+ access_locked? ? :locked : super
81
+ end
82
+
83
+ # Overwrites valid_for_authentication? from Devise::Models::Authenticatable
84
+ # for verifying whether a user is allowed to sign in or not. If the user
85
+ # is locked, it should never be allowed.
86
+ def valid_for_authentication?
87
+ return super unless persisted? && lock_strategy_enabled?(:failed_attempts)
88
+
89
+ # Unlock the user if the lock is expired, no matter
90
+ # if the user can login or not (wrong password, etc)
91
+ unlock_access! if lock_expired?
92
+
93
+ if super && !access_locked?
94
+ true
95
+ else
96
+ self.failed_attempts ||= 0
97
+ self.failed_attempts += 1
98
+ if attempts_exceeded?
99
+ lock_access! unless access_locked?
100
+ else
101
+ save(:validate => false)
102
+ end
103
+ false
104
+ end
105
+ end
106
+
107
+ def unauthenticated_message
108
+ if lock_strategy_enabled?(:failed_attempts) && attempts_exceeded?
109
+ :locked
110
+ else
111
+ super
112
+ end
113
+ end
114
+
115
+ protected
116
+
117
+ def attempts_exceeded?
118
+ self.failed_attempts > self.class.maximum_attempts
119
+ end
120
+
121
+ # Generates unlock token
122
+ def generate_unlock_token
123
+ self.unlock_token = self.class.unlock_token
124
+ end
125
+
126
+ # Tells if the lock is expired if :time unlock strategy is active
127
+ def lock_expired?
128
+ if unlock_strategy_enabled?(:time)
129
+ locked_at && locked_at < self.class.unlock_in.ago
130
+ else
131
+ false
132
+ end
133
+ end
134
+
135
+ # Checks whether the record is locked or not, yielding to the block
136
+ # if it's locked, otherwise adds an error to email.
137
+ def if_access_locked
138
+ if access_locked?
139
+ yield
140
+ else
141
+ self.errors.add(:email, :not_locked)
142
+ false
143
+ end
144
+ end
145
+
146
+ module ClassMethods
147
+ # Attempt to find a user by its email. If a record is found, send new
148
+ # unlock instructions to it. If not user is found, returns a new user
149
+ # with an email not found error.
150
+ # Options must contain the user email
151
+ def send_unlock_instructions(attributes={})
152
+ lockable = find_or_initialize_with_errors(unlock_keys, attributes, :not_found)
153
+ lockable.resend_unlock_token if lockable.persisted?
154
+ lockable
155
+ end
156
+
157
+ # Find a user by its unlock token and try to unlock it.
158
+ # If no user is found, returns a new user with an error.
159
+ # If the user is not locked, creates an error for the user
160
+ # Options must have the unlock_token
161
+ def unlock_access_by_token(unlock_token)
162
+ lockable = find_or_initialize_with_error_by(:unlock_token, unlock_token)
163
+ lockable.unlock_access! if lockable.persisted?
164
+ lockable
165
+ end
166
+
167
+ # Is the unlock enabled for the given unlock strategy?
168
+ def unlock_strategy_enabled?(strategy)
169
+ [:both, strategy].include?(self.unlock_strategy)
170
+ end
171
+
172
+ # Is the lock enabled for the given lock strategy?
173
+ def lock_strategy_enabled?(strategy)
174
+ self.lock_strategy == strategy
175
+ end
176
+
177
+ def unlock_token
178
+ Devise.friendly_token
179
+ end
180
+
181
+ Devise::Models.config(self, :maximum_attempts, :lock_strategy, :unlock_strategy, :unlock_in, :unlock_keys)
182
+ end
183
+ end
184
+ end
185
+ end
@@ -0,0 +1,27 @@
1
+ require 'devise/omniauth'
2
+
3
+ module Devise
4
+ module Models
5
+ # Adds OmniAuth support to your model.
6
+ #
7
+ # == Options
8
+ #
9
+ # Oauthable adds the following options to devise_for:
10
+ #
11
+ # * +omniauth_providers+: Which providers are avaialble to this model. It expects an array:
12
+ #
13
+ # devise_for :database_authenticatable, :omniauthable, :omniauth_providers => [:twitter]
14
+ #
15
+ module Omniauthable
16
+ extend ActiveSupport::Concern
17
+
18
+ def self.required_fields(klass)
19
+ []
20
+ end
21
+
22
+ module ClassMethods
23
+ Devise::Models.config(self, :omniauth_providers)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,140 @@
1
+ module Devise
2
+ module Models
3
+
4
+ # Recoverable takes care of reseting the user password and send reset instructions.
5
+ #
6
+ # ==Options
7
+ #
8
+ # Recoverable adds the following options to devise_for:
9
+ #
10
+ # * +reset_password_keys+: the keys you want to use when recovering the password for an account
11
+ #
12
+ # == Examples
13
+ #
14
+ # # resets the user password and save the record, true if valid passwords are given, otherwise false
15
+ # User.find(1).reset_password!('password123', 'password123')
16
+ #
17
+ # # only resets the user password, without saving the record
18
+ # user = User.find(1)
19
+ # user.reset_password('password123', 'password123')
20
+ #
21
+ # # creates a new token and send it with instructions about how to reset the password
22
+ # User.find(1).send_reset_password_instructions
23
+ #
24
+ module Recoverable
25
+ extend ActiveSupport::Concern
26
+
27
+ def self.required_fields(klass)
28
+ [:reset_password_sent_at, :reset_password_token]
29
+ end
30
+
31
+ # Update password saving the record and clearing token. Returns true if
32
+ # the passwords are valid and the record was saved, false otherwise.
33
+ def reset_password!(new_password, new_password_confirmation)
34
+ self.password = new_password
35
+ self.password_confirmation = new_password_confirmation
36
+
37
+ if valid?
38
+ clear_reset_password_token
39
+ after_password_reset
40
+ end
41
+
42
+ save
43
+ end
44
+
45
+ # Resets reset password token and send reset password instructions by email
46
+ def send_reset_password_instructions
47
+ generate_reset_password_token! if should_generate_reset_token?
48
+ self.devise_mailer.reset_password_instructions(self).deliver
49
+ end
50
+
51
+ # Checks if the reset password token sent is within the limit time.
52
+ # We do this by calculating if the difference between today and the
53
+ # sending date does not exceed the confirm in time configured.
54
+ # Returns true if the resource is not responding to reset_password_sent_at at all.
55
+ # reset_password_within is a model configuration, must always be an integer value.
56
+ #
57
+ # Example:
58
+ #
59
+ # # reset_password_within = 1.day and reset_password_sent_at = today
60
+ # reset_password_period_valid? # returns true
61
+ #
62
+ # # reset_password_within = 5.days and reset_password_sent_at = 4.days.ago
63
+ # reset_password_period_valid? # returns true
64
+ #
65
+ # # reset_password_within = 5.days and reset_password_sent_at = 5.days.ago
66
+ # reset_password_period_valid? # returns false
67
+ #
68
+ # # reset_password_within = 0.days
69
+ # reset_password_period_valid? # will always return false
70
+ #
71
+ def reset_password_period_valid?
72
+ reset_password_sent_at && reset_password_sent_at.utc >= self.class.reset_password_within.ago
73
+ end
74
+
75
+ protected
76
+
77
+ def should_generate_reset_token?
78
+ reset_password_token.nil? || !reset_password_period_valid?
79
+ end
80
+
81
+ # Generates a new random token for reset password
82
+ def generate_reset_password_token
83
+ self.reset_password_token = self.class.reset_password_token
84
+ self.reset_password_sent_at = Time.now.utc
85
+ self.reset_password_token
86
+ end
87
+
88
+ # Resets the reset password token with and save the record without
89
+ # validating
90
+ def generate_reset_password_token!
91
+ generate_reset_password_token && save(:validate => false)
92
+ end
93
+
94
+ # Removes reset_password token
95
+ def clear_reset_password_token
96
+ self.reset_password_token = nil
97
+ self.reset_password_sent_at = nil
98
+ end
99
+
100
+ def after_password_reset
101
+ end
102
+
103
+ module ClassMethods
104
+ # Attempt to find a user by its email. If a record is found, send new
105
+ # password instructions to it. If not user is found, returns a new user
106
+ # with an email not found error.
107
+ # Attributes must contain the user email
108
+ def send_reset_password_instructions(attributes={})
109
+ recoverable = find_or_initialize_with_errors(reset_password_keys, attributes, :not_found)
110
+ recoverable.send_reset_password_instructions if recoverable.persisted?
111
+ recoverable
112
+ end
113
+
114
+ # Generate a token checking if one does not already exist in the database.
115
+ def reset_password_token
116
+ generate_token(:reset_password_token)
117
+ end
118
+
119
+ # Attempt to find a user by its reset_password_token to reset its
120
+ # password. If a user is found and token is still valid, reset its password and automatically
121
+ # try saving the record. If not user is found, returns a new user
122
+ # containing an error in reset_password_token attribute.
123
+ # Attributes must contain reset_password_token, password and confirmation
124
+ def reset_password_by_token(attributes={})
125
+ recoverable = find_or_initialize_with_error_by(:reset_password_token, attributes[:reset_password_token])
126
+ if recoverable.persisted?
127
+ if recoverable.reset_password_period_valid?
128
+ recoverable.reset_password!(attributes[:password], attributes[:password_confirmation])
129
+ else
130
+ recoverable.errors.add(:reset_password_token, :expired)
131
+ end
132
+ end
133
+ recoverable
134
+ end
135
+
136
+ Devise::Models.config(self, :reset_password_keys, :reset_password_within)
137
+ end
138
+ end
139
+ end
140
+ end