##// END OF EJS Templates
Refactor and documentation for User#find_by_login....
Eric Davis -
r3694:6cb4ff7d8981
parent child
Show More
@@ -1,379 +1,379
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 # case-insensitive fall-over
225 # Find a user account by matching the exact login and then a case-insensitive
226 # version. Exact matches will be given priority.
226 def self.find_by_login(login)
227 def self.find_by_login(login)
227 # First look for an exact match
228 # First look for an exact match
228 user = find(:first, :conditions => ["login = ?", login])
229 user = first(:conditions => {:login => login})
229 # Fail over to case-insensitive if none was found
230 # Fail over to case-insensitive if none was found
230 user = find(:first, :conditions => ["LOWER(login) = ?", login.to_s.downcase]) if user.nil?
231 user ||= first(:conditions => ["LOWER(login) = ?", login.to_s.downcase])
231 return user
232 end
232 end
233
233
234 def self.find_by_rss_key(key)
234 def self.find_by_rss_key(key)
235 token = Token.find_by_value(key)
235 token = Token.find_by_value(key)
236 token && token.user.active? ? token.user : nil
236 token && token.user.active? ? token.user : nil
237 end
237 end
238
238
239 def self.find_by_api_key(key)
239 def self.find_by_api_key(key)
240 token = Token.find_by_action_and_value('api', key)
240 token = Token.find_by_action_and_value('api', key)
241 token && token.user.active? ? token.user : nil
241 token && token.user.active? ? token.user : nil
242 end
242 end
243
243
244 # Makes find_by_mail case-insensitive
244 # Makes find_by_mail case-insensitive
245 def self.find_by_mail(mail)
245 def self.find_by_mail(mail)
246 find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
246 find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
247 end
247 end
248
248
249 def to_s
249 def to_s
250 name
250 name
251 end
251 end
252
252
253 # Returns the current day according to user's time zone
253 # Returns the current day according to user's time zone
254 def today
254 def today
255 if time_zone.nil?
255 if time_zone.nil?
256 Date.today
256 Date.today
257 else
257 else
258 Time.now.in_time_zone(time_zone).to_date
258 Time.now.in_time_zone(time_zone).to_date
259 end
259 end
260 end
260 end
261
261
262 def logged?
262 def logged?
263 true
263 true
264 end
264 end
265
265
266 def anonymous?
266 def anonymous?
267 !logged?
267 !logged?
268 end
268 end
269
269
270 # Return user's roles for project
270 # Return user's roles for project
271 def roles_for_project(project)
271 def roles_for_project(project)
272 roles = []
272 roles = []
273 # No role on archived projects
273 # No role on archived projects
274 return roles unless project && project.active?
274 return roles unless project && project.active?
275 if logged?
275 if logged?
276 # Find project membership
276 # Find project membership
277 membership = memberships.detect {|m| m.project_id == project.id}
277 membership = memberships.detect {|m| m.project_id == project.id}
278 if membership
278 if membership
279 roles = membership.roles
279 roles = membership.roles
280 else
280 else
281 @role_non_member ||= Role.non_member
281 @role_non_member ||= Role.non_member
282 roles << @role_non_member
282 roles << @role_non_member
283 end
283 end
284 else
284 else
285 @role_anonymous ||= Role.anonymous
285 @role_anonymous ||= Role.anonymous
286 roles << @role_anonymous
286 roles << @role_anonymous
287 end
287 end
288 roles
288 roles
289 end
289 end
290
290
291 # Return true if the user is a member of project
291 # Return true if the user is a member of project
292 def member_of?(project)
292 def member_of?(project)
293 !roles_for_project(project).detect {|role| role.member?}.nil?
293 !roles_for_project(project).detect {|role| role.member?}.nil?
294 end
294 end
295
295
296 # Return true if the user is allowed to do the specified action on project
296 # Return true if the user is allowed to do the specified action on project
297 # action can be:
297 # action can be:
298 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
298 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
299 # * a permission Symbol (eg. :edit_project)
299 # * a permission Symbol (eg. :edit_project)
300 def allowed_to?(action, project, options={})
300 def allowed_to?(action, project, options={})
301 if project
301 if project
302 # No action allowed on archived projects
302 # No action allowed on archived projects
303 return false unless project.active?
303 return false unless project.active?
304 # No action allowed on disabled modules
304 # No action allowed on disabled modules
305 return false unless project.allows_to?(action)
305 return false unless project.allows_to?(action)
306 # Admin users are authorized for anything else
306 # Admin users are authorized for anything else
307 return true if admin?
307 return true if admin?
308
308
309 roles = roles_for_project(project)
309 roles = roles_for_project(project)
310 return false unless roles
310 return false unless roles
311 roles.detect {|role| (project.is_public? || role.member?) && role.allowed_to?(action)}
311 roles.detect {|role| (project.is_public? || role.member?) && role.allowed_to?(action)}
312
312
313 elsif options[:global]
313 elsif options[:global]
314 # Admin users are always authorized
314 # Admin users are always authorized
315 return true if admin?
315 return true if admin?
316
316
317 # authorize if user has at least one role that has this permission
317 # authorize if user has at least one role that has this permission
318 roles = memberships.collect {|m| m.roles}.flatten.uniq
318 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))
319 roles.detect {|r| r.allowed_to?(action)} || (self.logged? ? Role.non_member.allowed_to?(action) : Role.anonymous.allowed_to?(action))
320 else
320 else
321 false
321 false
322 end
322 end
323 end
323 end
324
324
325 def self.current=(user)
325 def self.current=(user)
326 @current_user = user
326 @current_user = user
327 end
327 end
328
328
329 def self.current
329 def self.current
330 @current_user ||= User.anonymous
330 @current_user ||= User.anonymous
331 end
331 end
332
332
333 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
333 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
334 # one anonymous user per database.
334 # one anonymous user per database.
335 def self.anonymous
335 def self.anonymous
336 anonymous_user = AnonymousUser.find(:first)
336 anonymous_user = AnonymousUser.find(:first)
337 if anonymous_user.nil?
337 if anonymous_user.nil?
338 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
338 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
339 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
339 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
340 end
340 end
341 anonymous_user
341 anonymous_user
342 end
342 end
343
343
344 protected
344 protected
345
345
346 def validate
346 def validate
347 # Password length validation based on setting
347 # Password length validation based on setting
348 if !password.nil? && password.size < Setting.password_min_length.to_i
348 if !password.nil? && password.size < Setting.password_min_length.to_i
349 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
349 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
350 end
350 end
351 end
351 end
352
352
353 private
353 private
354
354
355 # Return password digest
355 # Return password digest
356 def self.hash_password(clear_password)
356 def self.hash_password(clear_password)
357 Digest::SHA1.hexdigest(clear_password || "")
357 Digest::SHA1.hexdigest(clear_password || "")
358 end
358 end
359 end
359 end
360
360
361 class AnonymousUser < User
361 class AnonymousUser < User
362
362
363 def validate_on_create
363 def validate_on_create
364 # There should be only one AnonymousUser in the database
364 # There should be only one AnonymousUser in the database
365 errors.add_to_base 'An anonymous user already exists.' if AnonymousUser.find(:first)
365 errors.add_to_base 'An anonymous user already exists.' if AnonymousUser.find(:first)
366 end
366 end
367
367
368 def available_custom_fields
368 def available_custom_fields
369 []
369 []
370 end
370 end
371
371
372 # Overrides a few properties
372 # Overrides a few properties
373 def logged?; false end
373 def logged?; false end
374 def admin; false end
374 def admin; false end
375 def name(*args); I18n.t(:label_user_anonymous) end
375 def name(*args); I18n.t(:label_user_anonymous) end
376 def mail; nil end
376 def mail; nil end
377 def time_zone; nil end
377 def time_zone; nil end
378 def rss_key; nil end
378 def rss_key; nil end
379 end
379 end
General Comments 0
You need to be logged in to leave comments. Login now