##// END OF EJS Templates
Display users then groups on project memberships view (#4389)....
Jean-Philippe Lang -
r3047:21b52d2fd968
parent child
Show More
@@ -1,38 +1,43
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 class Principal < ActiveRecord::Base
19 19 set_table_name 'users'
20 20
21 21 has_many :members, :foreign_key => 'user_id', :dependent => :destroy
22 22 has_many :memberships, :class_name => 'Member', :foreign_key => 'user_id', :include => [ :project, :roles ], :conditions => "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}", :order => "#{Project.table_name}.name"
23 23 has_many :projects, :through => :memberships
24 24
25 25 # Groups and active users
26 26 named_scope :active, :conditions => "#{Principal.table_name}.type='Group' OR (#{Principal.table_name}.type='User' AND #{Principal.table_name}.status = 1)"
27 27
28 28 named_scope :like, lambda {|q|
29 29 s = "%#{q.to_s.strip.downcase}%"
30 30 {:conditions => ["LOWER(login) LIKE :s OR LOWER(firstname) LIKE :s OR LOWER(lastname) LIKE :s OR LOWER(mail) LIKE :s", {:s => s}],
31 31 :order => 'type, login, lastname, firstname, mail'
32 32 }
33 33 }
34 34
35 35 def <=>(principal)
36 self.to_s.downcase <=> principal.to_s.downcase
36 if self.class.name == principal.class.name
37 self.to_s.downcase <=> principal.to_s.downcase
38 else
39 # groups after users
40 principal.class.name <=> self.class.name
41 end
37 42 end
38 43 end
@@ -1,353 +1,348
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 belongs_to :auth_source
43 43
44 44 # Active non-anonymous users scope
45 45 named_scope :active, :conditions => "#{User.table_name}.status = #{STATUS_ACTIVE}"
46 46
47 47 acts_as_customizable
48 48
49 49 attr_accessor :password, :password_confirmation
50 50 attr_accessor :last_before_login_on
51 51 # Prevents unauthorized assignments
52 52 attr_protected :login, :admin, :password, :password_confirmation, :hashed_password, :group_ids
53 53
54 54 validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
55 55 validates_uniqueness_of :login, :if => Proc.new { |user| !user.login.blank? }
56 56 validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }, :case_sensitive => false
57 57 # Login must contain lettres, numbers, underscores only
58 58 validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
59 59 validates_length_of :login, :maximum => 30
60 60 validates_format_of :firstname, :lastname, :with => /^[\w\s\'\-\.]*$/i
61 61 validates_length_of :firstname, :lastname, :maximum => 30
62 62 validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_nil => true
63 63 validates_length_of :mail, :maximum => 60, :allow_nil => true
64 64 validates_confirmation_of :password, :allow_nil => true
65 65
66 66 def before_create
67 67 self.mail_notification = false
68 68 true
69 69 end
70 70
71 71 def before_save
72 72 # update hashed_password if password was set
73 73 self.hashed_password = User.hash_password(self.password) if self.password
74 74 end
75 75
76 76 def reload(*args)
77 77 @name = nil
78 78 super
79 79 end
80 80
81 81 def identity_url=(url)
82 82 if url.blank?
83 83 write_attribute(:identity_url, '')
84 84 else
85 85 begin
86 86 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
87 87 rescue OpenIdAuthentication::InvalidOpenId
88 88 # Invlaid url, don't save
89 89 end
90 90 end
91 91 self.read_attribute(:identity_url)
92 92 end
93 93
94 94 # Returns the user that matches provided login and password, or nil
95 95 def self.try_to_login(login, password)
96 96 # Make sure no one can sign in with an empty password
97 97 return nil if password.to_s.empty?
98 98 user = find(:first, :conditions => ["login=?", login])
99 99 if user
100 100 # user is already in local database
101 101 return nil if !user.active?
102 102 if user.auth_source
103 103 # user has an external authentication method
104 104 return nil unless user.auth_source.authenticate(login, password)
105 105 else
106 106 # authentication with local password
107 107 return nil unless User.hash_password(password) == user.hashed_password
108 108 end
109 109 else
110 110 # user is not yet registered, try to authenticate with available sources
111 111 attrs = AuthSource.authenticate(login, password)
112 112 if attrs
113 113 user = new(*attrs)
114 114 user.login = login
115 115 user.language = Setting.default_language
116 116 if user.save
117 117 user.reload
118 118 logger.info("User '#{user.login}' created from the LDAP") if logger
119 119 end
120 120 end
121 121 end
122 122 user.update_attribute(:last_login_on, Time.now) if user && !user.new_record?
123 123 user
124 124 rescue => text
125 125 raise text
126 126 end
127 127
128 128 # Returns the user who matches the given autologin +key+ or nil
129 129 def self.try_to_autologin(key)
130 130 tokens = Token.find_all_by_action_and_value('autologin', key)
131 131 # Make sure there's only 1 token that matches the key
132 132 if tokens.size == 1
133 133 token = tokens.first
134 134 if (token.created_on > Setting.autologin.to_i.day.ago) && token.user && token.user.active?
135 135 token.user.update_attribute(:last_login_on, Time.now)
136 136 token.user
137 137 end
138 138 end
139 139 end
140 140
141 141 # Return user's full name for display
142 142 def name(formatter = nil)
143 143 if formatter
144 144 eval('"' + (USER_FORMATS[formatter] || USER_FORMATS[:firstname_lastname]) + '"')
145 145 else
146 146 @name ||= eval('"' + (USER_FORMATS[Setting.user_format] || USER_FORMATS[:firstname_lastname]) + '"')
147 147 end
148 148 end
149 149
150 150 def active?
151 151 self.status == STATUS_ACTIVE
152 152 end
153 153
154 154 def registered?
155 155 self.status == STATUS_REGISTERED
156 156 end
157 157
158 158 def locked?
159 159 self.status == STATUS_LOCKED
160 160 end
161 161
162 162 def check_password?(clear_password)
163 163 User.hash_password(clear_password) == self.hashed_password
164 164 end
165 165
166 166 # Generate and set a random password. Useful for automated user creation
167 167 # Based on Token#generate_token_value
168 168 #
169 169 def random_password
170 170 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
171 171 password = ''
172 172 40.times { |i| password << chars[rand(chars.size-1)] }
173 173 self.password = password
174 174 self.password_confirmation = password
175 175 self
176 176 end
177 177
178 178 def pref
179 179 self.preference ||= UserPreference.new(:user => self)
180 180 end
181 181
182 182 def time_zone
183 183 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
184 184 end
185 185
186 186 def wants_comments_in_reverse_order?
187 187 self.pref[:comments_sorting] == 'desc'
188 188 end
189 189
190 190 # Return user's RSS key (a 40 chars long string), used to access feeds
191 191 def rss_key
192 192 token = self.rss_token || Token.create(:user => self, :action => 'feeds')
193 193 token.value
194 194 end
195 195
196 196 # Return an array of project ids for which the user has explicitly turned mail notifications on
197 197 def notified_projects_ids
198 198 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
199 199 end
200 200
201 201 def notified_project_ids=(ids)
202 202 Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
203 203 Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
204 204 @notified_projects_ids = nil
205 205 notified_projects_ids
206 206 end
207 207
208 208 def self.find_by_rss_key(key)
209 209 token = Token.find_by_value(key)
210 210 token && token.user.active? ? token.user : nil
211 211 end
212 212
213 213 # Makes find_by_mail case-insensitive
214 214 def self.find_by_mail(mail)
215 215 find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
216 216 end
217
218 # Sort users by their display names
219 def <=>(user)
220 self.to_s.downcase <=> user.to_s.downcase
221 end
222 217
223 218 def to_s
224 219 name
225 220 end
226 221
227 222 # Returns the current day according to user's time zone
228 223 def today
229 224 if time_zone.nil?
230 225 Date.today
231 226 else
232 227 Time.now.in_time_zone(time_zone).to_date
233 228 end
234 229 end
235 230
236 231 def logged?
237 232 true
238 233 end
239 234
240 235 def anonymous?
241 236 !logged?
242 237 end
243 238
244 239 # Return user's roles for project
245 240 def roles_for_project(project)
246 241 roles = []
247 242 # No role on archived projects
248 243 return roles unless project && project.active?
249 244 if logged?
250 245 # Find project membership
251 246 membership = memberships.detect {|m| m.project_id == project.id}
252 247 if membership
253 248 roles = membership.roles
254 249 else
255 250 @role_non_member ||= Role.non_member
256 251 roles << @role_non_member
257 252 end
258 253 else
259 254 @role_anonymous ||= Role.anonymous
260 255 roles << @role_anonymous
261 256 end
262 257 roles
263 258 end
264 259
265 260 # Return true if the user is a member of project
266 261 def member_of?(project)
267 262 !roles_for_project(project).detect {|role| role.member?}.nil?
268 263 end
269 264
270 265 # Return true if the user is allowed to do the specified action on project
271 266 # action can be:
272 267 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
273 268 # * a permission Symbol (eg. :edit_project)
274 269 def allowed_to?(action, project, options={})
275 270 if project
276 271 # No action allowed on archived projects
277 272 return false unless project.active?
278 273 # No action allowed on disabled modules
279 274 return false unless project.allows_to?(action)
280 275 # Admin users are authorized for anything else
281 276 return true if admin?
282 277
283 278 roles = roles_for_project(project)
284 279 return false unless roles
285 280 roles.detect {|role| (project.is_public? || role.member?) && role.allowed_to?(action)}
286 281
287 282 elsif options[:global]
288 283 # Admin users are always authorized
289 284 return true if admin?
290 285
291 286 # authorize if user has at least one role that has this permission
292 287 roles = memberships.collect {|m| m.roles}.flatten.uniq
293 288 roles.detect {|r| r.allowed_to?(action)} || (self.logged? ? Role.non_member.allowed_to?(action) : Role.anonymous.allowed_to?(action))
294 289 else
295 290 false
296 291 end
297 292 end
298 293
299 294 def self.current=(user)
300 295 @current_user = user
301 296 end
302 297
303 298 def self.current
304 299 @current_user ||= User.anonymous
305 300 end
306 301
307 302 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
308 303 # one anonymous user per database.
309 304 def self.anonymous
310 305 anonymous_user = AnonymousUser.find(:first)
311 306 if anonymous_user.nil?
312 307 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
313 308 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
314 309 end
315 310 anonymous_user
316 311 end
317 312
318 313 protected
319 314
320 315 def validate
321 316 # Password length validation based on setting
322 317 if !password.nil? && password.size < Setting.password_min_length.to_i
323 318 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
324 319 end
325 320 end
326 321
327 322 private
328 323
329 324 # Return password digest
330 325 def self.hash_password(clear_password)
331 326 Digest::SHA1.hexdigest(clear_password || "")
332 327 end
333 328 end
334 329
335 330 class AnonymousUser < User
336 331
337 332 def validate_on_create
338 333 # There should be only one AnonymousUser in the database
339 334 errors.add_to_base 'An anonymous user already exists.' if AnonymousUser.find(:first)
340 335 end
341 336
342 337 def available_custom_fields
343 338 []
344 339 end
345 340
346 341 # Overrides a few properties
347 342 def logged?; false end
348 343 def admin; false end
349 344 def name(*args); I18n.t(:label_user_anonymous) end
350 345 def mail; nil end
351 346 def time_zone; nil end
352 347 def rss_key; nil end
353 348 end
General Comments 0
You need to be logged in to leave comments. Login now