cloudfoundry-devise 1.5.2

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 (210) hide show
  1. data/.gitignore +12 -0
  2. data/.travis.yml +13 -0
  3. data/CHANGELOG.rdoc +755 -0
  4. data/Gemfile +35 -0
  5. data/MIT-LICENSE +20 -0
  6. data/README.rdoc +366 -0
  7. data/Rakefile +34 -0
  8. data/app/controllers/devise/confirmations_controller.rb +46 -0
  9. data/app/controllers/devise/omniauth_callbacks_controller.rb +26 -0
  10. data/app/controllers/devise/passwords_controller.rb +50 -0
  11. data/app/controllers/devise/registrations_controller.rb +114 -0
  12. data/app/controllers/devise/sessions_controller.rb +49 -0
  13. data/app/controllers/devise/unlocks_controller.rb +34 -0
  14. data/app/helpers/devise_helper.rb +25 -0
  15. data/app/mailers/devise/mailer.rb +15 -0
  16. data/app/views/devise/confirmations/new.html.erb +12 -0
  17. data/app/views/devise/mailer/confirmation_instructions.html.erb +5 -0
  18. data/app/views/devise/mailer/reset_password_instructions.html.erb +8 -0
  19. data/app/views/devise/mailer/unlock_instructions.html.erb +7 -0
  20. data/app/views/devise/passwords/edit.html.erb +16 -0
  21. data/app/views/devise/passwords/new.html.erb +12 -0
  22. data/app/views/devise/registrations/edit.html.erb +25 -0
  23. data/app/views/devise/registrations/new.html.erb +18 -0
  24. data/app/views/devise/sessions/new.html.erb +17 -0
  25. data/app/views/devise/shared/_links.erb +25 -0
  26. data/app/views/devise/unlocks/new.html.erb +12 -0
  27. data/cloudfoundry-devise.gemspec +25 -0
  28. data/config/locales/en.yml +59 -0
  29. data/lib/devise.rb +453 -0
  30. data/lib/devise/controllers/helpers.rb +260 -0
  31. data/lib/devise/controllers/internal_helpers.rb +161 -0
  32. data/lib/devise/controllers/rememberable.rb +52 -0
  33. data/lib/devise/controllers/scoped_views.rb +33 -0
  34. data/lib/devise/controllers/shared_helpers.rb +26 -0
  35. data/lib/devise/controllers/url_helpers.rb +53 -0
  36. data/lib/devise/delegator.rb +16 -0
  37. data/lib/devise/encryptors/authlogic_sha512.rb +19 -0
  38. data/lib/devise/encryptors/base.rb +20 -0
  39. data/lib/devise/encryptors/clearance_sha1.rb +17 -0
  40. data/lib/devise/encryptors/restful_authentication_sha1.rb +22 -0
  41. data/lib/devise/encryptors/sha1.rb +25 -0
  42. data/lib/devise/encryptors/sha512.rb +25 -0
  43. data/lib/devise/failure_app.rb +149 -0
  44. data/lib/devise/hooks/activatable.rb +11 -0
  45. data/lib/devise/hooks/forgetable.rb +9 -0
  46. data/lib/devise/hooks/rememberable.rb +6 -0
  47. data/lib/devise/hooks/timeoutable.rb +24 -0
  48. data/lib/devise/hooks/trackable.rb +9 -0
  49. data/lib/devise/mailers/helpers.rb +86 -0
  50. data/lib/devise/mapping.rb +175 -0
  51. data/lib/devise/models.rb +91 -0
  52. data/lib/devise/models/authenticatable.rb +181 -0
  53. data/lib/devise/models/confirmable.rb +220 -0
  54. data/lib/devise/models/database_authenticatable.rb +122 -0
  55. data/lib/devise/models/encryptable.rb +72 -0
  56. data/lib/devise/models/lockable.rb +169 -0
  57. data/lib/devise/models/omniauthable.rb +23 -0
  58. data/lib/devise/models/recoverable.rb +136 -0
  59. data/lib/devise/models/registerable.rb +21 -0
  60. data/lib/devise/models/rememberable.rb +114 -0
  61. data/lib/devise/models/serializable.rb +43 -0
  62. data/lib/devise/models/timeoutable.rb +45 -0
  63. data/lib/devise/models/token_authenticatable.rb +72 -0
  64. data/lib/devise/models/trackable.rb +30 -0
  65. data/lib/devise/models/validatable.rb +62 -0
  66. data/lib/devise/modules.rb +30 -0
  67. data/lib/devise/omniauth.rb +28 -0
  68. data/lib/devise/omniauth/config.rb +45 -0
  69. data/lib/devise/omniauth/url_helpers.rb +33 -0
  70. data/lib/devise/orm/active_record.rb +44 -0
  71. data/lib/devise/orm/mongoid.rb +31 -0
  72. data/lib/devise/param_filter.rb +41 -0
  73. data/lib/devise/path_checker.rb +18 -0
  74. data/lib/devise/rails.rb +73 -0
  75. data/lib/devise/rails/routes.rb +385 -0
  76. data/lib/devise/rails/warden_compat.rb +120 -0
  77. data/lib/devise/schema.rb +109 -0
  78. data/lib/devise/strategies/authenticatable.rb +155 -0
  79. data/lib/devise/strategies/base.rb +15 -0
  80. data/lib/devise/strategies/database_authenticatable.rb +21 -0
  81. data/lib/devise/strategies/rememberable.rb +53 -0
  82. data/lib/devise/strategies/token_authenticatable.rb +57 -0
  83. data/lib/devise/test_helpers.rb +90 -0
  84. data/lib/devise/version.rb +3 -0
  85. data/lib/generators/active_record/devise_generator.rb +71 -0
  86. data/lib/generators/active_record/templates/migration.rb +29 -0
  87. data/lib/generators/active_record/templates/migration_existing.rb +26 -0
  88. data/lib/generators/devise/devise_generator.rb +22 -0
  89. data/lib/generators/devise/install_generator.rb +24 -0
  90. data/lib/generators/devise/orm_helpers.rb +31 -0
  91. data/lib/generators/devise/views_generator.rb +98 -0
  92. data/lib/generators/mongoid/devise_generator.rb +60 -0
  93. data/lib/generators/templates/README +32 -0
  94. data/lib/generators/templates/devise.rb +215 -0
  95. data/lib/generators/templates/markerb/confirmation_instructions.markerb +5 -0
  96. data/lib/generators/templates/markerb/reset_password_instructions.markerb +8 -0
  97. data/lib/generators/templates/markerb/unlock_instructions.markerb +7 -0
  98. data/lib/generators/templates/simple_form_for/confirmations/new.html.erb +15 -0
  99. data/lib/generators/templates/simple_form_for/passwords/edit.html.erb +19 -0
  100. data/lib/generators/templates/simple_form_for/passwords/new.html.erb +15 -0
  101. data/lib/generators/templates/simple_form_for/registrations/edit.html.erb +22 -0
  102. data/lib/generators/templates/simple_form_for/registrations/new.html.erb +17 -0
  103. data/lib/generators/templates/simple_form_for/sessions/new.html.erb +15 -0
  104. data/lib/generators/templates/simple_form_for/unlocks/new.html.erb +15 -0
  105. data/test/controllers/helpers_test.rb +254 -0
  106. data/test/controllers/internal_helpers_test.rb +96 -0
  107. data/test/controllers/sessions_controller_test.rb +16 -0
  108. data/test/controllers/url_helpers_test.rb +59 -0
  109. data/test/delegator_test.rb +19 -0
  110. data/test/devise_test.rb +72 -0
  111. data/test/encryptors_test.rb +30 -0
  112. data/test/failure_app_test.rb +207 -0
  113. data/test/generators/active_record_generator_test.rb +47 -0
  114. data/test/generators/devise_generator_test.rb +39 -0
  115. data/test/generators/install_generator_test.rb +13 -0
  116. data/test/generators/mongoid_generator_test.rb +23 -0
  117. data/test/generators/views_generator_test.rb +52 -0
  118. data/test/helpers/devise_helper_test.rb +51 -0
  119. data/test/indifferent_hash.rb +33 -0
  120. data/test/integration/authenticatable_test.rb +590 -0
  121. data/test/integration/confirmable_test.rb +262 -0
  122. data/test/integration/database_authenticatable_test.rb +82 -0
  123. data/test/integration/http_authenticatable_test.rb +82 -0
  124. data/test/integration/lockable_test.rb +212 -0
  125. data/test/integration/omniauthable_test.rb +133 -0
  126. data/test/integration/recoverable_test.rb +287 -0
  127. data/test/integration/registerable_test.rb +335 -0
  128. data/test/integration/rememberable_test.rb +158 -0
  129. data/test/integration/timeoutable_test.rb +98 -0
  130. data/test/integration/token_authenticatable_test.rb +148 -0
  131. data/test/integration/trackable_test.rb +92 -0
  132. data/test/mailers/confirmation_instructions_test.rb +95 -0
  133. data/test/mailers/reset_password_instructions_test.rb +83 -0
  134. data/test/mailers/unlock_instructions_test.rb +77 -0
  135. data/test/mapping_test.rb +128 -0
  136. data/test/models/confirmable_test.rb +334 -0
  137. data/test/models/database_authenticatable_test.rb +167 -0
  138. data/test/models/encryptable_test.rb +67 -0
  139. data/test/models/lockable_test.rb +225 -0
  140. data/test/models/recoverable_test.rb +198 -0
  141. data/test/models/rememberable_test.rb +168 -0
  142. data/test/models/serializable_test.rb +38 -0
  143. data/test/models/timeoutable_test.rb +42 -0
  144. data/test/models/token_authenticatable_test.rb +49 -0
  145. data/test/models/trackable_test.rb +5 -0
  146. data/test/models/validatable_test.rb +113 -0
  147. data/test/models_test.rb +109 -0
  148. data/test/omniauth/config_test.rb +57 -0
  149. data/test/omniauth/url_helpers_test.rb +58 -0
  150. data/test/orm/active_record.rb +9 -0
  151. data/test/orm/mongoid.rb +14 -0
  152. data/test/rails_app/Rakefile +10 -0
  153. data/test/rails_app/app/active_record/admin.rb +6 -0
  154. data/test/rails_app/app/active_record/shim.rb +2 -0
  155. data/test/rails_app/app/active_record/user.rb +6 -0
  156. data/test/rails_app/app/controllers/admins/sessions_controller.rb +6 -0
  157. data/test/rails_app/app/controllers/admins_controller.rb +6 -0
  158. data/test/rails_app/app/controllers/application_controller.rb +8 -0
  159. data/test/rails_app/app/controllers/home_controller.rb +25 -0
  160. data/test/rails_app/app/controllers/publisher/registrations_controller.rb +2 -0
  161. data/test/rails_app/app/controllers/publisher/sessions_controller.rb +2 -0
  162. data/test/rails_app/app/controllers/users/omniauth_callbacks_controller.rb +14 -0
  163. data/test/rails_app/app/controllers/users_controller.rb +23 -0
  164. data/test/rails_app/app/helpers/application_helper.rb +3 -0
  165. data/test/rails_app/app/mailers/users/mailer.rb +3 -0
  166. data/test/rails_app/app/mongoid/admin.rb +24 -0
  167. data/test/rails_app/app/mongoid/shim.rb +24 -0
  168. data/test/rails_app/app/mongoid/user.rb +45 -0
  169. data/test/rails_app/app/views/admins/index.html.erb +1 -0
  170. data/test/rails_app/app/views/admins/sessions/new.html.erb +2 -0
  171. data/test/rails_app/app/views/home/admin_dashboard.html.erb +1 -0
  172. data/test/rails_app/app/views/home/index.html.erb +1 -0
  173. data/test/rails_app/app/views/home/join.html.erb +1 -0
  174. data/test/rails_app/app/views/home/private.html.erb +1 -0
  175. data/test/rails_app/app/views/home/user_dashboard.html.erb +1 -0
  176. data/test/rails_app/app/views/layouts/application.html.erb +24 -0
  177. data/test/rails_app/app/views/users/index.html.erb +1 -0
  178. data/test/rails_app/app/views/users/mailer/confirmation_instructions.erb +1 -0
  179. data/test/rails_app/app/views/users/sessions/new.html.erb +1 -0
  180. data/test/rails_app/config.ru +4 -0
  181. data/test/rails_app/config/application.rb +41 -0
  182. data/test/rails_app/config/boot.rb +8 -0
  183. data/test/rails_app/config/database.yml +18 -0
  184. data/test/rails_app/config/environment.rb +5 -0
  185. data/test/rails_app/config/environments/development.rb +18 -0
  186. data/test/rails_app/config/environments/production.rb +33 -0
  187. data/test/rails_app/config/environments/test.rb +33 -0
  188. data/test/rails_app/config/initializers/backtrace_silencers.rb +7 -0
  189. data/test/rails_app/config/initializers/devise.rb +197 -0
  190. data/test/rails_app/config/initializers/inflections.rb +2 -0
  191. data/test/rails_app/config/initializers/secret_token.rb +2 -0
  192. data/test/rails_app/config/routes.rb +87 -0
  193. data/test/rails_app/db/migrate/20100401102949_create_tables.rb +71 -0
  194. data/test/rails_app/db/schema.rb +52 -0
  195. data/test/rails_app/lib/shared_admin.rb +10 -0
  196. data/test/rails_app/lib/shared_user.rb +26 -0
  197. data/test/rails_app/public/404.html +26 -0
  198. data/test/rails_app/public/422.html +26 -0
  199. data/test/rails_app/public/500.html +26 -0
  200. data/test/rails_app/public/favicon.ico +0 -0
  201. data/test/rails_app/script/rails +10 -0
  202. data/test/routes_test.rb +240 -0
  203. data/test/support/assertions.rb +27 -0
  204. data/test/support/helpers.rb +109 -0
  205. data/test/support/integration.rb +88 -0
  206. data/test/support/locale/en.yml +4 -0
  207. data/test/support/webrat/integrations/rails.rb +24 -0
  208. data/test/test_helper.rb +27 -0
  209. data/test/test_helpers_test.rb +134 -0
  210. metadata +295 -0
@@ -0,0 +1,220 @@
1
+ module Devise
2
+ module Models
3
+ # Confirmable is responsible to verify if an account is already confirmed to
4
+ # sign in, and to send emails with confirmation instructions.
5
+ # Confirmation instructions are sent to the user email after creating a
6
+ # record and when manually requested by a new confirmation instruction request.
7
+ #
8
+ # == Options
9
+ #
10
+ # Confirmable adds the following options to devise_for:
11
+ #
12
+ # * +confirm_within+: the time you want to allow the user to access his account
13
+ # before confirming it. After this period, the user access is denied. You can
14
+ # use this to let your user access some features of your application without
15
+ # confirming the account, but blocking it after a certain period (ie 7 days).
16
+ # By default confirm_within is zero, it means users always have to confirm to sign in.
17
+ # * +reconfirmable+: requires any email changes to be confirmed (exactly the same way as
18
+ # initial account confirmation) to be applied. Requires additional unconfirmed_email
19
+ # db field to be setup (t.reconfirmable in migrations). Until confirmed new email is
20
+ # stored in unconfirmed email column, and copied to email column on successful
21
+ # confirmation.
22
+ #
23
+ # == Examples
24
+ #
25
+ # User.find(1).confirm! # returns true unless it's already confirmed
26
+ # User.find(1).confirmed? # true/false
27
+ # User.find(1).send_confirmation_instructions # manually send instructions
28
+ #
29
+ module Confirmable
30
+ extend ActiveSupport::Concern
31
+
32
+ included do
33
+ before_create :generate_confirmation_token, :if => :confirmation_required?
34
+ after_create :send_confirmation_instructions, :if => :confirmation_required?
35
+ before_update :postpone_email_change_until_confirmation, :if => :postpone_email_change?
36
+ after_update :send_confirmation_instructions, :if => :reconfirmation_required?
37
+ end
38
+
39
+ # Confirm a user by setting it's confirmed_at to actual time. If the user
40
+ # is already confirmed, add an error to email field. If the user is invalid
41
+ # add errors
42
+ def confirm!
43
+ unless_confirmed do
44
+ self.confirmation_token = nil
45
+ self.confirmed_at = Time.now.utc
46
+
47
+ if self.class.reconfirmable
48
+ @bypass_postpone = true
49
+ self.email = unconfirmed_email if unconfirmed_email.present?
50
+ self.unconfirmed_email = nil
51
+ save
52
+ else
53
+ save(:validate => false)
54
+ end
55
+ end
56
+ end
57
+
58
+ # Verifies whether a user is confirmed or not
59
+ def confirmed?
60
+ !!confirmed_at
61
+ end
62
+
63
+ def pending_reconfirmation?
64
+ self.class.reconfirmable && unconfirmed_email.present?
65
+ end
66
+
67
+ # Send confirmation instructions by email
68
+ def send_confirmation_instructions
69
+ @reconfirmation_required = false
70
+ generate_confirmation_token! if self.confirmation_token.blank?
71
+ self.devise_mailer.confirmation_instructions(self).deliver
72
+ end
73
+
74
+ # Resend confirmation token. This method does not need to generate a new token.
75
+ def resend_confirmation_token
76
+ unless_confirmed { send_confirmation_instructions }
77
+ end
78
+
79
+ # Overwrites active_for_authentication? for confirmation
80
+ # by verifying whether a user is active to sign in or not. If the user
81
+ # is already confirmed, it should never be blocked. Otherwise we need to
82
+ # calculate if the confirm time has not expired for this user.
83
+ def active_for_authentication?
84
+ super && (!confirmation_required? || confirmed? || confirmation_period_valid?)
85
+ end
86
+
87
+ # The message to be shown if the account is inactive.
88
+ def inactive_message
89
+ !confirmed? ? :unconfirmed : super
90
+ end
91
+
92
+ # If you don't want confirmation to be sent on create, neither a code
93
+ # to be generated, call skip_confirmation!
94
+ def skip_confirmation!
95
+ self.confirmed_at = Time.now.utc
96
+ end
97
+
98
+ def headers_for(action)
99
+ headers = super
100
+ if action == :confirmation_instructions && pending_reconfirmation?
101
+ headers[:to] = unconfirmed_email
102
+ end
103
+ headers
104
+ end
105
+
106
+ protected
107
+
108
+ # Callback to overwrite if confirmation is required or not.
109
+ def confirmation_required?
110
+ !confirmed?
111
+ end
112
+
113
+ # Checks if the confirmation for the user is within the limit time.
114
+ # We do this by calculating if the difference between today and the
115
+ # confirmation sent date does not exceed the confirm in time configured.
116
+ # Confirm_within is a model configuration, must always be an integer value.
117
+ #
118
+ # Example:
119
+ #
120
+ # # confirm_within = 1.day and confirmation_sent_at = today
121
+ # confirmation_period_valid? # returns true
122
+ #
123
+ # # confirm_within = 5.days and confirmation_sent_at = 4.days.ago
124
+ # confirmation_period_valid? # returns true
125
+ #
126
+ # # confirm_within = 5.days and confirmation_sent_at = 5.days.ago
127
+ # confirmation_period_valid? # returns false
128
+ #
129
+ # # confirm_within = 0.days
130
+ # confirmation_period_valid? # will always return false
131
+ #
132
+ def confirmation_period_valid?
133
+ confirmation_sent_at && confirmation_sent_at.utc >= self.class.confirm_within.ago
134
+ end
135
+
136
+ # Checks whether the record is confirmed or not or a new email has been added, yielding to the block
137
+ # if it's already confirmed, otherwise adds an error to email.
138
+ def unless_confirmed
139
+ unless confirmed? && !pending_reconfirmation?
140
+ yield
141
+ else
142
+ self.errors.add(:email, :already_confirmed)
143
+ false
144
+ end
145
+ end
146
+
147
+ # Generates a new random token for confirmation, and stores the time
148
+ # this token is being generated
149
+ def generate_confirmation_token
150
+ self.confirmation_token = self.class.confirmation_token
151
+ self.confirmation_sent_at = Time.now.utc
152
+ end
153
+
154
+ def generate_confirmation_token!
155
+ generate_confirmation_token && save(:validate => false)
156
+ end
157
+
158
+ def after_password_reset
159
+ super
160
+ confirm! unless confirmed?
161
+ end
162
+
163
+ def postpone_email_change_until_confirmation
164
+ @reconfirmation_required = true
165
+ self.unconfirmed_email = self.email
166
+ self.email = self.email_was
167
+ end
168
+
169
+ def postpone_email_change?
170
+ postpone = self.class.reconfirmable && email_changed? && !@bypass_postpone
171
+ @bypass_postpone = nil
172
+ postpone
173
+ end
174
+
175
+ def reconfirmation_required?
176
+ self.class.reconfirmable && @reconfirmation_required
177
+ end
178
+
179
+ module ClassMethods
180
+ # Attempt to find a user by its email. If a record is found, send new
181
+ # confirmation instructions to it. If not, try searching for a user by unconfirmed_email
182
+ # field. If no user is found, returns a new user with an email not found error.
183
+ # Options must contain the user email
184
+ def send_confirmation_instructions(attributes={})
185
+ confirmable = find_by_unconfirmed_email_with_errors(attributes) if reconfirmable
186
+ unless confirmable.try(:persisted?)
187
+ confirmable = find_or_initialize_with_errors(confirmation_keys, attributes, :not_found)
188
+ end
189
+ confirmable.resend_confirmation_token if confirmable.persisted?
190
+ confirmable
191
+ end
192
+
193
+ # Find a user by its confirmation token and try to confirm it.
194
+ # If no user is found, returns a new user with an error.
195
+ # If the user is already confirmed, create an error for the user
196
+ # Options must have the confirmation_token
197
+ def confirm_by_token(confirmation_token)
198
+ confirmable = find_or_initialize_with_error_by(:confirmation_token, confirmation_token)
199
+ confirmable.confirm! if confirmable.persisted?
200
+ confirmable
201
+ end
202
+
203
+ # Generate a token checking if one does not already exist in the database.
204
+ def confirmation_token
205
+ generate_token(:confirmation_token)
206
+ end
207
+
208
+ # Find a record for confirmation by unconfirmed email field
209
+ def find_by_unconfirmed_email_with_errors(attributes = {})
210
+ unconfirmed_required_attributes = confirmation_keys.map { |k| k == :email ? :unconfirmed_email : k }
211
+ unconfirmed_attributes = attributes.symbolize_keys
212
+ unconfirmed_attributes[:unconfirmed_email] = unconfirmed_attributes.delete(:email)
213
+ find_or_initialize_with_errors(unconfirmed_required_attributes, unconfirmed_attributes, :not_found)
214
+ end
215
+
216
+ Devise::Models.config(self, :confirm_within, :confirmation_keys, :reconfirmable)
217
+ end
218
+ end
219
+ end
220
+ end
@@ -0,0 +1,122 @@
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
+ # Generates password encryption based on the given value.
31
+ def password=(new_password)
32
+ @password = new_password
33
+ self.encrypted_password = password_digest(@password) if @password.present?
34
+ end
35
+
36
+ # Verifies whether an password (ie from sign in) is the user password.
37
+ def valid_password?(password)
38
+ return false if encrypted_password.blank?
39
+ bcrypt = ::BCrypt::Password.new(self.encrypted_password)
40
+ password = ::BCrypt::Engine.hash_secret("#{password}#{self.class.pepper}", bcrypt.salt)
41
+ Devise.secure_compare(password, self.encrypted_password)
42
+ end
43
+
44
+ # Set password and password confirmation to nil
45
+ def clean_up_passwords
46
+ self.password = self.password_confirmation = nil
47
+ end
48
+
49
+ # Update record attributes when :current_password matches, otherwise returns
50
+ # error on :current_password. It also automatically rejects :password and
51
+ # :password_confirmation if they are blank.
52
+ def update_with_password(params, *options)
53
+ current_password = params.delete(:current_password)
54
+
55
+ if params[:password].blank?
56
+ params.delete(:password)
57
+ params.delete(:password_confirmation) if params[:password_confirmation].blank?
58
+ end
59
+
60
+ result = if valid_password?(current_password)
61
+ update_attributes(params, *options)
62
+ else
63
+ self.attributes = params
64
+ self.valid?
65
+ self.errors.add(:current_password, current_password.blank? ? :blank : :invalid)
66
+ false
67
+ end
68
+
69
+ clean_up_passwords
70
+ result
71
+ end
72
+
73
+ # Updates record attributes without asking for the current password.
74
+ # Never allows to change the current password. If you are using this
75
+ # method, you should probably override this method to protect other
76
+ # attributes you would not like to be updated without a password.
77
+ #
78
+ # Example:
79
+ #
80
+ # def update_without_password(params={})
81
+ # params.delete(:email)
82
+ # super(params)
83
+ # end
84
+ #
85
+ def update_without_password(params, *options)
86
+ params.delete(:password)
87
+ params.delete(:password_confirmation)
88
+
89
+ result = update_attributes(params, *options)
90
+ clean_up_passwords
91
+ result
92
+ end
93
+
94
+ def after_database_authentication
95
+ end
96
+
97
+ # A reliable way to expose the salt regardless of the implementation.
98
+ def authenticatable_salt
99
+ self.encrypted_password[0,29] if self.encrypted_password
100
+ end
101
+
102
+ protected
103
+
104
+ # Digests the password using bcrypt.
105
+ def password_digest(password)
106
+ ::BCrypt::Password.create("#{password}#{self.class.pepper}", :cost => self.class.stretches).to_s
107
+ end
108
+
109
+ module ClassMethods
110
+ Devise::Models.config(self, :pepper, :stretches)
111
+
112
+ # We assume this method already gets the sanitized values from the
113
+ # DatabaseAuthenticatable strategy. If you are using this method on
114
+ # your own, be sure to sanitize the conditions hash to only include
115
+ # the proper fields.
116
+ def find_for_database_authentication(conditions)
117
+ find_for_authentication(conditions)
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,72 @@
1
+ require 'devise/strategies/database_authenticatable'
2
+
3
+ module Devise
4
+ module Models
5
+ # Encryptable Module adds support to several encryptors.
6
+ #
7
+ # == Options
8
+ #
9
+ # Encryptable adds the following options to devise_for:
10
+ #
11
+ # * +pepper+: a random string used to provide a more secure hash.
12
+ #
13
+ # * +encryptor+: the encryptor going to be used. By default is nil.
14
+ #
15
+ # == Examples
16
+ #
17
+ # User.find(1).valid_password?('password123') # returns true/false
18
+ #
19
+ module Encryptable
20
+ extend ActiveSupport::Concern
21
+
22
+ included do
23
+ attr_reader :password, :current_password
24
+ attr_accessor :password_confirmation
25
+ end
26
+
27
+ # Generates password salt.
28
+ def password=(new_password)
29
+ self.password_salt = self.class.password_salt if new_password.present?
30
+ super
31
+ end
32
+
33
+ def authenticatable_salt
34
+ self.password_salt
35
+ end
36
+
37
+ # Verifies whether an incoming_password (ie from sign in) is the user password.
38
+ def valid_password?(incoming_password)
39
+ Devise.secure_compare(password_digest(incoming_password), self.encrypted_password)
40
+ end
41
+
42
+ protected
43
+
44
+ # Digests the password using the configured encryptor.
45
+ def password_digest(password)
46
+ if self.password_salt.present?
47
+ self.class.encryptor_class.digest(password, self.class.stretches, self.password_salt, self.class.pepper)
48
+ end
49
+ end
50
+
51
+ module ClassMethods
52
+ Devise::Models.config(self, :encryptor)
53
+
54
+ # Returns the class for the configured encryptor.
55
+ def encryptor_class
56
+ @encryptor_class ||= case encryptor
57
+ when :bcrypt
58
+ raise "In order to use bcrypt as encryptor, simply remove :encryptable from your devise model"
59
+ when nil
60
+ raise "You need to give an :encryptor as option in order to use :encryptable"
61
+ else
62
+ ::Devise::Encryptors.const_get(encryptor.to_s.classify)
63
+ end
64
+ end
65
+
66
+ def password_salt
67
+ self.encryptor_class.salt(self.stretches)
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,169 @@
1
+ module Devise
2
+ module Models
3
+ # Handles blocking a user access after a certain number of attempts.
4
+ # Lockable accepts two different strategies to unlock a user after it's
5
+ # blocked: email and time. The former will send an email to the user when
6
+ # the lock happens, containing a link to unlock its account. The second
7
+ # will unlock the user automatically after some configured time (ie 2.hours).
8
+ # It's also possible to setup lockable to use both email and time strategies.
9
+ #
10
+ # == Options
11
+ #
12
+ # Lockable adds the following options to +devise+:
13
+ #
14
+ # * +maximum_attempts+: how many attempts should be accepted before blocking the user.
15
+ # * +lock_strategy+: lock the user account by :failed_attempts or :none.
16
+ # * +unlock_strategy+: unlock the user account by :time, :email, :both or :none.
17
+ # * +unlock_in+: the time you want to lock the user after to lock happens. Only available when unlock_strategy is :time or :both.
18
+ # * +unlock_keys+: the keys you want to use when locking and unlocking an account
19
+ #
20
+ module Lockable
21
+ extend ActiveSupport::Concern
22
+
23
+ delegate :lock_strategy_enabled?, :unlock_strategy_enabled?, :to => "self.class"
24
+
25
+ # Lock a user setting its locked_at to actual time.
26
+ def lock_access!
27
+ self.locked_at = Time.now.utc
28
+
29
+ if unlock_strategy_enabled?(:email)
30
+ generate_unlock_token
31
+ send_unlock_instructions
32
+ end
33
+
34
+ save(:validate => false)
35
+ end
36
+
37
+ # Unlock a user by cleaning locket_at and failed_attempts.
38
+ def unlock_access!
39
+ self.locked_at = nil
40
+ self.failed_attempts = 0 if respond_to?(:failed_attempts=)
41
+ self.unlock_token = nil if respond_to?(:unlock_token=)
42
+ save(:validate => false)
43
+ end
44
+
45
+ # Verifies whether a user is locked or not.
46
+ def access_locked?
47
+ locked_at && !lock_expired?
48
+ end
49
+
50
+ # Send unlock instructions by email
51
+ def send_unlock_instructions
52
+ self.devise_mailer.unlock_instructions(self).deliver
53
+ end
54
+
55
+ # Resend the unlock instructions if the user is locked.
56
+ def resend_unlock_token
57
+ if_access_locked { send_unlock_instructions }
58
+ end
59
+
60
+ # Overwrites active_for_authentication? from Devise::Models::Activatable for locking purposes
61
+ # by verifying whether a user is active to sign in or not based on locked?
62
+ def active_for_authentication?
63
+ super && !access_locked?
64
+ end
65
+
66
+ # Overwrites invalid_message from Devise::Models::Authenticatable to define
67
+ # the correct reason for blocking the sign in.
68
+ def inactive_message
69
+ access_locked? ? :locked : super
70
+ end
71
+
72
+ # Overwrites valid_for_authentication? from Devise::Models::Authenticatable
73
+ # for verifying whether a user is allowed to sign in or not. If the user
74
+ # is locked, it should never be allowed.
75
+ def valid_for_authentication?
76
+ return super unless persisted? && lock_strategy_enabled?(:failed_attempts)
77
+
78
+ # Unlock the user if the lock is expired, no matter
79
+ # if the user can login or not (wrong password, etc)
80
+ unlock_access! if lock_expired?
81
+
82
+ if super
83
+ self.failed_attempts = 0
84
+ save(:validate => false)
85
+ true
86
+ else
87
+ self.failed_attempts ||= 0
88
+ self.failed_attempts += 1
89
+ if attempts_exceeded?
90
+ lock_access! unless access_locked?
91
+ return :locked
92
+ else
93
+ save(:validate => false)
94
+ end
95
+ false
96
+ end
97
+ end
98
+
99
+ protected
100
+
101
+ def attempts_exceeded?
102
+ self.failed_attempts > self.class.maximum_attempts
103
+ end
104
+
105
+ # Generates unlock token
106
+ def generate_unlock_token
107
+ self.unlock_token = self.class.unlock_token
108
+ end
109
+
110
+ # Tells if the lock is expired if :time unlock strategy is active
111
+ def lock_expired?
112
+ if unlock_strategy_enabled?(:time)
113
+ locked_at && locked_at < self.class.unlock_in.ago
114
+ else
115
+ false
116
+ end
117
+ end
118
+
119
+ # Checks whether the record is locked or not, yielding to the block
120
+ # if it's locked, otherwise adds an error to email.
121
+ def if_access_locked
122
+ if access_locked?
123
+ yield
124
+ else
125
+ self.errors.add(:email, :not_locked)
126
+ false
127
+ end
128
+ end
129
+
130
+ module ClassMethods
131
+ # Attempt to find a user by its email. If a record is found, send new
132
+ # unlock instructions to it. If not user is found, returns a new user
133
+ # with an email not found error.
134
+ # Options must contain the user email
135
+ def send_unlock_instructions(attributes={})
136
+ lockable = find_or_initialize_with_errors(unlock_keys, attributes, :not_found)
137
+ lockable.resend_unlock_token if lockable.persisted?
138
+ lockable
139
+ end
140
+
141
+ # Find a user by its unlock token and try to unlock it.
142
+ # If no user is found, returns a new user with an error.
143
+ # If the user is not locked, creates an error for the user
144
+ # Options must have the unlock_token
145
+ def unlock_access_by_token(unlock_token)
146
+ lockable = find_or_initialize_with_error_by(:unlock_token, unlock_token)
147
+ lockable.unlock_access! if lockable.persisted?
148
+ lockable
149
+ end
150
+
151
+ # Is the unlock enabled for the given unlock strategy?
152
+ def unlock_strategy_enabled?(strategy)
153
+ [:both, strategy].include?(self.unlock_strategy)
154
+ end
155
+
156
+ # Is the lock enabled for the given lock strategy?
157
+ def lock_strategy_enabled?(strategy)
158
+ self.lock_strategy == strategy
159
+ end
160
+
161
+ def unlock_token
162
+ Devise.friendly_token
163
+ end
164
+
165
+ Devise::Models.config(self, :maximum_attempts, :lock_strategy, :unlock_strategy, :unlock_in, :unlock_keys)
166
+ end
167
+ end
168
+ end
169
+ end