##// END OF EJS Templates
Use validation callback....
Jean-Philippe Lang -
r8173:f5987a5ca066
parent child
Show More
@@ -1,646 +1,647
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require "digest/sha1"
19 19
20 20 class User < Principal
21 21 include Redmine::SafeAttributes
22 22
23 23 # Account statuses
24 24 STATUS_ANONYMOUS = 0
25 25 STATUS_ACTIVE = 1
26 26 STATUS_REGISTERED = 2
27 27 STATUS_LOCKED = 3
28 28
29 29 # Different ways of displaying/sorting users
30 30 USER_FORMATS = {
31 31 :firstname_lastname => {:string => '#{firstname} #{lastname}', :order => %w(firstname lastname id)},
32 32 :firstname => {:string => '#{firstname}', :order => %w(firstname id)},
33 33 :lastname_firstname => {:string => '#{lastname} #{firstname}', :order => %w(lastname firstname id)},
34 34 :lastname_coma_firstname => {:string => '#{lastname}, #{firstname}', :order => %w(lastname firstname id)},
35 35 :username => {:string => '#{login}', :order => %w(login id)},
36 36 }
37 37
38 38 MAIL_NOTIFICATION_OPTIONS = [
39 39 ['all', :label_user_mail_option_all],
40 40 ['selected', :label_user_mail_option_selected],
41 41 ['only_my_events', :label_user_mail_option_only_my_events],
42 42 ['only_assigned', :label_user_mail_option_only_assigned],
43 43 ['only_owner', :label_user_mail_option_only_owner],
44 44 ['none', :label_user_mail_option_none]
45 45 ]
46 46
47 47 has_and_belongs_to_many :groups, :after_add => Proc.new {|user, group| group.user_added(user)},
48 48 :after_remove => Proc.new {|user, group| group.user_removed(user)}
49 49 has_many :changesets, :dependent => :nullify
50 50 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
51 51 has_one :rss_token, :class_name => 'Token', :conditions => "action='feeds'"
52 52 has_one :api_token, :class_name => 'Token', :conditions => "action='api'"
53 53 belongs_to :auth_source
54 54
55 55 # Active non-anonymous users scope
56 56 named_scope :active, :conditions => "#{User.table_name}.status = #{STATUS_ACTIVE}"
57 57 named_scope :logged, :conditions => "#{User.table_name}.status <> #{STATUS_ANONYMOUS}"
58 58 named_scope :status, lambda {|arg| arg.blank? ? {} : {:conditions => {:status => arg.to_i}} }
59 59 named_scope :like, lambda {|arg|
60 60 if arg.blank?
61 61 {}
62 62 else
63 63 pattern = "%#{arg.to_s.strip.downcase}%"
64 64 {:conditions => ["LOWER(login) LIKE :p OR LOWER(firstname) LIKE :p OR LOWER(lastname) LIKE :p OR LOWER(mail) LIKE :p", {:p => pattern}]}
65 65 end
66 66 }
67 67
68 68 acts_as_customizable
69 69
70 70 attr_accessor :password, :password_confirmation
71 71 attr_accessor :last_before_login_on
72 72 # Prevents unauthorized assignments
73 73 attr_protected :login, :admin, :password, :password_confirmation, :hashed_password
74 74
75 75 validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
76 76 validates_uniqueness_of :login, :if => Proc.new { |user| !user.login.blank? }, :case_sensitive => false
77 77 validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }, :case_sensitive => false
78 78 # Login must contain lettres, numbers, underscores only
79 79 validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
80 80 validates_length_of :login, :maximum => 30
81 81 validates_length_of :firstname, :lastname, :maximum => 30
82 82 validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_blank => true
83 83 validates_length_of :mail, :maximum => 60, :allow_nil => true
84 84 validates_confirmation_of :password, :allow_nil => true
85 85 validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true
86 86 validate :validate_password_length
87 87
88 88 before_create :set_mail_notification
89 89 before_save :update_hashed_password
90 90 before_destroy :remove_references_before_destroy
91 91
92 92 named_scope :in_group, lambda {|group|
93 93 group_id = group.is_a?(Group) ? group.id : group.to_i
94 94 { :conditions => ["#{User.table_name}.id IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id] }
95 95 }
96 96 named_scope :not_in_group, lambda {|group|
97 97 group_id = group.is_a?(Group) ? group.id : group.to_i
98 98 { :conditions => ["#{User.table_name}.id NOT IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id] }
99 99 }
100 100
101 101 def set_mail_notification
102 102 self.mail_notification = Setting.default_notification_option if self.mail_notification.blank?
103 103 true
104 104 end
105 105
106 106 def update_hashed_password
107 107 # update hashed_password if password was set
108 108 if self.password && self.auth_source_id.blank?
109 109 salt_password(password)
110 110 end
111 111 end
112 112
113 113 def reload(*args)
114 114 @name = nil
115 115 @projects_by_role = nil
116 116 super
117 117 end
118 118
119 119 def mail=(arg)
120 120 write_attribute(:mail, arg.to_s.strip)
121 121 end
122 122
123 123 def identity_url=(url)
124 124 if url.blank?
125 125 write_attribute(:identity_url, '')
126 126 else
127 127 begin
128 128 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
129 129 rescue OpenIdAuthentication::InvalidOpenId
130 130 # Invlaid url, don't save
131 131 end
132 132 end
133 133 self.read_attribute(:identity_url)
134 134 end
135 135
136 136 # Returns the user that matches provided login and password, or nil
137 137 def self.try_to_login(login, password)
138 138 # Make sure no one can sign in with an empty password
139 139 return nil if password.to_s.empty?
140 140 user = find_by_login(login)
141 141 if user
142 142 # user is already in local database
143 143 return nil if !user.active?
144 144 if user.auth_source
145 145 # user has an external authentication method
146 146 return nil unless user.auth_source.authenticate(login, password)
147 147 else
148 148 # authentication with local password
149 149 return nil unless user.check_password?(password)
150 150 end
151 151 else
152 152 # user is not yet registered, try to authenticate with available sources
153 153 attrs = AuthSource.authenticate(login, password)
154 154 if attrs
155 155 user = new(attrs)
156 156 user.login = login
157 157 user.language = Setting.default_language
158 158 if user.save
159 159 user.reload
160 160 logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source
161 161 end
162 162 end
163 163 end
164 164 user.update_attribute(:last_login_on, Time.now) if user && !user.new_record?
165 165 user
166 166 rescue => text
167 167 raise text
168 168 end
169 169
170 170 # Returns the user who matches the given autologin +key+ or nil
171 171 def self.try_to_autologin(key)
172 172 tokens = Token.find_all_by_action_and_value('autologin', key)
173 173 # Make sure there's only 1 token that matches the key
174 174 if tokens.size == 1
175 175 token = tokens.first
176 176 if (token.created_on > Setting.autologin.to_i.day.ago) && token.user && token.user.active?
177 177 token.user.update_attribute(:last_login_on, Time.now)
178 178 token.user
179 179 end
180 180 end
181 181 end
182 182
183 183 def self.name_formatter(formatter = nil)
184 184 USER_FORMATS[formatter || Setting.user_format] || USER_FORMATS[:firstname_lastname]
185 185 end
186 186
187 187 # Returns an array of fields names than can be used to make an order statement for users
188 188 # according to how user names are displayed
189 189 # Examples:
190 190 #
191 191 # User.fields_for_order_statement => ['users.login', 'users.id']
192 192 # User.fields_for_order_statement('authors') => ['authors.login', 'authors.id']
193 193 def self.fields_for_order_statement(table=nil)
194 194 table ||= table_name
195 195 name_formatter[:order].map {|field| "#{table}.#{field}"}
196 196 end
197 197
198 198 # Return user's full name for display
199 199 def name(formatter = nil)
200 200 f = self.class.name_formatter(formatter)
201 201 if formatter
202 202 eval('"' + f[:string] + '"')
203 203 else
204 204 @name ||= eval('"' + f[:string] + '"')
205 205 end
206 206 end
207 207
208 208 def active?
209 209 self.status == STATUS_ACTIVE
210 210 end
211 211
212 212 def registered?
213 213 self.status == STATUS_REGISTERED
214 214 end
215 215
216 216 def locked?
217 217 self.status == STATUS_LOCKED
218 218 end
219 219
220 220 def activate
221 221 self.status = STATUS_ACTIVE
222 222 end
223 223
224 224 def register
225 225 self.status = STATUS_REGISTERED
226 226 end
227 227
228 228 def lock
229 229 self.status = STATUS_LOCKED
230 230 end
231 231
232 232 def activate!
233 233 update_attribute(:status, STATUS_ACTIVE)
234 234 end
235 235
236 236 def register!
237 237 update_attribute(:status, STATUS_REGISTERED)
238 238 end
239 239
240 240 def lock!
241 241 update_attribute(:status, STATUS_LOCKED)
242 242 end
243 243
244 244 # Returns true if +clear_password+ is the correct user's password, otherwise false
245 245 def check_password?(clear_password)
246 246 if auth_source_id.present?
247 247 auth_source.authenticate(self.login, clear_password)
248 248 else
249 249 User.hash_password("#{salt}#{User.hash_password clear_password}") == hashed_password
250 250 end
251 251 end
252 252
253 253 # Generates a random salt and computes hashed_password for +clear_password+
254 254 # The hashed password is stored in the following form: SHA1(salt + SHA1(password))
255 255 def salt_password(clear_password)
256 256 self.salt = User.generate_salt
257 257 self.hashed_password = User.hash_password("#{salt}#{User.hash_password clear_password}")
258 258 end
259 259
260 260 # Does the backend storage allow this user to change their password?
261 261 def change_password_allowed?
262 return true if auth_source_id.blank?
262 return true if auth_source.nil?
263 263 return auth_source.allow_password_changes?
264 264 end
265 265
266 266 # Generate and set a random password. Useful for automated user creation
267 267 # Based on Token#generate_token_value
268 268 #
269 269 def random_password
270 270 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
271 271 password = ''
272 272 40.times { |i| password << chars[rand(chars.size-1)] }
273 273 self.password = password
274 274 self.password_confirmation = password
275 275 self
276 276 end
277 277
278 278 def pref
279 279 self.preference ||= UserPreference.new(:user => self)
280 280 end
281 281
282 282 def time_zone
283 283 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
284 284 end
285 285
286 286 def wants_comments_in_reverse_order?
287 287 self.pref[:comments_sorting] == 'desc'
288 288 end
289 289
290 290 # Return user's RSS key (a 40 chars long string), used to access feeds
291 291 def rss_key
292 292 token = self.rss_token || Token.create(:user => self, :action => 'feeds')
293 293 token.value
294 294 end
295 295
296 296 # Return user's API key (a 40 chars long string), used to access the API
297 297 def api_key
298 298 token = self.api_token || self.create_api_token(:action => 'api')
299 299 token.value
300 300 end
301 301
302 302 # Return an array of project ids for which the user has explicitly turned mail notifications on
303 303 def notified_projects_ids
304 304 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
305 305 end
306 306
307 307 def notified_project_ids=(ids)
308 308 Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
309 309 Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
310 310 @notified_projects_ids = nil
311 311 notified_projects_ids
312 312 end
313 313
314 314 def valid_notification_options
315 315 self.class.valid_notification_options(self)
316 316 end
317 317
318 318 # Only users that belong to more than 1 project can select projects for which they are notified
319 319 def self.valid_notification_options(user=nil)
320 320 # Note that @user.membership.size would fail since AR ignores
321 321 # :include association option when doing a count
322 322 if user.nil? || user.memberships.length < 1
323 323 MAIL_NOTIFICATION_OPTIONS.reject {|option| option.first == 'selected'}
324 324 else
325 325 MAIL_NOTIFICATION_OPTIONS
326 326 end
327 327 end
328 328
329 329 # Find a user account by matching the exact login and then a case-insensitive
330 330 # version. Exact matches will be given priority.
331 331 def self.find_by_login(login)
332 332 # force string comparison to be case sensitive on MySQL
333 333 type_cast = (ActiveRecord::Base.connection.adapter_name == 'MySQL') ? 'BINARY' : ''
334 334
335 335 # First look for an exact match
336 336 user = first(:conditions => ["#{type_cast} login = ?", login])
337 337 # Fail over to case-insensitive if none was found
338 338 user ||= first(:conditions => ["#{type_cast} LOWER(login) = ?", login.to_s.downcase])
339 339 end
340 340
341 341 def self.find_by_rss_key(key)
342 342 token = Token.find_by_value(key)
343 343 token && token.user.active? ? token.user : nil
344 344 end
345 345
346 346 def self.find_by_api_key(key)
347 347 token = Token.find_by_action_and_value('api', key)
348 348 token && token.user.active? ? token.user : nil
349 349 end
350 350
351 351 # Makes find_by_mail case-insensitive
352 352 def self.find_by_mail(mail)
353 353 find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
354 354 end
355 355
356 356 def to_s
357 357 name
358 358 end
359 359
360 360 # Returns the current day according to user's time zone
361 361 def today
362 362 if time_zone.nil?
363 363 Date.today
364 364 else
365 365 Time.now.in_time_zone(time_zone).to_date
366 366 end
367 367 end
368 368
369 369 def logged?
370 370 true
371 371 end
372 372
373 373 def anonymous?
374 374 !logged?
375 375 end
376 376
377 377 # Return user's roles for project
378 378 def roles_for_project(project)
379 379 roles = []
380 380 # No role on archived projects
381 381 return roles unless project && project.active?
382 382 if logged?
383 383 # Find project membership
384 384 membership = memberships.detect {|m| m.project_id == project.id}
385 385 if membership
386 386 roles = membership.roles
387 387 else
388 388 @role_non_member ||= Role.non_member
389 389 roles << @role_non_member
390 390 end
391 391 else
392 392 @role_anonymous ||= Role.anonymous
393 393 roles << @role_anonymous
394 394 end
395 395 roles
396 396 end
397 397
398 398 # Return true if the user is a member of project
399 399 def member_of?(project)
400 400 !roles_for_project(project).detect {|role| role.member?}.nil?
401 401 end
402 402
403 403 # Returns a hash of user's projects grouped by roles
404 404 def projects_by_role
405 405 return @projects_by_role if @projects_by_role
406 406
407 407 @projects_by_role = Hash.new {|h,k| h[k]=[]}
408 408 memberships.each do |membership|
409 409 membership.roles.each do |role|
410 410 @projects_by_role[role] << membership.project if membership.project
411 411 end
412 412 end
413 413 @projects_by_role.each do |role, projects|
414 414 projects.uniq!
415 415 end
416 416
417 417 @projects_by_role
418 418 end
419 419
420 420 # Returns true if user is arg or belongs to arg
421 421 def is_or_belongs_to?(arg)
422 422 if arg.is_a?(User)
423 423 self == arg
424 424 elsif arg.is_a?(Group)
425 425 arg.users.include?(self)
426 426 else
427 427 false
428 428 end
429 429 end
430 430
431 431 # Return true if the user is allowed to do the specified action on a specific context
432 432 # Action can be:
433 433 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
434 434 # * a permission Symbol (eg. :edit_project)
435 435 # Context can be:
436 436 # * a project : returns true if user is allowed to do the specified action on this project
437 437 # * an array of projects : returns true if user is allowed on every project
438 438 # * nil with options[:global] set : check if user has at least one role allowed for this action,
439 439 # or falls back to Non Member / Anonymous permissions depending if the user is logged
440 440 def allowed_to?(action, context, options={}, &block)
441 441 if context && context.is_a?(Project)
442 442 # No action allowed on archived projects
443 443 return false unless context.active?
444 444 # No action allowed on disabled modules
445 445 return false unless context.allows_to?(action)
446 446 # Admin users are authorized for anything else
447 447 return true if admin?
448 448
449 449 roles = roles_for_project(context)
450 450 return false unless roles
451 451 roles.detect {|role|
452 452 (context.is_public? || role.member?) &&
453 453 role.allowed_to?(action) &&
454 454 (block_given? ? yield(role, self) : true)
455 455 }
456 456 elsif context && context.is_a?(Array)
457 457 # Authorize if user is authorized on every element of the array
458 458 context.map do |project|
459 459 allowed_to?(action, project, options, &block)
460 460 end.inject do |memo,allowed|
461 461 memo && allowed
462 462 end
463 463 elsif options[:global]
464 464 # Admin users are always authorized
465 465 return true if admin?
466 466
467 467 # authorize if user has at least one role that has this permission
468 468 roles = memberships.collect {|m| m.roles}.flatten.uniq
469 469 roles << (self.logged? ? Role.non_member : Role.anonymous)
470 470 roles.detect {|role|
471 471 role.allowed_to?(action) &&
472 472 (block_given? ? yield(role, self) : true)
473 473 }
474 474 else
475 475 false
476 476 end
477 477 end
478 478
479 479 # Is the user allowed to do the specified action on any project?
480 480 # See allowed_to? for the actions and valid options.
481 481 def allowed_to_globally?(action, options, &block)
482 482 allowed_to?(action, nil, options.reverse_merge(:global => true), &block)
483 483 end
484 484
485 485 safe_attributes 'login',
486 486 'firstname',
487 487 'lastname',
488 488 'mail',
489 489 'mail_notification',
490 490 'language',
491 491 'custom_field_values',
492 492 'custom_fields',
493 493 'identity_url'
494 494
495 495 safe_attributes 'status',
496 496 'auth_source_id',
497 497 :if => lambda {|user, current_user| current_user.admin?}
498 498
499 499 safe_attributes 'group_ids',
500 500 :if => lambda {|user, current_user| current_user.admin? && !user.new_record?}
501 501
502 502 # Utility method to help check if a user should be notified about an
503 503 # event.
504 504 #
505 505 # TODO: only supports Issue events currently
506 506 def notify_about?(object)
507 507 case mail_notification
508 508 when 'all'
509 509 true
510 510 when 'selected'
511 511 # user receives notifications for created/assigned issues on unselected projects
512 512 if object.is_a?(Issue) && (object.author == self || is_or_belongs_to?(object.assigned_to))
513 513 true
514 514 else
515 515 false
516 516 end
517 517 when 'none'
518 518 false
519 519 when 'only_my_events'
520 520 if object.is_a?(Issue) && (object.author == self || is_or_belongs_to?(object.assigned_to))
521 521 true
522 522 else
523 523 false
524 524 end
525 525 when 'only_assigned'
526 526 if object.is_a?(Issue) && is_or_belongs_to?(object.assigned_to)
527 527 true
528 528 else
529 529 false
530 530 end
531 531 when 'only_owner'
532 532 if object.is_a?(Issue) && object.author == self
533 533 true
534 534 else
535 535 false
536 536 end
537 537 else
538 538 false
539 539 end
540 540 end
541 541
542 542 def self.current=(user)
543 543 @current_user = user
544 544 end
545 545
546 546 def self.current
547 547 @current_user ||= User.anonymous
548 548 end
549 549
550 550 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
551 551 # one anonymous user per database.
552 552 def self.anonymous
553 553 anonymous_user = AnonymousUser.find(:first)
554 554 if anonymous_user.nil?
555 555 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
556 556 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
557 557 end
558 558 anonymous_user
559 559 end
560 560
561 561 # Salts all existing unsalted passwords
562 562 # It changes password storage scheme from SHA1(password) to SHA1(salt + SHA1(password))
563 563 # This method is used in the SaltPasswords migration and is to be kept as is
564 564 def self.salt_unsalted_passwords!
565 565 transaction do
566 566 User.find_each(:conditions => "salt IS NULL OR salt = ''") do |user|
567 567 next if user.hashed_password.blank?
568 568 salt = User.generate_salt
569 569 hashed_password = User.hash_password("#{salt}#{user.hashed_password}")
570 570 User.update_all("salt = '#{salt}', hashed_password = '#{hashed_password}'", ["id = ?", user.id] )
571 571 end
572 572 end
573 573 end
574 574
575 575 protected
576 576
577 577 def validate_password_length
578 578 # Password length validation based on setting
579 579 if !password.nil? && password.size < Setting.password_min_length.to_i
580 580 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
581 581 end
582 582 end
583 583
584 584 private
585 585
586 586 # Removes references that are not handled by associations
587 587 # Things that are not deleted are reassociated with the anonymous user
588 588 def remove_references_before_destroy
589 589 return if self.id.nil?
590 590
591 591 substitute = User.anonymous
592 592 Attachment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
593 593 Comment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
594 594 Issue.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
595 595 Issue.update_all 'assigned_to_id = NULL', ['assigned_to_id = ?', id]
596 596 Journal.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
597 597 JournalDetail.update_all ['old_value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND old_value = ?", id.to_s]
598 598 JournalDetail.update_all ['value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND value = ?", id.to_s]
599 599 Message.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
600 600 News.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
601 601 # Remove private queries and keep public ones
602 602 ::Query.delete_all ['user_id = ? AND is_public = ?', id, false]
603 603 ::Query.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
604 604 TimeEntry.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
605 605 Token.delete_all ['user_id = ?', id]
606 606 Watcher.delete_all ['user_id = ?', id]
607 607 WikiContent.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
608 608 WikiContent::Version.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
609 609 end
610 610
611 611 # Return password digest
612 612 def self.hash_password(clear_password)
613 613 Digest::SHA1.hexdigest(clear_password || "")
614 614 end
615 615
616 616 # Returns a 128bits random salt as a hex string (32 chars long)
617 617 def self.generate_salt
618 618 ActiveSupport::SecureRandom.hex(16)
619 619 end
620 620
621 621 end
622 622
623 623 class AnonymousUser < User
624 validate :validate_anonymous_uniqueness, :on => :create
624 625
625 def validate_on_create
626 def validate_anonymous_uniqueness
626 627 # There should be only one AnonymousUser in the database
627 628 errors.add :base, 'An anonymous user already exists.' if AnonymousUser.find(:first)
628 629 end
629 630
630 631 def available_custom_fields
631 632 []
632 633 end
633 634
634 635 # Overrides a few properties
635 636 def logged?; false end
636 637 def admin; false end
637 638 def name(*args); I18n.t(:label_user_anonymous) end
638 639 def mail; nil end
639 640 def time_zone; nil end
640 641 def rss_key; nil end
641 642
642 643 # Anonymous user can not be destroyed
643 644 def destroy
644 645 false
645 646 end
646 647 end
General Comments 0
You need to be logged in to leave comments. Login now