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