##// END OF EJS Templates
Code cleanup: renamed variables in User#allowed_to? with explicit names...
Jean-Baptiste Barth -
r4120:c43ef6e7696e
parent child
Show More
@@ -1,483 +1,483
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 MAIL_NOTIFICATION_OPTIONS = [
37 37 [:all, :label_user_mail_option_all],
38 38 [:selected, :label_user_mail_option_selected],
39 39 [:none, :label_user_mail_option_none],
40 40 [:only_my_events, :label_user_mail_option_only_my_events],
41 41 [:only_assigned, :label_user_mail_option_only_assigned],
42 42 [:only_owner, :label_user_mail_option_only_owner]
43 43 ]
44 44
45 45 has_and_belongs_to_many :groups, :after_add => Proc.new {|user, group| group.user_added(user)},
46 46 :after_remove => Proc.new {|user, group| group.user_removed(user)}
47 47 has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
48 48 has_many :changesets, :dependent => :nullify
49 49 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
50 50 has_one :rss_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='feeds'"
51 51 has_one :api_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='api'"
52 52 belongs_to :auth_source
53 53
54 54 # Active non-anonymous users scope
55 55 named_scope :active, :conditions => "#{User.table_name}.status = #{STATUS_ACTIVE}"
56 56
57 57 acts_as_customizable
58 58
59 59 attr_accessor :password, :password_confirmation
60 60 attr_accessor :last_before_login_on
61 61 # Prevents unauthorized assignments
62 62 attr_protected :login, :admin, :password, :password_confirmation, :hashed_password, :group_ids
63 63
64 64 validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
65 65 validates_uniqueness_of :login, :if => Proc.new { |user| !user.login.blank? }, :case_sensitive => false
66 66 validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }, :case_sensitive => false
67 67 # Login must contain lettres, numbers, underscores only
68 68 validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
69 69 validates_length_of :login, :maximum => 30
70 70 validates_format_of :firstname, :lastname, :with => /^[\w\s\'\-\.]*$/i
71 71 validates_length_of :firstname, :lastname, :maximum => 30
72 72 validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_nil => true
73 73 validates_length_of :mail, :maximum => 60, :allow_nil => true
74 74 validates_confirmation_of :password, :allow_nil => true
75 75
76 76 def before_create
77 77 self.mail_notification = Setting.default_notification_option if self.mail_notification.blank?
78 78 true
79 79 end
80 80
81 81 def before_save
82 82 # update hashed_password if password was set
83 83 self.hashed_password = User.hash_password(self.password) if self.password && self.auth_source_id.blank?
84 84 end
85 85
86 86 def reload(*args)
87 87 @name = nil
88 88 super
89 89 end
90 90
91 91 def mail=(arg)
92 92 write_attribute(:mail, arg.to_s.strip)
93 93 end
94 94
95 95 def identity_url=(url)
96 96 if url.blank?
97 97 write_attribute(:identity_url, '')
98 98 else
99 99 begin
100 100 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
101 101 rescue OpenIdAuthentication::InvalidOpenId
102 102 # Invlaid url, don't save
103 103 end
104 104 end
105 105 self.read_attribute(:identity_url)
106 106 end
107 107
108 108 # Returns the user that matches provided login and password, or nil
109 109 def self.try_to_login(login, password)
110 110 # Make sure no one can sign in with an empty password
111 111 return nil if password.to_s.empty?
112 112 user = find_by_login(login)
113 113 if user
114 114 # user is already in local database
115 115 return nil if !user.active?
116 116 if user.auth_source
117 117 # user has an external authentication method
118 118 return nil unless user.auth_source.authenticate(login, password)
119 119 else
120 120 # authentication with local password
121 121 return nil unless User.hash_password(password) == user.hashed_password
122 122 end
123 123 else
124 124 # user is not yet registered, try to authenticate with available sources
125 125 attrs = AuthSource.authenticate(login, password)
126 126 if attrs
127 127 user = new(attrs)
128 128 user.login = login
129 129 user.language = Setting.default_language
130 130 if user.save
131 131 user.reload
132 132 logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source
133 133 end
134 134 end
135 135 end
136 136 user.update_attribute(:last_login_on, Time.now) if user && !user.new_record?
137 137 user
138 138 rescue => text
139 139 raise text
140 140 end
141 141
142 142 # Returns the user who matches the given autologin +key+ or nil
143 143 def self.try_to_autologin(key)
144 144 tokens = Token.find_all_by_action_and_value('autologin', key)
145 145 # Make sure there's only 1 token that matches the key
146 146 if tokens.size == 1
147 147 token = tokens.first
148 148 if (token.created_on > Setting.autologin.to_i.day.ago) && token.user && token.user.active?
149 149 token.user.update_attribute(:last_login_on, Time.now)
150 150 token.user
151 151 end
152 152 end
153 153 end
154 154
155 155 # Return user's full name for display
156 156 def name(formatter = nil)
157 157 if formatter
158 158 eval('"' + (USER_FORMATS[formatter] || USER_FORMATS[:firstname_lastname]) + '"')
159 159 else
160 160 @name ||= eval('"' + (USER_FORMATS[Setting.user_format] || USER_FORMATS[:firstname_lastname]) + '"')
161 161 end
162 162 end
163 163
164 164 def active?
165 165 self.status == STATUS_ACTIVE
166 166 end
167 167
168 168 def registered?
169 169 self.status == STATUS_REGISTERED
170 170 end
171 171
172 172 def locked?
173 173 self.status == STATUS_LOCKED
174 174 end
175 175
176 176 def activate
177 177 self.status = STATUS_ACTIVE
178 178 end
179 179
180 180 def register
181 181 self.status = STATUS_REGISTERED
182 182 end
183 183
184 184 def lock
185 185 self.status = STATUS_LOCKED
186 186 end
187 187
188 188 def activate!
189 189 update_attribute(:status, STATUS_ACTIVE)
190 190 end
191 191
192 192 def register!
193 193 update_attribute(:status, STATUS_REGISTERED)
194 194 end
195 195
196 196 def lock!
197 197 update_attribute(:status, STATUS_LOCKED)
198 198 end
199 199
200 200 def check_password?(clear_password)
201 201 if auth_source_id.present?
202 202 auth_source.authenticate(self.login, clear_password)
203 203 else
204 204 User.hash_password(clear_password) == self.hashed_password
205 205 end
206 206 end
207 207
208 208 # Does the backend storage allow this user to change their password?
209 209 def change_password_allowed?
210 210 return true if auth_source_id.blank?
211 211 return auth_source.allow_password_changes?
212 212 end
213 213
214 214 # Generate and set a random password. Useful for automated user creation
215 215 # Based on Token#generate_token_value
216 216 #
217 217 def random_password
218 218 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
219 219 password = ''
220 220 40.times { |i| password << chars[rand(chars.size-1)] }
221 221 self.password = password
222 222 self.password_confirmation = password
223 223 self
224 224 end
225 225
226 226 def pref
227 227 self.preference ||= UserPreference.new(:user => self)
228 228 end
229 229
230 230 def time_zone
231 231 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
232 232 end
233 233
234 234 def wants_comments_in_reverse_order?
235 235 self.pref[:comments_sorting] == 'desc'
236 236 end
237 237
238 238 # Return user's RSS key (a 40 chars long string), used to access feeds
239 239 def rss_key
240 240 token = self.rss_token || Token.create(:user => self, :action => 'feeds')
241 241 token.value
242 242 end
243 243
244 244 # Return user's API key (a 40 chars long string), used to access the API
245 245 def api_key
246 246 token = self.api_token || self.create_api_token(:action => 'api')
247 247 token.value
248 248 end
249 249
250 250 # Return an array of project ids for which the user has explicitly turned mail notifications on
251 251 def notified_projects_ids
252 252 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
253 253 end
254 254
255 255 def notified_project_ids=(ids)
256 256 Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
257 257 Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
258 258 @notified_projects_ids = nil
259 259 notified_projects_ids
260 260 end
261 261
262 262 # Only users that belong to more than 1 project can select projects for which they are notified
263 263 def valid_notification_options
264 264 # Note that @user.membership.size would fail since AR ignores
265 265 # :include association option when doing a count
266 266 if memberships.length < 1
267 267 MAIL_NOTIFICATION_OPTIONS.delete_if {|option| option.first == :selected}
268 268 else
269 269 MAIL_NOTIFICATION_OPTIONS
270 270 end
271 271 end
272 272
273 273 # Find a user account by matching the exact login and then a case-insensitive
274 274 # version. Exact matches will be given priority.
275 275 def self.find_by_login(login)
276 276 # force string comparison to be case sensitive on MySQL
277 277 type_cast = (ActiveRecord::Base.connection.adapter_name == 'MySQL') ? 'BINARY' : ''
278 278
279 279 # First look for an exact match
280 280 user = first(:conditions => ["#{type_cast} login = ?", login])
281 281 # Fail over to case-insensitive if none was found
282 282 user ||= first(:conditions => ["#{type_cast} LOWER(login) = ?", login.to_s.downcase])
283 283 end
284 284
285 285 def self.find_by_rss_key(key)
286 286 token = Token.find_by_value(key)
287 287 token && token.user.active? ? token.user : nil
288 288 end
289 289
290 290 def self.find_by_api_key(key)
291 291 token = Token.find_by_action_and_value('api', key)
292 292 token && token.user.active? ? token.user : nil
293 293 end
294 294
295 295 # Makes find_by_mail case-insensitive
296 296 def self.find_by_mail(mail)
297 297 find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
298 298 end
299 299
300 300 def to_s
301 301 name
302 302 end
303 303
304 304 # Returns the current day according to user's time zone
305 305 def today
306 306 if time_zone.nil?
307 307 Date.today
308 308 else
309 309 Time.now.in_time_zone(time_zone).to_date
310 310 end
311 311 end
312 312
313 313 def logged?
314 314 true
315 315 end
316 316
317 317 def anonymous?
318 318 !logged?
319 319 end
320 320
321 321 # Return user's roles for project
322 322 def roles_for_project(project)
323 323 roles = []
324 324 # No role on archived projects
325 325 return roles unless project && project.active?
326 326 if logged?
327 327 # Find project membership
328 328 membership = memberships.detect {|m| m.project_id == project.id}
329 329 if membership
330 330 roles = membership.roles
331 331 else
332 332 @role_non_member ||= Role.non_member
333 333 roles << @role_non_member
334 334 end
335 335 else
336 336 @role_anonymous ||= Role.anonymous
337 337 roles << @role_anonymous
338 338 end
339 339 roles
340 340 end
341 341
342 342 # Return true if the user is a member of project
343 343 def member_of?(project)
344 344 !roles_for_project(project).detect {|role| role.member?}.nil?
345 345 end
346 346
347 347 # Return true if the user is allowed to do the specified action on a specific context
348 348 # Action can be:
349 349 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
350 350 # * a permission Symbol (eg. :edit_project)
351 351 # Context can be:
352 352 # * a project : returns true if user is allowed to do the specified action on this project
353 353 # * a group of projects : returns true if user is allowed on every project
354 354 # * nil with options[:global] set : check if user has at least one role allowed for this action,
355 355 # or falls back to Non Member / Anonymous permissions depending if the user is logged
356 def allowed_to?(action, project, options={})
357 if project && project.is_a?(Project)
356 def allowed_to?(action, context, options={})
357 if context && context.is_a?(Project)
358 358 # No action allowed on archived projects
359 return false unless project.active?
359 return false unless context.active?
360 360 # No action allowed on disabled modules
361 return false unless project.allows_to?(action)
361 return false unless context.allows_to?(action)
362 362 # Admin users are authorized for anything else
363 363 return true if admin?
364 364
365 roles = roles_for_project(project)
365 roles = roles_for_project(context)
366 366 return false unless roles
367 roles.detect {|role| (project.is_public? || role.member?) && role.allowed_to?(action)}
367 roles.detect {|role| (context.is_public? || role.member?) && role.allowed_to?(action)}
368 368
369 elsif project && project.is_a?(Array)
369 elsif context && context.is_a?(Array)
370 370 # Authorize if user is authorized on every element of the array
371 project.map do |p|
372 allowed_to?(action,p,options)
373 end.inject do |memo,p|
374 memo && p
371 context.map do |project|
372 allowed_to?(action,project,options)
373 end.inject do |memo,allowed|
374 memo && allowed
375 375 end
376 376 elsif options[:global]
377 377 # Admin users are always authorized
378 378 return true if admin?
379 379
380 380 # authorize if user has at least one role that has this permission
381 381 roles = memberships.collect {|m| m.roles}.flatten.uniq
382 382 roles.detect {|r| r.allowed_to?(action)} || (self.logged? ? Role.non_member.allowed_to?(action) : Role.anonymous.allowed_to?(action))
383 383 else
384 384 false
385 385 end
386 386 end
387 387
388 388 # Is the user allowed to do the specified action on any project?
389 389 # See allowed_to? for the actions and valid options.
390 390 def allowed_to_globally?(action, options)
391 391 allowed_to?(action, nil, options.reverse_merge(:global => true))
392 392 end
393 393
394 394 # Utility method to help check if a user should be notified about an
395 395 # event.
396 396 #
397 397 # TODO: only supports Issue events currently
398 398 def notify_about?(object)
399 399 case mail_notification.to_sym
400 400 when :all
401 401 true
402 402 when :selected
403 403 # Handled by the Project
404 404 when :none
405 405 false
406 406 when :only_my_events
407 407 if object.is_a?(Issue) && (object.author == self || object.assigned_to == self)
408 408 true
409 409 else
410 410 false
411 411 end
412 412 when :only_assigned
413 413 if object.is_a?(Issue) && object.assigned_to == self
414 414 true
415 415 else
416 416 false
417 417 end
418 418 when :only_owner
419 419 if object.is_a?(Issue) && object.author == self
420 420 true
421 421 else
422 422 false
423 423 end
424 424 else
425 425 false
426 426 end
427 427 end
428 428
429 429 def self.current=(user)
430 430 @current_user = user
431 431 end
432 432
433 433 def self.current
434 434 @current_user ||= User.anonymous
435 435 end
436 436
437 437 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
438 438 # one anonymous user per database.
439 439 def self.anonymous
440 440 anonymous_user = AnonymousUser.find(:first)
441 441 if anonymous_user.nil?
442 442 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
443 443 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
444 444 end
445 445 anonymous_user
446 446 end
447 447
448 448 protected
449 449
450 450 def validate
451 451 # Password length validation based on setting
452 452 if !password.nil? && password.size < Setting.password_min_length.to_i
453 453 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
454 454 end
455 455 end
456 456
457 457 private
458 458
459 459 # Return password digest
460 460 def self.hash_password(clear_password)
461 461 Digest::SHA1.hexdigest(clear_password || "")
462 462 end
463 463 end
464 464
465 465 class AnonymousUser < User
466 466
467 467 def validate_on_create
468 468 # There should be only one AnonymousUser in the database
469 469 errors.add_to_base 'An anonymous user already exists.' if AnonymousUser.find(:first)
470 470 end
471 471
472 472 def available_custom_fields
473 473 []
474 474 end
475 475
476 476 # Overrides a few properties
477 477 def logged?; false end
478 478 def admin; false end
479 479 def name(*args); I18n.t(:label_user_anonymous) end
480 480 def mail; nil end
481 481 def time_zone; nil end
482 482 def rss_key; nil end
483 483 end
General Comments 0
You need to be logged in to leave comments. Login now