devise-jdguyot 1.2.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 (185) hide show
  1. data/.gitignore +10 -0
  2. data/CHANGELOG.rdoc +532 -0
  3. data/Gemfile +29 -0
  4. data/Gemfile.lock +152 -0
  5. data/MIT-LICENSE +20 -0
  6. data/README.rdoc +353 -0
  7. data/Rakefile +36 -0
  8. data/TODO +4 -0
  9. data/app/controllers/devise/confirmations_controller.rb +33 -0
  10. data/app/controllers/devise/omniauth_callbacks_controller.rb +26 -0
  11. data/app/controllers/devise/passwords_controller.rb +41 -0
  12. data/app/controllers/devise/registrations_controller.rb +110 -0
  13. data/app/controllers/devise/sessions_controller.rb +25 -0
  14. data/app/controllers/devise/unlocks_controller.rb +34 -0
  15. data/app/helpers/devise_helper.rb +19 -0
  16. data/app/mailers/devise/mailer.rb +88 -0
  17. data/app/views/devise/confirmations/new.html.erb +12 -0
  18. data/app/views/devise/mailer/confirmation_instructions.html.erb +5 -0
  19. data/app/views/devise/mailer/reset_password_instructions.html.erb +8 -0
  20. data/app/views/devise/mailer/unlock_instructions.html.erb +7 -0
  21. data/app/views/devise/passwords/edit.html.erb +16 -0
  22. data/app/views/devise/passwords/new.html.erb +12 -0
  23. data/app/views/devise/registrations/edit.html.erb +25 -0
  24. data/app/views/devise/registrations/new.html.erb +18 -0
  25. data/app/views/devise/sessions/new.html.erb +17 -0
  26. data/app/views/devise/shared/_links.erb +25 -0
  27. data/app/views/devise/unlocks/new.html.erb +12 -0
  28. data/config/locales/en.yml +46 -0
  29. data/devise.gemspec +25 -0
  30. data/lib/devise/controllers/helpers.rb +227 -0
  31. data/lib/devise/controllers/internal_helpers.rb +119 -0
  32. data/lib/devise/controllers/scoped_views.rb +33 -0
  33. data/lib/devise/controllers/url_helpers.rb +39 -0
  34. data/lib/devise/encryptors/authlogic_sha512.rb +19 -0
  35. data/lib/devise/encryptors/base.rb +20 -0
  36. data/lib/devise/encryptors/clearance_sha1.rb +17 -0
  37. data/lib/devise/encryptors/restful_authentication_sha1.rb +22 -0
  38. data/lib/devise/encryptors/sha1.rb +25 -0
  39. data/lib/devise/encryptors/sha512.rb +25 -0
  40. data/lib/devise/failure_app.rb +132 -0
  41. data/lib/devise/hooks/activatable.rb +11 -0
  42. data/lib/devise/hooks/forgetable.rb +12 -0
  43. data/lib/devise/hooks/rememberable.rb +48 -0
  44. data/lib/devise/hooks/timeoutable.rb +22 -0
  45. data/lib/devise/hooks/trackable.rb +9 -0
  46. data/lib/devise/mapping.rb +110 -0
  47. data/lib/devise/models/authenticatable.rb +146 -0
  48. data/lib/devise/models/confirmable.rb +160 -0
  49. data/lib/devise/models/database_authenticatable.rb +100 -0
  50. data/lib/devise/models/encryptable.rb +72 -0
  51. data/lib/devise/models/lockable.rb +169 -0
  52. data/lib/devise/models/omniauthable.rb +23 -0
  53. data/lib/devise/models/recoverable.rb +123 -0
  54. data/lib/devise/models/registerable.rb +21 -0
  55. data/lib/devise/models/rememberable.rb +130 -0
  56. data/lib/devise/models/timeoutable.rb +43 -0
  57. data/lib/devise/models/token_authenticatable.rb +72 -0
  58. data/lib/devise/models/trackable.rb +30 -0
  59. data/lib/devise/models/validatable.rb +65 -0
  60. data/lib/devise/models.rb +68 -0
  61. data/lib/devise/modules.rb +30 -0
  62. data/lib/devise/omniauth/config.rb +30 -0
  63. data/lib/devise/omniauth/test_helpers.rb +57 -0
  64. data/lib/devise/omniauth/url_helpers.rb +29 -0
  65. data/lib/devise/omniauth.rb +47 -0
  66. data/lib/devise/orm/active_record.rb +38 -0
  67. data/lib/devise/orm/mongoid.rb +31 -0
  68. data/lib/devise/path_checker.rb +18 -0
  69. data/lib/devise/rails/routes.rb +292 -0
  70. data/lib/devise/rails/warden_compat.rb +125 -0
  71. data/lib/devise/rails.rb +50 -0
  72. data/lib/devise/schema.rb +97 -0
  73. data/lib/devise/strategies/authenticatable.rb +150 -0
  74. data/lib/devise/strategies/base.rb +15 -0
  75. data/lib/devise/strategies/database_authenticatable.rb +21 -0
  76. data/lib/devise/strategies/rememberable.rb +51 -0
  77. data/lib/devise/strategies/token_authenticatable.rb +53 -0
  78. data/lib/devise/test_helpers.rb +100 -0
  79. data/lib/devise/version.rb +3 -0
  80. data/lib/devise.rb +381 -0
  81. data/lib/generators/active_record/devise_generator.rb +28 -0
  82. data/lib/generators/active_record/templates/migration.rb +31 -0
  83. data/lib/generators/devise/devise_generator.rb +17 -0
  84. data/lib/generators/devise/install_generator.rb +24 -0
  85. data/lib/generators/devise/orm_helpers.rb +23 -0
  86. data/lib/generators/devise/views_generator.rb +106 -0
  87. data/lib/generators/mongoid/devise_generator.rb +17 -0
  88. data/lib/generators/templates/README +25 -0
  89. data/lib/generators/templates/devise.rb +186 -0
  90. data/test/controllers/helpers_test.rb +237 -0
  91. data/test/controllers/internal_helpers_test.rb +72 -0
  92. data/test/controllers/url_helpers_test.rb +59 -0
  93. data/test/devise_test.rb +65 -0
  94. data/test/encryptors_test.rb +30 -0
  95. data/test/failure_app_test.rb +187 -0
  96. data/test/generators/active_record_generator_test.rb +24 -0
  97. data/test/generators/install_generator_test.rb +13 -0
  98. data/test/generators/mongoid_generator_test.rb +22 -0
  99. data/test/generators/views_generator_test.rb +35 -0
  100. data/test/indifferent_hash.rb +33 -0
  101. data/test/integration/authenticatable_test.rb +447 -0
  102. data/test/integration/confirmable_test.rb +104 -0
  103. data/test/integration/database_authenticatable_test.rb +60 -0
  104. data/test/integration/http_authenticatable_test.rb +74 -0
  105. data/test/integration/lockable_test.rb +109 -0
  106. data/test/integration/omniauthable_test.rb +107 -0
  107. data/test/integration/recoverable_test.rb +160 -0
  108. data/test/integration/registerable_test.rb +179 -0
  109. data/test/integration/rememberable_test.rb +180 -0
  110. data/test/integration/timeoutable_test.rb +89 -0
  111. data/test/integration/token_authenticatable_test.rb +99 -0
  112. data/test/integration/trackable_test.rb +64 -0
  113. data/test/mailers/confirmation_instructions_test.rb +84 -0
  114. data/test/mailers/reset_password_instructions_test.rb +72 -0
  115. data/test/mailers/unlock_instructions_test.rb +66 -0
  116. data/test/mapping_test.rb +119 -0
  117. data/test/models/confirmable_test.rb +221 -0
  118. data/test/models/database_authenticatable_test.rb +98 -0
  119. data/test/models/encryptable_test.rb +65 -0
  120. data/test/models/lockable_test.rb +204 -0
  121. data/test/models/recoverable_test.rb +190 -0
  122. data/test/models/rememberable_test.rb +279 -0
  123. data/test/models/timeoutable_test.rb +28 -0
  124. data/test/models/token_authenticatable_test.rb +37 -0
  125. data/test/models/trackable_test.rb +5 -0
  126. data/test/models/validatable_test.rb +99 -0
  127. data/test/models_test.rb +84 -0
  128. data/test/omniauth/url_helpers_test.rb +47 -0
  129. data/test/orm/active_record.rb +9 -0
  130. data/test/orm/mongoid.rb +11 -0
  131. data/test/rails_app/Rakefile +10 -0
  132. data/test/rails_app/app/active_record/admin.rb +6 -0
  133. data/test/rails_app/app/active_record/shim.rb +2 -0
  134. data/test/rails_app/app/active_record/user.rb +8 -0
  135. data/test/rails_app/app/controllers/admins/sessions_controller.rb +6 -0
  136. data/test/rails_app/app/controllers/admins_controller.rb +6 -0
  137. data/test/rails_app/app/controllers/application_controller.rb +8 -0
  138. data/test/rails_app/app/controllers/home_controller.rb +16 -0
  139. data/test/rails_app/app/controllers/publisher/registrations_controller.rb +2 -0
  140. data/test/rails_app/app/controllers/publisher/sessions_controller.rb +2 -0
  141. data/test/rails_app/app/controllers/users/omniauth_callbacks_controller.rb +7 -0
  142. data/test/rails_app/app/controllers/users_controller.rb +18 -0
  143. data/test/rails_app/app/helpers/application_helper.rb +3 -0
  144. data/test/rails_app/app/mongoid/admin.rb +9 -0
  145. data/test/rails_app/app/mongoid/shim.rb +29 -0
  146. data/test/rails_app/app/mongoid/user.rb +10 -0
  147. data/test/rails_app/app/views/admins/index.html.erb +1 -0
  148. data/test/rails_app/app/views/admins/sessions/new.html.erb +2 -0
  149. data/test/rails_app/app/views/home/index.html.erb +1 -0
  150. data/test/rails_app/app/views/home/private.html.erb +1 -0
  151. data/test/rails_app/app/views/layouts/application.html.erb +24 -0
  152. data/test/rails_app/app/views/users/index.html.erb +1 -0
  153. data/test/rails_app/app/views/users/mailer/confirmation_instructions.erb +1 -0
  154. data/test/rails_app/app/views/users/sessions/new.html.erb +1 -0
  155. data/test/rails_app/config/application.rb +40 -0
  156. data/test/rails_app/config/boot.rb +13 -0
  157. data/test/rails_app/config/database.yml +18 -0
  158. data/test/rails_app/config/environment.rb +5 -0
  159. data/test/rails_app/config/environments/development.rb +19 -0
  160. data/test/rails_app/config/environments/production.rb +33 -0
  161. data/test/rails_app/config/environments/test.rb +33 -0
  162. data/test/rails_app/config/initializers/backtrace_silencers.rb +7 -0
  163. data/test/rails_app/config/initializers/devise.rb +176 -0
  164. data/test/rails_app/config/initializers/inflections.rb +2 -0
  165. data/test/rails_app/config/initializers/secret_token.rb +2 -0
  166. data/test/rails_app/config/routes.rb +55 -0
  167. data/test/rails_app/config.ru +4 -0
  168. data/test/rails_app/db/migrate/20100401102949_create_tables.rb +31 -0
  169. data/test/rails_app/db/schema.rb +52 -0
  170. data/test/rails_app/lib/shared_admin.rb +9 -0
  171. data/test/rails_app/lib/shared_user.rb +23 -0
  172. data/test/rails_app/public/404.html +26 -0
  173. data/test/rails_app/public/422.html +26 -0
  174. data/test/rails_app/public/500.html +26 -0
  175. data/test/rails_app/public/favicon.ico +0 -0
  176. data/test/rails_app/script/rails +10 -0
  177. data/test/routes_test.rb +179 -0
  178. data/test/support/assertions.rb +24 -0
  179. data/test/support/helpers.rb +60 -0
  180. data/test/support/integration.rb +88 -0
  181. data/test/support/locale/en.yml +4 -0
  182. data/test/support/webrat/integrations/rails.rb +24 -0
  183. data/test/test_helper.rb +29 -0
  184. data/test/test_helpers_test.rb +118 -0
  185. metadata +388 -0
@@ -0,0 +1,11 @@
1
+ # Deny user access whenever his account is not active yet. All strategies that inherits from
2
+ # Devise::Strategies::Authenticatable and uses the validate already check if the user is active?
3
+ # before actively signing him in. However, we need this as hook to validate the user activity
4
+ # in each request and in case the user is using other strategies beside Devise ones.
5
+ Warden::Manager.after_set_user do |record, warden, options|
6
+ if record && record.respond_to?(:active?) && !record.active?
7
+ scope = options[:scope]
8
+ warden.logout(scope)
9
+ throw :warden, :scope => scope, :message => record.inactive_message
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ # Before logout hook to forget the user in the given scope, if it responds
2
+ # to forget_me! Also clear remember token to ensure the user won't be
3
+ # remembered again. Notice that we forget the user unless the record is frozen.
4
+ # This avoids forgetting deleted users.
5
+ Warden::Manager.before_logout do |record, warden, options|
6
+ if record.respond_to?(:forget_me!)
7
+ record.forget_me! unless record.frozen?
8
+ cookie_options = Rails.configuration.session_options.slice(:path, :domain, :secure)
9
+ cookie_options.merge!(record.cookie_options)
10
+ warden.cookies.delete("remember_#{options[:scope]}_token", cookie_options)
11
+ end
12
+ end
@@ -0,0 +1,48 @@
1
+ module Devise
2
+ module Hooks
3
+ # Overwrite success! in authentication strategies allowing users to be remembered.
4
+ # We choose to implement this as an strategy hook instead of a warden hook to allow a specific
5
+ # strategy (like token authenticatable or facebook authenticatable) to turn off remember_me?
6
+ # cookies.
7
+ module Rememberable #:nodoc:
8
+ def success!(resource)
9
+ super
10
+
11
+ if succeeded? && resource.respond_to?(:remember_me!) && remember_me?
12
+ resource.remember_me!(extend_remember_period?)
13
+ cookies.signed["remember_#{scope}_token"] = cookie_values(resource)
14
+ end
15
+ end
16
+
17
+ protected
18
+
19
+ def cookie_values(resource)
20
+ options = Rails.configuration.session_options.slice(:path, :domain, :secure)
21
+ options[:httponly] = true
22
+
23
+ options.merge!(resource.cookie_options)
24
+ options.merge!(
25
+ :value => resource.class.serialize_into_cookie(resource),
26
+ :expires => resource.remember_expires_at
27
+ )
28
+
29
+ options
30
+ end
31
+
32
+ def succeeded?
33
+ @result == :success
34
+ end
35
+
36
+ def extend_remember_period?
37
+ false
38
+ end
39
+
40
+ def remember_me?
41
+ valid_params? && Devise::TRUE_VALUES.include?(params_auth_hash[:remember_me])
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ Devise::Strategies::Authenticatable.send :include, Devise::Hooks::Rememberable
48
+
@@ -0,0 +1,22 @@
1
+ # Each time a record is set we check whether its session has already timed out
2
+ # or not, based on last request time. If so, the record is logged out and
3
+ # redirected to the sign in page. Also, each time the request comes and the
4
+ # record is set, we set the last request time inside it's scoped session to
5
+ # verify timeout in the following request.
6
+ Warden::Manager.after_set_user do |record, warden, options|
7
+ scope = options[:scope]
8
+
9
+ if record && record.respond_to?(:timedout?) && warden.authenticated?(scope)
10
+ last_request_at = warden.session(scope)['last_request_at']
11
+
12
+ if record.timedout?(last_request_at)
13
+ path_checker = Devise::PathChecker.new(warden.env, scope)
14
+ unless path_checker.signing_out?
15
+ warden.logout(scope)
16
+ throw :warden, :scope => scope, :message => :timeout
17
+ end
18
+ end
19
+
20
+ warden.session(scope)['last_request_at'] = Time.now.utc
21
+ end
22
+ end
@@ -0,0 +1,9 @@
1
+ # After each sign in, update sign in time, sign in count and sign in IP.
2
+ # This is only triggered when the user is explicitly set (with set_user)
3
+ # and on authentication. Retrieving the user from session (:fetch) does
4
+ # not trigger it.
5
+ Warden::Manager.after_set_user :except => :fetch do |record, warden, options|
6
+ if record.respond_to?(:update_tracked_fields!) && warden.authenticated?(options[:scope])
7
+ record.update_tracked_fields!(warden.request)
8
+ end
9
+ end
@@ -0,0 +1,110 @@
1
+ module Devise
2
+ # Responsible for handling devise mappings and routes configuration. Each
3
+ # resource configured by devise_for in routes is actually creating a mapping
4
+ # object. You can refer to devise_for in routes for usage options.
5
+ #
6
+ # The required value in devise_for is actually not used internally, but it's
7
+ # inflected to find all other values.
8
+ #
9
+ # map.devise_for :users
10
+ # mapping = Devise.mappings[:user]
11
+ #
12
+ # mapping.name #=> :user
13
+ # # is the scope used in controllers and warden, given in the route as :singular.
14
+ #
15
+ # mapping.as #=> "users"
16
+ # # how the mapping should be search in the path, given in the route as :as.
17
+ #
18
+ # mapping.to #=> User
19
+ # # is the class to be loaded from routes, given in the route as :class_name.
20
+ #
21
+ # mapping.modules #=> [:authenticatable]
22
+ # # is the modules included in the class
23
+ #
24
+ class Mapping #:nodoc:
25
+ attr_reader :singular, :scoped_path, :path, :controllers, :path_names, :class_name, :sign_out_via
26
+ alias :name :singular
27
+
28
+ # Receives an object and find a scope for it. If a scope cannot be found,
29
+ # raises an error. If a symbol is given, it's considered to be the scope.
30
+ def self.find_scope!(duck)
31
+ case duck
32
+ when String, Symbol
33
+ return duck
34
+ when Class
35
+ Devise.mappings.each_value { |m| return m.name if duck <= m.to }
36
+ else
37
+ Devise.mappings.each_value { |m| return m.name if duck.is_a?(m.to) }
38
+ end
39
+
40
+ raise "Could not find a valid mapping for #{duck.inspect}"
41
+ end
42
+
43
+ def self.find_by_path!(path, path_type=:fullpath)
44
+ Devise.mappings.each_value { |m| return m if path.include?(m.send(path_type)) }
45
+ raise "Could not find a valid mapping for path #{path.inspect}"
46
+ end
47
+
48
+ def initialize(name, options) #:nodoc:
49
+ @scoped_path = options[:as] ? "#{options[:as]}/#{name}" : name.to_s
50
+ @singular = (options[:singular] || @scoped_path.tr('/', '_').singularize).to_sym
51
+
52
+ @class_name = (options[:class_name] || name.to_s.classify).to_s
53
+ @ref = ActiveSupport::Dependencies.ref(@class_name)
54
+
55
+ @path = (options[:path] || name).to_s
56
+ @path_prefix = options[:path_prefix]
57
+
58
+ mod = options[:module] || "devise"
59
+ @controllers = Hash.new { |h,k| h[k] = "#{mod}/#{k}" }
60
+ @controllers.merge!(options[:controllers] || {})
61
+
62
+ @path_names = Hash.new { |h,k| h[k] = k.to_s }
63
+ @path_names.merge!(:registration => "")
64
+ @path_names.merge!(options[:path_names] || {})
65
+
66
+ @sign_out_via = options[:sign_out_via] || Devise.sign_out_via
67
+ end
68
+
69
+ # Return modules for the mapping.
70
+ def modules
71
+ @modules ||= to.respond_to?(:devise_modules) ? to.devise_modules : []
72
+ end
73
+
74
+ # Gives the class the mapping points to.
75
+ def to
76
+ @ref.get
77
+ end
78
+
79
+ def strategies
80
+ @strategies ||= STRATEGIES.values_at(*self.modules).compact.uniq.reverse
81
+ end
82
+
83
+ def routes
84
+ @routes ||= ROUTES.values_at(*self.modules).compact.uniq
85
+ end
86
+
87
+ def authenticatable?
88
+ @authenticatable ||= self.modules.any? { |m| m.to_s =~ /authenticatable/ }
89
+ end
90
+
91
+ def fullpath
92
+ "/#{@path_prefix}/#{@path}".squeeze("/")
93
+ end
94
+
95
+ # Create magic predicates for verifying what module is activated by this map.
96
+ # Example:
97
+ #
98
+ # def confirmable?
99
+ # self.modules.include?(:confirmable)
100
+ # end
101
+ #
102
+ def self.add_module(m)
103
+ class_eval <<-METHOD, __FILE__, __LINE__ + 1
104
+ def #{m}?
105
+ self.modules.include?(:#{m})
106
+ end
107
+ METHOD
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,146 @@
1
+ require 'devise/hooks/activatable'
2
+
3
+ module Devise
4
+ module Models
5
+ # Authenticatable module. Holds common settings for authentication.
6
+ #
7
+ # == Options
8
+ #
9
+ # Authenticatable adds the following options to devise_for:
10
+ #
11
+ # * +authentication_keys+: parameters used for authentication. By default [:email].
12
+ #
13
+ # * +request_keys+: parameters from the request object used for authentication.
14
+ # By specifying a symbol (which should be a request method), it will automatically be
15
+ # passed to find_for_authentication method and considered in your model lookup.
16
+ #
17
+ # For instance, if you set :request_keys to [:subdomain], :subdomain will be considered
18
+ # as key on authentication. This can also be a hash where the value is a boolean expliciting
19
+ # if the value is required or not.
20
+ #
21
+ # * +http_authenticatable+: if this model allows http authentication. By default true.
22
+ # It also accepts an array specifying the strategies that should allow http.
23
+ #
24
+ # * +params_authenticatable+: if this model allows authentication through request params. By default true.
25
+ # It also accepts an array specifying the strategies that should allow params authentication.
26
+ #
27
+ # == Active?
28
+ #
29
+ # Before authenticating an user and in each request, Devise checks if your model is active by
30
+ # calling model.active?. This method is overwriten by other devise modules. For instance,
31
+ # :confirmable overwrites .active? to only return true if your model was confirmed.
32
+ #
33
+ # You overwrite this method yourself, but if you do, don't forget to call super:
34
+ #
35
+ # def active?
36
+ # super && special_condition_is_valid?
37
+ # end
38
+ #
39
+ # Whenever active? returns false, Devise asks the reason why your model is inactive using
40
+ # the inactive_message method. You can overwrite it as well:
41
+ #
42
+ # def inactive_message
43
+ # special_condition_is_valid? ? super : :special_condition_is_not_valid
44
+ # end
45
+ #
46
+ module Authenticatable
47
+ extend ActiveSupport::Concern
48
+
49
+ included do
50
+ class_attribute :devise_modules, :instance_writer => false
51
+ self.devise_modules ||= []
52
+ end
53
+
54
+ # Check if the current object is valid for authentication. This method and
55
+ # find_for_authentication are the methods used in a Warden::Strategy to check
56
+ # if a model should be signed in or not.
57
+ #
58
+ # However, you should not overwrite this method, you should overwrite active? and
59
+ # inactive_message instead.
60
+ def valid_for_authentication?
61
+ if active?
62
+ block_given? ? yield : true
63
+ else
64
+ inactive_message
65
+ end
66
+ end
67
+
68
+ def active?
69
+ true
70
+ end
71
+
72
+ def inactive_message
73
+ :inactive
74
+ end
75
+
76
+ def authenticatable_salt
77
+ end
78
+
79
+ module ClassMethods
80
+ Devise::Models.config(self, :authentication_keys, :request_keys, :case_insensitive_keys, :http_authenticatable, :params_authenticatable)
81
+
82
+ def params_authenticatable?(strategy)
83
+ params_authenticatable.is_a?(Array) ?
84
+ params_authenticatable.include?(strategy) : params_authenticatable
85
+ end
86
+
87
+ def http_authenticatable?(strategy)
88
+ http_authenticatable.is_a?(Array) ?
89
+ http_authenticatable.include?(strategy) : http_authenticatable
90
+ end
91
+
92
+ # Find first record based on conditions given (ie by the sign in form).
93
+ # Overwrite to add customized conditions, create a join, or maybe use a
94
+ # namedscope to filter records while authenticating.
95
+ # Example:
96
+ #
97
+ # def self.find_for_authentication(conditions={})
98
+ # conditions[:active] = true
99
+ # super
100
+ # end
101
+ #
102
+ def find_for_authentication(conditions)
103
+ case_insensitive_keys.each { |k| conditions[k].try(:downcase!) }
104
+ to_adapter.find_first(conditions)
105
+ end
106
+
107
+ # Find an initialize a record setting an error if it can't be found.
108
+ def find_or_initialize_with_error_by(attribute, value, error=:invalid) #:nodoc:
109
+ find_or_initialize_with_errors([attribute], { attribute => value }, error)
110
+ end
111
+
112
+ # Find an initialize a group of attributes based on a list of required attributes.
113
+ def find_or_initialize_with_errors(required_attributes, attributes, error=:invalid) #:nodoc:
114
+ case_insensitive_keys.each { |k| attributes[k].try(:downcase!) }
115
+
116
+ attributes = attributes.slice(*required_attributes)
117
+ attributes.delete_if { |key, value| value.blank? }
118
+
119
+ if attributes.size == required_attributes.size
120
+ record = to_adapter.find_first(attributes)
121
+ end
122
+
123
+ unless record
124
+ record = new
125
+
126
+ required_attributes.each do |key|
127
+ value = attributes[key]
128
+ record.send("#{key}=", value)
129
+ record.errors.add(key, value.present? ? error : :blank)
130
+ end
131
+ end
132
+
133
+ record
134
+ end
135
+
136
+ # Generate a token by looping and ensuring does not already exist.
137
+ def generate_token(column)
138
+ loop do
139
+ token = Devise.friendly_token
140
+ break token unless to_adapter.find_first({ column => token })
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,160 @@
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
+ #
18
+ # == Examples
19
+ #
20
+ # User.find(1).confirm! # returns true unless it's already confirmed
21
+ # User.find(1).confirmed? # true/false
22
+ # User.find(1).send_confirmation_instructions # manually send instructions
23
+ #
24
+ module Confirmable
25
+ extend ActiveSupport::Concern
26
+
27
+ included do
28
+ before_create :generate_confirmation_token, :if => :confirmation_required?
29
+ after_create :send_confirmation_instructions, :if => :confirmation_required?
30
+ end
31
+
32
+ # Confirm a user by setting it's confirmed_at to actual time. If the user
33
+ # is already confirmed, add en error to email field
34
+ def confirm!
35
+ unless_confirmed do
36
+ self.confirmation_token = nil
37
+ self.confirmed_at = Time.now
38
+ save(:validate => false)
39
+ end
40
+ end
41
+
42
+ # Verifies whether a user is confirmed or not
43
+ def confirmed?
44
+ !!confirmed_at
45
+ end
46
+
47
+ # Send confirmation instructions by email
48
+ def send_confirmation_instructions
49
+ generate_confirmation_token! if self.confirmation_token.nil?
50
+ ::Devise.mailer.confirmation_instructions(self).deliver
51
+ end
52
+
53
+ # Resend confirmation token. This method does not need to generate a new token.
54
+ def resend_confirmation_token
55
+ unless_confirmed { send_confirmation_instructions }
56
+ end
57
+
58
+ # Overwrites active? from Devise::Models::Activatable for confirmation
59
+ # by verifying whether an user is active to sign in or not. If the user
60
+ # is already confirmed, it should never be blocked. Otherwise we need to
61
+ # calculate if the confirm time has not expired for this user.
62
+ def active?
63
+ super && (!confirmation_required? || confirmed? || confirmation_period_valid?)
64
+ end
65
+
66
+ # The message to be shown if the account is inactive.
67
+ def inactive_message
68
+ !confirmed? ? :unconfirmed : super
69
+ end
70
+
71
+ # If you don't want confirmation to be sent on create, neither a code
72
+ # to be generated, call skip_confirmation!
73
+ def skip_confirmation!
74
+ self.confirmed_at = Time.now
75
+ end
76
+
77
+ protected
78
+
79
+ # Callback to overwrite if confirmation is required or not.
80
+ def confirmation_required?
81
+ !confirmed?
82
+ end
83
+
84
+ # Checks if the confirmation for the user is within the limit time.
85
+ # We do this by calculating if the difference between today and the
86
+ # confirmation sent date does not exceed the confirm in time configured.
87
+ # Confirm_within is a model configuration, must always be an integer value.
88
+ #
89
+ # Example:
90
+ #
91
+ # # confirm_within = 1.day and confirmation_sent_at = today
92
+ # confirmation_period_valid? # returns true
93
+ #
94
+ # # confirm_within = 5.days and confirmation_sent_at = 4.days.ago
95
+ # confirmation_period_valid? # returns true
96
+ #
97
+ # # confirm_within = 5.days and confirmation_sent_at = 5.days.ago
98
+ # confirmation_period_valid? # returns false
99
+ #
100
+ # # confirm_within = 0.days
101
+ # confirmation_period_valid? # will always return false
102
+ #
103
+ def confirmation_period_valid?
104
+ confirmation_sent_at && confirmation_sent_at.utc >= self.class.confirm_within.ago
105
+ end
106
+
107
+ # Checks whether the record is confirmed or not, yielding to the block
108
+ # if it's already confirmed, otherwise adds an error to email.
109
+ def unless_confirmed
110
+ unless confirmed?
111
+ yield
112
+ else
113
+ self.errors.add(:email, :already_confirmed)
114
+ false
115
+ end
116
+ end
117
+
118
+ # Generates a new random token for confirmation, and stores the time
119
+ # this token is being generated
120
+ def generate_confirmation_token
121
+ self.confirmed_at = nil
122
+ self.confirmation_token = self.class.confirmation_token
123
+ self.confirmation_sent_at = Time.now.utc
124
+ end
125
+
126
+ def generate_confirmation_token!
127
+ generate_confirmation_token && save(:validate => false)
128
+ end
129
+
130
+ module ClassMethods
131
+ # Attempt to find a user by it's email. If a record is found, send new
132
+ # confirmation 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_confirmation_instructions(attributes={})
136
+ confirmable = find_or_initialize_with_error_by(:email, attributes[:email], :not_found)
137
+ confirmable.resend_confirmation_token if confirmable.persisted?
138
+ confirmable
139
+ end
140
+
141
+ # Find a user by it's confirmation token and try to confirm it.
142
+ # If no user is found, returns a new user with an error.
143
+ # If the user is already confirmed, create an error for the user
144
+ # Options must have the confirmation_token
145
+ def confirm_by_token(confirmation_token)
146
+ confirmable = find_or_initialize_with_error_by(:confirmation_token, confirmation_token)
147
+ confirmable.confirm! if confirmable.persisted?
148
+ confirmable
149
+ end
150
+
151
+ # Generate a token checking if one does not already exist in the database.
152
+ def confirmation_token
153
+ generate_token(:confirmation_token)
154
+ end
155
+
156
+ Devise::Models.config(self, :confirm_within)
157
+ end
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,100 @@
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
+ # * +stretches+: the cost given to bcrypt.
14
+ #
15
+ # == Examples
16
+ #
17
+ # User.find(1).valid_password?('password123') # returns true/false
18
+ #
19
+ module DatabaseAuthenticatable
20
+ extend ActiveSupport::Concern
21
+
22
+ included do
23
+ attr_reader :password, :current_password
24
+ attr_accessor :password_confirmation
25
+ before_save :downcase_keys
26
+ end
27
+
28
+ # Generates password encryption based on the given value.
29
+ def password=(new_password)
30
+ @password = new_password
31
+ self.encrypted_password = password_digest(@password) if @password.present?
32
+ end
33
+
34
+ # Verifies whether an incoming_password (ie from sign in) is the user password.
35
+ def valid_password?(password)
36
+ ::BCrypt::Password.new(self.encrypted_password) == "#{password}#{self.class.pepper}"
37
+ end
38
+
39
+ # Set password and password confirmation to nil
40
+ def clean_up_passwords
41
+ self.password = self.password_confirmation = nil
42
+ end
43
+
44
+ # Update record attributes when :current_password matches, otherwise returns
45
+ # error on :current_password. It also automatically rejects :password and
46
+ # :password_confirmation if they are blank.
47
+ def update_with_password(params={})
48
+ current_password = params.delete(:current_password)
49
+
50
+ if params[:password].blank?
51
+ params.delete(:password)
52
+ params.delete(:password_confirmation) if params[:password_confirmation].blank?
53
+ end
54
+
55
+ result = if valid_password?(current_password)
56
+ update_attributes(params)
57
+ else
58
+ self.errors.add(:current_password, current_password.blank? ? :blank : :invalid)
59
+ self.attributes = params
60
+ false
61
+ end
62
+
63
+ clean_up_passwords
64
+ result
65
+ end
66
+
67
+ def after_database_authentication
68
+ end
69
+
70
+ # A reliable way to expose the salt regardless of the implementation.
71
+ def authenticatable_salt
72
+ self.encrypted_password[0,29] if self.encrypted_password
73
+ end
74
+
75
+ protected
76
+
77
+ # Downcase case-insensitive keys
78
+ def downcase_keys
79
+ self.class.case_insensitive_keys.each { |k| self[k].try(:downcase!) }
80
+ end
81
+
82
+ # Digests the password using bcrypt.
83
+ def password_digest(password)
84
+ ::BCrypt::Password.create("#{password}#{self.class.pepper}", :cost => self.class.stretches).to_s
85
+ end
86
+
87
+ module ClassMethods
88
+ Devise::Models.config(self, :pepper, :stretches)
89
+
90
+ # We assume this method already gets the sanitized values from the
91
+ # DatabaseAuthenticatable strategy. If you are using this method on
92
+ # your own, be sure to sanitize the conditions hash to only include
93
+ # the proper fields.
94
+ def find_for_database_authentication(conditions)
95
+ find_for_authentication(conditions)
96
+ end
97
+ end
98
+ end
99
+ end
100
+ 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
+ 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