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