##// 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 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2009 Jean-Philippe Lang
2 # Copyright (C) 2006-2009 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class UsersController < ApplicationController
18 class UsersController < ApplicationController
19 layout 'admin'
19 layout 'admin'
20
20
21 before_filter :require_admin, :except => :show
21 before_filter :require_admin, :except => :show
22
22
23 helper :sort
23 helper :sort
24 include SortHelper
24 include SortHelper
25 helper :custom_fields
25 helper :custom_fields
26 include CustomFieldsHelper
26 include CustomFieldsHelper
27
27
28 def index
28 def index
29 sort_init 'login', 'asc'
29 sort_init 'login', 'asc'
30 sort_update %w(login firstname lastname mail admin created_on last_login_on)
30 sort_update %w(login firstname lastname mail admin created_on last_login_on)
31
31
32 @status = params[:status] ? params[:status].to_i : 1
32 @status = params[:status] ? params[:status].to_i : 1
33 c = ARCondition.new(@status == 0 ? "status <> 0" : ["status = ?", @status])
33 c = ARCondition.new(@status == 0 ? "status <> 0" : ["status = ?", @status])
34
34
35 unless params[:name].blank?
35 unless params[:name].blank?
36 name = "%#{params[:name].strip.downcase}%"
36 name = "%#{params[:name].strip.downcase}%"
37 c << ["LOWER(login) LIKE ? OR LOWER(firstname) LIKE ? OR LOWER(lastname) LIKE ? OR LOWER(mail) LIKE ?", name, name, name, name]
37 c << ["LOWER(login) LIKE ? OR LOWER(firstname) LIKE ? OR LOWER(lastname) LIKE ? OR LOWER(mail) LIKE ?", name, name, name, name]
38 end
38 end
39
39
40 @user_count = User.count(:conditions => c.conditions)
40 @user_count = User.count(:conditions => c.conditions)
41 @user_pages = Paginator.new self, @user_count,
41 @user_pages = Paginator.new self, @user_count,
42 per_page_option,
42 per_page_option,
43 params['page']
43 params['page']
44 @users = User.find :all,:order => sort_clause,
44 @users = User.find :all,:order => sort_clause,
45 :conditions => c.conditions,
45 :conditions => c.conditions,
46 :limit => @user_pages.items_per_page,
46 :limit => @user_pages.items_per_page,
47 :offset => @user_pages.current.offset
47 :offset => @user_pages.current.offset
48
48
49 render :layout => !request.xhr?
49 render :layout => !request.xhr?
50 end
50 end
51
51
52 def show
52 def show
53 @user = User.find(params[:id])
53 @user = User.find(params[:id])
54 @custom_values = @user.custom_values
54 @custom_values = @user.custom_values
55
55
56 # show projects based on current user visibility
56 # show projects based on current user visibility
57 @memberships = @user.memberships.all(:conditions => Project.visible_by(User.current))
57 @memberships = @user.memberships.all(:conditions => Project.visible_by(User.current))
58
58
59 events = Redmine::Activity::Fetcher.new(User.current, :author => @user).events(nil, nil, :limit => 10)
59 events = Redmine::Activity::Fetcher.new(User.current, :author => @user).events(nil, nil, :limit => 10)
60 @events_by_day = events.group_by(&:event_date)
60 @events_by_day = events.group_by(&:event_date)
61
61
62 unless User.current.admin?
62 unless User.current.admin?
63 if !@user.active? || (@user != User.current && @memberships.empty? && events.empty?)
63 if !@user.active? || (@user != User.current && @memberships.empty? && events.empty?)
64 render_404
64 render_404
65 return
65 return
66 end
66 end
67 end
67 end
68 render :layout => 'base'
68 render :layout => 'base'
69
69
70 rescue ActiveRecord::RecordNotFound
70 rescue ActiveRecord::RecordNotFound
71 render_404
71 render_404
72 end
72 end
73
73
74 def add
74 def add
75 @notification_options = User::MAIL_NOTIFICATION_OPTIONS
76 @notification_option = Setting.default_notification_option
77
75 @user = User.new(:language => Setting.default_language)
78 @user = User.new(:language => Setting.default_language)
76 @auth_sources = AuthSource.find(:all)
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 end
88 end
78
89
79 verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
90 verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
80 def create
91 def create
92 @notification_options = User::MAIL_NOTIFICATION_OPTIONS
93 @notification_option = Setting.default_notification_option
94
81 @user = User.new(params[:user])
95 @user = User.new(params[:user])
82 @user.admin = params[:user][:admin] || false
96 @user.admin = params[:user][:admin] || false
83 @user.login = params[:user][:login]
97 @user.login = params[:user][:login]
84 @user.password, @user.password_confirmation = params[:password], params[:password_confirmation] unless @user.auth_source_id
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 if @user.save
105 if @user.save
106 @user.pref.save
107 @user.notified_project_ids = (params[:notification_option] == 'selected' ? params[:notified_project_ids] : [])
108
86 Mailer.deliver_account_information(@user, params[:password]) if params[:send_information]
109 Mailer.deliver_account_information(@user, params[:password]) if params[:send_information]
87 flash[:notice] = l(:notice_successful_create)
110 flash[:notice] = l(:notice_successful_create)
88 redirect_to(params[:continue] ? {:controller => 'users', :action => 'add'} :
111 redirect_to(params[:continue] ? {:controller => 'users', :action => 'add'} :
89 {:controller => 'users', :action => 'edit', :id => @user})
112 {:controller => 'users', :action => 'edit', :id => @user})
90 return
113 return
91 else
114 else
92 @auth_sources = AuthSource.find(:all)
115 @auth_sources = AuthSource.find(:all)
116 @notification_option = @user.mail_notification
117
93 render :action => 'add'
118 render :action => 'add'
94 end
119 end
95 end
120 end
96
121
97 def edit
122 def edit
98 @user = User.find(params[:id])
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 if request.post?
134 if request.post?
100 @user.admin = params[:user][:admin] if params[:user][:admin]
135 @user.admin = params[:user][:admin] if params[:user][:admin]
101 @user.login = params[:user][:login] if params[:user][:login]
136 @user.login = params[:user][:login] if params[:user][:login]
102 if params[:password].present? && (@user.auth_source_id.nil? || params[:user][:auth_source_id].blank?)
137 if params[:password].present? && (@user.auth_source_id.nil? || params[:user][:auth_source_id].blank?)
103 @user.password, @user.password_confirmation = params[:password], params[:password_confirmation]
138 @user.password, @user.password_confirmation = params[:password], params[:password_confirmation]
104 end
139 end
105 @user.group_ids = params[:user][:group_ids] if params[:user][:group_ids]
140 @user.group_ids = params[:user][:group_ids] if params[:user][:group_ids]
106 @user.attributes = params[:user]
141 @user.attributes = params[:user]
107 # Was the account actived ? (do it before User#save clears the change)
142 # Was the account actived ? (do it before User#save clears the change)
108 was_activated = (@user.status_change == [User::STATUS_REGISTERED, User::STATUS_ACTIVE])
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 if @user.save
149 if @user.save
150 @user.pref.save
151 @user.notified_project_ids = (params[:notification_option] == 'selected' ? params[:notified_project_ids] : [])
152
110 if was_activated
153 if was_activated
111 Mailer.deliver_account_activated(@user)
154 Mailer.deliver_account_activated(@user)
112 elsif @user.active? && params[:send_information] && !params[:password].blank? && @user.auth_source_id.nil?
155 elsif @user.active? && params[:send_information] && !params[:password].blank? && @user.auth_source_id.nil?
113 Mailer.deliver_account_information(@user, params[:password])
156 Mailer.deliver_account_information(@user, params[:password])
114 end
157 end
115 flash[:notice] = l(:notice_successful_update)
158 flash[:notice] = l(:notice_successful_update)
116 redirect_to :back
159 redirect_to :back
117 end
160 end
118 end
161 end
119 @auth_sources = AuthSource.find(:all)
162 @auth_sources = AuthSource.find(:all)
120 @membership ||= Member.new
163 @membership ||= Member.new
121 rescue ::ActionController::RedirectBackError
164 rescue ::ActionController::RedirectBackError
122 redirect_to :controller => 'users', :action => 'edit', :id => @user
165 redirect_to :controller => 'users', :action => 'edit', :id => @user
123 end
166 end
124
167
125 def edit_membership
168 def edit_membership
126 @user = User.find(params[:id])
169 @user = User.find(params[:id])
127 @membership = Member.edit_membership(params[:membership_id], params[:membership], @user)
170 @membership = Member.edit_membership(params[:membership_id], params[:membership], @user)
128 @membership.save if request.post?
171 @membership.save if request.post?
129 respond_to do |format|
172 respond_to do |format|
130 if @membership.valid?
173 if @membership.valid?
131 format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' }
174 format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' }
132 format.js {
175 format.js {
133 render(:update) {|page|
176 render(:update) {|page|
134 page.replace_html "tab-content-memberships", :partial => 'users/memberships'
177 page.replace_html "tab-content-memberships", :partial => 'users/memberships'
135 page.visual_effect(:highlight, "member-#{@membership.id}")
178 page.visual_effect(:highlight, "member-#{@membership.id}")
136 }
179 }
137 }
180 }
138 else
181 else
139 format.js {
182 format.js {
140 render(:update) {|page|
183 render(:update) {|page|
141 page.alert(l(:notice_failed_to_save_members, :errors => @membership.errors.full_messages.join(', ')))
184 page.alert(l(:notice_failed_to_save_members, :errors => @membership.errors.full_messages.join(', ')))
142 }
185 }
143 }
186 }
144 end
187 end
145 end
188 end
146 end
189 end
147
190
148 def destroy_membership
191 def destroy_membership
149 @user = User.find(params[:id])
192 @user = User.find(params[:id])
150 @membership = Member.find(params[:membership_id])
193 @membership = Member.find(params[:membership_id])
151 if request.post? && @membership.deletable?
194 if request.post? && @membership.deletable?
152 @membership.destroy
195 @membership.destroy
153 end
196 end
154 respond_to do |format|
197 respond_to do |format|
155 format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' }
198 format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' }
156 format.js { render(:update) {|page| page.replace_html "tab-content-memberships", :partial => 'users/memberships'} }
199 format.js { render(:update) {|page| page.replace_html "tab-content-memberships", :partial => 'users/memberships'} }
157 end
200 end
158 end
201 end
159 end
202 end
@@ -1,460 +1,460
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2009 Jean-Philippe Lang
2 # Copyright (C) 2006-2009 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require "digest/sha1"
18 require "digest/sha1"
19
19
20 class User < Principal
20 class User < Principal
21
21
22 # Account statuses
22 # Account statuses
23 STATUS_ANONYMOUS = 0
23 STATUS_ANONYMOUS = 0
24 STATUS_ACTIVE = 1
24 STATUS_ACTIVE = 1
25 STATUS_REGISTERED = 2
25 STATUS_REGISTERED = 2
26 STATUS_LOCKED = 3
26 STATUS_LOCKED = 3
27
27
28 USER_FORMATS = {
28 USER_FORMATS = {
29 :firstname_lastname => '#{firstname} #{lastname}',
29 :firstname_lastname => '#{firstname} #{lastname}',
30 :firstname => '#{firstname}',
30 :firstname => '#{firstname}',
31 :lastname_firstname => '#{lastname} #{firstname}',
31 :lastname_firstname => '#{lastname} #{firstname}',
32 :lastname_coma_firstname => '#{lastname}, #{firstname}',
32 :lastname_coma_firstname => '#{lastname}, #{firstname}',
33 :username => '#{login}'
33 :username => '#{login}'
34 }
34 }
35
35
36 MAIL_NOTIFICATION_OPTIONS = [
36 MAIL_NOTIFICATION_OPTIONS = [
37 [:all, :label_user_mail_option_all],
37 [:all, :label_user_mail_option_all],
38 [:selected, :label_user_mail_option_selected],
38 [:selected, :label_user_mail_option_selected],
39 [:none, :label_user_mail_option_none],
39 [:none, :label_user_mail_option_none],
40 [:only_my_events, :label_user_mail_option_only_my_events],
40 [:only_my_events, :label_user_mail_option_only_my_events],
41 [:only_assigned, :label_user_mail_option_only_assigned],
41 [:only_assigned, :label_user_mail_option_only_assigned],
42 [:only_owner, :label_user_mail_option_only_owner]
42 [:only_owner, :label_user_mail_option_only_owner]
43 ]
43 ]
44
44
45 has_and_belongs_to_many :groups, :after_add => Proc.new {|user, group| group.user_added(user)},
45 has_and_belongs_to_many :groups, :after_add => Proc.new {|user, group| group.user_added(user)},
46 :after_remove => Proc.new {|user, group| group.user_removed(user)}
46 :after_remove => Proc.new {|user, group| group.user_removed(user)}
47 has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
47 has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
48 has_many :changesets, :dependent => :nullify
48 has_many :changesets, :dependent => :nullify
49 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
49 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
50 has_one :rss_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='feeds'"
50 has_one :rss_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='feeds'"
51 has_one :api_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='api'"
51 has_one :api_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='api'"
52 belongs_to :auth_source
52 belongs_to :auth_source
53
53
54 # Active non-anonymous users scope
54 # Active non-anonymous users scope
55 named_scope :active, :conditions => "#{User.table_name}.status = #{STATUS_ACTIVE}"
55 named_scope :active, :conditions => "#{User.table_name}.status = #{STATUS_ACTIVE}"
56
56
57 acts_as_customizable
57 acts_as_customizable
58
58
59 attr_accessor :password, :password_confirmation
59 attr_accessor :password, :password_confirmation
60 attr_accessor :last_before_login_on
60 attr_accessor :last_before_login_on
61 # Prevents unauthorized assignments
61 # Prevents unauthorized assignments
62 attr_protected :login, :admin, :password, :password_confirmation, :hashed_password, :group_ids
62 attr_protected :login, :admin, :password, :password_confirmation, :hashed_password, :group_ids
63
63
64 validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
64 validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
65 validates_uniqueness_of :login, :if => Proc.new { |user| !user.login.blank? }, :case_sensitive => false
65 validates_uniqueness_of :login, :if => Proc.new { |user| !user.login.blank? }, :case_sensitive => false
66 validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }, :case_sensitive => false
66 validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }, :case_sensitive => false
67 # Login must contain lettres, numbers, underscores only
67 # Login must contain lettres, numbers, underscores only
68 validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
68 validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
69 validates_length_of :login, :maximum => 30
69 validates_length_of :login, :maximum => 30
70 validates_format_of :firstname, :lastname, :with => /^[\w\s\'\-\.]*$/i
70 validates_format_of :firstname, :lastname, :with => /^[\w\s\'\-\.]*$/i
71 validates_length_of :firstname, :lastname, :maximum => 30
71 validates_length_of :firstname, :lastname, :maximum => 30
72 validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_nil => true
72 validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_nil => true
73 validates_length_of :mail, :maximum => 60, :allow_nil => true
73 validates_length_of :mail, :maximum => 60, :allow_nil => true
74 validates_confirmation_of :password, :allow_nil => true
74 validates_confirmation_of :password, :allow_nil => true
75
75
76 def before_create
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 true
78 true
79 end
79 end
80
80
81 def before_save
81 def before_save
82 # update hashed_password if password was set
82 # update hashed_password if password was set
83 self.hashed_password = User.hash_password(self.password) if self.password && self.auth_source_id.blank?
83 self.hashed_password = User.hash_password(self.password) if self.password && self.auth_source_id.blank?
84 end
84 end
85
85
86 def reload(*args)
86 def reload(*args)
87 @name = nil
87 @name = nil
88 super
88 super
89 end
89 end
90
90
91 def mail=(arg)
91 def mail=(arg)
92 write_attribute(:mail, arg.to_s.strip)
92 write_attribute(:mail, arg.to_s.strip)
93 end
93 end
94
94
95 def identity_url=(url)
95 def identity_url=(url)
96 if url.blank?
96 if url.blank?
97 write_attribute(:identity_url, '')
97 write_attribute(:identity_url, '')
98 else
98 else
99 begin
99 begin
100 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
100 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
101 rescue OpenIdAuthentication::InvalidOpenId
101 rescue OpenIdAuthentication::InvalidOpenId
102 # Invlaid url, don't save
102 # Invlaid url, don't save
103 end
103 end
104 end
104 end
105 self.read_attribute(:identity_url)
105 self.read_attribute(:identity_url)
106 end
106 end
107
107
108 # Returns the user that matches provided login and password, or nil
108 # Returns the user that matches provided login and password, or nil
109 def self.try_to_login(login, password)
109 def self.try_to_login(login, password)
110 # Make sure no one can sign in with an empty password
110 # Make sure no one can sign in with an empty password
111 return nil if password.to_s.empty?
111 return nil if password.to_s.empty?
112 user = find_by_login(login)
112 user = find_by_login(login)
113 if user
113 if user
114 # user is already in local database
114 # user is already in local database
115 return nil if !user.active?
115 return nil if !user.active?
116 if user.auth_source
116 if user.auth_source
117 # user has an external authentication method
117 # user has an external authentication method
118 return nil unless user.auth_source.authenticate(login, password)
118 return nil unless user.auth_source.authenticate(login, password)
119 else
119 else
120 # authentication with local password
120 # authentication with local password
121 return nil unless User.hash_password(password) == user.hashed_password
121 return nil unless User.hash_password(password) == user.hashed_password
122 end
122 end
123 else
123 else
124 # user is not yet registered, try to authenticate with available sources
124 # user is not yet registered, try to authenticate with available sources
125 attrs = AuthSource.authenticate(login, password)
125 attrs = AuthSource.authenticate(login, password)
126 if attrs
126 if attrs
127 user = new(attrs)
127 user = new(attrs)
128 user.login = login
128 user.login = login
129 user.language = Setting.default_language
129 user.language = Setting.default_language
130 if user.save
130 if user.save
131 user.reload
131 user.reload
132 logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source
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 end
133 end
134 end
134 end
135 end
135 end
136 user.update_attribute(:last_login_on, Time.now) if user && !user.new_record?
136 user.update_attribute(:last_login_on, Time.now) if user && !user.new_record?
137 user
137 user
138 rescue => text
138 rescue => text
139 raise text
139 raise text
140 end
140 end
141
141
142 # Returns the user who matches the given autologin +key+ or nil
142 # Returns the user who matches the given autologin +key+ or nil
143 def self.try_to_autologin(key)
143 def self.try_to_autologin(key)
144 tokens = Token.find_all_by_action_and_value('autologin', key)
144 tokens = Token.find_all_by_action_and_value('autologin', key)
145 # Make sure there's only 1 token that matches the key
145 # Make sure there's only 1 token that matches the key
146 if tokens.size == 1
146 if tokens.size == 1
147 token = tokens.first
147 token = tokens.first
148 if (token.created_on > Setting.autologin.to_i.day.ago) && token.user && token.user.active?
148 if (token.created_on > Setting.autologin.to_i.day.ago) && token.user && token.user.active?
149 token.user.update_attribute(:last_login_on, Time.now)
149 token.user.update_attribute(:last_login_on, Time.now)
150 token.user
150 token.user
151 end
151 end
152 end
152 end
153 end
153 end
154
154
155 # Return user's full name for display
155 # Return user's full name for display
156 def name(formatter = nil)
156 def name(formatter = nil)
157 if formatter
157 if formatter
158 eval('"' + (USER_FORMATS[formatter] || USER_FORMATS[:firstname_lastname]) + '"')
158 eval('"' + (USER_FORMATS[formatter] || USER_FORMATS[:firstname_lastname]) + '"')
159 else
159 else
160 @name ||= eval('"' + (USER_FORMATS[Setting.user_format] || USER_FORMATS[:firstname_lastname]) + '"')
160 @name ||= eval('"' + (USER_FORMATS[Setting.user_format] || USER_FORMATS[:firstname_lastname]) + '"')
161 end
161 end
162 end
162 end
163
163
164 def active?
164 def active?
165 self.status == STATUS_ACTIVE
165 self.status == STATUS_ACTIVE
166 end
166 end
167
167
168 def registered?
168 def registered?
169 self.status == STATUS_REGISTERED
169 self.status == STATUS_REGISTERED
170 end
170 end
171
171
172 def locked?
172 def locked?
173 self.status == STATUS_LOCKED
173 self.status == STATUS_LOCKED
174 end
174 end
175
175
176 def activate
176 def activate
177 self.status = STATUS_ACTIVE
177 self.status = STATUS_ACTIVE
178 end
178 end
179
179
180 def register
180 def register
181 self.status = STATUS_REGISTERED
181 self.status = STATUS_REGISTERED
182 end
182 end
183
183
184 def lock
184 def lock
185 self.status = STATUS_LOCKED
185 self.status = STATUS_LOCKED
186 end
186 end
187
187
188 def activate!
188 def activate!
189 update_attribute(:status, STATUS_ACTIVE)
189 update_attribute(:status, STATUS_ACTIVE)
190 end
190 end
191
191
192 def register!
192 def register!
193 update_attribute(:status, STATUS_REGISTERED)
193 update_attribute(:status, STATUS_REGISTERED)
194 end
194 end
195
195
196 def lock!
196 def lock!
197 update_attribute(:status, STATUS_LOCKED)
197 update_attribute(:status, STATUS_LOCKED)
198 end
198 end
199
199
200 def check_password?(clear_password)
200 def check_password?(clear_password)
201 if auth_source_id.present?
201 if auth_source_id.present?
202 auth_source.authenticate(self.login, clear_password)
202 auth_source.authenticate(self.login, clear_password)
203 else
203 else
204 User.hash_password(clear_password) == self.hashed_password
204 User.hash_password(clear_password) == self.hashed_password
205 end
205 end
206 end
206 end
207
207
208 # Does the backend storage allow this user to change their password?
208 # Does the backend storage allow this user to change their password?
209 def change_password_allowed?
209 def change_password_allowed?
210 return true if auth_source_id.blank?
210 return true if auth_source_id.blank?
211 return auth_source.allow_password_changes?
211 return auth_source.allow_password_changes?
212 end
212 end
213
213
214 # Generate and set a random password. Useful for automated user creation
214 # Generate and set a random password. Useful for automated user creation
215 # Based on Token#generate_token_value
215 # Based on Token#generate_token_value
216 #
216 #
217 def random_password
217 def random_password
218 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
218 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
219 password = ''
219 password = ''
220 40.times { |i| password << chars[rand(chars.size-1)] }
220 40.times { |i| password << chars[rand(chars.size-1)] }
221 self.password = password
221 self.password = password
222 self.password_confirmation = password
222 self.password_confirmation = password
223 self
223 self
224 end
224 end
225
225
226 def pref
226 def pref
227 self.preference ||= UserPreference.new(:user => self)
227 self.preference ||= UserPreference.new(:user => self)
228 end
228 end
229
229
230 def time_zone
230 def time_zone
231 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
231 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
232 end
232 end
233
233
234 def wants_comments_in_reverse_order?
234 def wants_comments_in_reverse_order?
235 self.pref[:comments_sorting] == 'desc'
235 self.pref[:comments_sorting] == 'desc'
236 end
236 end
237
237
238 # Return user's RSS key (a 40 chars long string), used to access feeds
238 # Return user's RSS key (a 40 chars long string), used to access feeds
239 def rss_key
239 def rss_key
240 token = self.rss_token || Token.create(:user => self, :action => 'feeds')
240 token = self.rss_token || Token.create(:user => self, :action => 'feeds')
241 token.value
241 token.value
242 end
242 end
243
243
244 # Return user's API key (a 40 chars long string), used to access the API
244 # Return user's API key (a 40 chars long string), used to access the API
245 def api_key
245 def api_key
246 token = self.api_token || self.create_api_token(:action => 'api')
246 token = self.api_token || self.create_api_token(:action => 'api')
247 token.value
247 token.value
248 end
248 end
249
249
250 # Return an array of project ids for which the user has explicitly turned mail notifications on
250 # Return an array of project ids for which the user has explicitly turned mail notifications on
251 def notified_projects_ids
251 def notified_projects_ids
252 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
252 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
253 end
253 end
254
254
255 def notified_project_ids=(ids)
255 def notified_project_ids=(ids)
256 Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
256 Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
257 Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
257 Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
258 @notified_projects_ids = nil
258 @notified_projects_ids = nil
259 notified_projects_ids
259 notified_projects_ids
260 end
260 end
261
261
262 # Find a user account by matching the exact login and then a case-insensitive
262 # Find a user account by matching the exact login and then a case-insensitive
263 # version. Exact matches will be given priority.
263 # version. Exact matches will be given priority.
264 def self.find_by_login(login)
264 def self.find_by_login(login)
265 # force string comparison to be case sensitive on MySQL
265 # force string comparison to be case sensitive on MySQL
266 type_cast = (ActiveRecord::Base.connection.adapter_name == 'MySQL') ? 'BINARY' : ''
266 type_cast = (ActiveRecord::Base.connection.adapter_name == 'MySQL') ? 'BINARY' : ''
267
267
268 # First look for an exact match
268 # First look for an exact match
269 user = first(:conditions => ["#{type_cast} login = ?", login])
269 user = first(:conditions => ["#{type_cast} login = ?", login])
270 # Fail over to case-insensitive if none was found
270 # Fail over to case-insensitive if none was found
271 user ||= first(:conditions => ["#{type_cast} LOWER(login) = ?", login.to_s.downcase])
271 user ||= first(:conditions => ["#{type_cast} LOWER(login) = ?", login.to_s.downcase])
272 end
272 end
273
273
274 def self.find_by_rss_key(key)
274 def self.find_by_rss_key(key)
275 token = Token.find_by_value(key)
275 token = Token.find_by_value(key)
276 token && token.user.active? ? token.user : nil
276 token && token.user.active? ? token.user : nil
277 end
277 end
278
278
279 def self.find_by_api_key(key)
279 def self.find_by_api_key(key)
280 token = Token.find_by_action_and_value('api', key)
280 token = Token.find_by_action_and_value('api', key)
281 token && token.user.active? ? token.user : nil
281 token && token.user.active? ? token.user : nil
282 end
282 end
283
283
284 # Makes find_by_mail case-insensitive
284 # Makes find_by_mail case-insensitive
285 def self.find_by_mail(mail)
285 def self.find_by_mail(mail)
286 find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
286 find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
287 end
287 end
288
288
289 def to_s
289 def to_s
290 name
290 name
291 end
291 end
292
292
293 # Returns the current day according to user's time zone
293 # Returns the current day according to user's time zone
294 def today
294 def today
295 if time_zone.nil?
295 if time_zone.nil?
296 Date.today
296 Date.today
297 else
297 else
298 Time.now.in_time_zone(time_zone).to_date
298 Time.now.in_time_zone(time_zone).to_date
299 end
299 end
300 end
300 end
301
301
302 def logged?
302 def logged?
303 true
303 true
304 end
304 end
305
305
306 def anonymous?
306 def anonymous?
307 !logged?
307 !logged?
308 end
308 end
309
309
310 # Return user's roles for project
310 # Return user's roles for project
311 def roles_for_project(project)
311 def roles_for_project(project)
312 roles = []
312 roles = []
313 # No role on archived projects
313 # No role on archived projects
314 return roles unless project && project.active?
314 return roles unless project && project.active?
315 if logged?
315 if logged?
316 # Find project membership
316 # Find project membership
317 membership = memberships.detect {|m| m.project_id == project.id}
317 membership = memberships.detect {|m| m.project_id == project.id}
318 if membership
318 if membership
319 roles = membership.roles
319 roles = membership.roles
320 else
320 else
321 @role_non_member ||= Role.non_member
321 @role_non_member ||= Role.non_member
322 roles << @role_non_member
322 roles << @role_non_member
323 end
323 end
324 else
324 else
325 @role_anonymous ||= Role.anonymous
325 @role_anonymous ||= Role.anonymous
326 roles << @role_anonymous
326 roles << @role_anonymous
327 end
327 end
328 roles
328 roles
329 end
329 end
330
330
331 # Return true if the user is a member of project
331 # Return true if the user is a member of project
332 def member_of?(project)
332 def member_of?(project)
333 !roles_for_project(project).detect {|role| role.member?}.nil?
333 !roles_for_project(project).detect {|role| role.member?}.nil?
334 end
334 end
335
335
336 # Return true if the user is allowed to do the specified action on project
336 # Return true if the user is allowed to do the specified action on project
337 # action can be:
337 # action can be:
338 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
338 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
339 # * a permission Symbol (eg. :edit_project)
339 # * a permission Symbol (eg. :edit_project)
340 def allowed_to?(action, project, options={})
340 def allowed_to?(action, project, options={})
341 if project
341 if project
342 # No action allowed on archived projects
342 # No action allowed on archived projects
343 return false unless project.active?
343 return false unless project.active?
344 # No action allowed on disabled modules
344 # No action allowed on disabled modules
345 return false unless project.allows_to?(action)
345 return false unless project.allows_to?(action)
346 # Admin users are authorized for anything else
346 # Admin users are authorized for anything else
347 return true if admin?
347 return true if admin?
348
348
349 roles = roles_for_project(project)
349 roles = roles_for_project(project)
350 return false unless roles
350 return false unless roles
351 roles.detect {|role| (project.is_public? || role.member?) && role.allowed_to?(action)}
351 roles.detect {|role| (project.is_public? || role.member?) && role.allowed_to?(action)}
352
352
353 elsif options[:global]
353 elsif options[:global]
354 # Admin users are always authorized
354 # Admin users are always authorized
355 return true if admin?
355 return true if admin?
356
356
357 # authorize if user has at least one role that has this permission
357 # authorize if user has at least one role that has this permission
358 roles = memberships.collect {|m| m.roles}.flatten.uniq
358 roles = memberships.collect {|m| m.roles}.flatten.uniq
359 roles.detect {|r| r.allowed_to?(action)} || (self.logged? ? Role.non_member.allowed_to?(action) : Role.anonymous.allowed_to?(action))
359 roles.detect {|r| r.allowed_to?(action)} || (self.logged? ? Role.non_member.allowed_to?(action) : Role.anonymous.allowed_to?(action))
360 else
360 else
361 false
361 false
362 end
362 end
363 end
363 end
364
364
365 # Is the user allowed to do the specified action on any project?
365 # Is the user allowed to do the specified action on any project?
366 # See allowed_to? for the actions and valid options.
366 # See allowed_to? for the actions and valid options.
367 def allowed_to_globally?(action, options)
367 def allowed_to_globally?(action, options)
368 allowed_to?(action, nil, options.reverse_merge(:global => true))
368 allowed_to?(action, nil, options.reverse_merge(:global => true))
369 end
369 end
370
370
371 # Utility method to help check if a user should be notified about an
371 # Utility method to help check if a user should be notified about an
372 # event.
372 # event.
373 #
373 #
374 # TODO: only supports Issue events currently
374 # TODO: only supports Issue events currently
375 def notify_about?(object)
375 def notify_about?(object)
376 case mail_notification.to_sym
376 case mail_notification.to_sym
377 when :all
377 when :all
378 true
378 true
379 when :selected
379 when :selected
380 # Handled by the Project
380 # Handled by the Project
381 when :none
381 when :none
382 false
382 false
383 when :only_my_events
383 when :only_my_events
384 if object.is_a?(Issue) && (object.author == self || object.assigned_to == self)
384 if object.is_a?(Issue) && (object.author == self || object.assigned_to == self)
385 true
385 true
386 else
386 else
387 false
387 false
388 end
388 end
389 when :only_assigned
389 when :only_assigned
390 if object.is_a?(Issue) && object.assigned_to == self
390 if object.is_a?(Issue) && object.assigned_to == self
391 true
391 true
392 else
392 else
393 false
393 false
394 end
394 end
395 when :only_owner
395 when :only_owner
396 if object.is_a?(Issue) && object.author == self
396 if object.is_a?(Issue) && object.author == self
397 true
397 true
398 else
398 else
399 false
399 false
400 end
400 end
401 else
401 else
402 false
402 false
403 end
403 end
404 end
404 end
405
405
406 def self.current=(user)
406 def self.current=(user)
407 @current_user = user
407 @current_user = user
408 end
408 end
409
409
410 def self.current
410 def self.current
411 @current_user ||= User.anonymous
411 @current_user ||= User.anonymous
412 end
412 end
413
413
414 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
414 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
415 # one anonymous user per database.
415 # one anonymous user per database.
416 def self.anonymous
416 def self.anonymous
417 anonymous_user = AnonymousUser.find(:first)
417 anonymous_user = AnonymousUser.find(:first)
418 if anonymous_user.nil?
418 if anonymous_user.nil?
419 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
419 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
420 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
420 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
421 end
421 end
422 anonymous_user
422 anonymous_user
423 end
423 end
424
424
425 protected
425 protected
426
426
427 def validate
427 def validate
428 # Password length validation based on setting
428 # Password length validation based on setting
429 if !password.nil? && password.size < Setting.password_min_length.to_i
429 if !password.nil? && password.size < Setting.password_min_length.to_i
430 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
430 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
431 end
431 end
432 end
432 end
433
433
434 private
434 private
435
435
436 # Return password digest
436 # Return password digest
437 def self.hash_password(clear_password)
437 def self.hash_password(clear_password)
438 Digest::SHA1.hexdigest(clear_password || "")
438 Digest::SHA1.hexdigest(clear_password || "")
439 end
439 end
440 end
440 end
441
441
442 class AnonymousUser < User
442 class AnonymousUser < User
443
443
444 def validate_on_create
444 def validate_on_create
445 # There should be only one AnonymousUser in the database
445 # There should be only one AnonymousUser in the database
446 errors.add_to_base 'An anonymous user already exists.' if AnonymousUser.find(:first)
446 errors.add_to_base 'An anonymous user already exists.' if AnonymousUser.find(:first)
447 end
447 end
448
448
449 def available_custom_fields
449 def available_custom_fields
450 []
450 []
451 end
451 end
452
452
453 # Overrides a few properties
453 # Overrides a few properties
454 def logged?; false end
454 def logged?; false end
455 def admin; false end
455 def admin; false end
456 def name(*args); I18n.t(:label_user_anonymous) end
456 def name(*args); I18n.t(:label_user_anonymous) end
457 def mail; nil end
457 def mail; nil end
458 def time_zone; nil end
458 def time_zone; nil end
459 def rss_key; nil end
459 def rss_key; nil end
460 end
460 end
@@ -1,62 +1,50
1 <div class="contextual">
1 <div class="contextual">
2 <%= link_to(l(:button_change_password), :action => 'password') if @user.change_password_allowed? %>
2 <%= link_to(l(:button_change_password), :action => 'password') if @user.change_password_allowed? %>
3 <%= call_hook(:view_my_account_contextual, :user => @user)%>
3 <%= call_hook(:view_my_account_contextual, :user => @user)%>
4 </div>
4 </div>
5 <h2><%=l(:label_my_account)%></h2>
5 <h2><%=l(:label_my_account)%></h2>
6 <%= error_messages_for 'user' %>
6 <%= error_messages_for 'user' %>
7
7
8 <% form_for :user, @user, :url => { :action => "account" },
8 <% form_for :user, @user, :url => { :action => "account" },
9 :builder => TabularFormBuilder,
9 :builder => TabularFormBuilder,
10 :lang => current_language,
10 :lang => current_language,
11 :html => { :id => 'my_account_form' } do |f| %>
11 :html => { :id => 'my_account_form' } do |f| %>
12 <div class="splitcontentleft">
12 <div class="splitcontentleft">
13 <h3><%=l(:label_information_plural)%></h3>
13 <h3><%=l(:label_information_plural)%></h3>
14 <div class="box tabular">
14 <div class="box tabular">
15 <p><%= f.text_field :firstname, :required => true %></p>
15 <p><%= f.text_field :firstname, :required => true %></p>
16 <p><%= f.text_field :lastname, :required => true %></p>
16 <p><%= f.text_field :lastname, :required => true %></p>
17 <p><%= f.text_field :mail, :required => true %></p>
17 <p><%= f.text_field :mail, :required => true %></p>
18 <p><%= f.select :language, lang_options_for_select %></p>
18 <p><%= f.select :language, lang_options_for_select %></p>
19 <% if Setting.openid? %>
19 <% if Setting.openid? %>
20 <p><%= f.text_field :identity_url %></p>
20 <p><%= f.text_field :identity_url %></p>
21 <% end %>
21 <% end %>
22
22
23 <% @user.custom_field_values.select(&:editable?).each do |value| %>
23 <% @user.custom_field_values.select(&:editable?).each do |value| %>
24 <p><%= custom_field_tag_with_label :user, value %></p>
24 <p><%= custom_field_tag_with_label :user, value %></p>
25 <% end %>
25 <% end %>
26 <%= call_hook(:view_my_account, :user => @user, :form => f) %>
26 <%= call_hook(:view_my_account, :user => @user, :form => f) %>
27 </div>
27 </div>
28
28
29 <%= submit_tag l(:button_save) %>
29 <%= submit_tag l(:button_save) %>
30 </div>
30 </div>
31
31
32 <div class="splitcontentright">
32 <div class="splitcontentright">
33 <h3><%=l(:field_mail_notification)%></h3>
33 <h3><%=l(:field_mail_notification)%></h3>
34 <div class="box">
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),
35 <%= render :partial => 'users/mail_notifications' %>
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>
44 </div>
36 </div>
45
37
46 <h3><%=l(:label_preferences)%></h3>
38 <h3><%=l(:label_preferences)%></h3>
47 <div class="box tabular">
39 <div class="box tabular">
48 <% fields_for :pref, @user.pref, :builder => TabularFormBuilder, :lang => current_language do |pref_fields| %>
40 <%= render :partial => 'users/preferences' %>
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 %>
53 </div>
41 </div>
54
42
55 </div>
43 </div>
56 <% end %>
44 <% end %>
57
45
58 <% content_for :sidebar do %>
46 <% content_for :sidebar do %>
59 <%= render :partial => 'sidebar' %>
47 <%= render :partial => 'sidebar' %>
60 <% end %>
48 <% end %>
61
49
62 <% html_title(l(:label_my_account)) -%>
50 <% html_title(l(:label_my_account)) -%>
@@ -1,35 +1,45
1 <%= error_messages_for 'user' %>
1 <%= error_messages_for 'user' %>
2
2
3 <!--[form:user]-->
3 <!--[form:user]-->
4 <div class="box tabular">
4 <div class="box tabular">
5 <p><%= f.text_field :login, :required => true, :size => 25 %></p>
5 <p><%= f.text_field :login, :required => true, :size => 25 %></p>
6 <p><%= f.text_field :firstname, :required => true %></p>
6 <p><%= f.text_field :firstname, :required => true %></p>
7 <p><%= f.text_field :lastname, :required => true %></p>
7 <p><%= f.text_field :lastname, :required => true %></p>
8 <p><%= f.text_field :mail, :required => true %></p>
8 <p><%= f.text_field :mail, :required => true %></p>
9 <p><%= f.select :language, lang_options_for_select %></p>
9 <p><%= f.select :language, lang_options_for_select %></p>
10 <% if Setting.openid? %>
10 <% if Setting.openid? %>
11 <p><%= f.text_field :identity_url %></p>
11 <p><%= f.text_field :identity_url %></p>
12 <% end %>
12 <% end %>
13
13
14 <% @user.custom_field_values.each do |value| %>
14 <% @user.custom_field_values.each do |value| %>
15 <p><%= custom_field_tag_with_label :user, value %></p>
15 <p><%= custom_field_tag_with_label :user, value %></p>
16 <% end %>
16 <% end %>
17
17
18 <p><%= f.check_box :admin, :disabled => (@user == User.current) %></p>
18 <p><%= f.check_box :admin, :disabled => (@user == User.current) %></p>
19 <%= call_hook(:view_users_form, :user => @user, :form => f) %>
19 <%= call_hook(:view_users_form, :user => @user, :form => f) %>
20 </div>
20 </div>
21
21
22 <div class="box tabular">
22 <div class="box tabular">
23 <h3><%=l(:label_authentication)%></h3>
23 <h3><%=l(:label_authentication)%></h3>
24 <% unless @auth_sources.empty? %>
24 <% unless @auth_sources.empty? %>
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>
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 <% end %>
26 <% end %>
27 <div id="password_fields" style="<%= 'display:none;' if @user.auth_source %>">
27 <div id="password_fields" style="<%= 'display:none;' if @user.auth_source %>">
28 <p><label for="password"><%=l(:field_password)%><span class="required"> *</span></label>
28 <p><label for="password"><%=l(:field_password)%><span class="required"> *</span></label>
29 <%= password_field_tag 'password', nil, :size => 25 %><br />
29 <%= password_field_tag 'password', nil, :size => 25 %><br />
30 <em><%= l(:text_caracters_minimum, :count => Setting.password_min_length) %></em></p>
30 <em><%= l(:text_caracters_minimum, :count => Setting.password_min_length) %></em></p>
31 <p><label for="password_confirmation"><%=l(:field_password_confirmation)%><span class="required"> *</span></label>
31 <p><label for="password_confirmation"><%=l(:field_password_confirmation)%><span class="required"> *</span></label>
32 <%= password_field_tag 'password_confirmation', nil, :size => 25 %></p>
32 <%= password_field_tag 'password_confirmation', nil, :size => 25 %></p>
33 </div>
33 </div>
34 </div>
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 <!--[eoform:user]-->
45 <!--[eoform:user]-->
@@ -1,211 +1,222
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19 require 'users_controller'
19 require 'users_controller'
20
20
21 # Re-raise errors caught by the controller.
21 # Re-raise errors caught by the controller.
22 class UsersController; def rescue_action(e) raise e end; end
22 class UsersController; def rescue_action(e) raise e end; end
23
23
24 class UsersControllerTest < ActionController::TestCase
24 class UsersControllerTest < ActionController::TestCase
25 include Redmine::I18n
25 include Redmine::I18n
26
26
27 fixtures :users, :projects, :members, :member_roles, :roles, :auth_sources
27 fixtures :users, :projects, :members, :member_roles, :roles, :auth_sources
28
28
29 def setup
29 def setup
30 @controller = UsersController.new
30 @controller = UsersController.new
31 @request = ActionController::TestRequest.new
31 @request = ActionController::TestRequest.new
32 @response = ActionController::TestResponse.new
32 @response = ActionController::TestResponse.new
33 User.current = nil
33 User.current = nil
34 @request.session[:user_id] = 1 # admin
34 @request.session[:user_id] = 1 # admin
35 end
35 end
36
36
37 def test_index
37 def test_index
38 get :index
38 get :index
39 assert_response :success
39 assert_response :success
40 assert_template 'index'
40 assert_template 'index'
41 end
41 end
42
42
43 def test_index
43 def test_index
44 get :index
44 get :index
45 assert_response :success
45 assert_response :success
46 assert_template 'index'
46 assert_template 'index'
47 assert_not_nil assigns(:users)
47 assert_not_nil assigns(:users)
48 # active users only
48 # active users only
49 assert_nil assigns(:users).detect {|u| !u.active?}
49 assert_nil assigns(:users).detect {|u| !u.active?}
50 end
50 end
51
51
52 def test_index_with_name_filter
52 def test_index_with_name_filter
53 get :index, :name => 'john'
53 get :index, :name => 'john'
54 assert_response :success
54 assert_response :success
55 assert_template 'index'
55 assert_template 'index'
56 users = assigns(:users)
56 users = assigns(:users)
57 assert_not_nil users
57 assert_not_nil users
58 assert_equal 1, users.size
58 assert_equal 1, users.size
59 assert_equal 'John', users.first.firstname
59 assert_equal 'John', users.first.firstname
60 end
60 end
61
61
62 def test_show
62 def test_show
63 @request.session[:user_id] = nil
63 @request.session[:user_id] = nil
64 get :show, :id => 2
64 get :show, :id => 2
65 assert_response :success
65 assert_response :success
66 assert_template 'show'
66 assert_template 'show'
67 assert_not_nil assigns(:user)
67 assert_not_nil assigns(:user)
68 end
68 end
69
69
70 def test_show_should_not_fail_when_custom_values_are_nil
70 def test_show_should_not_fail_when_custom_values_are_nil
71 user = User.find(2)
71 user = User.find(2)
72
72
73 # Create a custom field to illustrate the issue
73 # Create a custom field to illustrate the issue
74 custom_field = CustomField.create!(:name => 'Testing', :field_format => 'text')
74 custom_field = CustomField.create!(:name => 'Testing', :field_format => 'text')
75 custom_value = user.custom_values.build(:custom_field => custom_field).save!
75 custom_value = user.custom_values.build(:custom_field => custom_field).save!
76
76
77 get :show, :id => 2
77 get :show, :id => 2
78 assert_response :success
78 assert_response :success
79 end
79 end
80
80
81 def test_show_inactive
81 def test_show_inactive
82 @request.session[:user_id] = nil
82 @request.session[:user_id] = nil
83 get :show, :id => 5
83 get :show, :id => 5
84 assert_response 404
84 assert_response 404
85 end
85 end
86
86
87 def test_show_should_not_reveal_users_with_no_visible_activity_or_project
87 def test_show_should_not_reveal_users_with_no_visible_activity_or_project
88 @request.session[:user_id] = nil
88 @request.session[:user_id] = nil
89 get :show, :id => 9
89 get :show, :id => 9
90 assert_response 404
90 assert_response 404
91 end
91 end
92
92
93 def test_show_inactive_by_admin
93 def test_show_inactive_by_admin
94 @request.session[:user_id] = 1
94 @request.session[:user_id] = 1
95 get :show, :id => 5
95 get :show, :id => 5
96 assert_response 200
96 assert_response 200
97 assert_not_nil assigns(:user)
97 assert_not_nil assigns(:user)
98 end
98 end
99
99
100 def test_show_displays_memberships_based_on_project_visibility
100 def test_show_displays_memberships_based_on_project_visibility
101 @request.session[:user_id] = 1
101 @request.session[:user_id] = 1
102 get :show, :id => 2
102 get :show, :id => 2
103 assert_response :success
103 assert_response :success
104 memberships = assigns(:memberships)
104 memberships = assigns(:memberships)
105 assert_not_nil memberships
105 assert_not_nil memberships
106 project_ids = memberships.map(&:project_id)
106 project_ids = memberships.map(&:project_id)
107 assert project_ids.include?(2) #private project admin can see
107 assert project_ids.include?(2) #private project admin can see
108 end
108 end
109
109
110 context "GET :add" do
110 context "GET :add" do
111 setup do
111 setup do
112 get :add
112 get :add
113 end
113 end
114
114
115 should_assign_to :user
115 should_assign_to :user
116 should_respond_with :success
116 should_respond_with :success
117 should_render_template :add
117 should_render_template :add
118 end
118 end
119
119
120 context "POST :create" do
120 context "POST :create" do
121 context "when successful" do
121 context "when successful" do
122 setup do
122 setup do
123 post :create, :user => {
123 post :create, :user => {
124 :firstname => 'John',
124 :firstname => 'John',
125 :lastname => 'Doe',
125 :lastname => 'Doe',
126 :login => 'jdoe',
126 :login => 'jdoe',
127 :password => 'test',
127 :password => 'test',
128 :password_confirmation => 'test',
128 :password_confirmation => 'test',
129 :mail => 'jdoe@gmail.com'
129 :mail => 'jdoe@gmail.com'
130 }
130 },
131 :notification_option => 'none'
131 end
132 end
132
133
133 should_assign_to :user
134 should_assign_to :user
134 should_respond_with :redirect
135 should_respond_with :redirect
135 should_redirect_to('user edit') { {:controller => 'users', :action => 'edit', :id => User.find_by_login('jdoe')}}
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 end
142 end
137
143
138 context "when unsuccessful" do
144 context "when unsuccessful" do
139 setup do
145 setup do
140 post :create, :user => {}
146 post :create, :user => {}
141 end
147 end
142
148
143 should_assign_to :user
149 should_assign_to :user
144 should_respond_with :success
150 should_respond_with :success
145 should_render_template :add
151 should_render_template :add
146 end
152 end
147
153
148 end
154 end
149
155
150 def test_edit
156 def test_edit
151 ActionMailer::Base.deliveries.clear
157 ActionMailer::Base.deliveries.clear
152 post :edit, :id => 2, :user => {:firstname => 'Changed'}
158 post :edit, :id => 2, :user => {:firstname => 'Changed'}, :notification_option => 'all', :pref => {:hide_mail => '1', :comments_sorting => 'desc'}
153 assert_equal 'Changed', User.find(2).firstname
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 assert ActionMailer::Base.deliveries.empty?
165 assert ActionMailer::Base.deliveries.empty?
155 end
166 end
156
167
157 def test_edit_with_activation_should_send_a_notification
168 def test_edit_with_activation_should_send_a_notification
158 u = User.new(:firstname => 'Foo', :lastname => 'Bar', :mail => 'foo.bar@somenet.foo', :language => 'fr')
169 u = User.new(:firstname => 'Foo', :lastname => 'Bar', :mail => 'foo.bar@somenet.foo', :language => 'fr')
159 u.login = 'foo'
170 u.login = 'foo'
160 u.status = User::STATUS_REGISTERED
171 u.status = User::STATUS_REGISTERED
161 u.save!
172 u.save!
162 ActionMailer::Base.deliveries.clear
173 ActionMailer::Base.deliveries.clear
163 Setting.bcc_recipients = '1'
174 Setting.bcc_recipients = '1'
164
175
165 post :edit, :id => u.id, :user => {:status => User::STATUS_ACTIVE}
176 post :edit, :id => u.id, :user => {:status => User::STATUS_ACTIVE}
166 assert u.reload.active?
177 assert u.reload.active?
167 mail = ActionMailer::Base.deliveries.last
178 mail = ActionMailer::Base.deliveries.last
168 assert_not_nil mail
179 assert_not_nil mail
169 assert_equal ['foo.bar@somenet.foo'], mail.bcc
180 assert_equal ['foo.bar@somenet.foo'], mail.bcc
170 assert mail.body.include?(ll('fr', :notice_account_activated))
181 assert mail.body.include?(ll('fr', :notice_account_activated))
171 end
182 end
172
183
173 def test_edit_with_password_change_should_send_a_notification
184 def test_edit_with_password_change_should_send_a_notification
174 ActionMailer::Base.deliveries.clear
185 ActionMailer::Base.deliveries.clear
175 Setting.bcc_recipients = '1'
186 Setting.bcc_recipients = '1'
176
187
177 u = User.find(2)
188 u = User.find(2)
178 post :edit, :id => u.id, :user => {}, :password => 'newpass', :password_confirmation => 'newpass', :send_information => '1'
189 post :edit, :id => u.id, :user => {}, :password => 'newpass', :password_confirmation => 'newpass', :send_information => '1'
179 assert_equal User.hash_password('newpass'), u.reload.hashed_password
190 assert_equal User.hash_password('newpass'), u.reload.hashed_password
180
191
181 mail = ActionMailer::Base.deliveries.last
192 mail = ActionMailer::Base.deliveries.last
182 assert_not_nil mail
193 assert_not_nil mail
183 assert_equal [u.mail], mail.bcc
194 assert_equal [u.mail], mail.bcc
184 assert mail.body.include?('newpass')
195 assert mail.body.include?('newpass')
185 end
196 end
186
197
187 test "POST :edit with a password change to an AuthSource user switching to Internal authentication" do
198 test "POST :edit with a password change to an AuthSource user switching to Internal authentication" do
188 # Configure as auth source
199 # Configure as auth source
189 u = User.find(2)
200 u = User.find(2)
190 u.auth_source = AuthSource.find(1)
201 u.auth_source = AuthSource.find(1)
191 u.save!
202 u.save!
192
203
193 post :edit, :id => u.id, :user => {:auth_source_id => ''}, :password => 'newpass', :password_confirmation => 'newpass'
204 post :edit, :id => u.id, :user => {:auth_source_id => ''}, :password => 'newpass', :password_confirmation => 'newpass'
194
205
195 assert_equal nil, u.reload.auth_source
206 assert_equal nil, u.reload.auth_source
196 assert_equal User.hash_password('newpass'), u.reload.hashed_password
207 assert_equal User.hash_password('newpass'), u.reload.hashed_password
197 end
208 end
198
209
199 def test_edit_membership
210 def test_edit_membership
200 post :edit_membership, :id => 2, :membership_id => 1,
211 post :edit_membership, :id => 2, :membership_id => 1,
201 :membership => { :role_ids => [2]}
212 :membership => { :role_ids => [2]}
202 assert_redirected_to :action => 'edit', :id => '2', :tab => 'memberships'
213 assert_redirected_to :action => 'edit', :id => '2', :tab => 'memberships'
203 assert_equal [2], Member.find(1).role_ids
214 assert_equal [2], Member.find(1).role_ids
204 end
215 end
205
216
206 def test_destroy_membership
217 def test_destroy_membership
207 post :destroy_membership, :id => 2, :membership_id => 1
218 post :destroy_membership, :id => 2, :membership_id => 1
208 assert_redirected_to :action => 'edit', :id => '2', :tab => 'memberships'
219 assert_redirected_to :action => 'edit', :id => '2', :tab => 'memberships'
209 assert_nil Member.find_by_id(1)
220 assert_nil Member.find_by_id(1)
210 end
221 end
211 end
222 end
General Comments 0
You need to be logged in to leave comments. Login now