##// END OF EJS Templates
Refactor and documentation for User#find_by_login....
Eric Davis -
r3694:6cb4ff7d8981
parent child
Show More
@@ -1,379 +1,379
1 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 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_by_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 external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source
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 131 tokens = Token.find_all_by_action_and_value('autologin', key)
132 132 # Make sure there's only 1 token that matches the key
133 133 if tokens.size == 1
134 134 token = tokens.first
135 135 if (token.created_on > Setting.autologin.to_i.day.ago) && token.user && token.user.active?
136 136 token.user.update_attribute(:last_login_on, Time.now)
137 137 token.user
138 138 end
139 139 end
140 140 end
141 141
142 142 # Return user's full name for display
143 143 def name(formatter = nil)
144 144 if formatter
145 145 eval('"' + (USER_FORMATS[formatter] || USER_FORMATS[:firstname_lastname]) + '"')
146 146 else
147 147 @name ||= eval('"' + (USER_FORMATS[Setting.user_format] || USER_FORMATS[:firstname_lastname]) + '"')
148 148 end
149 149 end
150 150
151 151 def active?
152 152 self.status == STATUS_ACTIVE
153 153 end
154 154
155 155 def registered?
156 156 self.status == STATUS_REGISTERED
157 157 end
158 158
159 159 def locked?
160 160 self.status == STATUS_LOCKED
161 161 end
162 162
163 163 def check_password?(clear_password)
164 164 if auth_source_id.present?
165 165 auth_source.authenticate(self.login, clear_password)
166 166 else
167 167 User.hash_password(clear_password) == self.hashed_password
168 168 end
169 169 end
170 170
171 171 # Does the backend storage allow this user to change their password?
172 172 def change_password_allowed?
173 173 return true if auth_source_id.blank?
174 174 return auth_source.allow_password_changes?
175 175 end
176 176
177 177 # Generate and set a random password. Useful for automated user creation
178 178 # Based on Token#generate_token_value
179 179 #
180 180 def random_password
181 181 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
182 182 password = ''
183 183 40.times { |i| password << chars[rand(chars.size-1)] }
184 184 self.password = password
185 185 self.password_confirmation = password
186 186 self
187 187 end
188 188
189 189 def pref
190 190 self.preference ||= UserPreference.new(:user => self)
191 191 end
192 192
193 193 def time_zone
194 194 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
195 195 end
196 196
197 197 def wants_comments_in_reverse_order?
198 198 self.pref[:comments_sorting] == 'desc'
199 199 end
200 200
201 201 # Return user's RSS key (a 40 chars long string), used to access feeds
202 202 def rss_key
203 203 token = self.rss_token || Token.create(:user => self, :action => 'feeds')
204 204 token.value
205 205 end
206 206
207 207 # Return user's API key (a 40 chars long string), used to access the API
208 208 def api_key
209 209 token = self.api_token || self.create_api_token(:action => 'api')
210 210 token.value
211 211 end
212 212
213 213 # Return an array of project ids for which the user has explicitly turned mail notifications on
214 214 def notified_projects_ids
215 215 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
216 216 end
217 217
218 218 def notified_project_ids=(ids)
219 219 Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
220 220 Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
221 221 @notified_projects_ids = nil
222 222 notified_projects_ids
223 223 end
224
225 # case-insensitive fall-over
224
225 # Find a user account by matching the exact login and then a case-insensitive
226 # version. Exact matches will be given priority.
226 227 def self.find_by_login(login)
227 228 # First look for an exact match
228 user = find(:first, :conditions => ["login = ?", login])
229 user = first(:conditions => {:login => login})
229 230 # Fail over to case-insensitive if none was found
230 user = find(:first, :conditions => ["LOWER(login) = ?", login.to_s.downcase]) if user.nil?
231 return user
231 user ||= first(:conditions => ["LOWER(login) = ?", login.to_s.downcase])
232 232 end
233 233
234 234 def self.find_by_rss_key(key)
235 235 token = Token.find_by_value(key)
236 236 token && token.user.active? ? token.user : nil
237 237 end
238 238
239 239 def self.find_by_api_key(key)
240 240 token = Token.find_by_action_and_value('api', key)
241 241 token && token.user.active? ? token.user : nil
242 242 end
243 243
244 244 # Makes find_by_mail case-insensitive
245 245 def self.find_by_mail(mail)
246 246 find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
247 247 end
248 248
249 249 def to_s
250 250 name
251 251 end
252 252
253 253 # Returns the current day according to user's time zone
254 254 def today
255 255 if time_zone.nil?
256 256 Date.today
257 257 else
258 258 Time.now.in_time_zone(time_zone).to_date
259 259 end
260 260 end
261 261
262 262 def logged?
263 263 true
264 264 end
265 265
266 266 def anonymous?
267 267 !logged?
268 268 end
269 269
270 270 # Return user's roles for project
271 271 def roles_for_project(project)
272 272 roles = []
273 273 # No role on archived projects
274 274 return roles unless project && project.active?
275 275 if logged?
276 276 # Find project membership
277 277 membership = memberships.detect {|m| m.project_id == project.id}
278 278 if membership
279 279 roles = membership.roles
280 280 else
281 281 @role_non_member ||= Role.non_member
282 282 roles << @role_non_member
283 283 end
284 284 else
285 285 @role_anonymous ||= Role.anonymous
286 286 roles << @role_anonymous
287 287 end
288 288 roles
289 289 end
290 290
291 291 # Return true if the user is a member of project
292 292 def member_of?(project)
293 293 !roles_for_project(project).detect {|role| role.member?}.nil?
294 294 end
295 295
296 296 # Return true if the user is allowed to do the specified action on project
297 297 # action can be:
298 298 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
299 299 # * a permission Symbol (eg. :edit_project)
300 300 def allowed_to?(action, project, options={})
301 301 if project
302 302 # No action allowed on archived projects
303 303 return false unless project.active?
304 304 # No action allowed on disabled modules
305 305 return false unless project.allows_to?(action)
306 306 # Admin users are authorized for anything else
307 307 return true if admin?
308 308
309 309 roles = roles_for_project(project)
310 310 return false unless roles
311 311 roles.detect {|role| (project.is_public? || role.member?) && role.allowed_to?(action)}
312 312
313 313 elsif options[:global]
314 314 # Admin users are always authorized
315 315 return true if admin?
316 316
317 317 # authorize if user has at least one role that has this permission
318 318 roles = memberships.collect {|m| m.roles}.flatten.uniq
319 319 roles.detect {|r| r.allowed_to?(action)} || (self.logged? ? Role.non_member.allowed_to?(action) : Role.anonymous.allowed_to?(action))
320 320 else
321 321 false
322 322 end
323 323 end
324 324
325 325 def self.current=(user)
326 326 @current_user = user
327 327 end
328 328
329 329 def self.current
330 330 @current_user ||= User.anonymous
331 331 end
332 332
333 333 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
334 334 # one anonymous user per database.
335 335 def self.anonymous
336 336 anonymous_user = AnonymousUser.find(:first)
337 337 if anonymous_user.nil?
338 338 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
339 339 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
340 340 end
341 341 anonymous_user
342 342 end
343 343
344 344 protected
345 345
346 346 def validate
347 347 # Password length validation based on setting
348 348 if !password.nil? && password.size < Setting.password_min_length.to_i
349 349 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
350 350 end
351 351 end
352 352
353 353 private
354 354
355 355 # Return password digest
356 356 def self.hash_password(clear_password)
357 357 Digest::SHA1.hexdigest(clear_password || "")
358 358 end
359 359 end
360 360
361 361 class AnonymousUser < User
362 362
363 363 def validate_on_create
364 364 # There should be only one AnonymousUser in the database
365 365 errors.add_to_base 'An anonymous user already exists.' if AnonymousUser.find(:first)
366 366 end
367 367
368 368 def available_custom_fields
369 369 []
370 370 end
371 371
372 372 # Overrides a few properties
373 373 def logged?; false end
374 374 def admin; false end
375 375 def name(*args); I18n.t(:label_user_anonymous) end
376 376 def mail; nil end
377 377 def time_zone; nil end
378 378 def rss_key; nil end
379 379 end
General Comments 0
You need to be logged in to leave comments. Login now