##// END OF EJS Templates
Force string comparison for login search to be case sensitive on MySQL. #2473...
Eric Davis -
r3699:672852baafdd
parent child
Show More
@@ -1,379 +1,382
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2009 Jean-Philippe Lang
2 # Copyright (C) 2006-2009 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
21
22 # Account statuses
22 # Account statuses
23 STATUS_ANONYMOUS = 0
23 STATUS_ANONYMOUS = 0
24 STATUS_ACTIVE = 1
24 STATUS_ACTIVE = 1
25 STATUS_REGISTERED = 2
25 STATUS_REGISTERED = 2
26 STATUS_LOCKED = 3
26 STATUS_LOCKED = 3
27
27
28 USER_FORMATS = {
28 USER_FORMATS = {
29 :firstname_lastname => '#{firstname} #{lastname}',
29 :firstname_lastname => '#{firstname} #{lastname}',
30 :firstname => '#{firstname}',
30 :firstname => '#{firstname}',
31 :lastname_firstname => '#{lastname} #{firstname}',
31 :lastname_firstname => '#{lastname} #{firstname}',
32 :lastname_coma_firstname => '#{lastname}, #{firstname}',
32 :lastname_coma_firstname => '#{lastname}, #{firstname}',
33 :username => '#{login}'
33 :username => '#{login}'
34 }
34 }
35
35
36 has_and_belongs_to_many :groups, :after_add => Proc.new {|user, group| group.user_added(user)},
36 has_and_belongs_to_many :groups, :after_add => Proc.new {|user, group| group.user_added(user)},
37 :after_remove => Proc.new {|user, group| group.user_removed(user)}
37 :after_remove => Proc.new {|user, group| group.user_removed(user)}
38 has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
38 has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
39 has_many :changesets, :dependent => :nullify
39 has_many :changesets, :dependent => :nullify
40 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
40 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
41 has_one :rss_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='feeds'"
41 has_one :rss_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='feeds'"
42 has_one :api_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='api'"
42 has_one :api_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='api'"
43 belongs_to :auth_source
43 belongs_to :auth_source
44
44
45 # Active non-anonymous users scope
45 # Active non-anonymous users scope
46 named_scope :active, :conditions => "#{User.table_name}.status = #{STATUS_ACTIVE}"
46 named_scope :active, :conditions => "#{User.table_name}.status = #{STATUS_ACTIVE}"
47
47
48 acts_as_customizable
48 acts_as_customizable
49
49
50 attr_accessor :password, :password_confirmation
50 attr_accessor :password, :password_confirmation
51 attr_accessor :last_before_login_on
51 attr_accessor :last_before_login_on
52 # Prevents unauthorized assignments
52 # Prevents unauthorized assignments
53 attr_protected :login, :admin, :password, :password_confirmation, :hashed_password, :group_ids
53 attr_protected :login, :admin, :password, :password_confirmation, :hashed_password, :group_ids
54
54
55 validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
55 validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
56 validates_uniqueness_of :login, :if => Proc.new { |user| !user.login.blank? }, :case_sensitive => false
56 validates_uniqueness_of :login, :if => Proc.new { |user| !user.login.blank? }, :case_sensitive => false
57 validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }, :case_sensitive => false
57 validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }, :case_sensitive => false
58 # Login must contain lettres, numbers, underscores only
58 # Login must contain lettres, numbers, underscores only
59 validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
59 validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
60 validates_length_of :login, :maximum => 30
60 validates_length_of :login, :maximum => 30
61 validates_format_of :firstname, :lastname, :with => /^[\w\s\'\-\.]*$/i
61 validates_format_of :firstname, :lastname, :with => /^[\w\s\'\-\.]*$/i
62 validates_length_of :firstname, :lastname, :maximum => 30
62 validates_length_of :firstname, :lastname, :maximum => 30
63 validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_nil => true
63 validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_nil => true
64 validates_length_of :mail, :maximum => 60, :allow_nil => true
64 validates_length_of :mail, :maximum => 60, :allow_nil => true
65 validates_confirmation_of :password, :allow_nil => true
65 validates_confirmation_of :password, :allow_nil => true
66
66
67 def before_create
67 def before_create
68 self.mail_notification = false
68 self.mail_notification = false
69 true
69 true
70 end
70 end
71
71
72 def before_save
72 def before_save
73 # update hashed_password if password was set
73 # update hashed_password if password was set
74 self.hashed_password = User.hash_password(self.password) if self.password && self.auth_source_id.blank?
74 self.hashed_password = User.hash_password(self.password) if self.password && self.auth_source_id.blank?
75 end
75 end
76
76
77 def reload(*args)
77 def reload(*args)
78 @name = nil
78 @name = nil
79 super
79 super
80 end
80 end
81
81
82 def identity_url=(url)
82 def identity_url=(url)
83 if url.blank?
83 if url.blank?
84 write_attribute(:identity_url, '')
84 write_attribute(:identity_url, '')
85 else
85 else
86 begin
86 begin
87 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
87 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
88 rescue OpenIdAuthentication::InvalidOpenId
88 rescue OpenIdAuthentication::InvalidOpenId
89 # Invlaid url, don't save
89 # Invlaid url, don't save
90 end
90 end
91 end
91 end
92 self.read_attribute(:identity_url)
92 self.read_attribute(:identity_url)
93 end
93 end
94
94
95 # Returns the user that matches provided login and password, or nil
95 # Returns the user that matches provided login and password, or nil
96 def self.try_to_login(login, password)
96 def self.try_to_login(login, password)
97 # Make sure no one can sign in with an empty password
97 # Make sure no one can sign in with an empty password
98 return nil if password.to_s.empty?
98 return nil if password.to_s.empty?
99 user = find_by_login(login)
99 user = find_by_login(login)
100 if user
100 if user
101 # user is already in local database
101 # user is already in local database
102 return nil if !user.active?
102 return nil if !user.active?
103 if user.auth_source
103 if user.auth_source
104 # user has an external authentication method
104 # user has an external authentication method
105 return nil unless user.auth_source.authenticate(login, password)
105 return nil unless user.auth_source.authenticate(login, password)
106 else
106 else
107 # authentication with local password
107 # authentication with local password
108 return nil unless User.hash_password(password) == user.hashed_password
108 return nil unless User.hash_password(password) == user.hashed_password
109 end
109 end
110 else
110 else
111 # user is not yet registered, try to authenticate with available sources
111 # user is not yet registered, try to authenticate with available sources
112 attrs = AuthSource.authenticate(login, password)
112 attrs = AuthSource.authenticate(login, password)
113 if attrs
113 if attrs
114 user = new(attrs)
114 user = new(attrs)
115 user.login = login
115 user.login = login
116 user.language = Setting.default_language
116 user.language = Setting.default_language
117 if user.save
117 if user.save
118 user.reload
118 user.reload
119 logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source
119 logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source
120 end
120 end
121 end
121 end
122 end
122 end
123 user.update_attribute(:last_login_on, Time.now) if user && !user.new_record?
123 user.update_attribute(:last_login_on, Time.now) if user && !user.new_record?
124 user
124 user
125 rescue => text
125 rescue => text
126 raise text
126 raise text
127 end
127 end
128
128
129 # Returns the user who matches the given autologin +key+ or nil
129 # Returns the user who matches the given autologin +key+ or nil
130 def self.try_to_autologin(key)
130 def self.try_to_autologin(key)
131 tokens = Token.find_all_by_action_and_value('autologin', key)
131 tokens = Token.find_all_by_action_and_value('autologin', key)
132 # Make sure there's only 1 token that matches the key
132 # Make sure there's only 1 token that matches the key
133 if tokens.size == 1
133 if tokens.size == 1
134 token = tokens.first
134 token = tokens.first
135 if (token.created_on > Setting.autologin.to_i.day.ago) && token.user && token.user.active?
135 if (token.created_on > Setting.autologin.to_i.day.ago) && token.user && token.user.active?
136 token.user.update_attribute(:last_login_on, Time.now)
136 token.user.update_attribute(:last_login_on, Time.now)
137 token.user
137 token.user
138 end
138 end
139 end
139 end
140 end
140 end
141
141
142 # Return user's full name for display
142 # Return user's full name for display
143 def name(formatter = nil)
143 def name(formatter = nil)
144 if formatter
144 if formatter
145 eval('"' + (USER_FORMATS[formatter] || USER_FORMATS[:firstname_lastname]) + '"')
145 eval('"' + (USER_FORMATS[formatter] || USER_FORMATS[:firstname_lastname]) + '"')
146 else
146 else
147 @name ||= eval('"' + (USER_FORMATS[Setting.user_format] || USER_FORMATS[:firstname_lastname]) + '"')
147 @name ||= eval('"' + (USER_FORMATS[Setting.user_format] || USER_FORMATS[:firstname_lastname]) + '"')
148 end
148 end
149 end
149 end
150
150
151 def active?
151 def active?
152 self.status == STATUS_ACTIVE
152 self.status == STATUS_ACTIVE
153 end
153 end
154
154
155 def registered?
155 def registered?
156 self.status == STATUS_REGISTERED
156 self.status == STATUS_REGISTERED
157 end
157 end
158
158
159 def locked?
159 def locked?
160 self.status == STATUS_LOCKED
160 self.status == STATUS_LOCKED
161 end
161 end
162
162
163 def check_password?(clear_password)
163 def check_password?(clear_password)
164 if auth_source_id.present?
164 if auth_source_id.present?
165 auth_source.authenticate(self.login, clear_password)
165 auth_source.authenticate(self.login, clear_password)
166 else
166 else
167 User.hash_password(clear_password) == self.hashed_password
167 User.hash_password(clear_password) == self.hashed_password
168 end
168 end
169 end
169 end
170
170
171 # Does the backend storage allow this user to change their password?
171 # Does the backend storage allow this user to change their password?
172 def change_password_allowed?
172 def change_password_allowed?
173 return true if auth_source_id.blank?
173 return true if auth_source_id.blank?
174 return auth_source.allow_password_changes?
174 return auth_source.allow_password_changes?
175 end
175 end
176
176
177 # Generate and set a random password. Useful for automated user creation
177 # Generate and set a random password. Useful for automated user creation
178 # Based on Token#generate_token_value
178 # Based on Token#generate_token_value
179 #
179 #
180 def random_password
180 def random_password
181 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
181 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
182 password = ''
182 password = ''
183 40.times { |i| password << chars[rand(chars.size-1)] }
183 40.times { |i| password << chars[rand(chars.size-1)] }
184 self.password = password
184 self.password = password
185 self.password_confirmation = password
185 self.password_confirmation = password
186 self
186 self
187 end
187 end
188
188
189 def pref
189 def pref
190 self.preference ||= UserPreference.new(:user => self)
190 self.preference ||= UserPreference.new(:user => self)
191 end
191 end
192
192
193 def time_zone
193 def time_zone
194 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
194 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
195 end
195 end
196
196
197 def wants_comments_in_reverse_order?
197 def wants_comments_in_reverse_order?
198 self.pref[:comments_sorting] == 'desc'
198 self.pref[:comments_sorting] == 'desc'
199 end
199 end
200
200
201 # Return user's RSS key (a 40 chars long string), used to access feeds
201 # Return user's RSS key (a 40 chars long string), used to access feeds
202 def rss_key
202 def rss_key
203 token = self.rss_token || Token.create(:user => self, :action => 'feeds')
203 token = self.rss_token || Token.create(:user => self, :action => 'feeds')
204 token.value
204 token.value
205 end
205 end
206
206
207 # Return user's API key (a 40 chars long string), used to access the API
207 # Return user's API key (a 40 chars long string), used to access the API
208 def api_key
208 def api_key
209 token = self.api_token || self.create_api_token(:action => 'api')
209 token = self.api_token || self.create_api_token(:action => 'api')
210 token.value
210 token.value
211 end
211 end
212
212
213 # Return an array of project ids for which the user has explicitly turned mail notifications on
213 # Return an array of project ids for which the user has explicitly turned mail notifications on
214 def notified_projects_ids
214 def notified_projects_ids
215 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
215 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
216 end
216 end
217
217
218 def notified_project_ids=(ids)
218 def notified_project_ids=(ids)
219 Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
219 Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
220 Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
220 Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
221 @notified_projects_ids = nil
221 @notified_projects_ids = nil
222 notified_projects_ids
222 notified_projects_ids
223 end
223 end
224
224
225 # Find a user account by matching the exact login and then a case-insensitive
225 # Find a user account by matching the exact login and then a case-insensitive
226 # version. Exact matches will be given priority.
226 # version. Exact matches will be given priority.
227 def self.find_by_login(login)
227 def self.find_by_login(login)
228 # force string comparison to be case sensitive on MySQL
229 type_cast = (ActiveRecord::Base.connection.adapter_name == 'MySQL') ? 'BINARY' : ''
230
228 # First look for an exact match
231 # First look for an exact match
229 user = first(:conditions => {:login => login})
232 user = first(:conditions => ["#{type_cast} login = ?", login])
230 # Fail over to case-insensitive if none was found
233 # Fail over to case-insensitive if none was found
231 user ||= first(:conditions => ["LOWER(login) = ?", login.to_s.downcase])
234 user ||= first(:conditions => ["#{type_cast} LOWER(login) = ?", login.to_s.downcase])
232 end
235 end
233
236
234 def self.find_by_rss_key(key)
237 def self.find_by_rss_key(key)
235 token = Token.find_by_value(key)
238 token = Token.find_by_value(key)
236 token && token.user.active? ? token.user : nil
239 token && token.user.active? ? token.user : nil
237 end
240 end
238
241
239 def self.find_by_api_key(key)
242 def self.find_by_api_key(key)
240 token = Token.find_by_action_and_value('api', key)
243 token = Token.find_by_action_and_value('api', key)
241 token && token.user.active? ? token.user : nil
244 token && token.user.active? ? token.user : nil
242 end
245 end
243
246
244 # Makes find_by_mail case-insensitive
247 # Makes find_by_mail case-insensitive
245 def self.find_by_mail(mail)
248 def self.find_by_mail(mail)
246 find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
249 find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
247 end
250 end
248
251
249 def to_s
252 def to_s
250 name
253 name
251 end
254 end
252
255
253 # Returns the current day according to user's time zone
256 # Returns the current day according to user's time zone
254 def today
257 def today
255 if time_zone.nil?
258 if time_zone.nil?
256 Date.today
259 Date.today
257 else
260 else
258 Time.now.in_time_zone(time_zone).to_date
261 Time.now.in_time_zone(time_zone).to_date
259 end
262 end
260 end
263 end
261
264
262 def logged?
265 def logged?
263 true
266 true
264 end
267 end
265
268
266 def anonymous?
269 def anonymous?
267 !logged?
270 !logged?
268 end
271 end
269
272
270 # Return user's roles for project
273 # Return user's roles for project
271 def roles_for_project(project)
274 def roles_for_project(project)
272 roles = []
275 roles = []
273 # No role on archived projects
276 # No role on archived projects
274 return roles unless project && project.active?
277 return roles unless project && project.active?
275 if logged?
278 if logged?
276 # Find project membership
279 # Find project membership
277 membership = memberships.detect {|m| m.project_id == project.id}
280 membership = memberships.detect {|m| m.project_id == project.id}
278 if membership
281 if membership
279 roles = membership.roles
282 roles = membership.roles
280 else
283 else
281 @role_non_member ||= Role.non_member
284 @role_non_member ||= Role.non_member
282 roles << @role_non_member
285 roles << @role_non_member
283 end
286 end
284 else
287 else
285 @role_anonymous ||= Role.anonymous
288 @role_anonymous ||= Role.anonymous
286 roles << @role_anonymous
289 roles << @role_anonymous
287 end
290 end
288 roles
291 roles
289 end
292 end
290
293
291 # Return true if the user is a member of project
294 # Return true if the user is a member of project
292 def member_of?(project)
295 def member_of?(project)
293 !roles_for_project(project).detect {|role| role.member?}.nil?
296 !roles_for_project(project).detect {|role| role.member?}.nil?
294 end
297 end
295
298
296 # Return true if the user is allowed to do the specified action on project
299 # Return true if the user is allowed to do the specified action on project
297 # action can be:
300 # action can be:
298 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
301 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
299 # * a permission Symbol (eg. :edit_project)
302 # * a permission Symbol (eg. :edit_project)
300 def allowed_to?(action, project, options={})
303 def allowed_to?(action, project, options={})
301 if project
304 if project
302 # No action allowed on archived projects
305 # No action allowed on archived projects
303 return false unless project.active?
306 return false unless project.active?
304 # No action allowed on disabled modules
307 # No action allowed on disabled modules
305 return false unless project.allows_to?(action)
308 return false unless project.allows_to?(action)
306 # Admin users are authorized for anything else
309 # Admin users are authorized for anything else
307 return true if admin?
310 return true if admin?
308
311
309 roles = roles_for_project(project)
312 roles = roles_for_project(project)
310 return false unless roles
313 return false unless roles
311 roles.detect {|role| (project.is_public? || role.member?) && role.allowed_to?(action)}
314 roles.detect {|role| (project.is_public? || role.member?) && role.allowed_to?(action)}
312
315
313 elsif options[:global]
316 elsif options[:global]
314 # Admin users are always authorized
317 # Admin users are always authorized
315 return true if admin?
318 return true if admin?
316
319
317 # authorize if user has at least one role that has this permission
320 # authorize if user has at least one role that has this permission
318 roles = memberships.collect {|m| m.roles}.flatten.uniq
321 roles = memberships.collect {|m| m.roles}.flatten.uniq
319 roles.detect {|r| r.allowed_to?(action)} || (self.logged? ? Role.non_member.allowed_to?(action) : Role.anonymous.allowed_to?(action))
322 roles.detect {|r| r.allowed_to?(action)} || (self.logged? ? Role.non_member.allowed_to?(action) : Role.anonymous.allowed_to?(action))
320 else
323 else
321 false
324 false
322 end
325 end
323 end
326 end
324
327
325 def self.current=(user)
328 def self.current=(user)
326 @current_user = user
329 @current_user = user
327 end
330 end
328
331
329 def self.current
332 def self.current
330 @current_user ||= User.anonymous
333 @current_user ||= User.anonymous
331 end
334 end
332
335
333 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
336 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
334 # one anonymous user per database.
337 # one anonymous user per database.
335 def self.anonymous
338 def self.anonymous
336 anonymous_user = AnonymousUser.find(:first)
339 anonymous_user = AnonymousUser.find(:first)
337 if anonymous_user.nil?
340 if anonymous_user.nil?
338 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
341 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
339 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
342 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
340 end
343 end
341 anonymous_user
344 anonymous_user
342 end
345 end
343
346
344 protected
347 protected
345
348
346 def validate
349 def validate
347 # Password length validation based on setting
350 # Password length validation based on setting
348 if !password.nil? && password.size < Setting.password_min_length.to_i
351 if !password.nil? && password.size < Setting.password_min_length.to_i
349 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
352 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
350 end
353 end
351 end
354 end
352
355
353 private
356 private
354
357
355 # Return password digest
358 # Return password digest
356 def self.hash_password(clear_password)
359 def self.hash_password(clear_password)
357 Digest::SHA1.hexdigest(clear_password || "")
360 Digest::SHA1.hexdigest(clear_password || "")
358 end
361 end
359 end
362 end
360
363
361 class AnonymousUser < User
364 class AnonymousUser < User
362
365
363 def validate_on_create
366 def validate_on_create
364 # There should be only one AnonymousUser in the database
367 # There should be only one AnonymousUser in the database
365 errors.add_to_base 'An anonymous user already exists.' if AnonymousUser.find(:first)
368 errors.add_to_base 'An anonymous user already exists.' if AnonymousUser.find(:first)
366 end
369 end
367
370
368 def available_custom_fields
371 def available_custom_fields
369 []
372 []
370 end
373 end
371
374
372 # Overrides a few properties
375 # Overrides a few properties
373 def logged?; false end
376 def logged?; false end
374 def admin; false end
377 def admin; false end
375 def name(*args); I18n.t(:label_user_anonymous) end
378 def name(*args); I18n.t(:label_user_anonymous) end
376 def mail; nil end
379 def mail; nil end
377 def time_zone; nil end
380 def time_zone; nil end
378 def rss_key; nil end
381 def rss_key; nil end
379 end
382 end
General Comments 0
You need to be logged in to leave comments. Login now