##// END OF EJS Templates
Add User#allowed_to_globally? which wraps User#allowed_to?...
Eric Davis -
r4050:6a76aef37520
parent child
Show More
@@ -1,410 +1,416
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 mail=(arg)
83 83 write_attribute(:mail, arg.to_s.strip)
84 84 end
85 85
86 86 def identity_url=(url)
87 87 if url.blank?
88 88 write_attribute(:identity_url, '')
89 89 else
90 90 begin
91 91 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
92 92 rescue OpenIdAuthentication::InvalidOpenId
93 93 # Invlaid url, don't save
94 94 end
95 95 end
96 96 self.read_attribute(:identity_url)
97 97 end
98 98
99 99 # Returns the user that matches provided login and password, or nil
100 100 def self.try_to_login(login, password)
101 101 # Make sure no one can sign in with an empty password
102 102 return nil if password.to_s.empty?
103 103 user = find_by_login(login)
104 104 if user
105 105 # user is already in local database
106 106 return nil if !user.active?
107 107 if user.auth_source
108 108 # user has an external authentication method
109 109 return nil unless user.auth_source.authenticate(login, password)
110 110 else
111 111 # authentication with local password
112 112 return nil unless User.hash_password(password) == user.hashed_password
113 113 end
114 114 else
115 115 # user is not yet registered, try to authenticate with available sources
116 116 attrs = AuthSource.authenticate(login, password)
117 117 if attrs
118 118 user = new(attrs)
119 119 user.login = login
120 120 user.language = Setting.default_language
121 121 if user.save
122 122 user.reload
123 123 logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source
124 124 end
125 125 end
126 126 end
127 127 user.update_attribute(:last_login_on, Time.now) if user && !user.new_record?
128 128 user
129 129 rescue => text
130 130 raise text
131 131 end
132 132
133 133 # Returns the user who matches the given autologin +key+ or nil
134 134 def self.try_to_autologin(key)
135 135 tokens = Token.find_all_by_action_and_value('autologin', key)
136 136 # Make sure there's only 1 token that matches the key
137 137 if tokens.size == 1
138 138 token = tokens.first
139 139 if (token.created_on > Setting.autologin.to_i.day.ago) && token.user && token.user.active?
140 140 token.user.update_attribute(:last_login_on, Time.now)
141 141 token.user
142 142 end
143 143 end
144 144 end
145 145
146 146 # Return user's full name for display
147 147 def name(formatter = nil)
148 148 if formatter
149 149 eval('"' + (USER_FORMATS[formatter] || USER_FORMATS[:firstname_lastname]) + '"')
150 150 else
151 151 @name ||= eval('"' + (USER_FORMATS[Setting.user_format] || USER_FORMATS[:firstname_lastname]) + '"')
152 152 end
153 153 end
154 154
155 155 def active?
156 156 self.status == STATUS_ACTIVE
157 157 end
158 158
159 159 def registered?
160 160 self.status == STATUS_REGISTERED
161 161 end
162 162
163 163 def locked?
164 164 self.status == STATUS_LOCKED
165 165 end
166 166
167 167 def activate
168 168 self.status = STATUS_ACTIVE
169 169 end
170 170
171 171 def register
172 172 self.status = STATUS_REGISTERED
173 173 end
174 174
175 175 def lock
176 176 self.status = STATUS_LOCKED
177 177 end
178 178
179 179 def activate!
180 180 update_attribute(:status, STATUS_ACTIVE)
181 181 end
182 182
183 183 def register!
184 184 update_attribute(:status, STATUS_REGISTERED)
185 185 end
186 186
187 187 def lock!
188 188 update_attribute(:status, STATUS_LOCKED)
189 189 end
190 190
191 191 def check_password?(clear_password)
192 192 if auth_source_id.present?
193 193 auth_source.authenticate(self.login, clear_password)
194 194 else
195 195 User.hash_password(clear_password) == self.hashed_password
196 196 end
197 197 end
198 198
199 199 # Does the backend storage allow this user to change their password?
200 200 def change_password_allowed?
201 201 return true if auth_source_id.blank?
202 202 return auth_source.allow_password_changes?
203 203 end
204 204
205 205 # Generate and set a random password. Useful for automated user creation
206 206 # Based on Token#generate_token_value
207 207 #
208 208 def random_password
209 209 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
210 210 password = ''
211 211 40.times { |i| password << chars[rand(chars.size-1)] }
212 212 self.password = password
213 213 self.password_confirmation = password
214 214 self
215 215 end
216 216
217 217 def pref
218 218 self.preference ||= UserPreference.new(:user => self)
219 219 end
220 220
221 221 def time_zone
222 222 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
223 223 end
224 224
225 225 def wants_comments_in_reverse_order?
226 226 self.pref[:comments_sorting] == 'desc'
227 227 end
228 228
229 229 # Return user's RSS key (a 40 chars long string), used to access feeds
230 230 def rss_key
231 231 token = self.rss_token || Token.create(:user => self, :action => 'feeds')
232 232 token.value
233 233 end
234 234
235 235 # Return user's API key (a 40 chars long string), used to access the API
236 236 def api_key
237 237 token = self.api_token || self.create_api_token(:action => 'api')
238 238 token.value
239 239 end
240 240
241 241 # Return an array of project ids for which the user has explicitly turned mail notifications on
242 242 def notified_projects_ids
243 243 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
244 244 end
245 245
246 246 def notified_project_ids=(ids)
247 247 Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
248 248 Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
249 249 @notified_projects_ids = nil
250 250 notified_projects_ids
251 251 end
252 252
253 253 # Find a user account by matching the exact login and then a case-insensitive
254 254 # version. Exact matches will be given priority.
255 255 def self.find_by_login(login)
256 256 # force string comparison to be case sensitive on MySQL
257 257 type_cast = (ActiveRecord::Base.connection.adapter_name == 'MySQL') ? 'BINARY' : ''
258 258
259 259 # First look for an exact match
260 260 user = first(:conditions => ["#{type_cast} login = ?", login])
261 261 # Fail over to case-insensitive if none was found
262 262 user ||= first(:conditions => ["#{type_cast} LOWER(login) = ?", login.to_s.downcase])
263 263 end
264 264
265 265 def self.find_by_rss_key(key)
266 266 token = Token.find_by_value(key)
267 267 token && token.user.active? ? token.user : nil
268 268 end
269 269
270 270 def self.find_by_api_key(key)
271 271 token = Token.find_by_action_and_value('api', key)
272 272 token && token.user.active? ? token.user : nil
273 273 end
274 274
275 275 # Makes find_by_mail case-insensitive
276 276 def self.find_by_mail(mail)
277 277 find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
278 278 end
279 279
280 280 def to_s
281 281 name
282 282 end
283 283
284 284 # Returns the current day according to user's time zone
285 285 def today
286 286 if time_zone.nil?
287 287 Date.today
288 288 else
289 289 Time.now.in_time_zone(time_zone).to_date
290 290 end
291 291 end
292 292
293 293 def logged?
294 294 true
295 295 end
296 296
297 297 def anonymous?
298 298 !logged?
299 299 end
300 300
301 301 # Return user's roles for project
302 302 def roles_for_project(project)
303 303 roles = []
304 304 # No role on archived projects
305 305 return roles unless project && project.active?
306 306 if logged?
307 307 # Find project membership
308 308 membership = memberships.detect {|m| m.project_id == project.id}
309 309 if membership
310 310 roles = membership.roles
311 311 else
312 312 @role_non_member ||= Role.non_member
313 313 roles << @role_non_member
314 314 end
315 315 else
316 316 @role_anonymous ||= Role.anonymous
317 317 roles << @role_anonymous
318 318 end
319 319 roles
320 320 end
321 321
322 322 # Return true if the user is a member of project
323 323 def member_of?(project)
324 324 !roles_for_project(project).detect {|role| role.member?}.nil?
325 325 end
326 326
327 327 # Return true if the user is allowed to do the specified action on project
328 328 # action can be:
329 329 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
330 330 # * a permission Symbol (eg. :edit_project)
331 331 def allowed_to?(action, project, options={})
332 332 if project
333 333 # No action allowed on archived projects
334 334 return false unless project.active?
335 335 # No action allowed on disabled modules
336 336 return false unless project.allows_to?(action)
337 337 # Admin users are authorized for anything else
338 338 return true if admin?
339 339
340 340 roles = roles_for_project(project)
341 341 return false unless roles
342 342 roles.detect {|role| (project.is_public? || role.member?) && role.allowed_to?(action)}
343 343
344 344 elsif options[:global]
345 345 # Admin users are always authorized
346 346 return true if admin?
347 347
348 348 # authorize if user has at least one role that has this permission
349 349 roles = memberships.collect {|m| m.roles}.flatten.uniq
350 350 roles.detect {|r| r.allowed_to?(action)} || (self.logged? ? Role.non_member.allowed_to?(action) : Role.anonymous.allowed_to?(action))
351 351 else
352 352 false
353 353 end
354 354 end
355
356 # Is the user allowed to do the specified action on any project?
357 # See allowed_to? for the actions and valid options.
358 def allowed_to_globally?(action, options)
359 allowed_to?(action, nil, options.reverse_merge(:global => true))
360 end
355 361
356 362 def self.current=(user)
357 363 @current_user = user
358 364 end
359 365
360 366 def self.current
361 367 @current_user ||= User.anonymous
362 368 end
363 369
364 370 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
365 371 # one anonymous user per database.
366 372 def self.anonymous
367 373 anonymous_user = AnonymousUser.find(:first)
368 374 if anonymous_user.nil?
369 375 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
370 376 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
371 377 end
372 378 anonymous_user
373 379 end
374 380
375 381 protected
376 382
377 383 def validate
378 384 # Password length validation based on setting
379 385 if !password.nil? && password.size < Setting.password_min_length.to_i
380 386 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
381 387 end
382 388 end
383 389
384 390 private
385 391
386 392 # Return password digest
387 393 def self.hash_password(clear_password)
388 394 Digest::SHA1.hexdigest(clear_password || "")
389 395 end
390 396 end
391 397
392 398 class AnonymousUser < User
393 399
394 400 def validate_on_create
395 401 # There should be only one AnonymousUser in the database
396 402 errors.add_to_base 'An anonymous user already exists.' if AnonymousUser.find(:first)
397 403 end
398 404
399 405 def available_custom_fields
400 406 []
401 407 end
402 408
403 409 # Overrides a few properties
404 410 def logged?; false end
405 411 def admin; false end
406 412 def name(*args); I18n.t(:label_user_anonymous) end
407 413 def mail; nil end
408 414 def time_zone; nil end
409 415 def rss_key; nil end
410 416 end
General Comments 0
You need to be logged in to leave comments. Login now