##// END OF EJS Templates
Allow admins to edit user's email notifications and preferences. #3503...
Eric Davis -
r4109:437690119b0b
parent child
Show More
@@ -0,0 +1,12
1 <p>
2 <%= select_tag 'notification_option', options_for_select(@notification_options.collect {|o| [l(o.last), o.first]}, @notification_option.to_sym),
3 :onchange => 'if ($("notification_option").value == "selected") {Element.show("notified-projects")} else {Element.hide("notified-projects")}' %>
4 </p>
5 <% content_tag 'div', :id => 'notified-projects', :style => (@notification_option == 'selected' ? '' : 'display:none;') do %>
6 <p><% @user.projects.each do |project| %>
7 <label><%= check_box_tag 'notified_project_ids[]', project.id, @user.notified_projects_ids.include?(project.id) %> <%=h project.name %></label><br />
8 <% end %></p>
9 <p><em><%= l(:text_user_mail_option) %></em></p>
10 <% end %>
11 <p><label><%= l(:label_user_mail_no_self_notified) %></label><%= check_box_tag 'no_self_notified', 1, @user.pref[:no_self_notified] %></p>
12
@@ -0,0 +1,6
1 <% fields_for :pref, @user.pref, :builder => TabularFormBuilder, :lang => current_language do |pref_fields| %>
2 <p><%= pref_fields.check_box :hide_mail %></p>
3 <p><%= pref_fields.select :time_zone, ActiveSupport::TimeZone.all.collect {|z| [ z.to_s, z.name ]}, :include_blank => true %></p>
4 <p><%= pref_fields.select :comments_sorting, [[l(:label_chronological_order), 'asc'], [l(:label_reverse_chronological_order), 'desc']] %></p>
5 <% end %>
6
@@ -1,159 +1,202
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 UsersController < ApplicationController
19 19 layout 'admin'
20 20
21 21 before_filter :require_admin, :except => :show
22 22
23 23 helper :sort
24 24 include SortHelper
25 25 helper :custom_fields
26 26 include CustomFieldsHelper
27 27
28 28 def index
29 29 sort_init 'login', 'asc'
30 30 sort_update %w(login firstname lastname mail admin created_on last_login_on)
31 31
32 32 @status = params[:status] ? params[:status].to_i : 1
33 33 c = ARCondition.new(@status == 0 ? "status <> 0" : ["status = ?", @status])
34 34
35 35 unless params[:name].blank?
36 36 name = "%#{params[:name].strip.downcase}%"
37 37 c << ["LOWER(login) LIKE ? OR LOWER(firstname) LIKE ? OR LOWER(lastname) LIKE ? OR LOWER(mail) LIKE ?", name, name, name, name]
38 38 end
39 39
40 40 @user_count = User.count(:conditions => c.conditions)
41 41 @user_pages = Paginator.new self, @user_count,
42 42 per_page_option,
43 43 params['page']
44 44 @users = User.find :all,:order => sort_clause,
45 45 :conditions => c.conditions,
46 46 :limit => @user_pages.items_per_page,
47 47 :offset => @user_pages.current.offset
48 48
49 49 render :layout => !request.xhr?
50 50 end
51 51
52 52 def show
53 53 @user = User.find(params[:id])
54 54 @custom_values = @user.custom_values
55 55
56 56 # show projects based on current user visibility
57 57 @memberships = @user.memberships.all(:conditions => Project.visible_by(User.current))
58 58
59 59 events = Redmine::Activity::Fetcher.new(User.current, :author => @user).events(nil, nil, :limit => 10)
60 60 @events_by_day = events.group_by(&:event_date)
61 61
62 62 unless User.current.admin?
63 63 if !@user.active? || (@user != User.current && @memberships.empty? && events.empty?)
64 64 render_404
65 65 return
66 66 end
67 67 end
68 68 render :layout => 'base'
69 69
70 70 rescue ActiveRecord::RecordNotFound
71 71 render_404
72 72 end
73 73
74 74 def add
75 @notification_options = User::MAIL_NOTIFICATION_OPTIONS
76 @notification_option = Setting.default_notification_option
77
75 78 @user = User.new(:language => Setting.default_language)
76 79 @auth_sources = AuthSource.find(:all)
80
81 # TODO: Similar to My#account
82 # Only users that belong to more than 1 project can select projects for which they are notified
83 # Note that @user.membership.size would fail since AR ignores
84 # :include association option when doing a count
85 if @user.memberships.length < 1
86 @notification_options.delete_if {|option| option.first == :selected}
87 end
77 88 end
78 89
79 90 verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
80 91 def create
92 @notification_options = User::MAIL_NOTIFICATION_OPTIONS
93 @notification_option = Setting.default_notification_option
94
81 95 @user = User.new(params[:user])
82 96 @user.admin = params[:user][:admin] || false
83 97 @user.login = params[:user][:login]
84 98 @user.password, @user.password_confirmation = params[:password], params[:password_confirmation] unless @user.auth_source_id
99
100 # TODO: Similar to My#account
101 @user.mail_notification = params[:notification_option] || 'only_my_events'
102 @user.pref.attributes = params[:pref]
103 @user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
104
85 105 if @user.save
106 @user.pref.save
107 @user.notified_project_ids = (params[:notification_option] == 'selected' ? params[:notified_project_ids] : [])
108
86 109 Mailer.deliver_account_information(@user, params[:password]) if params[:send_information]
87 110 flash[:notice] = l(:notice_successful_create)
88 111 redirect_to(params[:continue] ? {:controller => 'users', :action => 'add'} :
89 112 {:controller => 'users', :action => 'edit', :id => @user})
90 113 return
91 114 else
92 115 @auth_sources = AuthSource.find(:all)
116 @notification_option = @user.mail_notification
117
93 118 render :action => 'add'
94 119 end
95 120 end
96 121
97 122 def edit
98 123 @user = User.find(params[:id])
124 # TODO: Similar to My#account
125 @notification_options = User::MAIL_NOTIFICATION_OPTIONS
126 # Only users that belong to more than 1 project can select projects for which they are notified
127 # Note that @user.membership.size would fail since AR ignores
128 # :include association option when doing a count
129 if @user.memberships.length < 1
130 @notification_options.delete_if {|option| option.first == :selected}
131 end
132 @notification_option = @user.mail_notification
133
99 134 if request.post?
100 135 @user.admin = params[:user][:admin] if params[:user][:admin]
101 136 @user.login = params[:user][:login] if params[:user][:login]
102 137 if params[:password].present? && (@user.auth_source_id.nil? || params[:user][:auth_source_id].blank?)
103 138 @user.password, @user.password_confirmation = params[:password], params[:password_confirmation]
104 139 end
105 140 @user.group_ids = params[:user][:group_ids] if params[:user][:group_ids]
106 141 @user.attributes = params[:user]
107 142 # Was the account actived ? (do it before User#save clears the change)
108 143 was_activated = (@user.status_change == [User::STATUS_REGISTERED, User::STATUS_ACTIVE])
144 # TODO: Similar to My#account
145 @user.mail_notification = params[:notification_option] || 'only_my_events'
146 @user.pref.attributes = params[:pref]
147 @user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
148
109 149 if @user.save
150 @user.pref.save
151 @user.notified_project_ids = (params[:notification_option] == 'selected' ? params[:notified_project_ids] : [])
152
110 153 if was_activated
111 154 Mailer.deliver_account_activated(@user)
112 155 elsif @user.active? && params[:send_information] && !params[:password].blank? && @user.auth_source_id.nil?
113 156 Mailer.deliver_account_information(@user, params[:password])
114 157 end
115 158 flash[:notice] = l(:notice_successful_update)
116 159 redirect_to :back
117 160 end
118 161 end
119 162 @auth_sources = AuthSource.find(:all)
120 163 @membership ||= Member.new
121 164 rescue ::ActionController::RedirectBackError
122 165 redirect_to :controller => 'users', :action => 'edit', :id => @user
123 166 end
124 167
125 168 def edit_membership
126 169 @user = User.find(params[:id])
127 170 @membership = Member.edit_membership(params[:membership_id], params[:membership], @user)
128 171 @membership.save if request.post?
129 172 respond_to do |format|
130 173 if @membership.valid?
131 174 format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' }
132 175 format.js {
133 176 render(:update) {|page|
134 177 page.replace_html "tab-content-memberships", :partial => 'users/memberships'
135 178 page.visual_effect(:highlight, "member-#{@membership.id}")
136 179 }
137 180 }
138 181 else
139 182 format.js {
140 183 render(:update) {|page|
141 184 page.alert(l(:notice_failed_to_save_members, :errors => @membership.errors.full_messages.join(', ')))
142 185 }
143 186 }
144 187 end
145 188 end
146 189 end
147 190
148 191 def destroy_membership
149 192 @user = User.find(params[:id])
150 193 @membership = Member.find(params[:membership_id])
151 194 if request.post? && @membership.deletable?
152 195 @membership.destroy
153 196 end
154 197 respond_to do |format|
155 198 format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' }
156 199 format.js { render(:update) {|page| page.replace_html "tab-content-memberships", :partial => 'users/memberships'} }
157 200 end
158 201 end
159 202 end
@@ -1,460 +1,460
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 self.mail_notification = Setting.default_notification_option
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 # Find a user account by matching the exact login and then a case-insensitive
263 263 # version. Exact matches will be given priority.
264 264 def self.find_by_login(login)
265 265 # force string comparison to be case sensitive on MySQL
266 266 type_cast = (ActiveRecord::Base.connection.adapter_name == 'MySQL') ? 'BINARY' : ''
267 267
268 268 # First look for an exact match
269 269 user = first(:conditions => ["#{type_cast} login = ?", login])
270 270 # Fail over to case-insensitive if none was found
271 271 user ||= first(:conditions => ["#{type_cast} LOWER(login) = ?", login.to_s.downcase])
272 272 end
273 273
274 274 def self.find_by_rss_key(key)
275 275 token = Token.find_by_value(key)
276 276 token && token.user.active? ? token.user : nil
277 277 end
278 278
279 279 def self.find_by_api_key(key)
280 280 token = Token.find_by_action_and_value('api', key)
281 281 token && token.user.active? ? token.user : nil
282 282 end
283 283
284 284 # Makes find_by_mail case-insensitive
285 285 def self.find_by_mail(mail)
286 286 find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
287 287 end
288 288
289 289 def to_s
290 290 name
291 291 end
292 292
293 293 # Returns the current day according to user's time zone
294 294 def today
295 295 if time_zone.nil?
296 296 Date.today
297 297 else
298 298 Time.now.in_time_zone(time_zone).to_date
299 299 end
300 300 end
301 301
302 302 def logged?
303 303 true
304 304 end
305 305
306 306 def anonymous?
307 307 !logged?
308 308 end
309 309
310 310 # Return user's roles for project
311 311 def roles_for_project(project)
312 312 roles = []
313 313 # No role on archived projects
314 314 return roles unless project && project.active?
315 315 if logged?
316 316 # Find project membership
317 317 membership = memberships.detect {|m| m.project_id == project.id}
318 318 if membership
319 319 roles = membership.roles
320 320 else
321 321 @role_non_member ||= Role.non_member
322 322 roles << @role_non_member
323 323 end
324 324 else
325 325 @role_anonymous ||= Role.anonymous
326 326 roles << @role_anonymous
327 327 end
328 328 roles
329 329 end
330 330
331 331 # Return true if the user is a member of project
332 332 def member_of?(project)
333 333 !roles_for_project(project).detect {|role| role.member?}.nil?
334 334 end
335 335
336 336 # Return true if the user is allowed to do the specified action on project
337 337 # action can be:
338 338 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
339 339 # * a permission Symbol (eg. :edit_project)
340 340 def allowed_to?(action, project, options={})
341 341 if project
342 342 # No action allowed on archived projects
343 343 return false unless project.active?
344 344 # No action allowed on disabled modules
345 345 return false unless project.allows_to?(action)
346 346 # Admin users are authorized for anything else
347 347 return true if admin?
348 348
349 349 roles = roles_for_project(project)
350 350 return false unless roles
351 351 roles.detect {|role| (project.is_public? || role.member?) && role.allowed_to?(action)}
352 352
353 353 elsif options[:global]
354 354 # Admin users are always authorized
355 355 return true if admin?
356 356
357 357 # authorize if user has at least one role that has this permission
358 358 roles = memberships.collect {|m| m.roles}.flatten.uniq
359 359 roles.detect {|r| r.allowed_to?(action)} || (self.logged? ? Role.non_member.allowed_to?(action) : Role.anonymous.allowed_to?(action))
360 360 else
361 361 false
362 362 end
363 363 end
364 364
365 365 # Is the user allowed to do the specified action on any project?
366 366 # See allowed_to? for the actions and valid options.
367 367 def allowed_to_globally?(action, options)
368 368 allowed_to?(action, nil, options.reverse_merge(:global => true))
369 369 end
370 370
371 371 # Utility method to help check if a user should be notified about an
372 372 # event.
373 373 #
374 374 # TODO: only supports Issue events currently
375 375 def notify_about?(object)
376 376 case mail_notification.to_sym
377 377 when :all
378 378 true
379 379 when :selected
380 380 # Handled by the Project
381 381 when :none
382 382 false
383 383 when :only_my_events
384 384 if object.is_a?(Issue) && (object.author == self || object.assigned_to == self)
385 385 true
386 386 else
387 387 false
388 388 end
389 389 when :only_assigned
390 390 if object.is_a?(Issue) && object.assigned_to == self
391 391 true
392 392 else
393 393 false
394 394 end
395 395 when :only_owner
396 396 if object.is_a?(Issue) && object.author == self
397 397 true
398 398 else
399 399 false
400 400 end
401 401 else
402 402 false
403 403 end
404 404 end
405 405
406 406 def self.current=(user)
407 407 @current_user = user
408 408 end
409 409
410 410 def self.current
411 411 @current_user ||= User.anonymous
412 412 end
413 413
414 414 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
415 415 # one anonymous user per database.
416 416 def self.anonymous
417 417 anonymous_user = AnonymousUser.find(:first)
418 418 if anonymous_user.nil?
419 419 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
420 420 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
421 421 end
422 422 anonymous_user
423 423 end
424 424
425 425 protected
426 426
427 427 def validate
428 428 # Password length validation based on setting
429 429 if !password.nil? && password.size < Setting.password_min_length.to_i
430 430 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
431 431 end
432 432 end
433 433
434 434 private
435 435
436 436 # Return password digest
437 437 def self.hash_password(clear_password)
438 438 Digest::SHA1.hexdigest(clear_password || "")
439 439 end
440 440 end
441 441
442 442 class AnonymousUser < User
443 443
444 444 def validate_on_create
445 445 # There should be only one AnonymousUser in the database
446 446 errors.add_to_base 'An anonymous user already exists.' if AnonymousUser.find(:first)
447 447 end
448 448
449 449 def available_custom_fields
450 450 []
451 451 end
452 452
453 453 # Overrides a few properties
454 454 def logged?; false end
455 455 def admin; false end
456 456 def name(*args); I18n.t(:label_user_anonymous) end
457 457 def mail; nil end
458 458 def time_zone; nil end
459 459 def rss_key; nil end
460 460 end
@@ -1,62 +1,50
1 1 <div class="contextual">
2 2 <%= link_to(l(:button_change_password), :action => 'password') if @user.change_password_allowed? %>
3 3 <%= call_hook(:view_my_account_contextual, :user => @user)%>
4 4 </div>
5 5 <h2><%=l(:label_my_account)%></h2>
6 6 <%= error_messages_for 'user' %>
7 7
8 8 <% form_for :user, @user, :url => { :action => "account" },
9 9 :builder => TabularFormBuilder,
10 10 :lang => current_language,
11 11 :html => { :id => 'my_account_form' } do |f| %>
12 12 <div class="splitcontentleft">
13 13 <h3><%=l(:label_information_plural)%></h3>
14 14 <div class="box tabular">
15 15 <p><%= f.text_field :firstname, :required => true %></p>
16 16 <p><%= f.text_field :lastname, :required => true %></p>
17 17 <p><%= f.text_field :mail, :required => true %></p>
18 18 <p><%= f.select :language, lang_options_for_select %></p>
19 19 <% if Setting.openid? %>
20 20 <p><%= f.text_field :identity_url %></p>
21 21 <% end %>
22 22
23 23 <% @user.custom_field_values.select(&:editable?).each do |value| %>
24 24 <p><%= custom_field_tag_with_label :user, value %></p>
25 25 <% end %>
26 26 <%= call_hook(:view_my_account, :user => @user, :form => f) %>
27 27 </div>
28 28
29 29 <%= submit_tag l(:button_save) %>
30 30 </div>
31 31
32 32 <div class="splitcontentright">
33 33 <h3><%=l(:field_mail_notification)%></h3>
34 34 <div class="box">
35 <%= select_tag 'notification_option', options_for_select(@notification_options.collect {|o| [l(o.last), o.first]}, @notification_option.to_sym),
36 :onchange => 'if ($("notification_option").value == "selected") {Element.show("notified-projects")} else {Element.hide("notified-projects")}' %>
37 <% content_tag 'div', :id => 'notified-projects', :style => (@notification_option == 'selected' ? '' : 'display:none;') do %>
38 <p><% User.current.projects.each do |project| %>
39 <label><%= check_box_tag 'notified_project_ids[]', project.id, @user.notified_projects_ids.include?(project.id) %> <%=h project.name %></label><br />
40 <% end %></p>
41 <p><em><%= l(:text_user_mail_option) %></em></p>
42 <% end %>
43 <p><label><%= check_box_tag 'no_self_notified', 1, @user.pref[:no_self_notified] %> <%= l(:label_user_mail_no_self_notified) %></label></p>
35 <%= render :partial => 'users/mail_notifications' %>
44 36 </div>
45 37
46 38 <h3><%=l(:label_preferences)%></h3>
47 39 <div class="box tabular">
48 <% fields_for :pref, @user.pref, :builder => TabularFormBuilder, :lang => current_language do |pref_fields| %>
49 <p><%= pref_fields.check_box :hide_mail %></p>
50 <p><%= pref_fields.select :time_zone, ActiveSupport::TimeZone.all.collect {|z| [ z.to_s, z.name ]}, :include_blank => true %></p>
51 <p><%= pref_fields.select :comments_sorting, [[l(:label_chronological_order), 'asc'], [l(:label_reverse_chronological_order), 'desc']] %></p>
52 <% end %>
40 <%= render :partial => 'users/preferences' %>
53 41 </div>
54 42
55 43 </div>
56 44 <% end %>
57 45
58 46 <% content_for :sidebar do %>
59 47 <%= render :partial => 'sidebar' %>
60 48 <% end %>
61 49
62 50 <% html_title(l(:label_my_account)) -%>
@@ -1,35 +1,45
1 1 <%= error_messages_for 'user' %>
2 2
3 3 <!--[form:user]-->
4 4 <div class="box tabular">
5 5 <p><%= f.text_field :login, :required => true, :size => 25 %></p>
6 6 <p><%= f.text_field :firstname, :required => true %></p>
7 7 <p><%= f.text_field :lastname, :required => true %></p>
8 8 <p><%= f.text_field :mail, :required => true %></p>
9 9 <p><%= f.select :language, lang_options_for_select %></p>
10 10 <% if Setting.openid? %>
11 11 <p><%= f.text_field :identity_url %></p>
12 12 <% end %>
13 13
14 14 <% @user.custom_field_values.each do |value| %>
15 15 <p><%= custom_field_tag_with_label :user, value %></p>
16 16 <% end %>
17 17
18 18 <p><%= f.check_box :admin, :disabled => (@user == User.current) %></p>
19 19 <%= call_hook(:view_users_form, :user => @user, :form => f) %>
20 20 </div>
21 21
22 22 <div class="box tabular">
23 23 <h3><%=l(:label_authentication)%></h3>
24 24 <% unless @auth_sources.empty? %>
25 25 <p><%= f.select :auth_source_id, ([[l(:label_internal), ""]] + @auth_sources.collect { |a| [a.name, a.id] }), {}, :onchange => "if (this.value=='') {Element.show('password_fields');} else {Element.hide('password_fields');}" %></p>
26 26 <% end %>
27 27 <div id="password_fields" style="<%= 'display:none;' if @user.auth_source %>">
28 28 <p><label for="password"><%=l(:field_password)%><span class="required"> *</span></label>
29 29 <%= password_field_tag 'password', nil, :size => 25 %><br />
30 30 <em><%= l(:text_caracters_minimum, :count => Setting.password_min_length) %></em></p>
31 31 <p><label for="password_confirmation"><%=l(:field_password_confirmation)%><span class="required"> *</span></label>
32 32 <%= password_field_tag 'password_confirmation', nil, :size => 25 %></p>
33 33 </div>
34 34 </div>
35
36 <div class="box">
37 <h3><%=l(:field_mail_notification)%></h3>
38 <%= render :partial => 'users/mail_notifications' %>
39 </div>
40
41 <div class="box tabular">
42 <h3><%=l(:label_preferences)%></h3>
43 <%= render :partial => 'users/preferences' %>
44 </div>
35 45 <!--[eoform:user]-->
@@ -1,211 +1,222
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
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 end
69 69
70 70 def test_show_should_not_fail_when_custom_values_are_nil
71 71 user = User.find(2)
72 72
73 73 # Create a custom field to illustrate the issue
74 74 custom_field = CustomField.create!(:name => 'Testing', :field_format => 'text')
75 75 custom_value = user.custom_values.build(:custom_field => custom_field).save!
76 76
77 77 get :show, :id => 2
78 78 assert_response :success
79 79 end
80 80
81 81 def test_show_inactive
82 82 @request.session[:user_id] = nil
83 83 get :show, :id => 5
84 84 assert_response 404
85 85 end
86 86
87 87 def test_show_should_not_reveal_users_with_no_visible_activity_or_project
88 88 @request.session[:user_id] = nil
89 89 get :show, :id => 9
90 90 assert_response 404
91 91 end
92 92
93 93 def test_show_inactive_by_admin
94 94 @request.session[:user_id] = 1
95 95 get :show, :id => 5
96 96 assert_response 200
97 97 assert_not_nil assigns(:user)
98 98 end
99 99
100 100 def test_show_displays_memberships_based_on_project_visibility
101 101 @request.session[:user_id] = 1
102 102 get :show, :id => 2
103 103 assert_response :success
104 104 memberships = assigns(:memberships)
105 105 assert_not_nil memberships
106 106 project_ids = memberships.map(&:project_id)
107 107 assert project_ids.include?(2) #private project admin can see
108 108 end
109 109
110 110 context "GET :add" do
111 111 setup do
112 112 get :add
113 113 end
114 114
115 115 should_assign_to :user
116 116 should_respond_with :success
117 117 should_render_template :add
118 118 end
119 119
120 120 context "POST :create" do
121 121 context "when successful" do
122 122 setup do
123 123 post :create, :user => {
124 124 :firstname => 'John',
125 125 :lastname => 'Doe',
126 126 :login => 'jdoe',
127 127 :password => 'test',
128 128 :password_confirmation => 'test',
129 129 :mail => 'jdoe@gmail.com'
130 }
130 },
131 :notification_option => 'none'
131 132 end
132 133
133 134 should_assign_to :user
134 135 should_respond_with :redirect
135 136 should_redirect_to('user edit') { {:controller => 'users', :action => 'edit', :id => User.find_by_login('jdoe')}}
137
138 should 'set the users mail notification' do
139 user = User.last
140 assert_equal 'none', user.mail_notification
141 end
136 142 end
137 143
138 144 context "when unsuccessful" do
139 145 setup do
140 146 post :create, :user => {}
141 147 end
142 148
143 149 should_assign_to :user
144 150 should_respond_with :success
145 151 should_render_template :add
146 152 end
147 153
148 154 end
149 155
150 156 def test_edit
151 157 ActionMailer::Base.deliveries.clear
152 post :edit, :id => 2, :user => {:firstname => 'Changed'}
153 assert_equal 'Changed', User.find(2).firstname
158 post :edit, :id => 2, :user => {:firstname => 'Changed'}, :notification_option => 'all', :pref => {:hide_mail => '1', :comments_sorting => 'desc'}
159
160 user = User.find(2)
161 assert_equal 'Changed', user.firstname
162 assert_equal 'all', user.mail_notification
163 assert_equal true, user.pref[:hide_mail]
164 assert_equal 'desc', user.pref[:comments_sorting]
154 165 assert ActionMailer::Base.deliveries.empty?
155 166 end
156 167
157 168 def test_edit_with_activation_should_send_a_notification
158 169 u = User.new(:firstname => 'Foo', :lastname => 'Bar', :mail => 'foo.bar@somenet.foo', :language => 'fr')
159 170 u.login = 'foo'
160 171 u.status = User::STATUS_REGISTERED
161 172 u.save!
162 173 ActionMailer::Base.deliveries.clear
163 174 Setting.bcc_recipients = '1'
164 175
165 176 post :edit, :id => u.id, :user => {:status => User::STATUS_ACTIVE}
166 177 assert u.reload.active?
167 178 mail = ActionMailer::Base.deliveries.last
168 179 assert_not_nil mail
169 180 assert_equal ['foo.bar@somenet.foo'], mail.bcc
170 181 assert mail.body.include?(ll('fr', :notice_account_activated))
171 182 end
172 183
173 184 def test_edit_with_password_change_should_send_a_notification
174 185 ActionMailer::Base.deliveries.clear
175 186 Setting.bcc_recipients = '1'
176 187
177 188 u = User.find(2)
178 189 post :edit, :id => u.id, :user => {}, :password => 'newpass', :password_confirmation => 'newpass', :send_information => '1'
179 190 assert_equal User.hash_password('newpass'), u.reload.hashed_password
180 191
181 192 mail = ActionMailer::Base.deliveries.last
182 193 assert_not_nil mail
183 194 assert_equal [u.mail], mail.bcc
184 195 assert mail.body.include?('newpass')
185 196 end
186 197
187 198 test "POST :edit with a password change to an AuthSource user switching to Internal authentication" do
188 199 # Configure as auth source
189 200 u = User.find(2)
190 201 u.auth_source = AuthSource.find(1)
191 202 u.save!
192 203
193 204 post :edit, :id => u.id, :user => {:auth_source_id => ''}, :password => 'newpass', :password_confirmation => 'newpass'
194 205
195 206 assert_equal nil, u.reload.auth_source
196 207 assert_equal User.hash_password('newpass'), u.reload.hashed_password
197 208 end
198 209
199 210 def test_edit_membership
200 211 post :edit_membership, :id => 2, :membership_id => 1,
201 212 :membership => { :role_ids => [2]}
202 213 assert_redirected_to :action => 'edit', :id => '2', :tab => 'memberships'
203 214 assert_equal [2], Member.find(1).role_ids
204 215 end
205 216
206 217 def test_destroy_membership
207 218 post :destroy_membership, :id => 2, :membership_id => 1
208 219 assert_redirected_to :action => 'edit', :id => '2', :tab => 'memberships'
209 220 assert_nil Member.find_by_id(1)
210 221 end
211 222 end
General Comments 0
You need to be logged in to leave comments. Login now