##// END OF EJS Templates
Merged r3906 from trunk....
Eric Davis -
r3842:8dde6e019d04
parent child
Show More
@@ -1,272 +1,272
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2009 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class AccountController < ApplicationController
19 19 helper :custom_fields
20 20 include CustomFieldsHelper
21 21
22 22 # prevents login action to be filtered by check_if_login_required application scope filter
23 23 skip_before_filter :check_if_login_required
24 24
25 25 # Login request and validation
26 26 def login
27 27 if request.get?
28 28 logout_user
29 29 else
30 30 authenticate_user
31 31 end
32 32 end
33 33
34 34 # Log out current user and redirect to welcome page
35 35 def logout
36 36 logout_user
37 37 redirect_to home_url
38 38 end
39 39
40 40 # Enable user to choose a new password
41 41 def lost_password
42 42 redirect_to(home_url) && return unless Setting.lost_password?
43 43 if params[:token]
44 44 @token = Token.find_by_action_and_value("recovery", params[:token])
45 45 redirect_to(home_url) && return unless @token and !@token.expired?
46 46 @user = @token.user
47 47 if request.post?
48 48 @user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
49 49 if @user.save
50 50 @token.destroy
51 51 flash[:notice] = l(:notice_account_password_updated)
52 52 redirect_to :action => 'login'
53 53 return
54 54 end
55 55 end
56 56 render :template => "account/password_recovery"
57 57 return
58 58 else
59 59 if request.post?
60 60 user = User.find_by_mail(params[:mail])
61 61 # user not found in db
62 62 (flash.now[:error] = l(:notice_account_unknown_email); return) unless user
63 63 # user uses an external authentification
64 64 (flash.now[:error] = l(:notice_can_t_change_password); return) if user.auth_source_id
65 65 # create a new token for password recovery
66 66 token = Token.new(:user => user, :action => "recovery")
67 67 if token.save
68 68 Mailer.deliver_lost_password(token)
69 69 flash[:notice] = l(:notice_account_lost_email_sent)
70 70 redirect_to :action => 'login'
71 71 return
72 72 end
73 73 end
74 74 end
75 75 end
76 76
77 77 # User self-registration
78 78 def register
79 79 redirect_to(home_url) && return unless Setting.self_registration? || session[:auth_source_registration]
80 80 if request.get?
81 81 session[:auth_source_registration] = nil
82 82 @user = User.new(:language => Setting.default_language)
83 83 else
84 84 @user = User.new(params[:user])
85 85 @user.admin = false
86 @user.status = User::STATUS_REGISTERED
86 @user.register
87 87 if session[:auth_source_registration]
88 @user.status = User::STATUS_ACTIVE
88 @user.activate
89 89 @user.login = session[:auth_source_registration][:login]
90 90 @user.auth_source_id = session[:auth_source_registration][:auth_source_id]
91 91 if @user.save
92 92 session[:auth_source_registration] = nil
93 93 self.logged_user = @user
94 94 flash[:notice] = l(:notice_account_activated)
95 95 redirect_to :controller => 'my', :action => 'account'
96 96 end
97 97 else
98 98 @user.login = params[:user][:login]
99 99 @user.password, @user.password_confirmation = params[:password], params[:password_confirmation]
100 100
101 101 case Setting.self_registration
102 102 when '1'
103 103 register_by_email_activation(@user)
104 104 when '3'
105 105 register_automatically(@user)
106 106 else
107 107 register_manually_by_administrator(@user)
108 108 end
109 109 end
110 110 end
111 111 end
112 112
113 113 # Token based account activation
114 114 def activate
115 115 redirect_to(home_url) && return unless Setting.self_registration? && params[:token]
116 116 token = Token.find_by_action_and_value('register', params[:token])
117 117 redirect_to(home_url) && return unless token and !token.expired?
118 118 user = token.user
119 redirect_to(home_url) && return unless user.status == User::STATUS_REGISTERED
120 user.status = User::STATUS_ACTIVE
119 redirect_to(home_url) && return unless user.registered?
120 user.activate
121 121 if user.save
122 122 token.destroy
123 123 flash[:notice] = l(:notice_account_activated)
124 124 end
125 125 redirect_to :action => 'login'
126 126 end
127 127
128 128 private
129 129
130 130 def logout_user
131 131 if User.current.logged?
132 132 cookies.delete :autologin
133 133 Token.delete_all(["user_id = ? AND action = ?", User.current.id, 'autologin'])
134 134 self.logged_user = nil
135 135 end
136 136 end
137 137
138 138 def authenticate_user
139 139 if Setting.openid? && using_open_id?
140 140 open_id_authenticate(params[:openid_url])
141 141 else
142 142 password_authentication
143 143 end
144 144 end
145 145
146 146 def password_authentication
147 147 user = User.try_to_login(params[:username], params[:password])
148 148
149 149 if user.nil?
150 150 invalid_credentials
151 151 elsif user.new_record?
152 152 onthefly_creation_failed(user, {:login => user.login, :auth_source_id => user.auth_source_id })
153 153 else
154 154 # Valid user
155 155 successful_authentication(user)
156 156 end
157 157 end
158 158
159 159
160 160 def open_id_authenticate(openid_url)
161 161 authenticate_with_open_id(openid_url, :required => [:nickname, :fullname, :email], :return_to => signin_url) do |result, identity_url, registration|
162 162 if result.successful?
163 163 user = User.find_or_initialize_by_identity_url(identity_url)
164 164 if user.new_record?
165 165 # Self-registration off
166 166 redirect_to(home_url) && return unless Setting.self_registration?
167 167
168 168 # Create on the fly
169 169 user.login = registration['nickname'] unless registration['nickname'].nil?
170 170 user.mail = registration['email'] unless registration['email'].nil?
171 171 user.firstname, user.lastname = registration['fullname'].split(' ') unless registration['fullname'].nil?
172 172 user.random_password
173 user.status = User::STATUS_REGISTERED
173 user.register
174 174
175 175 case Setting.self_registration
176 176 when '1'
177 177 register_by_email_activation(user) do
178 178 onthefly_creation_failed(user)
179 179 end
180 180 when '3'
181 181 register_automatically(user) do
182 182 onthefly_creation_failed(user)
183 183 end
184 184 else
185 185 register_manually_by_administrator(user) do
186 186 onthefly_creation_failed(user)
187 187 end
188 188 end
189 189 else
190 190 # Existing record
191 191 if user.active?
192 192 successful_authentication(user)
193 193 else
194 194 account_pending
195 195 end
196 196 end
197 197 end
198 198 end
199 199 end
200 200
201 201 def successful_authentication(user)
202 202 # Valid user
203 203 self.logged_user = user
204 204 # generate a key and set cookie if autologin
205 205 if params[:autologin] && Setting.autologin?
206 206 token = Token.create(:user => user, :action => 'autologin')
207 207 cookies[:autologin] = { :value => token.value, :expires => 1.year.from_now }
208 208 end
209 209 call_hook(:controller_account_success_authentication_after, {:user => user })
210 210 redirect_back_or_default :controller => 'my', :action => 'page'
211 211 end
212 212
213 213 # Onthefly creation failed, display the registration form to fill/fix attributes
214 214 def onthefly_creation_failed(user, auth_source_options = { })
215 215 @user = user
216 216 session[:auth_source_registration] = auth_source_options unless auth_source_options.empty?
217 217 render :action => 'register'
218 218 end
219 219
220 220 def invalid_credentials
221 221 logger.warn "Failed login for '#{params[:username]}' from #{request.remote_ip} at #{Time.now.utc}"
222 222 flash.now[:error] = l(:notice_account_invalid_creditentials)
223 223 end
224 224
225 225 # Register a user for email activation.
226 226 #
227 227 # Pass a block for behavior when a user fails to save
228 228 def register_by_email_activation(user, &block)
229 229 token = Token.new(:user => user, :action => "register")
230 230 if user.save and token.save
231 231 Mailer.deliver_register(token)
232 232 flash[:notice] = l(:notice_account_register_done)
233 233 redirect_to :action => 'login'
234 234 else
235 235 yield if block_given?
236 236 end
237 237 end
238 238
239 239 # Automatically register a user
240 240 #
241 241 # Pass a block for behavior when a user fails to save
242 242 def register_automatically(user, &block)
243 243 # Automatic activation
244 user.status = User::STATUS_ACTIVE
244 user.activate
245 245 user.last_login_on = Time.now
246 246 if user.save
247 247 self.logged_user = user
248 248 flash[:notice] = l(:notice_account_activated)
249 249 redirect_to :controller => 'my', :action => 'account'
250 250 else
251 251 yield if block_given?
252 252 end
253 253 end
254 254
255 255 # Manual activation by the administrator
256 256 #
257 257 # Pass a block for behavior when a user fails to save
258 258 def register_manually_by_administrator(user, &block)
259 259 if user.save
260 260 # Sends an email to the administrators
261 261 Mailer.deliver_account_activation_request(user)
262 262 account_pending
263 263 else
264 264 yield if block_given?
265 265 end
266 266 end
267 267
268 268 def account_pending
269 269 flash[:notice] = l(:notice_account_pending)
270 270 redirect_to :action => 'login'
271 271 end
272 272 end
@@ -1,386 +1,410
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2009 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require "digest/sha1"
19 19
20 20 class User < Principal
21 21
22 22 # Account statuses
23 23 STATUS_ANONYMOUS = 0
24 24 STATUS_ACTIVE = 1
25 25 STATUS_REGISTERED = 2
26 26 STATUS_LOCKED = 3
27 27
28 28 USER_FORMATS = {
29 29 :firstname_lastname => '#{firstname} #{lastname}',
30 30 :firstname => '#{firstname}',
31 31 :lastname_firstname => '#{lastname} #{firstname}',
32 32 :lastname_coma_firstname => '#{lastname}, #{firstname}',
33 33 :username => '#{login}'
34 34 }
35 35
36 36 has_and_belongs_to_many :groups, :after_add => Proc.new {|user, group| group.user_added(user)},
37 37 :after_remove => Proc.new {|user, group| group.user_removed(user)}
38 38 has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
39 39 has_many :changesets, :dependent => :nullify
40 40 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
41 41 has_one :rss_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='feeds'"
42 42 has_one :api_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='api'"
43 43 belongs_to :auth_source
44 44
45 45 # Active non-anonymous users scope
46 46 named_scope :active, :conditions => "#{User.table_name}.status = #{STATUS_ACTIVE}"
47 47
48 48 acts_as_customizable
49 49
50 50 attr_accessor :password, :password_confirmation
51 51 attr_accessor :last_before_login_on
52 52 # Prevents unauthorized assignments
53 53 attr_protected :login, :admin, :password, :password_confirmation, :hashed_password, :group_ids
54 54
55 55 validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
56 56 validates_uniqueness_of :login, :if => Proc.new { |user| !user.login.blank? }, :case_sensitive => false
57 57 validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }, :case_sensitive => false
58 58 # Login must contain lettres, numbers, underscores only
59 59 validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
60 60 validates_length_of :login, :maximum => 30
61 61 validates_format_of :firstname, :lastname, :with => /^[\w\s\'\-\.]*$/i
62 62 validates_length_of :firstname, :lastname, :maximum => 30
63 63 validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_nil => true
64 64 validates_length_of :mail, :maximum => 60, :allow_nil => true
65 65 validates_confirmation_of :password, :allow_nil => true
66 66
67 67 def before_create
68 68 self.mail_notification = false
69 69 true
70 70 end
71 71
72 72 def before_save
73 73 # update hashed_password if password was set
74 74 self.hashed_password = User.hash_password(self.password) if self.password && self.auth_source_id.blank?
75 75 end
76 76
77 77 def reload(*args)
78 78 @name = nil
79 79 super
80 80 end
81 81
82 82 def mail=(arg)
83 83 write_attribute(:mail, arg.to_s.strip)
84 84 end
85 85
86 86 def identity_url=(url)
87 87 if url.blank?
88 88 write_attribute(:identity_url, '')
89 89 else
90 90 begin
91 91 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
92 92 rescue OpenIdAuthentication::InvalidOpenId
93 93 # Invlaid url, don't save
94 94 end
95 95 end
96 96 self.read_attribute(:identity_url)
97 97 end
98 98
99 99 # Returns the user that matches provided login and password, or nil
100 100 def self.try_to_login(login, password)
101 101 # Make sure no one can sign in with an empty password
102 102 return nil if password.to_s.empty?
103 103 user = find_by_login(login)
104 104 if user
105 105 # user is already in local database
106 106 return nil if !user.active?
107 107 if user.auth_source
108 108 # user has an external authentication method
109 109 return nil unless user.auth_source.authenticate(login, password)
110 110 else
111 111 # authentication with local password
112 112 return nil unless User.hash_password(password) == user.hashed_password
113 113 end
114 114 else
115 115 # user is not yet registered, try to authenticate with available sources
116 116 attrs = AuthSource.authenticate(login, password)
117 117 if attrs
118 118 user = new(attrs)
119 119 user.login = login
120 120 user.language = Setting.default_language
121 121 if user.save
122 122 user.reload
123 123 logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source
124 124 end
125 125 end
126 126 end
127 127 user.update_attribute(:last_login_on, Time.now) if user && !user.new_record?
128 128 user
129 129 rescue => text
130 130 raise text
131 131 end
132 132
133 133 # Returns the user who matches the given autologin +key+ or nil
134 134 def self.try_to_autologin(key)
135 135 tokens = Token.find_all_by_action_and_value('autologin', key)
136 136 # Make sure there's only 1 token that matches the key
137 137 if tokens.size == 1
138 138 token = tokens.first
139 139 if (token.created_on > Setting.autologin.to_i.day.ago) && token.user && token.user.active?
140 140 token.user.update_attribute(:last_login_on, Time.now)
141 141 token.user
142 142 end
143 143 end
144 144 end
145 145
146 146 # Return user's full name for display
147 147 def name(formatter = nil)
148 148 if formatter
149 149 eval('"' + (USER_FORMATS[formatter] || USER_FORMATS[:firstname_lastname]) + '"')
150 150 else
151 151 @name ||= eval('"' + (USER_FORMATS[Setting.user_format] || USER_FORMATS[:firstname_lastname]) + '"')
152 152 end
153 153 end
154 154
155 155 def active?
156 156 self.status == STATUS_ACTIVE
157 157 end
158 158
159 159 def registered?
160 160 self.status == STATUS_REGISTERED
161 161 end
162 162
163 163 def locked?
164 164 self.status == STATUS_LOCKED
165 165 end
166 166
167 def activate
168 self.status = STATUS_ACTIVE
169 end
170
171 def register
172 self.status = STATUS_REGISTERED
173 end
174
175 def lock
176 self.status = STATUS_LOCKED
177 end
178
179 def activate!
180 update_attribute(:status, STATUS_ACTIVE)
181 end
182
183 def register!
184 update_attribute(:status, STATUS_REGISTERED)
185 end
186
187 def lock!
188 update_attribute(:status, STATUS_LOCKED)
189 end
190
167 191 def check_password?(clear_password)
168 192 if auth_source_id.present?
169 193 auth_source.authenticate(self.login, clear_password)
170 194 else
171 195 User.hash_password(clear_password) == self.hashed_password
172 196 end
173 197 end
174 198
175 199 # Does the backend storage allow this user to change their password?
176 200 def change_password_allowed?
177 201 return true if auth_source_id.blank?
178 202 return auth_source.allow_password_changes?
179 203 end
180 204
181 205 # Generate and set a random password. Useful for automated user creation
182 206 # Based on Token#generate_token_value
183 207 #
184 208 def random_password
185 209 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
186 210 password = ''
187 211 40.times { |i| password << chars[rand(chars.size-1)] }
188 212 self.password = password
189 213 self.password_confirmation = password
190 214 self
191 215 end
192 216
193 217 def pref
194 218 self.preference ||= UserPreference.new(:user => self)
195 219 end
196 220
197 221 def time_zone
198 222 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
199 223 end
200 224
201 225 def wants_comments_in_reverse_order?
202 226 self.pref[:comments_sorting] == 'desc'
203 227 end
204 228
205 229 # Return user's RSS key (a 40 chars long string), used to access feeds
206 230 def rss_key
207 231 token = self.rss_token || Token.create(:user => self, :action => 'feeds')
208 232 token.value
209 233 end
210 234
211 235 # Return user's API key (a 40 chars long string), used to access the API
212 236 def api_key
213 237 token = self.api_token || self.create_api_token(:action => 'api')
214 238 token.value
215 239 end
216 240
217 241 # Return an array of project ids for which the user has explicitly turned mail notifications on
218 242 def notified_projects_ids
219 243 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
220 244 end
221 245
222 246 def notified_project_ids=(ids)
223 247 Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
224 248 Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
225 249 @notified_projects_ids = nil
226 250 notified_projects_ids
227 251 end
228 252
229 253 # Find a user account by matching the exact login and then a case-insensitive
230 254 # version. Exact matches will be given priority.
231 255 def self.find_by_login(login)
232 256 # force string comparison to be case sensitive on MySQL
233 257 type_cast = (ActiveRecord::Base.connection.adapter_name == 'MySQL') ? 'BINARY' : ''
234 258
235 259 # First look for an exact match
236 260 user = first(:conditions => ["#{type_cast} login = ?", login])
237 261 # Fail over to case-insensitive if none was found
238 262 user ||= first(:conditions => ["#{type_cast} LOWER(login) = ?", login.to_s.downcase])
239 263 end
240 264
241 265 def self.find_by_rss_key(key)
242 266 token = Token.find_by_value(key)
243 267 token && token.user.active? ? token.user : nil
244 268 end
245 269
246 270 def self.find_by_api_key(key)
247 271 token = Token.find_by_action_and_value('api', key)
248 272 token && token.user.active? ? token.user : nil
249 273 end
250 274
251 275 # Makes find_by_mail case-insensitive
252 276 def self.find_by_mail(mail)
253 277 find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
254 278 end
255 279
256 280 def to_s
257 281 name
258 282 end
259 283
260 284 # Returns the current day according to user's time zone
261 285 def today
262 286 if time_zone.nil?
263 287 Date.today
264 288 else
265 289 Time.now.in_time_zone(time_zone).to_date
266 290 end
267 291 end
268 292
269 293 def logged?
270 294 true
271 295 end
272 296
273 297 def anonymous?
274 298 !logged?
275 299 end
276 300
277 301 # Return user's roles for project
278 302 def roles_for_project(project)
279 303 roles = []
280 304 # No role on archived projects
281 305 return roles unless project && project.active?
282 306 if logged?
283 307 # Find project membership
284 308 membership = memberships.detect {|m| m.project_id == project.id}
285 309 if membership
286 310 roles = membership.roles
287 311 else
288 312 @role_non_member ||= Role.non_member
289 313 roles << @role_non_member
290 314 end
291 315 else
292 316 @role_anonymous ||= Role.anonymous
293 317 roles << @role_anonymous
294 318 end
295 319 roles
296 320 end
297 321
298 322 # Return true if the user is a member of project
299 323 def member_of?(project)
300 324 !roles_for_project(project).detect {|role| role.member?}.nil?
301 325 end
302 326
303 327 # Return true if the user is allowed to do the specified action on project
304 328 # action can be:
305 329 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
306 330 # * a permission Symbol (eg. :edit_project)
307 331 def allowed_to?(action, project, options={})
308 332 if project
309 333 # No action allowed on archived projects
310 334 return false unless project.active?
311 335 # No action allowed on disabled modules
312 336 return false unless project.allows_to?(action)
313 337 # Admin users are authorized for anything else
314 338 return true if admin?
315 339
316 340 roles = roles_for_project(project)
317 341 return false unless roles
318 342 roles.detect {|role| (project.is_public? || role.member?) && role.allowed_to?(action)}
319 343
320 344 elsif options[:global]
321 345 # Admin users are always authorized
322 346 return true if admin?
323 347
324 348 # authorize if user has at least one role that has this permission
325 349 roles = memberships.collect {|m| m.roles}.flatten.uniq
326 350 roles.detect {|r| r.allowed_to?(action)} || (self.logged? ? Role.non_member.allowed_to?(action) : Role.anonymous.allowed_to?(action))
327 351 else
328 352 false
329 353 end
330 354 end
331 355
332 356 def self.current=(user)
333 357 @current_user = user
334 358 end
335 359
336 360 def self.current
337 361 @current_user ||= User.anonymous
338 362 end
339 363
340 364 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
341 365 # one anonymous user per database.
342 366 def self.anonymous
343 367 anonymous_user = AnonymousUser.find(:first)
344 368 if anonymous_user.nil?
345 369 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
346 370 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
347 371 end
348 372 anonymous_user
349 373 end
350 374
351 375 protected
352 376
353 377 def validate
354 378 # Password length validation based on setting
355 379 if !password.nil? && password.size < Setting.password_min_length.to_i
356 380 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
357 381 end
358 382 end
359 383
360 384 private
361 385
362 386 # Return password digest
363 387 def self.hash_password(clear_password)
364 388 Digest::SHA1.hexdigest(clear_password || "")
365 389 end
366 390 end
367 391
368 392 class AnonymousUser < User
369 393
370 394 def validate_on_create
371 395 # There should be only one AnonymousUser in the database
372 396 errors.add_to_base 'An anonymous user already exists.' if AnonymousUser.find(:first)
373 397 end
374 398
375 399 def available_custom_fields
376 400 []
377 401 end
378 402
379 403 # Overrides a few properties
380 404 def logged?; false end
381 405 def admin; false end
382 406 def name(*args); I18n.t(:label_user_anonymous) end
383 407 def mail; nil end
384 408 def time_zone; nil end
385 409 def rss_key; nil end
386 410 end
General Comments 0
You need to be logged in to leave comments. Login now