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