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