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