##// END OF EJS Templates
Extracts user groups assignment from controller....
Jean-Philippe Lang -
r4385:0a2ec6ef0472
parent child
Show More
@@ -1,222 +1,221
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2010 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 UsersController < ApplicationController
19 19 layout 'admin'
20 20
21 21 before_filter :require_admin, :except => :show
22 22 accept_key_auth :index, :show, :create, :update
23 23
24 24 helper :sort
25 25 include SortHelper
26 26 helper :custom_fields
27 27 include CustomFieldsHelper
28 28
29 29 def index
30 30 sort_init 'login', 'asc'
31 31 sort_update %w(login firstname lastname mail admin created_on last_login_on)
32 32
33 33 case params[:format]
34 34 when 'xml', 'json'
35 35 @offset, @limit = api_offset_and_limit
36 36 else
37 37 @limit = per_page_option
38 38 end
39 39
40 40 @status = params[:status] ? params[:status].to_i : 1
41 41 c = ARCondition.new(@status == 0 ? "status <> 0" : ["status = ?", @status])
42 42
43 43 unless params[:name].blank?
44 44 name = "%#{params[:name].strip.downcase}%"
45 45 c << ["LOWER(login) LIKE ? OR LOWER(firstname) LIKE ? OR LOWER(lastname) LIKE ? OR LOWER(mail) LIKE ?", name, name, name, name]
46 46 end
47 47
48 48 @user_count = User.count(:conditions => c.conditions)
49 49 @user_pages = Paginator.new self, @user_count, @limit, params['page']
50 50 @offset ||= @user_pages.current.offset
51 51 @users = User.find :all,
52 52 :order => sort_clause,
53 53 :conditions => c.conditions,
54 54 :limit => @limit,
55 55 :offset => @offset
56 56
57 57 respond_to do |format|
58 58 format.html { render :layout => !request.xhr? }
59 59 format.api
60 60 end
61 61 end
62 62
63 63 def show
64 64 @user = User.find(params[:id])
65 65
66 66 # show projects based on current user visibility
67 67 @memberships = @user.memberships.all(:conditions => Project.visible_by(User.current))
68 68
69 69 events = Redmine::Activity::Fetcher.new(User.current, :author => @user).events(nil, nil, :limit => 10)
70 70 @events_by_day = events.group_by(&:event_date)
71 71
72 72 unless User.current.admin?
73 73 if !@user.active? || (@user != User.current && @memberships.empty? && events.empty?)
74 74 render_404
75 75 return
76 76 end
77 77 end
78 78
79 79 respond_to do |format|
80 80 format.html { render :layout => 'base' }
81 81 format.api
82 82 end
83 83 rescue ActiveRecord::RecordNotFound
84 84 render_404
85 85 end
86 86
87 87 def new
88 88 @user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option)
89 89 @auth_sources = AuthSource.find(:all)
90 90 end
91 91
92 92 verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
93 93 def create
94 94 @user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option)
95 95 @user.safe_attributes = params[:user]
96 96 @user.admin = params[:user][:admin] || false
97 97 @user.login = params[:user][:login]
98 98 @user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation] unless @user.auth_source_id
99 99
100 100 # TODO: Similar to My#account
101 101 @user.pref.attributes = params[:pref]
102 102 @user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
103 103
104 104 if @user.save
105 105 @user.pref.save
106 106 @user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : [])
107 107
108 108 Mailer.deliver_account_information(@user, params[:password]) if params[:send_information]
109 109
110 110 respond_to do |format|
111 111 format.html {
112 112 flash[:notice] = l(:notice_successful_create)
113 113 redirect_to(params[:continue] ?
114 114 {:controller => 'users', :action => 'new'} :
115 115 {:controller => 'users', :action => 'edit', :id => @user}
116 116 )
117 117 }
118 118 format.api { render :action => 'show', :status => :created, :location => user_url(@user) }
119 119 end
120 120 else
121 121 @auth_sources = AuthSource.find(:all)
122 122 # Clear password input
123 123 @user.password = @user.password_confirmation = nil
124 124
125 125 respond_to do |format|
126 126 format.html { render :action => 'new' }
127 127 format.api { render_validation_errors(@user) }
128 128 end
129 129 end
130 130 end
131 131
132 132 def edit
133 133 @user = User.find(params[:id])
134 134
135 135 @auth_sources = AuthSource.find(:all)
136 136 @membership ||= Member.new
137 137 end
138 138
139 139 verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
140 140 def update
141 141 @user = User.find(params[:id])
142 142
143 143 @user.admin = params[:user][:admin] if params[:user][:admin]
144 144 @user.login = params[:user][:login] if params[:user][:login]
145 145 if params[:user][:password].present? && (@user.auth_source_id.nil? || params[:user][:auth_source_id].blank?)
146 146 @user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation]
147 147 end
148 @user.group_ids = params[:user][:group_ids] if params[:user][:group_ids]
149 148 @user.safe_attributes = params[:user]
150 149 # Was the account actived ? (do it before User#save clears the change)
151 150 was_activated = (@user.status_change == [User::STATUS_REGISTERED, User::STATUS_ACTIVE])
152 151 # TODO: Similar to My#account
153 152 @user.pref.attributes = params[:pref]
154 153 @user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
155 154
156 155 if @user.save
157 156 @user.pref.save
158 157 @user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : [])
159 158
160 159 if was_activated
161 160 Mailer.deliver_account_activated(@user)
162 161 elsif @user.active? && params[:send_information] && !params[:user][:password].blank? && @user.auth_source_id.nil?
163 162 Mailer.deliver_account_information(@user, params[:user][:password])
164 163 end
165 164
166 165 respond_to do |format|
167 166 format.html {
168 167 flash[:notice] = l(:notice_successful_update)
169 168 redirect_to :back
170 169 }
171 170 format.api { head :ok }
172 171 end
173 172 else
174 173 @auth_sources = AuthSource.find(:all)
175 174 @membership ||= Member.new
176 175 # Clear password input
177 176 @user.password = @user.password_confirmation = nil
178 177
179 178 respond_to do |format|
180 179 format.html { render :action => :edit }
181 180 format.api { render_validation_errors(@user) }
182 181 end
183 182 end
184 183 rescue ::ActionController::RedirectBackError
185 184 redirect_to :controller => 'users', :action => 'edit', :id => @user
186 185 end
187 186
188 187 def edit_membership
189 188 @user = User.find(params[:id])
190 189 @membership = Member.edit_membership(params[:membership_id], params[:membership], @user)
191 190 @membership.save if request.post?
192 191 respond_to do |format|
193 192 if @membership.valid?
194 193 format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' }
195 194 format.js {
196 195 render(:update) {|page|
197 196 page.replace_html "tab-content-memberships", :partial => 'users/memberships'
198 197 page.visual_effect(:highlight, "member-#{@membership.id}")
199 198 }
200 199 }
201 200 else
202 201 format.js {
203 202 render(:update) {|page|
204 203 page.alert(l(:notice_failed_to_save_members, :errors => @membership.errors.full_messages.join(', ')))
205 204 }
206 205 }
207 206 end
208 207 end
209 208 end
210 209
211 210 def destroy_membership
212 211 @user = User.find(params[:id])
213 212 @membership = Member.find(params[:membership_id])
214 213 if request.post? && @membership.deletable?
215 214 @membership.destroy
216 215 end
217 216 respond_to do |format|
218 217 format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' }
219 218 format.js { render(:update) {|page| page.replace_html "tab-content-memberships", :partial => 'users/memberships'} }
220 219 end
221 220 end
222 221 end
@@ -1,499 +1,502
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 include Redmine::SafeAttributes
22 22
23 23 # Account statuses
24 24 STATUS_ANONYMOUS = 0
25 25 STATUS_ACTIVE = 1
26 26 STATUS_REGISTERED = 2
27 27 STATUS_LOCKED = 3
28 28
29 29 USER_FORMATS = {
30 30 :firstname_lastname => '#{firstname} #{lastname}',
31 31 :firstname => '#{firstname}',
32 32 :lastname_firstname => '#{lastname} #{firstname}',
33 33 :lastname_coma_firstname => '#{lastname}, #{firstname}',
34 34 :username => '#{login}'
35 35 }
36 36
37 37 MAIL_NOTIFICATION_OPTIONS = [
38 38 ['all', :label_user_mail_option_all],
39 39 ['selected', :label_user_mail_option_selected],
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 ['none', :label_user_mail_option_none]
44 44 ]
45 45
46 46 has_and_belongs_to_many :groups, :after_add => Proc.new {|user, group| group.user_added(user)},
47 47 :after_remove => Proc.new {|user, group| group.user_removed(user)}
48 48 has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
49 49 has_many :changesets, :dependent => :nullify
50 50 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
51 51 has_one :rss_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='feeds'"
52 52 has_one :api_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='api'"
53 53 belongs_to :auth_source
54 54
55 55 # Active non-anonymous users scope
56 56 named_scope :active, :conditions => "#{User.table_name}.status = #{STATUS_ACTIVE}"
57 57
58 58 acts_as_customizable
59 59
60 60 attr_accessor :password, :password_confirmation
61 61 attr_accessor :last_before_login_on
62 62 # Prevents unauthorized assignments
63 attr_protected :login, :admin, :password, :password_confirmation, :hashed_password, :group_ids
63 attr_protected :login, :admin, :password, :password_confirmation, :hashed_password
64 64
65 65 validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
66 66 validates_uniqueness_of :login, :if => Proc.new { |user| !user.login.blank? }, :case_sensitive => false
67 67 validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }, :case_sensitive => false
68 68 # Login must contain lettres, numbers, underscores only
69 69 validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
70 70 validates_length_of :login, :maximum => 30
71 71 validates_format_of :firstname, :lastname, :with => /^[\w\s\'\-\.]*$/i
72 72 validates_length_of :firstname, :lastname, :maximum => 30
73 73 validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_nil => true
74 74 validates_length_of :mail, :maximum => 60, :allow_nil => true
75 75 validates_confirmation_of :password, :allow_nil => true
76 76 validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true
77 77
78 78 def before_create
79 79 self.mail_notification = Setting.default_notification_option if self.mail_notification.blank?
80 80 true
81 81 end
82 82
83 83 def before_save
84 84 # update hashed_password if password was set
85 85 self.hashed_password = User.hash_password(self.password) if self.password && self.auth_source_id.blank?
86 86 end
87 87
88 88 def reload(*args)
89 89 @name = nil
90 90 super
91 91 end
92 92
93 93 def mail=(arg)
94 94 write_attribute(:mail, arg.to_s.strip)
95 95 end
96 96
97 97 def identity_url=(url)
98 98 if url.blank?
99 99 write_attribute(:identity_url, '')
100 100 else
101 101 begin
102 102 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
103 103 rescue OpenIdAuthentication::InvalidOpenId
104 104 # Invlaid url, don't save
105 105 end
106 106 end
107 107 self.read_attribute(:identity_url)
108 108 end
109 109
110 110 # Returns the user that matches provided login and password, or nil
111 111 def self.try_to_login(login, password)
112 112 # Make sure no one can sign in with an empty password
113 113 return nil if password.to_s.empty?
114 114 user = find_by_login(login)
115 115 if user
116 116 # user is already in local database
117 117 return nil if !user.active?
118 118 if user.auth_source
119 119 # user has an external authentication method
120 120 return nil unless user.auth_source.authenticate(login, password)
121 121 else
122 122 # authentication with local password
123 123 return nil unless User.hash_password(password) == user.hashed_password
124 124 end
125 125 else
126 126 # user is not yet registered, try to authenticate with available sources
127 127 attrs = AuthSource.authenticate(login, password)
128 128 if attrs
129 129 user = new(attrs)
130 130 user.login = login
131 131 user.language = Setting.default_language
132 132 if user.save
133 133 user.reload
134 134 logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source
135 135 end
136 136 end
137 137 end
138 138 user.update_attribute(:last_login_on, Time.now) if user && !user.new_record?
139 139 user
140 140 rescue => text
141 141 raise text
142 142 end
143 143
144 144 # Returns the user who matches the given autologin +key+ or nil
145 145 def self.try_to_autologin(key)
146 146 tokens = Token.find_all_by_action_and_value('autologin', key)
147 147 # Make sure there's only 1 token that matches the key
148 148 if tokens.size == 1
149 149 token = tokens.first
150 150 if (token.created_on > Setting.autologin.to_i.day.ago) && token.user && token.user.active?
151 151 token.user.update_attribute(:last_login_on, Time.now)
152 152 token.user
153 153 end
154 154 end
155 155 end
156 156
157 157 # Return user's full name for display
158 158 def name(formatter = nil)
159 159 if formatter
160 160 eval('"' + (USER_FORMATS[formatter] || USER_FORMATS[:firstname_lastname]) + '"')
161 161 else
162 162 @name ||= eval('"' + (USER_FORMATS[Setting.user_format] || USER_FORMATS[:firstname_lastname]) + '"')
163 163 end
164 164 end
165 165
166 166 def active?
167 167 self.status == STATUS_ACTIVE
168 168 end
169 169
170 170 def registered?
171 171 self.status == STATUS_REGISTERED
172 172 end
173 173
174 174 def locked?
175 175 self.status == STATUS_LOCKED
176 176 end
177 177
178 178 def activate
179 179 self.status = STATUS_ACTIVE
180 180 end
181 181
182 182 def register
183 183 self.status = STATUS_REGISTERED
184 184 end
185 185
186 186 def lock
187 187 self.status = STATUS_LOCKED
188 188 end
189 189
190 190 def activate!
191 191 update_attribute(:status, STATUS_ACTIVE)
192 192 end
193 193
194 194 def register!
195 195 update_attribute(:status, STATUS_REGISTERED)
196 196 end
197 197
198 198 def lock!
199 199 update_attribute(:status, STATUS_LOCKED)
200 200 end
201 201
202 202 def check_password?(clear_password)
203 203 if auth_source_id.present?
204 204 auth_source.authenticate(self.login, clear_password)
205 205 else
206 206 User.hash_password(clear_password) == self.hashed_password
207 207 end
208 208 end
209 209
210 210 # Does the backend storage allow this user to change their password?
211 211 def change_password_allowed?
212 212 return true if auth_source_id.blank?
213 213 return auth_source.allow_password_changes?
214 214 end
215 215
216 216 # Generate and set a random password. Useful for automated user creation
217 217 # Based on Token#generate_token_value
218 218 #
219 219 def random_password
220 220 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
221 221 password = ''
222 222 40.times { |i| password << chars[rand(chars.size-1)] }
223 223 self.password = password
224 224 self.password_confirmation = password
225 225 self
226 226 end
227 227
228 228 def pref
229 229 self.preference ||= UserPreference.new(:user => self)
230 230 end
231 231
232 232 def time_zone
233 233 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
234 234 end
235 235
236 236 def wants_comments_in_reverse_order?
237 237 self.pref[:comments_sorting] == 'desc'
238 238 end
239 239
240 240 # Return user's RSS key (a 40 chars long string), used to access feeds
241 241 def rss_key
242 242 token = self.rss_token || Token.create(:user => self, :action => 'feeds')
243 243 token.value
244 244 end
245 245
246 246 # Return user's API key (a 40 chars long string), used to access the API
247 247 def api_key
248 248 token = self.api_token || self.create_api_token(:action => 'api')
249 249 token.value
250 250 end
251 251
252 252 # Return an array of project ids for which the user has explicitly turned mail notifications on
253 253 def notified_projects_ids
254 254 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
255 255 end
256 256
257 257 def notified_project_ids=(ids)
258 258 Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
259 259 Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
260 260 @notified_projects_ids = nil
261 261 notified_projects_ids
262 262 end
263 263
264 264 # Only users that belong to more than 1 project can select projects for which they are notified
265 265 def valid_notification_options
266 266 # Note that @user.membership.size would fail since AR ignores
267 267 # :include association option when doing a count
268 268 if memberships.length < 1
269 269 MAIL_NOTIFICATION_OPTIONS.delete_if {|option| option.first == 'selected'}
270 270 else
271 271 MAIL_NOTIFICATION_OPTIONS
272 272 end
273 273 end
274 274
275 275 # Find a user account by matching the exact login and then a case-insensitive
276 276 # version. Exact matches will be given priority.
277 277 def self.find_by_login(login)
278 278 # force string comparison to be case sensitive on MySQL
279 279 type_cast = (ActiveRecord::Base.connection.adapter_name == 'MySQL') ? 'BINARY' : ''
280 280
281 281 # First look for an exact match
282 282 user = first(:conditions => ["#{type_cast} login = ?", login])
283 283 # Fail over to case-insensitive if none was found
284 284 user ||= first(:conditions => ["#{type_cast} LOWER(login) = ?", login.to_s.downcase])
285 285 end
286 286
287 287 def self.find_by_rss_key(key)
288 288 token = Token.find_by_value(key)
289 289 token && token.user.active? ? token.user : nil
290 290 end
291 291
292 292 def self.find_by_api_key(key)
293 293 token = Token.find_by_action_and_value('api', key)
294 294 token && token.user.active? ? token.user : nil
295 295 end
296 296
297 297 # Makes find_by_mail case-insensitive
298 298 def self.find_by_mail(mail)
299 299 find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
300 300 end
301 301
302 302 def to_s
303 303 name
304 304 end
305 305
306 306 # Returns the current day according to user's time zone
307 307 def today
308 308 if time_zone.nil?
309 309 Date.today
310 310 else
311 311 Time.now.in_time_zone(time_zone).to_date
312 312 end
313 313 end
314 314
315 315 def logged?
316 316 true
317 317 end
318 318
319 319 def anonymous?
320 320 !logged?
321 321 end
322 322
323 323 # Return user's roles for project
324 324 def roles_for_project(project)
325 325 roles = []
326 326 # No role on archived projects
327 327 return roles unless project && project.active?
328 328 if logged?
329 329 # Find project membership
330 330 membership = memberships.detect {|m| m.project_id == project.id}
331 331 if membership
332 332 roles = membership.roles
333 333 else
334 334 @role_non_member ||= Role.non_member
335 335 roles << @role_non_member
336 336 end
337 337 else
338 338 @role_anonymous ||= Role.anonymous
339 339 roles << @role_anonymous
340 340 end
341 341 roles
342 342 end
343 343
344 344 # Return true if the user is a member of project
345 345 def member_of?(project)
346 346 !roles_for_project(project).detect {|role| role.member?}.nil?
347 347 end
348 348
349 349 # Return true if the user is allowed to do the specified action on a specific context
350 350 # Action can be:
351 351 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
352 352 # * a permission Symbol (eg. :edit_project)
353 353 # Context can be:
354 354 # * a project : returns true if user is allowed to do the specified action on this project
355 355 # * a group of projects : returns true if user is allowed on every project
356 356 # * nil with options[:global] set : check if user has at least one role allowed for this action,
357 357 # or falls back to Non Member / Anonymous permissions depending if the user is logged
358 358 def allowed_to?(action, context, options={})
359 359 if context && context.is_a?(Project)
360 360 # No action allowed on archived projects
361 361 return false unless context.active?
362 362 # No action allowed on disabled modules
363 363 return false unless context.allows_to?(action)
364 364 # Admin users are authorized for anything else
365 365 return true if admin?
366 366
367 367 roles = roles_for_project(context)
368 368 return false unless roles
369 369 roles.detect {|role| (context.is_public? || role.member?) && role.allowed_to?(action)}
370 370
371 371 elsif context && context.is_a?(Array)
372 372 # Authorize if user is authorized on every element of the array
373 373 context.map do |project|
374 374 allowed_to?(action,project,options)
375 375 end.inject do |memo,allowed|
376 376 memo && allowed
377 377 end
378 378 elsif options[:global]
379 379 # Admin users are always authorized
380 380 return true if admin?
381 381
382 382 # authorize if user has at least one role that has this permission
383 383 roles = memberships.collect {|m| m.roles}.flatten.uniq
384 384 roles.detect {|r| r.allowed_to?(action)} || (self.logged? ? Role.non_member.allowed_to?(action) : Role.anonymous.allowed_to?(action))
385 385 else
386 386 false
387 387 end
388 388 end
389 389
390 390 # Is the user allowed to do the specified action on any project?
391 391 # See allowed_to? for the actions and valid options.
392 392 def allowed_to_globally?(action, options)
393 393 allowed_to?(action, nil, options.reverse_merge(:global => true))
394 394 end
395 395
396 396 safe_attributes 'login',
397 397 'firstname',
398 398 'lastname',
399 399 'mail',
400 400 'mail_notification',
401 401 'language',
402 402 'custom_field_values',
403 403 'custom_fields',
404 404 'identity_url'
405 405
406 406 safe_attributes 'status',
407 407 'auth_source_id',
408 408 :if => lambda {|user, current_user| current_user.admin?}
409 409
410 safe_attributes 'group_ids',
411 :if => lambda {|user, current_user| current_user.admin? && !user.new_record?}
412
410 413 # Utility method to help check if a user should be notified about an
411 414 # event.
412 415 #
413 416 # TODO: only supports Issue events currently
414 417 def notify_about?(object)
415 418 case mail_notification
416 419 when 'all'
417 420 true
418 421 when 'selected'
419 422 # Handled by the Project
420 423 when 'none'
421 424 false
422 425 when 'only_my_events'
423 426 if object.is_a?(Issue) && (object.author == self || object.assigned_to == self)
424 427 true
425 428 else
426 429 false
427 430 end
428 431 when 'only_assigned'
429 432 if object.is_a?(Issue) && object.assigned_to == self
430 433 true
431 434 else
432 435 false
433 436 end
434 437 when 'only_owner'
435 438 if object.is_a?(Issue) && object.author == self
436 439 true
437 440 else
438 441 false
439 442 end
440 443 else
441 444 false
442 445 end
443 446 end
444 447
445 448 def self.current=(user)
446 449 @current_user = user
447 450 end
448 451
449 452 def self.current
450 453 @current_user ||= User.anonymous
451 454 end
452 455
453 456 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
454 457 # one anonymous user per database.
455 458 def self.anonymous
456 459 anonymous_user = AnonymousUser.find(:first)
457 460 if anonymous_user.nil?
458 461 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
459 462 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
460 463 end
461 464 anonymous_user
462 465 end
463 466
464 467 protected
465 468
466 469 def validate
467 470 # Password length validation based on setting
468 471 if !password.nil? && password.size < Setting.password_min_length.to_i
469 472 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
470 473 end
471 474 end
472 475
473 476 private
474 477
475 478 # Return password digest
476 479 def self.hash_password(clear_password)
477 480 Digest::SHA1.hexdigest(clear_password || "")
478 481 end
479 482 end
480 483
481 484 class AnonymousUser < User
482 485
483 486 def validate_on_create
484 487 # There should be only one AnonymousUser in the database
485 488 errors.add_to_base 'An anonymous user already exists.' if AnonymousUser.find(:first)
486 489 end
487 490
488 491 def available_custom_fields
489 492 []
490 493 end
491 494
492 495 # Overrides a few properties
493 496 def logged?; false end
494 497 def admin; false end
495 498 def name(*args); I18n.t(:label_user_anonymous) end
496 499 def mail; nil end
497 500 def time_zone; nil end
498 501 def rss_key; nil end
499 502 end
@@ -1,200 +1,207
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 File.dirname(__FILE__) + '/../test_helper'
19 19 require 'my_controller'
20 20
21 21 # Re-raise errors caught by the controller.
22 22 class MyController; def rescue_action(e) raise e end; end
23 23
24 24 class MyControllerTest < ActionController::TestCase
25 25 fixtures :users, :user_preferences, :roles, :projects, :issues, :issue_statuses, :trackers, :enumerations, :custom_fields
26 26
27 27 def setup
28 28 @controller = MyController.new
29 29 @request = ActionController::TestRequest.new
30 30 @request.session[:user_id] = 2
31 31 @response = ActionController::TestResponse.new
32 32 end
33 33
34 34 def test_index
35 35 get :index
36 36 assert_response :success
37 37 assert_template 'page'
38 38 end
39 39
40 40 def test_page
41 41 get :page
42 42 assert_response :success
43 43 assert_template 'page'
44 44 end
45 45
46 46 def test_my_account_should_show_editable_custom_fields
47 47 get :account
48 48 assert_response :success
49 49 assert_template 'account'
50 50 assert_equal User.find(2), assigns(:user)
51 51
52 52 assert_tag :input, :attributes => { :name => 'user[custom_field_values][4]'}
53 53 end
54 54
55 55 def test_my_account_should_not_show_non_editable_custom_fields
56 56 UserCustomField.find(4).update_attribute :editable, false
57 57
58 58 get :account
59 59 assert_response :success
60 60 assert_template 'account'
61 61 assert_equal User.find(2), assigns(:user)
62 62
63 63 assert_no_tag :input, :attributes => { :name => 'user[custom_field_values][4]'}
64 64 end
65 65
66 66 def test_update_account
67 post :account, :user => {:firstname => "Joe",
68 :login => "root",
69 :admin => 1,
70 :custom_field_values => {"4" => "0100562500"}}
67 post :account,
68 :user => {
69 :firstname => "Joe",
70 :login => "root",
71 :admin => 1,
72 :group_ids => ['10'],
73 :custom_field_values => {"4" => "0100562500"}
74 }
75
71 76 assert_redirected_to '/my/account'
72 77 user = User.find(2)
73 78 assert_equal user, assigns(:user)
74 79 assert_equal "Joe", user.firstname
75 80 assert_equal "jsmith", user.login
76 81 assert_equal "0100562500", user.custom_value_for(4).value
82 # ignored
77 83 assert !user.admin?
84 assert user.groups.empty?
78 85 end
79 86
80 87 def test_change_password
81 88 get :password
82 89 assert_response :success
83 90 assert_template 'password'
84 91
85 92 # non matching password confirmation
86 93 post :password, :password => 'jsmith',
87 94 :new_password => 'hello',
88 95 :new_password_confirmation => 'hello2'
89 96 assert_response :success
90 97 assert_template 'password'
91 98 assert_tag :tag => "div", :attributes => { :class => "errorExplanation" }
92 99
93 100 # wrong password
94 101 post :password, :password => 'wrongpassword',
95 102 :new_password => 'hello',
96 103 :new_password_confirmation => 'hello'
97 104 assert_response :success
98 105 assert_template 'password'
99 106 assert_equal 'Wrong password', flash[:error]
100 107
101 108 # good password
102 109 post :password, :password => 'jsmith',
103 110 :new_password => 'hello',
104 111 :new_password_confirmation => 'hello'
105 112 assert_redirected_to '/my/account'
106 113 assert User.try_to_login('jsmith', 'hello')
107 114 end
108 115
109 116 def test_page_layout
110 117 get :page_layout
111 118 assert_response :success
112 119 assert_template 'page_layout'
113 120 end
114 121
115 122 def test_add_block
116 123 xhr :post, :add_block, :block => 'issuesreportedbyme'
117 124 assert_response :success
118 125 assert User.find(2).pref[:my_page_layout]['top'].include?('issuesreportedbyme')
119 126 end
120 127
121 128 def test_remove_block
122 129 xhr :post, :remove_block, :block => 'issuesassignedtome'
123 130 assert_response :success
124 131 assert !User.find(2).pref[:my_page_layout].values.flatten.include?('issuesassignedtome')
125 132 end
126 133
127 134 def test_order_blocks
128 135 xhr :post, :order_blocks, :group => 'left', 'list-left' => ['documents', 'calendar', 'latestnews']
129 136 assert_response :success
130 137 assert_equal ['documents', 'calendar', 'latestnews'], User.find(2).pref[:my_page_layout]['left']
131 138 end
132 139
133 140 context "POST to reset_rss_key" do
134 141 context "with an existing rss_token" do
135 142 setup do
136 143 @previous_token_value = User.find(2).rss_key # Will generate one if it's missing
137 144 post :reset_rss_key
138 145 end
139 146
140 147 should "destroy the existing token" do
141 148 assert_not_equal @previous_token_value, User.find(2).rss_key
142 149 end
143 150
144 151 should "create a new token" do
145 152 assert User.find(2).rss_token
146 153 end
147 154
148 155 should_set_the_flash_to /reset/
149 156 should_redirect_to('my account') {'/my/account' }
150 157 end
151 158
152 159 context "with no rss_token" do
153 160 setup do
154 161 assert_nil User.find(2).rss_token
155 162 post :reset_rss_key
156 163 end
157 164
158 165 should "create a new token" do
159 166 assert User.find(2).rss_token
160 167 end
161 168
162 169 should_set_the_flash_to /reset/
163 170 should_redirect_to('my account') {'/my/account' }
164 171 end
165 172 end
166 173
167 174 context "POST to reset_api_key" do
168 175 context "with an existing api_token" do
169 176 setup do
170 177 @previous_token_value = User.find(2).api_key # Will generate one if it's missing
171 178 post :reset_api_key
172 179 end
173 180
174 181 should "destroy the existing token" do
175 182 assert_not_equal @previous_token_value, User.find(2).api_key
176 183 end
177 184
178 185 should "create a new token" do
179 186 assert User.find(2).api_token
180 187 end
181 188
182 189 should_set_the_flash_to /reset/
183 190 should_redirect_to('my account') {'/my/account' }
184 191 end
185 192
186 193 context "with no api_token" do
187 194 setup do
188 195 assert_nil User.find(2).api_token
189 196 post :reset_api_key
190 197 end
191 198
192 199 should "create a new token" do
193 200 assert User.find(2).api_token
194 201 end
195 202
196 203 should_set_the_flash_to /reset/
197 204 should_redirect_to('my account') {'/my/account' }
198 205 end
199 206 end
200 207 end
@@ -1,240 +1,247
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 File.dirname(__FILE__) + '/../test_helper'
19 19 require 'users_controller'
20 20
21 21 # Re-raise errors caught by the controller.
22 22 class UsersController; def rescue_action(e) raise e end; end
23 23
24 24 class UsersControllerTest < ActionController::TestCase
25 25 include Redmine::I18n
26 26
27 27 fixtures :users, :projects, :members, :member_roles, :roles, :auth_sources, :custom_fields, :custom_values
28 28
29 29 def setup
30 30 @controller = UsersController.new
31 31 @request = ActionController::TestRequest.new
32 32 @response = ActionController::TestResponse.new
33 33 User.current = nil
34 34 @request.session[:user_id] = 1 # admin
35 35 end
36 36
37 37 def test_index
38 38 get :index
39 39 assert_response :success
40 40 assert_template 'index'
41 41 end
42 42
43 43 def test_index
44 44 get :index
45 45 assert_response :success
46 46 assert_template 'index'
47 47 assert_not_nil assigns(:users)
48 48 # active users only
49 49 assert_nil assigns(:users).detect {|u| !u.active?}
50 50 end
51 51
52 52 def test_index_with_name_filter
53 53 get :index, :name => 'john'
54 54 assert_response :success
55 55 assert_template 'index'
56 56 users = assigns(:users)
57 57 assert_not_nil users
58 58 assert_equal 1, users.size
59 59 assert_equal 'John', users.first.firstname
60 60 end
61 61
62 62 def test_show
63 63 @request.session[:user_id] = nil
64 64 get :show, :id => 2
65 65 assert_response :success
66 66 assert_template 'show'
67 67 assert_not_nil assigns(:user)
68 68
69 69 assert_tag 'li', :content => /Phone number/
70 70 end
71 71
72 72 def test_show_should_not_display_hidden_custom_fields
73 73 @request.session[:user_id] = nil
74 74 UserCustomField.find_by_name('Phone number').update_attribute :visible, false
75 75 get :show, :id => 2
76 76 assert_response :success
77 77 assert_template 'show'
78 78 assert_not_nil assigns(:user)
79 79
80 80 assert_no_tag 'li', :content => /Phone number/
81 81 end
82 82
83 83 def test_show_should_not_fail_when_custom_values_are_nil
84 84 user = User.find(2)
85 85
86 86 # Create a custom field to illustrate the issue
87 87 custom_field = CustomField.create!(:name => 'Testing', :field_format => 'text')
88 88 custom_value = user.custom_values.build(:custom_field => custom_field).save!
89 89
90 90 get :show, :id => 2
91 91 assert_response :success
92 92 end
93 93
94 94 def test_show_inactive
95 95 @request.session[:user_id] = nil
96 96 get :show, :id => 5
97 97 assert_response 404
98 98 end
99 99
100 100 def test_show_should_not_reveal_users_with_no_visible_activity_or_project
101 101 @request.session[:user_id] = nil
102 102 get :show, :id => 9
103 103 assert_response 404
104 104 end
105 105
106 106 def test_show_inactive_by_admin
107 107 @request.session[:user_id] = 1
108 108 get :show, :id => 5
109 109 assert_response 200
110 110 assert_not_nil assigns(:user)
111 111 end
112 112
113 113 def test_show_displays_memberships_based_on_project_visibility
114 114 @request.session[:user_id] = 1
115 115 get :show, :id => 2
116 116 assert_response :success
117 117 memberships = assigns(:memberships)
118 118 assert_not_nil memberships
119 119 project_ids = memberships.map(&:project_id)
120 120 assert project_ids.include?(2) #private project admin can see
121 121 end
122 122
123 123 context "GET :new" do
124 124 setup do
125 125 get :new
126 126 end
127 127
128 128 should_assign_to :user
129 129 should_respond_with :success
130 130 should_render_template :new
131 131 end
132 132
133 133 context "POST :create" do
134 134 context "when successful" do
135 135 setup do
136 136 post :create, :user => {
137 137 :firstname => 'John',
138 138 :lastname => 'Doe',
139 139 :login => 'jdoe',
140 140 :password => 'test',
141 141 :password_confirmation => 'test',
142 142 :mail => 'jdoe@gmail.com',
143 143 :mail_notification => 'none'
144 144 }
145 145 end
146 146
147 147 should_assign_to :user
148 148 should_respond_with :redirect
149 149 should_redirect_to('user edit') { {:controller => 'users', :action => 'edit', :id => User.find_by_login('jdoe')}}
150 150
151 151 should 'set the users mail notification' do
152 152 user = User.last
153 153 assert_equal 'none', user.mail_notification
154 154 end
155 155
156 156 should 'set the password' do
157 157 user = User.first(:order => 'id DESC')
158 158 assert user.check_password?('test')
159 159 end
160 160 end
161 161
162 162 context "when unsuccessful" do
163 163 setup do
164 164 post :create, :user => {}
165 165 end
166 166
167 167 should_assign_to :user
168 168 should_respond_with :success
169 169 should_render_template :new
170 170 end
171 171
172 172 end
173 173
174 174 def test_update
175 175 ActionMailer::Base.deliveries.clear
176 176 put :update, :id => 2, :user => {:firstname => 'Changed', :mail_notification => 'only_assigned'}, :pref => {:hide_mail => '1', :comments_sorting => 'desc'}
177 177
178 178 user = User.find(2)
179 179 assert_equal 'Changed', user.firstname
180 180 assert_equal 'only_assigned', user.mail_notification
181 181 assert_equal true, user.pref[:hide_mail]
182 182 assert_equal 'desc', user.pref[:comments_sorting]
183 183 assert ActionMailer::Base.deliveries.empty?
184 184 end
185 185
186 def test_update_with_group_ids_should_assign_groups
187 put :update, :id => 2, :user => {:group_ids => ['10']}
188
189 user = User.find(2)
190 assert_equal [10], user.group_ids
191 end
192
186 193 def test_update_with_activation_should_send_a_notification
187 194 u = User.new(:firstname => 'Foo', :lastname => 'Bar', :mail => 'foo.bar@somenet.foo', :language => 'fr')
188 195 u.login = 'foo'
189 196 u.status = User::STATUS_REGISTERED
190 197 u.save!
191 198 ActionMailer::Base.deliveries.clear
192 199 Setting.bcc_recipients = '1'
193 200
194 201 put :update, :id => u.id, :user => {:status => User::STATUS_ACTIVE}
195 202 assert u.reload.active?
196 203 mail = ActionMailer::Base.deliveries.last
197 204 assert_not_nil mail
198 205 assert_equal ['foo.bar@somenet.foo'], mail.bcc
199 206 assert mail.body.include?(ll('fr', :notice_account_activated))
200 207 end
201 208
202 209 def test_update_with_password_change_should_send_a_notification
203 210 ActionMailer::Base.deliveries.clear
204 211 Setting.bcc_recipients = '1'
205 212
206 213 put :update, :id => 2, :user => {:password => 'newpass', :password_confirmation => 'newpass'}, :send_information => '1'
207 214 u = User.find(2)
208 215 assert u.check_password?('newpass')
209 216
210 217 mail = ActionMailer::Base.deliveries.last
211 218 assert_not_nil mail
212 219 assert_equal [u.mail], mail.bcc
213 220 assert mail.body.include?('newpass')
214 221 end
215 222
216 223 test "put :update with a password change to an AuthSource user switching to Internal authentication" do
217 224 # Configure as auth source
218 225 u = User.find(2)
219 226 u.auth_source = AuthSource.find(1)
220 227 u.save!
221 228
222 229 put :update, :id => u.id, :user => {:auth_source_id => '', :password => 'newpass'}, :password_confirmation => 'newpass'
223 230
224 231 assert_equal nil, u.reload.auth_source
225 232 assert u.check_password?('newpass')
226 233 end
227 234
228 235 def test_edit_membership
229 236 post :edit_membership, :id => 2, :membership_id => 1,
230 237 :membership => { :role_ids => [2]}
231 238 assert_redirected_to :action => 'edit', :id => '2', :tab => 'memberships'
232 239 assert_equal [2], Member.find(1).role_ids
233 240 end
234 241
235 242 def test_destroy_membership
236 243 post :destroy_membership, :id => 2, :membership_id => 1
237 244 assert_redirected_to :action => 'edit', :id => '2', :tab => 'memberships'
238 245 assert_nil Member.find_by_id(1)
239 246 end
240 247 end
General Comments 0
You need to be logged in to leave comments. Login now