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