Class: User

Inherits:
Principal show all
Includes:
Redmine::SafeAttributes
Defined in:
app/models/user.rb

Direct Known Subclasses

AnonymousUser

Constant Summary collapse

STATUS_ANONYMOUS =

Account statuses

0
STATUS_ACTIVE =
1
STATUS_REGISTERED =
2
STATUS_LOCKED =
3
USER_FORMATS =
{
  :firstname_lastname => '#{firstname} #{lastname}',
  :firstname => '#{firstname}',
  :lastname_firstname => '#{lastname} #{firstname}',
  :lastname_coma_firstname => '#{lastname}, #{firstname}',
  :username => '#{login}'
}
MAIL_NOTIFICATION_OPTIONS =
[
  ['all', :label_user_mail_option_all],
  ['selected', :label_user_mail_option_selected],
  ['only_my_events', :label_user_mail_option_only_my_events],
  ['only_assigned', :label_user_mail_option_only_assigned],
  ['only_owner', :label_user_mail_option_only_owner],
  ['none', :label_user_mail_option_none]
]

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Redmine::SafeAttributes

#delete_unsafe_attributes, included, #safe_attribute_names, #safe_attributes=

Methods inherited from Principal

#<=>, #allowed_to?, #allowed_to_globally?, #roles_for_project, #to_liquid

Instance Attribute Details

#last_before_login_onObject

Returns the value of attribute last_before_login_on


58
59
60
# File 'app/models/user.rb', line 58

def 
  
end

#passwordObject

Returns the value of attribute password


57
58
59
# File 'app/models/user.rb', line 57

def password
  @password
end

#password_confirmationObject

Returns the value of attribute password_confirmation


57
58
59
# File 'app/models/user.rb', line 57

def password_confirmation
  @password_confirmation
end

Class Method Details

.anonymousObject

Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only one anonymous user per database.


438
439
440
441
442
443
444
445
# File 'app/models/user.rb', line 438

def self.anonymous
  anonymous_user = AnonymousUser.find(:first)
  if anonymous_user.nil?
    anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
    raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
  end
  anonymous_user
end

.currentObject


432
433
434
# File 'app/models/user.rb', line 432

def self.current
  @current_user ||= User.anonymous
end

.current=(user) ⇒ Object


428
429
430
# File 'app/models/user.rb', line 428

def self.current=(user)
  @current_user = user
end

.find_by_api_key(key) ⇒ Object


316
317
318
319
# File 'app/models/user.rb', line 316

def self.find_by_api_key(key)
  token = Token.find_by_action_and_value('api', key)
  token && token.user.active? ? token.user : nil
end

.find_by_login(login) ⇒ Object

Find a user account by matching the exact login and then a case-insensitive version. Exact matches will be given priority.


302
303
304
305
306
307
308
309
# File 'app/models/user.rb', line 302

def self.()
  # force string comparison to be case sensitive on MySQL
  type_cast = (ChiliProject::Database.mysql?) ? 'BINARY' : ''
  # First look for an exact match
  user = first(:conditions => ["#{type_cast} login = ?", ])
  # Fail over to case-insensitive if none was found
  user ||= first(:conditions => ["#{type_cast} LOWER(login) = ?", .to_s.downcase])
end

.find_by_mail(mail) ⇒ Object

Makes find_by_mail case-insensitive


322
323
324
# File 'app/models/user.rb', line 322

def self.find_by_mail(mail)
  find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
end

.find_by_rss_key(key) ⇒ Object


311
312
313
314
# File 'app/models/user.rb', line 311

def self.find_by_rss_key(key)
  token = Token.find_by_value(key)
  token && token.user.active? ? token.user : nil
end

.salt_unsalted_passwords!Object

Salts all existing unsalted passwords It changes password storage scheme from SHA1(password) to SHA1(salt + SHA1(password)) This method is used in the SaltPasswords migration and is to be kept as is


450
451
452
453
454
455
456
457
458
459
# File 'app/models/user.rb', line 450

def self.salt_unsalted_passwords!
  transaction do
    User.find_each(:conditions => "salt IS NULL OR salt = ''") do |user|
      next if user.hashed_password.blank?
      salt = User.generate_salt
      hashed_password = User.hash_password("#{salt}#{user.hashed_password}")
      User.update_all("salt = '#{salt}', hashed_password = '#{hashed_password}'", ["id = ?", user.id] )
    end
  end
end

.try_to_autologin(key) ⇒ Object

Returns the user who matches the given autologin key or nil


153
154
155
156
157
158
159
160
161
162
163
# File 'app/models/user.rb', line 153

def self.try_to_autologin(key)
  tokens = Token.find_all_by_action_and_value('autologin', key)
  # Make sure there's only 1 token that matches the key
  if tokens.size == 1
    token = tokens.first
    if (token.created_on > Setting.autologin.to_i.day.ago) && token.user && token.user.active?
      token.user.update_attribute(:last_login_on, Time.now)
      token.user
    end
  end
end

.try_to_login(login, password) ⇒ Object

Returns the user that matches provided login and password, or nil


119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'app/models/user.rb', line 119

def self.(, password)
  # Make sure no one can sign in with an empty password
  return nil if password.to_s.empty?
  user = ()
  if user
    # user is already in local database
    return nil if !user.active?
    if user.auth_source
      # user has an external authentication method
      return nil unless user.auth_source.authenticate(, password)
    else
      # authentication with local password
      return nil unless user.check_password?(password)
    end
  else
    # user is not yet registered, try to authenticate with available sources
    attrs = AuthSource.authenticate(, password)
    if attrs
      user = new(attrs)
      user. = 
      user.language = Setting.default_language
      if user.save
        user.reload
        logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source
      end
    end
  end
  user.update_attribute(:last_login_on, Time.now) if user && !user.new_record?
  user
rescue => text
  raise text
end

.valid_notification_options(user = nil) ⇒ Object

Only users that belong to more than 1 project can select projects for which they are notified


290
291
292
293
294
295
296
297
298
# File 'app/models/user.rb', line 290

def self.valid_notification_options(user=nil)
  # Note that @user.membership.size would fail since AR ignores
  # :include association option when doing a count
  if user.nil? || user.memberships.length < 1
    MAIL_NOTIFICATION_OPTIONS.reject {|option| option.first == 'selected'}
  else
    MAIL_NOTIFICATION_OPTIONS
  end
end

Instance Method Details

#activateObject


186
187
188
# File 'app/models/user.rb', line 186

def activate
  self.status = STATUS_ACTIVE
end

#activate!Object


198
199
200
# File 'app/models/user.rb', line 198

def activate!
  update_attribute(:status, STATUS_ACTIVE)
end

#active?Boolean


174
175
176
# File 'app/models/user.rb', line 174

def active?
  self.status == STATUS_ACTIVE
end

#anonymous?Boolean


343
344
345
# File 'app/models/user.rb', line 343

def anonymous?
  !logged?
end

#api_keyObject

Return user's API key (a 40 chars long string), used to access the API


268
269
270
271
# File 'app/models/user.rb', line 268

def api_key
  token = self.api_token || self.create_api_token(:action => 'api')
  token.value
end

#before_createObject


83
84
85
86
# File 'app/models/user.rb', line 83

def before_create
  self.mail_notification = Setting.default_notification_option if self.mail_notification.blank?
  true
end

#before_saveObject


88
89
90
91
92
93
# File 'app/models/user.rb', line 88

def before_save
  # update hashed_password if password was set
  if self.password && self.auth_source_id.blank?
    salt_password(password)
  end
end

#change_password_allowed?Boolean

Does the backend storage allow this user to change their password?


232
233
234
235
# File 'app/models/user.rb', line 232

def change_password_allowed?
  return true if auth_source_id.blank?
  return auth_source.allow_password_changes?
end

#check_password?(clear_password) ⇒ Boolean

Returns true if clear_password is the correct user's password, otherwise false


216
217
218
219
220
221
222
# File 'app/models/user.rb', line 216

def check_password?(clear_password)
  if auth_source_id.present?
    auth_source.authenticate(self., clear_password)
  else
    User.hash_password("#{salt}#{User.hash_password clear_password}") == hashed_password
  end
end

#deletable?Boolean


210
211
212
# File 'app/models/user.rb', line 210

def deletable?
  registered? && .nil?
end

#identity_url=(url) ⇒ Object


105
106
107
108
109
110
111
112
113
114
115
116
# File 'app/models/user.rb', line 105

def identity_url=(url)
  if url.blank?
    write_attribute(:identity_url, '')
  else
    begin
      write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
    rescue OpenIdAuthentication::InvalidOpenId
      # Invlaid url, don't save
    end
  end
  self.read_attribute(:identity_url)
end

#lockObject


194
195
196
# File 'app/models/user.rb', line 194

def lock
  self.status = STATUS_LOCKED
end

#lock!Object


206
207
208
# File 'app/models/user.rb', line 206

def lock!
  update_attribute(:status, STATUS_LOCKED)
end

#locked?Boolean


182
183
184
# File 'app/models/user.rb', line 182

def locked?
  self.status == STATUS_LOCKED
end

#logged?Boolean


339
340
341
# File 'app/models/user.rb', line 339

def logged?
  true
end

#mail=(arg) ⇒ Object


101
102
103
# File 'app/models/user.rb', line 101

def mail=(arg)
  write_attribute(:mail, arg.to_s.strip)
end

#member_of?(project) ⇒ Boolean

Return true if the user is a member of project


348
349
350
# File 'app/models/user.rb', line 348

def member_of?(project)
  !roles_for_project(project).detect {|role| role.member?}.nil?
end

#name(formatter = nil) ⇒ Object

Return user's full name for display


166
167
168
169
170
171
172
# File 'app/models/user.rb', line 166

def name(formatter = nil)
  if formatter
    eval('"' + (USER_FORMATS[formatter] || USER_FORMATS[:firstname_lastname]) + '"')
  else
    @name ||= eval('"' + (USER_FORMATS[Setting.user_format] || USER_FORMATS[:firstname_lastname]) + '"')
  end
end

#notified_project_ids=(ids) ⇒ Object


278
279
280
281
282
283
# File 'app/models/user.rb', line 278

def notified_project_ids=(ids)
  Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
  Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
  @notified_projects_ids = nil
  notified_projects_ids
end

#notified_projects_idsObject

Return an array of project ids for which the user has explicitly turned mail notifications on


274
275
276
# File 'app/models/user.rb', line 274

def notified_projects_ids
  @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
end

#notify_about?(object) ⇒ Boolean

Utility method to help check if a user should be notified about an event.

TODO: only supports Issue events currently


390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
# File 'app/models/user.rb', line 390

def notify_about?(object)
  case mail_notification
  when 'all'
    true
  when 'selected'
    # user receives notifications for created/assigned issues on unselected projects
    if object.is_a?(Issue) && (object.author == self || object.assigned_to == self)
      true
    else
      false
    end
  when 'none'
    false
  when 'only_my_events'
    if object.is_a?(Issue) && (object.author == self || object.assigned_to == self)
      true
    elsif object.respond_to?(:watched_by?) && object.watched_by?(self) # Make it clear that we always want to be notified about things we watch in this case
      true
    else
      false
    end
  when 'only_assigned'
    if object.is_a?(Issue) && object.assigned_to == self
      true
    else
      false
    end
  when 'only_owner'
    if object.is_a?(Issue) && object.author == self
      true
    else
      false
    end
  else
    false
  end
end

#prefObject


249
250
251
# File 'app/models/user.rb', line 249

def pref
  self.preference ||= UserPreference.new(:user => self)
end

#projects_by_roleObject

Returns a hash of user's projects grouped by roles


353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
# File 'app/models/user.rb', line 353

def projects_by_role
  return @projects_by_role if @projects_by_role

  @projects_by_role = Hash.new {|h,k| h[k]=[]}
  memberships.each do |membership|
    membership.roles.each do |role|
      @projects_by_role[role] << membership.project if membership.project
    end
  end
  @projects_by_role.each do |role, projects|
    projects.uniq!
  end

  @projects_by_role
end

#random_passwordObject

Generate and set a random password. Useful for automated user creation Based on Token#generate_token_value


240
241
242
243
244
245
246
247
# File 'app/models/user.rb', line 240

def random_password
  chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
  password = ''
  40.times { |i| password << chars[rand(chars.size-1)] }
  self.password = password
  self.password_confirmation = password
  self
end

#registerObject


190
191
192
# File 'app/models/user.rb', line 190

def register
  self.status = STATUS_REGISTERED
end

#register!Object


202
203
204
# File 'app/models/user.rb', line 202

def register!
  update_attribute(:status, STATUS_REGISTERED)
end

#registered?Boolean


178
179
180
# File 'app/models/user.rb', line 178

def registered?
  self.status == STATUS_REGISTERED
end

#reload(*args) ⇒ Object


95
96
97
98
99
# File 'app/models/user.rb', line 95

def reload(*args)
  @name = nil
  @projects_by_role = nil
  super
end

#rss_keyObject

Return user's RSS key (a 40 chars long string), used to access feeds


262
263
264
265
# File 'app/models/user.rb', line 262

def rss_key
  token = self.rss_token || Token.create(:user => self, :action => 'feeds')
  token.value
end

#salt_password(clear_password) ⇒ Object

Generates a random salt and computes hashed_password for clear_password The hashed password is stored in the following form: SHA1(salt + SHA1(password))


226
227
228
229
# File 'app/models/user.rb', line 226

def salt_password(clear_password)
  self.salt = User.generate_salt
  self.hashed_password = User.hash_password("#{salt}#{User.hash_password clear_password}")
end

#time_zoneObject


253
254
255
# File 'app/models/user.rb', line 253

def time_zone
  @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
end

#to_sObject


326
327
328
# File 'app/models/user.rb', line 326

def to_s
  name
end

#todayObject

Returns the current day according to user's time zone


331
332
333
334
335
336
337
# File 'app/models/user.rb', line 331

def today
  if time_zone.nil?
    Date.today
  else
    Time.now.in_time_zone(time_zone).to_date
  end
end

#valid_notification_optionsObject


285
286
287
# File 'app/models/user.rb', line 285

def valid_notification_options
  self.class.valid_notification_options(self)
end

#wants_comments_in_reverse_order?Boolean


257
258
259
# File 'app/models/user.rb', line 257

def wants_comments_in_reverse_order?
  self.pref[:comments_sorting] == 'desc'
end