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