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