-
Notifications
You must be signed in to change notification settings - Fork 92
/
user.rb
166 lines (122 loc) · 4.74 KB
/
user.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
require 'digest/sha1'
module Hobo
module User
@user_models = []
def self.default_user_model
@user_models.first._?.constantize
end
AUTHENTICATION_FIELDS = [:salt, :crypted_password, :remember_token, :remember_token_expires_at]
# Extend the base class with AuthenticatedUser functionality
# This includes:
# - plaintext password during login and encrypted password in the database
# - plaintext password validation
# - login token for rembering a login during multiple browser sessions
def self.included(base)
@user_models << base.name
base.extend(ClassMethods)
base.class_eval do
fields do
crypted_password :string, :limit => 40
salt :string, :limit => 40
remember_token :string
remember_token_expires_at :datetime
end
validates_confirmation_of :password, :if => :password_required?
# Virtual attributes for setting and changing the password
attr_accessor :current_password, :password, :password_confirmation, :type => :password
validate :validate_current_password_when_changing_password
before_save :encrypt_password
never_show *AUTHENTICATION_FIELDS
attr_protected *AUTHENTICATION_FIELDS
password_validations
end
end
# Additional classmethods for authentication
module ClassMethods
# Validation of the plaintext password
def password_validations
validates_length_of :password, :within => 4..40, :if => :password_required?
end
def login_attribute=(attr, validate=true)
@login_attribute = attr = attr.to_sym
unless attr == :login
alias_attribute(:login, attr)
declare_attr_type(:login, attr_type(attr)) if table_exists? # this breaks if the table doesn't exist
end
if validate
validates_length_of attr, :within => 3..100
validates_uniqueness_of attr, :case_sensitive => false
end
end
attr_reader :login_attribute
# Authenticates a user by their login name and unencrypted password. Returns the user or nil.
def authenticate(login, password)
u = find(:first, :conditions => ["#{@login_attribute} = ?", login]) # need to get the salt
if u && u.authenticated?(password)
if u.respond_to?(:last_login_at) || u.respond_to?(:login_count)
u.last_login_at = Time.now if u.respond_to?(:last_login_at)
u.login_count = (u.login_count.to_i + 1) if u.respond_to?(:login_count)
u.save
end
u
else
nil
end
end
# Encrypts some data with the salt.
def encrypt(password, salt)
Digest::SHA1.hexdigest("--#{salt}--#{password}--")
end
end
def account_active?
!self.class.has_lifecycle? || !'active'.in?(self.class::Lifecycle.state_names) || state == 'active'
end
# Encrypts the password with the user salt
def encrypt(password)
self.class.encrypt(password, salt)
end
# Check if the encrypted passwords match
def authenticated?(password)
account_active? && crypted_password == encrypt(password)
end
# Do we still need to remember the login token, or has it expired?
def remember_token?
remember_token_expires_at && Time.now.utc < remember_token_expires_at
end
# These create and unset the fields required for remembering users between browser closes
def remember_me
self.remember_token_expires_at = 2.weeks.from_now.utc
self.remember_token = encrypt("#{login}--#{remember_token_expires_at}")
save(false)
end
# Expire the login token, resulting in a forced login next time.
def forget_me
self.remember_token_expires_at = nil
self.remember_token = nil
save(false)
end
def guest?
false
end
def signed_up?
true
end
def changing_password?
crypted_password? && (password || password_confirmation) && !lifecycle.valid_key?
end
protected
# Before filter that encrypts the password before having it stored in the database.
def encrypt_password
return if password.blank?
self.salt = Digest::SHA1.hexdigest("--#{Time.now.to_s}--#{login}--") if salt.blank?
self.crypted_password = encrypt(password)
end
# Is a password required for login? (or do we have an empty password?)
def password_required?
(crypted_password.blank? && password != nil) || !password.blank? || changing_password?
end
def validate_current_password_when_changing_password
changing_password? && !authenticated?(current_password) and errors.add :current_password, "is not correct"
end
end
end