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