##// END OF EJS Templates
Do not autologin if more that one token is found (#3351)....
Jean-Philippe Lang -
r2643:b87753c90d2b
parent child
Show More
@@ -1,338 +1,342
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 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 < ActiveRecord::Base
20 class User < ActiveRecord::Base
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_many :memberships, :class_name => 'Member', :include => [ :project, :roles ], :conditions => "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}", :order => "#{Project.table_name}.name"
36 has_many :memberships, :class_name => 'Member', :include => [ :project, :roles ], :conditions => "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}", :order => "#{Project.table_name}.name"
37 has_many :members, :dependent => :delete_all
37 has_many :members, :dependent => :delete_all
38 has_many :projects, :through => :memberships
38 has_many :projects, :through => :memberships
39 has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
39 has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
40 has_many :changesets, :dependent => :nullify
40 has_many :changesets, :dependent => :nullify
41 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
41 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
42 has_one :rss_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='feeds'"
42 has_one :rss_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='feeds'"
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
53 attr_protected :login, :admin, :password, :password_confirmation, :hashed_password
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? }
56 validates_uniqueness_of :login, :if => Proc.new { |user| !user.login.blank? }
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
74 self.hashed_password = User.hash_password(self.password) if self.password
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(:first, :conditions => ["login=?", login])
99 user = find(:first, :conditions => ["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 the LDAP") if logger
119 logger.info("User '#{user.login}' created from the LDAP") if logger
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 token = Token.find_by_action_and_value('autologin', key)
131 tokens = Token.find_all_by_action_and_value('autologin', key)
132 if token && (token.created_on > Setting.autologin.to_i.day.ago) && token.user && token.user.active?
132 # Make sure there's only 1 token that matches the key
133 if tokens.size == 1
134 token = tokens.first
135 if (token.created_on > Setting.autologin.to_i.day.ago) && token.user && token.user.active?
133 token.user.update_attribute(:last_login_on, Time.now)
136 token.user.update_attribute(:last_login_on, Time.now)
134 token.user
137 token.user
135 end
138 end
136 end
139 end
140 end
137
141
138 # Return user's full name for display
142 # Return user's full name for display
139 def name(formatter = nil)
143 def name(formatter = nil)
140 if formatter
144 if formatter
141 eval('"' + (USER_FORMATS[formatter] || USER_FORMATS[:firstname_lastname]) + '"')
145 eval('"' + (USER_FORMATS[formatter] || USER_FORMATS[:firstname_lastname]) + '"')
142 else
146 else
143 @name ||= eval('"' + (USER_FORMATS[Setting.user_format] || USER_FORMATS[:firstname_lastname]) + '"')
147 @name ||= eval('"' + (USER_FORMATS[Setting.user_format] || USER_FORMATS[:firstname_lastname]) + '"')
144 end
148 end
145 end
149 end
146
150
147 def active?
151 def active?
148 self.status == STATUS_ACTIVE
152 self.status == STATUS_ACTIVE
149 end
153 end
150
154
151 def registered?
155 def registered?
152 self.status == STATUS_REGISTERED
156 self.status == STATUS_REGISTERED
153 end
157 end
154
158
155 def locked?
159 def locked?
156 self.status == STATUS_LOCKED
160 self.status == STATUS_LOCKED
157 end
161 end
158
162
159 def check_password?(clear_password)
163 def check_password?(clear_password)
160 User.hash_password(clear_password) == self.hashed_password
164 User.hash_password(clear_password) == self.hashed_password
161 end
165 end
162
166
163 # Generate and set a random password. Useful for automated user creation
167 # Generate and set a random password. Useful for automated user creation
164 # Based on Token#generate_token_value
168 # Based on Token#generate_token_value
165 #
169 #
166 def random_password
170 def random_password
167 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
171 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
168 password = ''
172 password = ''
169 40.times { |i| password << chars[rand(chars.size-1)] }
173 40.times { |i| password << chars[rand(chars.size-1)] }
170 self.password = password
174 self.password = password
171 self.password_confirmation = password
175 self.password_confirmation = password
172 self
176 self
173 end
177 end
174
178
175 def pref
179 def pref
176 self.preference ||= UserPreference.new(:user => self)
180 self.preference ||= UserPreference.new(:user => self)
177 end
181 end
178
182
179 def time_zone
183 def time_zone
180 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
184 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
181 end
185 end
182
186
183 def wants_comments_in_reverse_order?
187 def wants_comments_in_reverse_order?
184 self.pref[:comments_sorting] == 'desc'
188 self.pref[:comments_sorting] == 'desc'
185 end
189 end
186
190
187 # Return user's RSS key (a 40 chars long string), used to access feeds
191 # Return user's RSS key (a 40 chars long string), used to access feeds
188 def rss_key
192 def rss_key
189 token = self.rss_token || Token.create(:user => self, :action => 'feeds')
193 token = self.rss_token || Token.create(:user => self, :action => 'feeds')
190 token.value
194 token.value
191 end
195 end
192
196
193 # Return an array of project ids for which the user has explicitly turned mail notifications on
197 # Return an array of project ids for which the user has explicitly turned mail notifications on
194 def notified_projects_ids
198 def notified_projects_ids
195 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
199 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
196 end
200 end
197
201
198 def notified_project_ids=(ids)
202 def notified_project_ids=(ids)
199 Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
203 Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
200 Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
204 Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
201 @notified_projects_ids = nil
205 @notified_projects_ids = nil
202 notified_projects_ids
206 notified_projects_ids
203 end
207 end
204
208
205 def self.find_by_rss_key(key)
209 def self.find_by_rss_key(key)
206 token = Token.find_by_value(key)
210 token = Token.find_by_value(key)
207 token && token.user.active? ? token.user : nil
211 token && token.user.active? ? token.user : nil
208 end
212 end
209
213
210 # Makes find_by_mail case-insensitive
214 # Makes find_by_mail case-insensitive
211 def self.find_by_mail(mail)
215 def self.find_by_mail(mail)
212 find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
216 find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
213 end
217 end
214
218
215 # Sort users by their display names
219 # Sort users by their display names
216 def <=>(user)
220 def <=>(user)
217 self.to_s.downcase <=> user.to_s.downcase
221 self.to_s.downcase <=> user.to_s.downcase
218 end
222 end
219
223
220 def to_s
224 def to_s
221 name
225 name
222 end
226 end
223
227
224 def logged?
228 def logged?
225 true
229 true
226 end
230 end
227
231
228 def anonymous?
232 def anonymous?
229 !logged?
233 !logged?
230 end
234 end
231
235
232 # Return user's roles for project
236 # Return user's roles for project
233 def roles_for_project(project)
237 def roles_for_project(project)
234 roles = []
238 roles = []
235 # No role on archived projects
239 # No role on archived projects
236 return roles unless project && project.active?
240 return roles unless project && project.active?
237 if logged?
241 if logged?
238 # Find project membership
242 # Find project membership
239 membership = memberships.detect {|m| m.project_id == project.id}
243 membership = memberships.detect {|m| m.project_id == project.id}
240 if membership
244 if membership
241 roles = membership.roles
245 roles = membership.roles
242 else
246 else
243 @role_non_member ||= Role.non_member
247 @role_non_member ||= Role.non_member
244 roles << @role_non_member
248 roles << @role_non_member
245 end
249 end
246 else
250 else
247 @role_anonymous ||= Role.anonymous
251 @role_anonymous ||= Role.anonymous
248 roles << @role_anonymous
252 roles << @role_anonymous
249 end
253 end
250 roles
254 roles
251 end
255 end
252
256
253 # Return true if the user is a member of project
257 # Return true if the user is a member of project
254 def member_of?(project)
258 def member_of?(project)
255 !roles_for_project(project).detect {|role| role.member?}.nil?
259 !roles_for_project(project).detect {|role| role.member?}.nil?
256 end
260 end
257
261
258 # Return true if the user is allowed to do the specified action on project
262 # Return true if the user is allowed to do the specified action on project
259 # action can be:
263 # action can be:
260 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
264 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
261 # * a permission Symbol (eg. :edit_project)
265 # * a permission Symbol (eg. :edit_project)
262 def allowed_to?(action, project, options={})
266 def allowed_to?(action, project, options={})
263 if project
267 if project
264 # No action allowed on archived projects
268 # No action allowed on archived projects
265 return false unless project.active?
269 return false unless project.active?
266 # No action allowed on disabled modules
270 # No action allowed on disabled modules
267 return false unless project.allows_to?(action)
271 return false unless project.allows_to?(action)
268 # Admin users are authorized for anything else
272 # Admin users are authorized for anything else
269 return true if admin?
273 return true if admin?
270
274
271 roles = roles_for_project(project)
275 roles = roles_for_project(project)
272 return false unless roles
276 return false unless roles
273 roles.detect {|role| (project.is_public? || role.member?) && role.allowed_to?(action)}
277 roles.detect {|role| (project.is_public? || role.member?) && role.allowed_to?(action)}
274
278
275 elsif options[:global]
279 elsif options[:global]
276 # authorize if user has at least one role that has this permission
280 # authorize if user has at least one role that has this permission
277 roles = memberships.collect {|m| m.roles}.flatten.uniq
281 roles = memberships.collect {|m| m.roles}.flatten.uniq
278 roles.detect {|r| r.allowed_to?(action)} || (self.logged? ? Role.non_member.allowed_to?(action) : Role.anonymous.allowed_to?(action))
282 roles.detect {|r| r.allowed_to?(action)} || (self.logged? ? Role.non_member.allowed_to?(action) : Role.anonymous.allowed_to?(action))
279 else
283 else
280 false
284 false
281 end
285 end
282 end
286 end
283
287
284 def self.current=(user)
288 def self.current=(user)
285 @current_user = user
289 @current_user = user
286 end
290 end
287
291
288 def self.current
292 def self.current
289 @current_user ||= User.anonymous
293 @current_user ||= User.anonymous
290 end
294 end
291
295
292 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
296 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
293 # one anonymous user per database.
297 # one anonymous user per database.
294 def self.anonymous
298 def self.anonymous
295 anonymous_user = AnonymousUser.find(:first)
299 anonymous_user = AnonymousUser.find(:first)
296 if anonymous_user.nil?
300 if anonymous_user.nil?
297 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
301 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
298 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
302 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
299 end
303 end
300 anonymous_user
304 anonymous_user
301 end
305 end
302
306
303 protected
307 protected
304
308
305 def validate
309 def validate
306 # Password length validation based on setting
310 # Password length validation based on setting
307 if !password.nil? && password.size < Setting.password_min_length.to_i
311 if !password.nil? && password.size < Setting.password_min_length.to_i
308 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
312 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
309 end
313 end
310 end
314 end
311
315
312 private
316 private
313
317
314 # Return password digest
318 # Return password digest
315 def self.hash_password(clear_password)
319 def self.hash_password(clear_password)
316 Digest::SHA1.hexdigest(clear_password || "")
320 Digest::SHA1.hexdigest(clear_password || "")
317 end
321 end
318 end
322 end
319
323
320 class AnonymousUser < User
324 class AnonymousUser < User
321
325
322 def validate_on_create
326 def validate_on_create
323 # There should be only one AnonymousUser in the database
327 # There should be only one AnonymousUser in the database
324 errors.add_to_base 'An anonymous user already exists.' if AnonymousUser.find(:first)
328 errors.add_to_base 'An anonymous user already exists.' if AnonymousUser.find(:first)
325 end
329 end
326
330
327 def available_custom_fields
331 def available_custom_fields
328 []
332 []
329 end
333 end
330
334
331 # Overrides a few properties
335 # Overrides a few properties
332 def logged?; false end
336 def logged?; false end
333 def admin; false end
337 def admin; false end
334 def name; 'Anonymous' end
338 def name; 'Anonymous' end
335 def mail; nil end
339 def mail; nil end
336 def time_zone; nil end
340 def time_zone; nil end
337 def rss_key; nil end
341 def rss_key; nil end
338 end
342 end
General Comments 0
You need to be logged in to leave comments. Login now