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