devise 0.1.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of devise might be problematic. Click here for more details.

Files changed (74) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.rdoc +220 -0
  3. data/Rakefile +45 -0
  4. data/TODO +37 -0
  5. data/app/controllers/confirmations_controller.rb +32 -0
  6. data/app/controllers/passwords_controller.rb +38 -0
  7. data/app/controllers/sessions_controller.rb +35 -0
  8. data/app/models/notifier.rb +47 -0
  9. data/app/views/confirmations/new.html.erb +16 -0
  10. data/app/views/notifier/confirmation_instructions.html.erb +5 -0
  11. data/app/views/notifier/reset_password_instructions.html.erb +8 -0
  12. data/app/views/passwords/edit.html.erb +20 -0
  13. data/app/views/passwords/new.html.erb +16 -0
  14. data/app/views/sessions/new.html.erb +23 -0
  15. data/config/locales/en.yml +16 -0
  16. data/init.rb +2 -0
  17. data/lib/devise.rb +48 -0
  18. data/lib/devise/active_record.rb +86 -0
  19. data/lib/devise/controllers/filters.rb +109 -0
  20. data/lib/devise/controllers/helpers.rb +91 -0
  21. data/lib/devise/controllers/url_helpers.rb +47 -0
  22. data/lib/devise/hooks/rememberable.rb +24 -0
  23. data/lib/devise/mapping.rb +95 -0
  24. data/lib/devise/migrations.rb +50 -0
  25. data/lib/devise/models/authenticable.rb +98 -0
  26. data/lib/devise/models/confirmable.rb +125 -0
  27. data/lib/devise/models/recoverable.rb +88 -0
  28. data/lib/devise/models/rememberable.rb +71 -0
  29. data/lib/devise/models/validatable.rb +36 -0
  30. data/lib/devise/routes.rb +95 -0
  31. data/lib/devise/strategies/authenticable.rb +45 -0
  32. data/lib/devise/strategies/base.rb +24 -0
  33. data/lib/devise/strategies/rememberable.rb +33 -0
  34. data/lib/devise/version.rb +3 -0
  35. data/lib/devise/warden.rb +64 -0
  36. data/test/active_record_test.rb +96 -0
  37. data/test/controllers/filters_test.rb +97 -0
  38. data/test/controllers/helpers_test.rb +40 -0
  39. data/test/controllers/url_helpers_test.rb +47 -0
  40. data/test/integration/authenticable_test.rb +191 -0
  41. data/test/integration/confirmable_test.rb +60 -0
  42. data/test/integration/recoverable_test.rb +131 -0
  43. data/test/integration/rememberable_test.rb +56 -0
  44. data/test/mailers/confirmation_instructions_test.rb +59 -0
  45. data/test/mailers/reset_password_instructions_test.rb +62 -0
  46. data/test/mapping_test.rb +71 -0
  47. data/test/models/authenticable_test.rb +138 -0
  48. data/test/models/confirmable_test.rb +206 -0
  49. data/test/models/recoverable_test.rb +145 -0
  50. data/test/models/rememberable_test.rb +68 -0
  51. data/test/models/validatable_test.rb +99 -0
  52. data/test/rails_app/app/controllers/admins_controller.rb +6 -0
  53. data/test/rails_app/app/controllers/application_controller.rb +10 -0
  54. data/test/rails_app/app/controllers/home_controller.rb +4 -0
  55. data/test/rails_app/app/controllers/users_controller.rb +7 -0
  56. data/test/rails_app/app/helpers/application_helper.rb +3 -0
  57. data/test/rails_app/app/models/account.rb +3 -0
  58. data/test/rails_app/app/models/admin.rb +3 -0
  59. data/test/rails_app/app/models/organizer.rb +3 -0
  60. data/test/rails_app/app/models/user.rb +3 -0
  61. data/test/rails_app/config/boot.rb +110 -0
  62. data/test/rails_app/config/environment.rb +41 -0
  63. data/test/rails_app/config/environments/development.rb +17 -0
  64. data/test/rails_app/config/environments/production.rb +28 -0
  65. data/test/rails_app/config/environments/test.rb +28 -0
  66. data/test/rails_app/config/initializers/new_rails_defaults.rb +21 -0
  67. data/test/rails_app/config/initializers/session_store.rb +15 -0
  68. data/test/rails_app/config/routes.rb +18 -0
  69. data/test/routes_test.rb +75 -0
  70. data/test/support/assertions_helper.rb +22 -0
  71. data/test/support/integration_tests_helper.rb +66 -0
  72. data/test/support/model_tests_helper.rb +40 -0
  73. data/test/test_helper.rb +39 -0
  74. metadata +136 -0
@@ -0,0 +1,47 @@
1
+ module Devise
2
+ module Controllers
3
+ # Create url helpers to be used with resource/scope configuration. Acts as
4
+ # proxies to the generated routes created by devise.
5
+ # Resource param can be a string or symbol, a class, or an instance object.
6
+ # Example using a :user resource:
7
+ #
8
+ # new_session_path(:user) => new_user_session_path
9
+ # session_path(:user) => user_session_path
10
+ # destroy_session_path(:user) => destroy_user_session_path
11
+ #
12
+ # new_password_path(:user) => new_user_password_path
13
+ # password_path(:user) => user_password_path
14
+ # edit_password_path(:user) => edit_user_password_path
15
+ #
16
+ # new_confirmation_path(:user) => new_user_confirmation_path
17
+ # confirmation_path(:user) => user_confirmation_path
18
+ module UrlHelpers
19
+
20
+ [:session, :password, :confirmation].each do |module_name|
21
+ [:path, :url].each do |path_or_url|
22
+ actions = [ nil, :new_ ]
23
+ actions << :edit_ if module_name == :password
24
+ actions << :destroy_ if module_name == :session
25
+
26
+ actions.each do |action|
27
+ class_eval <<-URL_HELPERS
28
+ def #{action}#{module_name}_#{path_or_url}(resource, *args)
29
+ resource = case resource
30
+ when Symbol, String
31
+ resource
32
+ when Class
33
+ resource.name.underscore
34
+ else
35
+ resource.class.name.underscore
36
+ end
37
+
38
+ send("#{action}\#{resource}_#{module_name}_#{path_or_url}", *args)
39
+ end
40
+ URL_HELPERS
41
+ end
42
+ end
43
+ end
44
+
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,24 @@
1
+ # After authenticate hook to verify if the user in the given scope asked to be
2
+ # remembered while he does not sign out. Generates a new remember token for
3
+ # that specific user and adds a cookie with this user info to sign in this user
4
+ # automatically without asking for credentials. Refer to rememberable strategy
5
+ # for more info.
6
+ Warden::Manager.after_authentication do |record, auth, options|
7
+ scope = options[:scope]
8
+ remember_me = auth.params[scope].try(:fetch, :remember_me, nil)
9
+
10
+ if Devise::TRUE_VALUES.include?(remember_me) && record.respond_to?(:remember_me!)
11
+ record.remember_me!
12
+ auth.cookies['remember_token'] = record.class.serialize_into_cookie(record)
13
+ end
14
+ end
15
+
16
+ # Before logout hook to forget the user in the given scope, only if rememberable
17
+ # is activated for this scope. Also clear remember token to ensure the user
18
+ # won't be remembered again.
19
+ Warden::Manager.before_logout do |record, auth, scope|
20
+ if record.respond_to?(:forget_me!)
21
+ record.forget_me!
22
+ auth.cookies['remember_token'] = nil
23
+ end
24
+ end
@@ -0,0 +1,95 @@
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.for #=> [:authenticable]
22
+ # # is the modules included in the class
23
+ #
24
+ class Mapping #:nodoc:
25
+ attr_reader :name, :as, :path_names
26
+
27
+ def initialize(name, options)
28
+ @as = (options[:as] || name).to_sym
29
+ @klass = (options[:class_name] || name.to_s.classify).to_s
30
+ @name = (options[:singular] || name.to_s.singularize).to_sym
31
+ @path_names = options[:path_names] || {}
32
+ setup_path_names
33
+ end
34
+
35
+ # Return modules for the mapping.
36
+ def for
37
+ @for ||= to.devise_modules
38
+ end
39
+
40
+ # Reload mapped class each time when cache_classes is false.
41
+ def to
42
+ return @to if @to
43
+ klass = @klass.constantize
44
+ @to = klass if Rails.configuration.cache_classes
45
+ klass
46
+ end
47
+
48
+ # Check if the respective controller has a module in the mapping class.
49
+ def allows?(controller)
50
+ self.for.include?(CONTROLLERS[controller.to_sym])
51
+ end
52
+
53
+ # Create magic predicates for verifying what module is activated by this map.
54
+ # Example:
55
+ #
56
+ # def confirmable?
57
+ # self.for.include?(:confirmable)
58
+ # end
59
+ #
60
+ ALL.each do |m|
61
+ class_eval <<-METHOD, __FILE__, __LINE__
62
+ def #{m}?
63
+ self.for.include?(:#{m})
64
+ end
65
+ METHOD
66
+ end
67
+
68
+ private
69
+
70
+ # Configure default path names, allowing the user overwrite defaults by
71
+ # passing a hash in :path_names.
72
+ def setup_path_names
73
+ [:sign_in, :sign_out, :password, :confirmation].each do |path_name|
74
+ @path_names[path_name] ||= path_name.to_s
75
+ end
76
+ end
77
+ end
78
+
79
+ mattr_accessor :mappings
80
+ self.mappings = {}
81
+
82
+ # Loop through all mappings looking for a map that matches with the requested
83
+ # path (ie /users/sign_in). The important part here is the key :users. If no
84
+ # map is found just returns nil.
85
+ def self.find_mapping_by_path(path)
86
+ route = path.split("/")[1]
87
+ return nil unless route
88
+
89
+ route = route.to_sym
90
+ mappings.each do |key, map|
91
+ return map if map.as == route.to_sym
92
+ end
93
+ nil
94
+ end
95
+ end
@@ -0,0 +1,50 @@
1
+ module Devise
2
+ # Helpers to migration:
3
+ #
4
+ # create_table :accounts do |t|
5
+ # t.authenticable
6
+ # t.confirmable
7
+ # t.recoverable
8
+ # t.rememberable
9
+ # t.timestamps
10
+ # end
11
+ #
12
+ # However this method does not add indexes. If you need them, here is the declaration:
13
+ #
14
+ # add_index "accounts", ["email"], :name => "email", :unique => true
15
+ # add_index "accounts", ["confirmation_token"], :name => "confirmation_token", :unique => true
16
+ # add_index "accounts", ["reset_password_token"], :name => "reset_password_token", :unique => true
17
+ #
18
+ module Migrations
19
+
20
+ # Creates email, encrypted_password and password_salt.
21
+ #
22
+ def authenticable
23
+ string :email, :limit => 100, :null => false
24
+ string :encrypted_password, :limit => 40, :null => false
25
+ string :password_salt, :limit => 20, :null => false
26
+ end
27
+
28
+ # Creates confirmation_token, confirmed_at and confirmation_sent_at.
29
+ #
30
+ def confirmable
31
+ string :confirmation_token, :limit => 20
32
+ datetime :confirmed_at
33
+ datetime :confirmation_sent_at
34
+ end
35
+
36
+ # Creates reset_password_token.
37
+ #
38
+ def recoverable
39
+ string :reset_password_token, :limit => 20
40
+ end
41
+
42
+ # Creates remember_token and remember_expires_at.
43
+ #
44
+ def rememberable
45
+ string :remember_token, :limit => 20
46
+ datetime :remember_expires_at
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,98 @@
1
+ require 'digest/sha1'
2
+
3
+ module Devise
4
+ module Models
5
+
6
+ # Authenticable Module, responsible for encrypting password and validating
7
+ # authenticity of a user while signing in.
8
+ #
9
+ # Configuration:
10
+ #
11
+ # You can overwrite configuration values by setting in globally in Devise,
12
+ # using devise method or overwriting the respective instance method.
13
+ #
14
+ # pepper: encryption key used for creating encrypted password. Each time
15
+ # password changes, it's gonna be encrypted again, and this key
16
+ # is added to the password and salt to create a secure hash.
17
+ # Always use `rake secret' to generate a new key.
18
+ #
19
+ # stretches: defines how many times the password will be encrypted.
20
+ #
21
+ # Examples:
22
+ #
23
+ # User.authenticate('email@test.com', 'password123') # returns authenticated user or nil
24
+ # User.find(1).valid_password?('password123') # returns true/false
25
+ #
26
+ module Authenticable
27
+ Devise.model_config(self, :pepper)
28
+ Devise.model_config(self, :stretches, 10)
29
+
30
+ def self.included(base)
31
+ base.class_eval do
32
+ extend ClassMethods
33
+
34
+ attr_reader :password
35
+ attr_accessor :password_confirmation
36
+ attr_accessible :email, :password, :password_confirmation
37
+ end
38
+ end
39
+
40
+ # Regenerates password salt and encrypted password each time password is
41
+ # setted.
42
+ def password=(new_password)
43
+ @password = new_password
44
+ self.password_salt = friendly_token
45
+ self.encrypted_password = password_digest(@password)
46
+ end
47
+
48
+ # Verifies whether an incoming_password (ie from login) is the user
49
+ # password.
50
+ def valid_password?(incoming_password)
51
+ password_digest(incoming_password) == encrypted_password
52
+ end
53
+
54
+ protected
55
+
56
+ # Gererates a default password digest based on salt, pepper and the
57
+ # incoming password.
58
+ def password_digest(password_to_digest)
59
+ digest = pepper
60
+ stretches.times { digest = secure_digest(password_salt, digest, password_to_digest, pepper) }
61
+ digest
62
+ end
63
+
64
+ # Generate a SHA1 digest joining args. Generated token is something like
65
+ #
66
+ # --arg1--arg2--arg3--argN--
67
+ def secure_digest(*tokens)
68
+ ::Digest::SHA1.hexdigest('--' << tokens.flatten.join('--') << '--')
69
+ end
70
+
71
+ # Generate a friendly string randomically to be used as token.
72
+ def friendly_token
73
+ ActiveSupport::SecureRandom.base64(15).tr('+/=', '-_ ').strip.delete("\n")
74
+ end
75
+
76
+ module ClassMethods
77
+
78
+ # Authenticate a user based on email and password. Returns the
79
+ # authenticated user if it's valid or nil.
80
+ # Attributes are :email and :password
81
+ def authenticate(attributes={})
82
+ authenticable = find_by_email(attributes[:email])
83
+ authenticable if authenticable.try(:valid_password?, attributes[:password])
84
+ end
85
+
86
+ # Attempt to find a user by it's email. If not user is found, returns a
87
+ # new user with an email not found error.
88
+ def find_or_initialize_with_error_by_email(email)
89
+ perishable = find_or_initialize_by_email(email)
90
+ if perishable.new_record?
91
+ perishable.errors.add(:email, :not_found, :default => 'not found')
92
+ end
93
+ perishable
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,125 @@
1
+ module Devise
2
+ module Models
3
+
4
+ # Confirmable is responsible to verify if an account is already confirmed to
5
+ # sign in, and to send emails with confirmation instructions.
6
+ # Confirmation instructions are sent to the user email after creating a
7
+ # record, after updating it's email and also when manually requested by
8
+ # a new confirmation instruction request.
9
+ # Whenever the user update it's email, his account is automatically unconfirmed,
10
+ # it means it won't be able to sign in again without confirming the account
11
+ # again through the email that was sent.
12
+ # Examples:
13
+ #
14
+ # User.find(1).confirm! # returns true unless it's already confirmed
15
+ # User.find(1).confirmed? # true/false
16
+ # User.find(1).send_confirmation_instructions # manually send instructions
17
+ # User.find(1).reset_confirmation! # reset confirmation status and send instructions
18
+ module Confirmable
19
+
20
+ def self.included(base)
21
+ base.class_eval do
22
+ extend ClassMethods
23
+
24
+ before_save :reset_confirmation, :if => :email_changed?
25
+ after_save :send_confirmation_instructions, :if => :email_changed?
26
+ end
27
+ end
28
+
29
+ # Confirm a user by setting it's confirmed_at to actual time. If the user
30
+ # is already confirmed, add en error to email field
31
+ def confirm!
32
+ unless_confirmed do
33
+ clear_confirmation_token
34
+ update_attribute(:confirmed_at, Time.now)
35
+ end
36
+ end
37
+
38
+ # Verifies whether a user is confirmed or not
39
+ def confirmed?
40
+ !new_record? && confirmed_at?
41
+ end
42
+
43
+ # Send confirmation instructions by email
44
+ def send_confirmation_instructions
45
+ ::Notifier.deliver_confirmation_instructions(self)
46
+ end
47
+
48
+ # Remove confirmation date and send confirmation instructions, to ensure
49
+ # after sending these instructions the user won't be able to sign in without
50
+ # confirming it's account
51
+ def reset_confirmation!
52
+ unless_confirmed do
53
+ reset_confirmation
54
+ save(false)
55
+ send_confirmation_instructions
56
+ end
57
+ end
58
+
59
+ protected
60
+
61
+ # Remove confirmation date from the user, ensuring after a user update
62
+ # it's email, it won't be able to sign in without confirming it.
63
+ def reset_confirmation
64
+ generate_confirmation_token
65
+ self.confirmed_at = nil
66
+ end
67
+
68
+ # Checks whether the record is confirmed or not, yielding to the block
69
+ # if it's already confirmed, otherwise adds an error to email.
70
+ def unless_confirmed
71
+ unless confirmed?
72
+ yield
73
+ else
74
+ errors.add(:email, :already_confirmed, :default => 'already confirmed')
75
+ false
76
+ end
77
+ end
78
+
79
+ # Generates a new random token for confirmation, and stores the time
80
+ # this token is being generated
81
+ def generate_confirmation_token
82
+ self.confirmation_token = friendly_token
83
+ self.confirmation_sent_at = Time.now.utc
84
+ end
85
+
86
+ # Resets the confirmation token with and save the record without
87
+ # validating.
88
+ def generate_confirmation_token!
89
+ generate_confirmation_token && save(false)
90
+ end
91
+
92
+ # Removes confirmation token
93
+ def clear_confirmation_token
94
+ self.confirmation_token = nil
95
+ end
96
+
97
+ module ClassMethods
98
+
99
+ # Attempt to find a user by it's email. If a record is found, send new
100
+ # confirmation instructions to it. If not user is found, returns a new user
101
+ # with an email not found error.
102
+ # Options must contain the user email
103
+ def send_confirmation_instructions(attributes={})
104
+ confirmable = find_or_initialize_with_error_by_email(attributes[:email])
105
+ confirmable.reset_confirmation! unless confirmable.new_record?
106
+ confirmable
107
+ end
108
+
109
+ # Find a user by it's confirmation token and try to confirm it.
110
+ # If no user is found, returns a new user with an error.
111
+ # If the user is already confirmed, create an error for the user
112
+ # Options must have the confirmation_token
113
+ def confirm!(attributes={})
114
+ confirmable = find_or_initialize_by_confirmation_token(attributes[:confirmation_token])
115
+ if confirmable.new_record?
116
+ confirmable.errors.add(:confirmation_token, :invalid)
117
+ else
118
+ confirmable.confirm!
119
+ end
120
+ confirmable
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,88 @@
1
+ module Devise
2
+ module Models
3
+
4
+ # Recoverable takes care of reseting the user password and send reset instructions
5
+ # Examples:
6
+ #
7
+ # # resets the user password and save the record, true if valid passwords are given, otherwise false
8
+ # User.find(1).reset_password!('password123', 'password123')
9
+ # # only resets the user password, without saving the record
10
+ # user = User.find(1)
11
+ # user.reset_password('password123', 'password123')
12
+ # # creates a new token and send it with instructions about how to reset the password
13
+ # User.find(1).send_reset_password_instructions
14
+ module Recoverable
15
+ def self.included(base)
16
+ base.class_eval do
17
+ extend ClassMethods
18
+ end
19
+ end
20
+
21
+ # Update password
22
+ def reset_password(new_password, new_password_confirmation)
23
+ self.password = new_password
24
+ self.password_confirmation = new_password_confirmation
25
+ end
26
+
27
+ # Update password saving the record and clearing token. Returns true if
28
+ # the passwords are valid and the record was saved, false otherwise.
29
+ def reset_password!(new_password, new_password_confirmation)
30
+ reset_password(new_password, new_password_confirmation)
31
+ clear_reset_password_token if valid?
32
+ save
33
+ end
34
+
35
+ # Resets reset password token and send reset password instructions by email
36
+ def send_reset_password_instructions
37
+ generate_reset_password_token!
38
+ ::Notifier.deliver_reset_password_instructions(self)
39
+ end
40
+
41
+ protected
42
+
43
+ # Generates a new random token for reset password
44
+ def generate_reset_password_token
45
+ self.reset_password_token = friendly_token
46
+ end
47
+
48
+ # Resets the reset password token with and save the record without
49
+ # validating
50
+ def generate_reset_password_token!
51
+ generate_reset_password_token && save(false)
52
+ end
53
+
54
+ # Removes reset_password token
55
+ def clear_reset_password_token
56
+ self.reset_password_token = nil
57
+ end
58
+
59
+ module ClassMethods
60
+
61
+ # Attempt to find a user by it's email. If a record is found, send new
62
+ # password instructions to it. If not user is found, returns a new user
63
+ # with an email not found error.
64
+ # Attributes must contain the user email
65
+ def send_reset_password_instructions(attributes={})
66
+ recoverable = find_or_initialize_with_error_by_email(attributes[:email])
67
+ recoverable.send_reset_password_instructions unless recoverable.new_record?
68
+ recoverable
69
+ end
70
+
71
+ # Attempt to find a user by it's reset_password_token to reset it's
72
+ # password. If a user is found, reset it's password and automatically
73
+ # try saving the record. If not user is found, returns a new user
74
+ # containing an error in reset_password_token attribute.
75
+ # Attributes must contain reset_password_token, password and confirmation
76
+ def reset_password!(attributes={})
77
+ recoverable = find_or_initialize_by_reset_password_token(attributes[:reset_password_token])
78
+ if recoverable.new_record?
79
+ recoverable.errors.add(:reset_password_token, :invalid)
80
+ else
81
+ recoverable.reset_password!(attributes[:password], attributes[:password_confirmation])
82
+ end
83
+ recoverable
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end