Changesets can be listed by changeset number.
The Git repository is here.
- Revision:
- 11
- Log:
Applied acts_as_authenticated patch described here:
http://www.ruby-forum.com/topic/71303Archive obtained from:
http://clanwhiskey.net/bobfunk/rforum_modification.zipNote that the patch is for RForum 0.2 but it is being applied to a
slightly newer version. The database migration step has had to be
renamed as a result, to 008_... instead of 005_..., since 005 to
007 are now used.
- Author:
- adh
- Date:
- Sat Jul 22 20:23:23 +0100 2006
- Size:
- 7173 Bytes
1 | require 'digest/sha1' |
2 | require_dependency 'user_permissions' |
3 | |
4 | class User < ActiveRecord::Base |
5 | has_many :post_votes, :foreign_key => 'voter_id' |
6 | has_many :topic_subscriptions, :dependent => true |
7 | has_many :posts |
8 | has_many :topic_reads, :dependent => true |
9 | serialize :additional_information, Hash |
10 | |
11 | def self.inheritance_column() 'role' end |
12 | |
13 | include ErrorRaising, RForum::Localization |
14 | |
15 | include UserPermissions |
16 | |
17 | attr_accessor :old_password, :new_password, :retyped_password |
18 | |
19 | # CLASS METHODS |
20 | |
21 | # Find a user by user name and password. |
22 | def self.find_by_login(name, unencrypted_password) |
23 | raise ArgumentError if name.nil? |
24 | raise ArgumentError if unencrypted_password.nil? |
25 | # find_first ["name='%s' AND password='%s'", name, encrypt(password)] |
26 | u = find_by_name(name) |
27 | u && u.authenticated?(unencrypted_password) ? u : nil |
28 | end |
29 | |
30 | def self.find_by_token(id, token) |
31 | raise ArgumentError if id.nil? |
32 | raise ArgumentError if token.nil? |
33 | user = find_first ["id='%s' AND security_token='%s'", id, token] |
34 | if user.nil? or user.token_expired? |
35 | return nil |
36 | else |
37 | return user |
38 | end |
39 | end |
40 | |
41 | # Encrypts some data with the salt. |
42 | def self.encrypt(password, salt) |
43 | Digest::SHA1.hexdigest("--#{salt}--#{password}--") |
44 | end |
45 | |
46 | # CALLBACKS |
47 | |
48 | def after_initialize |
49 | reset_password_fields |
50 | end |
51 | |
52 | def before_validation_on_create |
53 | %w(name email name firstname surname).each do |attr| |
54 | self[attr] = self[attr].to_s.strip.squeeze(' ').chomp |
55 | end |
56 | |
57 | self.name.downcase! |
58 | self.email.downcase! |
59 | self.role = 'User' |
60 | @unencrypted_password = makepass |
61 | encrypt_stored_password |
62 | end |
63 | |
64 | def validate_on_create |
65 | # Nickname cannot be changed, so we only need to validate it on create |
66 | unless self.name =~ /^[a-z0-9\-]{3,15}$/i |
67 | errors.add 'name', :user_name_invalid |
68 | end |
69 | # Already existing nick is not allowed |
70 | errors.add_on_duplicate 'name', :user_name_duplicate |
71 | end |
72 | |
73 | def validate |
74 | errors.add 'email', :user_email_invalid unless valid_email?(self.email) |
75 | errors.add 'firstname', :user_firstname_invalid unless self.firstname =~ /^.{2,20}$/i |
76 | errors.add 'surname', :user_surname_invalid unless self.surname =~ /^.{2,20}$/i |
77 | errors.add_on_duplicate 'email', :user_email_duplicate |
78 | |
79 | if (@new_password or self.password.nil?) and password.size < 3 |
80 | errors.add 'new_password', :user_password_invalid |
81 | end |
82 | |
83 | # nick cannot be changed |
84 | unless self.new_record? |
85 | old_record = User.find(self.id) |
86 | errors.add 'name', l(:user_cannot_change_nick) unless old_record.name == self.name |
87 | end |
88 | end |
89 | |
90 | # NORMAL METHODS |
91 | |
92 | def guest? |
93 | false |
94 | end |
95 | |
96 | # Generates a temporary security token that can be passed in a URL to |
97 | # authenticate this user without a password. Typical use - to put that |
98 | # URL in an email sent to the user who forgotten his password and |
99 | # needs to reset it. |
100 | # |
101 | # This method will return an already existing token, if it is not |
102 | # older than half the maximum token lifetime. This is to avoid |
103 | # situations where user somehow regenerates the token (by clicking on |
104 | # the same link twice, or by browser back button), and then tries to |
105 | # use the first token. I don't want to create a new table for |
106 | # temporary security tokens. |
107 | def generate_security_token |
108 | if self.security_token.nil? or self.token_expiry.nil? or |
109 | (Time.now.to_i + token_lifetime / 2) >= self.token_expiry.to_i |
110 | return new_security_token |
111 | else |
112 | return self.security_token |
113 | end |
114 | end |
115 | |
116 | def authenticated?(unencrypted_password) |
117 | password == encrypt(unencrypted_password) |
118 | end |
119 | |
120 | def encrypt_password(new_password) |
121 | self['password'] = self.class.encrypt(new_password, salt) |
122 | end |
123 | |
124 | # Encrypts the password with the user salt |
125 | def encrypt(unencrypted_password) |
126 | self.class.encrypt(unencrypted_password, salt) |
127 | end |
128 | |
129 | def guest_email |
130 | nil |
131 | end |
132 | |
133 | def guest_name |
134 | nil |
135 | end |
136 | |
137 | # Create a random password. |
138 | # TODO: rewrite |
139 | def makepass |
140 | chars = ("a".."z").to_a + (1..9).to_a |
141 | chars = chars.sort_by { rand } |
142 | s = chars[0..7].to_s |
143 | end |
144 | |
145 | # TODO test me |
146 | def last_read_time(topic) |
147 | t = TopicRead.find(:first, :conditions => "user_id = #{self.id} AND topic_id = #{topic.id}") |
148 | if t |
149 | t.updated_at |
150 | else |
151 | nil |
152 | end |
153 | end |
154 | |
155 | # Gets the time the topics were last read by this user |
156 | # TODO test me |
157 | def topic_read_times |
158 | topic_read_times_hash = Hash.new |
159 | reads = self.topic_reads.find_all.each { |read| |
160 | topic_read_times_hash[read.topic_id] = read.updated_at |
161 | } |
162 | topic_read_times_hash |
163 | end |
164 | |
165 | # Enter a new vote or update an old vote. |
166 | # TODO: test me |
167 | def vote_post(post, value) |
168 | raise ArgumentError unless post.is_a?(Post) |
169 | transaction do |
170 | vote = find_all_in_post_votes("post_id = #{post.id}").first || PostVote.new |
171 | vote.voter = self |
172 | vote.post = post |
173 | vote.value = value |
174 | vote.save |
175 | end |
176 | end |
177 | |
178 | # Update the last time a topic was read. |
179 | def update_read_time(topic) |
180 | topic_read = self.topic_reads.find_all("topic_id = #{topic.id}") |
181 | |
182 | if topic_read.empty? |
183 | # first time this topic is read by this user |
184 | topic_read = TopicRead.new('topic_id' => topic.id, 'user_id' => self.id) |
185 | else |
186 | topic_read = topic_read[0] |
187 | end |
188 | topic_read.save |
189 | return topic_read |
190 | end |
191 | |
192 | def reset_password_fields |
193 | @old_password = @new_password = @retyped_password = '' |
194 | end |
195 | |
196 | # Subscribe to a topic to get notifications on new posts. |
197 | def subscribe_topic(topic) |
198 | subscription = TopicSubscription.new |
199 | subscription.user = self |
200 | subscription.topic = topic |
201 | |
202 | if TopicSubscription.count("topic_id = #{topic.id} AND user_id = #{self.id}") > 0 |
203 | # subscription already exists |
204 | return false |
205 | else |
206 | subscription.save |
207 | return true |
208 | end |
209 | end |
210 | |
211 | def unsubscribe_topic(topic) |
212 | TopicSubscription.find_first("topic_id = #{topic.id} AND user_id = #{self.id}").destroy |
213 | end |
214 | |
215 | # Unencrypted password field is needed in the controller to send an email notification. |
216 | # It is populated on creation, and not written to the database, so only a freshly created |
217 | # user instance has it set. |
218 | def tell_and_forget_unencrypted_password |
219 | raise "Unencrypted password not available" if @unencrypted_password.nil? |
220 | p = @unencrypted_password |
221 | @unencrypted_password = nil |
222 | return p |
223 | end |
224 | |
225 | def admin? |
226 | false |
227 | end |
228 | |
229 | def token_expired? |
230 | self.security_token and self.token_expiry and (Time.now > self.token_expiry) |
231 | end |
232 | |
233 | def get_display_name |
234 | "#{self.firstname} #{self.surname}" |
235 | end |
236 | |
237 | private |
238 | def new_security_token |
239 | self['security_token'] = |
240 | Digest::SHA1.hexdigest(self['password'] + (Time.now.to_i.to_s) + rand.to_s) |
241 | self.token_expiry = Time.at(Time.now.to_i + token_lifetime) |
242 | self.save |
243 | return self['security_token'] |
244 | end |
245 | |
246 | def token_lifetime |
247 | RForum::CONFIG[:security_token_life_hours] * 60 * 60 |
248 | end |
249 | |
250 | def encrypt_stored_password |
251 | return if @unencrypted_password.blank? |
252 | self.salt = Digest::SHA1.hexdigest("--#{Time.now.to_s}--#{name}--") if new_record? |
253 | self.password = encrypt(@unencrypted_password) |
254 | end |
255 | end |
256 |