##// END OF EJS Templates
Adds named scopes for users index....
Jean-Philippe Lang -
r7961:f52410be1922
parent child
Show More
@@ -1,240 +1,234
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2011 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 before_filter :find_user, :only => [:show, :edit, :update, :destroy, :edit_membership, :destroy_membership]
23 23 accept_api_auth :index, :show, :create, :update, :destroy
24 24
25 25 helper :sort
26 26 include SortHelper
27 27 helper :custom_fields
28 28 include CustomFieldsHelper
29 29
30 30 def index
31 31 sort_init 'login', 'asc'
32 32 sort_update %w(login firstname lastname mail admin created_on last_login_on)
33 33
34 34 case params[:format]
35 35 when 'xml', 'json'
36 36 @offset, @limit = api_offset_and_limit
37 37 else
38 38 @limit = per_page_option
39 39 end
40 40
41 scope = User
42 scope = scope.in_group(params[:group_id].to_i) if params[:group_id].present?
41 @status = params[:status] || 1
43 42
44 @status = params[:status] ? params[:status].to_i : 1
45 c = ARCondition.new(@status == 0 ? "status <> 0" : ["status = ?", @status])
43 scope = User.logged.status(@status)
44 scope = scope.like(params[:name]) if params[:name].present?
45 scope = scope.in_group(params[:group_id]) if params[:group_id].present?
46 46
47 unless params[:name].blank?
48 name = "%#{params[:name].strip.downcase}%"
49 c << ["LOWER(login) LIKE ? OR LOWER(firstname) LIKE ? OR LOWER(lastname) LIKE ? OR LOWER(mail) LIKE ?", name, name, name, name]
50 end
51
52 @user_count = scope.count(:conditions => c.conditions)
47 @user_count = scope.count
53 48 @user_pages = Paginator.new self, @user_count, @limit, params['page']
54 49 @offset ||= @user_pages.current.offset
55 50 @users = scope.find :all,
56 51 :order => sort_clause,
57 :conditions => c.conditions,
58 52 :limit => @limit,
59 53 :offset => @offset
60 54
61 55 respond_to do |format|
62 56 format.html {
63 57 @groups = Group.all.sort
64 58 render :layout => !request.xhr?
65 59 }
66 60 format.api
67 61 end
68 62 end
69 63
70 64 def show
71 65 # show projects based on current user visibility
72 66 @memberships = @user.memberships.all(:conditions => Project.visible_condition(User.current))
73 67
74 68 events = Redmine::Activity::Fetcher.new(User.current, :author => @user).events(nil, nil, :limit => 10)
75 69 @events_by_day = events.group_by(&:event_date)
76 70
77 71 unless User.current.admin?
78 72 if !@user.active? || (@user != User.current && @memberships.empty? && events.empty?)
79 73 render_404
80 74 return
81 75 end
82 76 end
83 77
84 78 respond_to do |format|
85 79 format.html { render :layout => 'base' }
86 80 format.api
87 81 end
88 82 end
89 83
90 84 def new
91 85 @user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option)
92 86 @auth_sources = AuthSource.find(:all)
93 87 end
94 88
95 89 verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
96 90 def create
97 91 @user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option)
98 92 @user.safe_attributes = params[:user]
99 93 @user.admin = params[:user][:admin] || false
100 94 @user.login = params[:user][:login]
101 95 @user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation] unless @user.auth_source_id
102 96
103 97 # TODO: Similar to My#account
104 98 @user.pref.attributes = params[:pref]
105 99 @user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
106 100
107 101 if @user.save
108 102 @user.pref.save
109 103 @user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : [])
110 104
111 105 Mailer.deliver_account_information(@user, params[:user][:password]) if params[:send_information]
112 106
113 107 respond_to do |format|
114 108 format.html {
115 109 flash[:notice] = l(:notice_successful_create)
116 110 redirect_to(params[:continue] ?
117 111 {:controller => 'users', :action => 'new'} :
118 112 {:controller => 'users', :action => 'edit', :id => @user}
119 113 )
120 114 }
121 115 format.api { render :action => 'show', :status => :created, :location => user_url(@user) }
122 116 end
123 117 else
124 118 @auth_sources = AuthSource.find(:all)
125 119 # Clear password input
126 120 @user.password = @user.password_confirmation = nil
127 121
128 122 respond_to do |format|
129 123 format.html { render :action => 'new' }
130 124 format.api { render_validation_errors(@user) }
131 125 end
132 126 end
133 127 end
134 128
135 129 def edit
136 130 @auth_sources = AuthSource.find(:all)
137 131 @membership ||= Member.new
138 132 end
139 133
140 134 verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
141 135 def update
142 136 @user.admin = params[:user][:admin] if params[:user][:admin]
143 137 @user.login = params[:user][:login] if params[:user][:login]
144 138 if params[:user][:password].present? && (@user.auth_source_id.nil? || params[:user][:auth_source_id].blank?)
145 139 @user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation]
146 140 end
147 141 @user.safe_attributes = params[:user]
148 142 # Was the account actived ? (do it before User#save clears the change)
149 143 was_activated = (@user.status_change == [User::STATUS_REGISTERED, User::STATUS_ACTIVE])
150 144 # TODO: Similar to My#account
151 145 @user.pref.attributes = params[:pref]
152 146 @user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
153 147
154 148 if @user.save
155 149 @user.pref.save
156 150 @user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : [])
157 151
158 152 if was_activated
159 153 Mailer.deliver_account_activated(@user)
160 154 elsif @user.active? && params[:send_information] && !params[:user][:password].blank? && @user.auth_source_id.nil?
161 155 Mailer.deliver_account_information(@user, params[:user][:password])
162 156 end
163 157
164 158 respond_to do |format|
165 159 format.html {
166 160 flash[:notice] = l(:notice_successful_update)
167 161 redirect_to :back
168 162 }
169 163 format.api { head :ok }
170 164 end
171 165 else
172 166 @auth_sources = AuthSource.find(:all)
173 167 @membership ||= Member.new
174 168 # Clear password input
175 169 @user.password = @user.password_confirmation = nil
176 170
177 171 respond_to do |format|
178 172 format.html { render :action => :edit }
179 173 format.api { render_validation_errors(@user) }
180 174 end
181 175 end
182 176 rescue ::ActionController::RedirectBackError
183 177 redirect_to :controller => 'users', :action => 'edit', :id => @user
184 178 end
185 179
186 180 verify :method => :delete, :only => :destroy, :render => {:nothing => true, :status => :method_not_allowed }
187 181 def destroy
188 182 @user.destroy
189 183 respond_to do |format|
190 184 format.html { redirect_to(users_url) }
191 185 format.api { head :ok }
192 186 end
193 187 end
194 188
195 189 def edit_membership
196 190 @membership = Member.edit_membership(params[:membership_id], params[:membership], @user)
197 191 @membership.save if request.post?
198 192 respond_to do |format|
199 193 if @membership.valid?
200 194 format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' }
201 195 format.js {
202 196 render(:update) {|page|
203 197 page.replace_html "tab-content-memberships", :partial => 'users/memberships'
204 198 page.visual_effect(:highlight, "member-#{@membership.id}")
205 199 }
206 200 }
207 201 else
208 202 format.js {
209 203 render(:update) {|page|
210 204 page.alert(l(:notice_failed_to_save_members, :errors => @membership.errors.full_messages.join(', ')))
211 205 }
212 206 }
213 207 end
214 208 end
215 209 end
216 210
217 211 def destroy_membership
218 212 @membership = Member.find(params[:membership_id])
219 213 if request.post? && @membership.deletable?
220 214 @membership.destroy
221 215 end
222 216 respond_to do |format|
223 217 format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' }
224 218 format.js { render(:update) {|page| page.replace_html "tab-content-memberships", :partial => 'users/memberships'} }
225 219 end
226 220 end
227 221
228 222 private
229 223
230 224 def find_user
231 225 if params[:id] == 'current'
232 226 require_login || return
233 227 @user = User.current
234 228 else
235 229 @user = User.find(params[:id])
236 230 end
237 231 rescue ActiveRecord::RecordNotFound
238 232 render_404
239 233 end
240 234 end
@@ -1,61 +1,61
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2011 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 module UsersHelper
19 19 def users_status_options_for_select(selected)
20 20 user_count_by_status = User.count(:group => 'status').to_hash
21 21 options_for_select([[l(:label_all), ''],
22 ["#{l(:status_active)} (#{user_count_by_status[1].to_i})", 1],
23 ["#{l(:status_registered)} (#{user_count_by_status[2].to_i})", 2],
24 ["#{l(:status_locked)} (#{user_count_by_status[3].to_i})", 3]], selected)
22 ["#{l(:status_active)} (#{user_count_by_status[1].to_i})", '1'],
23 ["#{l(:status_registered)} (#{user_count_by_status[2].to_i})", '2'],
24 ["#{l(:status_locked)} (#{user_count_by_status[3].to_i})", '3']], selected.to_s)
25 25 end
26 26
27 27 # Options for the new membership projects combo-box
28 28 def options_for_membership_project_select(user, projects)
29 29 options = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---")
30 30 options << project_tree_options_for_select(projects) do |p|
31 31 {:disabled => (user.projects.include?(p))}
32 32 end
33 33 options
34 34 end
35 35
36 36 def user_mail_notification_options(user)
37 37 user.valid_notification_options.collect {|o| [l(o.last), o.first]}
38 38 end
39 39
40 40 def change_status_link(user)
41 41 url = {:controller => 'users', :action => 'update', :id => user, :page => params[:page], :status => params[:status], :tab => nil}
42 42
43 43 if user.locked?
44 44 link_to l(:button_unlock), url.merge(:user => {:status => User::STATUS_ACTIVE}), :method => :put, :class => 'icon icon-unlock'
45 45 elsif user.registered?
46 46 link_to l(:button_activate), url.merge(:user => {:status => User::STATUS_ACTIVE}), :method => :put, :class => 'icon icon-unlock'
47 47 elsif user != User.current
48 48 link_to l(:button_lock), url.merge(:user => {:status => User::STATUS_LOCKED}), :method => :put, :class => 'icon icon-lock'
49 49 end
50 50 end
51 51
52 52 def user_settings_tabs
53 53 tabs = [{:name => 'general', :partial => 'users/general', :label => :label_general},
54 54 {:name => 'memberships', :partial => 'users/memberships', :label => :label_project_plural}
55 55 ]
56 56 if Group.all.any?
57 57 tabs.insert 1, {:name => 'groups', :partial => 'users/groups', :label => :label_group_plural}
58 58 end
59 59 tabs
60 60 end
61 61 end
@@ -1,636 +1,646
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require "digest/sha1"
19 19
20 20 class User < Principal
21 21 include Redmine::SafeAttributes
22 22
23 23 # Account statuses
24 24 STATUS_ANONYMOUS = 0
25 25 STATUS_ACTIVE = 1
26 26 STATUS_REGISTERED = 2
27 27 STATUS_LOCKED = 3
28 28
29 29 # Different ways of displaying/sorting users
30 30 USER_FORMATS = {
31 31 :firstname_lastname => {:string => '#{firstname} #{lastname}', :order => %w(firstname lastname id)},
32 32 :firstname => {:string => '#{firstname}', :order => %w(firstname id)},
33 33 :lastname_firstname => {:string => '#{lastname} #{firstname}', :order => %w(lastname firstname id)},
34 34 :lastname_coma_firstname => {:string => '#{lastname}, #{firstname}', :order => %w(lastname firstname id)},
35 35 :username => {:string => '#{login}', :order => %w(login id)},
36 36 }
37 37
38 38 MAIL_NOTIFICATION_OPTIONS = [
39 39 ['all', :label_user_mail_option_all],
40 40 ['selected', :label_user_mail_option_selected],
41 41 ['only_my_events', :label_user_mail_option_only_my_events],
42 42 ['only_assigned', :label_user_mail_option_only_assigned],
43 43 ['only_owner', :label_user_mail_option_only_owner],
44 44 ['none', :label_user_mail_option_none]
45 45 ]
46 46
47 47 has_and_belongs_to_many :groups, :after_add => Proc.new {|user, group| group.user_added(user)},
48 48 :after_remove => Proc.new {|user, group| group.user_removed(user)}
49 49 has_many :changesets, :dependent => :nullify
50 50 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
51 51 has_one :rss_token, :class_name => 'Token', :conditions => "action='feeds'"
52 52 has_one :api_token, :class_name => 'Token', :conditions => "action='api'"
53 53 belongs_to :auth_source
54 54
55 55 # Active non-anonymous users scope
56 56 named_scope :active, :conditions => "#{User.table_name}.status = #{STATUS_ACTIVE}"
57 named_scope :logged, :conditions => "#{User.table_name}.status <> #{STATUS_ANONYMOUS}"
58 named_scope :status, lambda {|arg| arg.blank? ? {} : {:conditions => {:status => arg.to_i}} }
59 named_scope :like, lambda {|arg|
60 if arg.blank?
61 {}
62 else
63 pattern = "%#{arg.to_s.strip.downcase}%"
64 {:conditions => ["LOWER(login) LIKE :p OR LOWER(firstname) LIKE :p OR LOWER(lastname) LIKE :p OR LOWER(mail) LIKE :p", {:p => pattern}]}
65 end
66 }
57 67
58 68 acts_as_customizable
59 69
60 70 attr_accessor :password, :password_confirmation
61 71 attr_accessor :last_before_login_on
62 72 # Prevents unauthorized assignments
63 73 attr_protected :login, :admin, :password, :password_confirmation, :hashed_password
64 74
65 75 validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
66 76 validates_uniqueness_of :login, :if => Proc.new { |user| !user.login.blank? }, :case_sensitive => false
67 77 validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }, :case_sensitive => false
68 78 # Login must contain lettres, numbers, underscores only
69 79 validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
70 80 validates_length_of :login, :maximum => 30
71 81 validates_length_of :firstname, :lastname, :maximum => 30
72 82 validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_blank => true
73 83 validates_length_of :mail, :maximum => 60, :allow_nil => true
74 84 validates_confirmation_of :password, :allow_nil => true
75 85 validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true
76 86 validate :validate_password_length
77 87
78 88 before_create :set_mail_notification
79 89 before_save :update_hashed_password
80 90 before_destroy :remove_references_before_destroy
81 91
82 92 named_scope :in_group, lambda {|group|
83 93 group_id = group.is_a?(Group) ? group.id : group.to_i
84 94 { :conditions => ["#{User.table_name}.id IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id] }
85 95 }
86 96 named_scope :not_in_group, lambda {|group|
87 97 group_id = group.is_a?(Group) ? group.id : group.to_i
88 98 { :conditions => ["#{User.table_name}.id NOT IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id] }
89 99 }
90 100
91 101 def set_mail_notification
92 102 self.mail_notification = Setting.default_notification_option if self.mail_notification.blank?
93 103 true
94 104 end
95 105
96 106 def update_hashed_password
97 107 # update hashed_password if password was set
98 108 if self.password && self.auth_source_id.blank?
99 109 salt_password(password)
100 110 end
101 111 end
102 112
103 113 def reload(*args)
104 114 @name = nil
105 115 @projects_by_role = nil
106 116 super
107 117 end
108 118
109 119 def mail=(arg)
110 120 write_attribute(:mail, arg.to_s.strip)
111 121 end
112 122
113 123 def identity_url=(url)
114 124 if url.blank?
115 125 write_attribute(:identity_url, '')
116 126 else
117 127 begin
118 128 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
119 129 rescue OpenIdAuthentication::InvalidOpenId
120 130 # Invlaid url, don't save
121 131 end
122 132 end
123 133 self.read_attribute(:identity_url)
124 134 end
125 135
126 136 # Returns the user that matches provided login and password, or nil
127 137 def self.try_to_login(login, password)
128 138 # Make sure no one can sign in with an empty password
129 139 return nil if password.to_s.empty?
130 140 user = find_by_login(login)
131 141 if user
132 142 # user is already in local database
133 143 return nil if !user.active?
134 144 if user.auth_source
135 145 # user has an external authentication method
136 146 return nil unless user.auth_source.authenticate(login, password)
137 147 else
138 148 # authentication with local password
139 149 return nil unless user.check_password?(password)
140 150 end
141 151 else
142 152 # user is not yet registered, try to authenticate with available sources
143 153 attrs = AuthSource.authenticate(login, password)
144 154 if attrs
145 155 user = new(attrs)
146 156 user.login = login
147 157 user.language = Setting.default_language
148 158 if user.save
149 159 user.reload
150 160 logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source
151 161 end
152 162 end
153 163 end
154 164 user.update_attribute(:last_login_on, Time.now) if user && !user.new_record?
155 165 user
156 166 rescue => text
157 167 raise text
158 168 end
159 169
160 170 # Returns the user who matches the given autologin +key+ or nil
161 171 def self.try_to_autologin(key)
162 172 tokens = Token.find_all_by_action_and_value('autologin', key)
163 173 # Make sure there's only 1 token that matches the key
164 174 if tokens.size == 1
165 175 token = tokens.first
166 176 if (token.created_on > Setting.autologin.to_i.day.ago) && token.user && token.user.active?
167 177 token.user.update_attribute(:last_login_on, Time.now)
168 178 token.user
169 179 end
170 180 end
171 181 end
172 182
173 183 def self.name_formatter(formatter = nil)
174 184 USER_FORMATS[formatter || Setting.user_format] || USER_FORMATS[:firstname_lastname]
175 185 end
176 186
177 187 # Returns an array of fields names than can be used to make an order statement for users
178 188 # according to how user names are displayed
179 189 # Examples:
180 190 #
181 191 # User.fields_for_order_statement => ['users.login', 'users.id']
182 192 # User.fields_for_order_statement('authors') => ['authors.login', 'authors.id']
183 193 def self.fields_for_order_statement(table=nil)
184 194 table ||= table_name
185 195 name_formatter[:order].map {|field| "#{table}.#{field}"}
186 196 end
187 197
188 198 # Return user's full name for display
189 199 def name(formatter = nil)
190 200 f = self.class.name_formatter(formatter)
191 201 if formatter
192 202 eval('"' + f[:string] + '"')
193 203 else
194 204 @name ||= eval('"' + f[:string] + '"')
195 205 end
196 206 end
197 207
198 208 def active?
199 209 self.status == STATUS_ACTIVE
200 210 end
201 211
202 212 def registered?
203 213 self.status == STATUS_REGISTERED
204 214 end
205 215
206 216 def locked?
207 217 self.status == STATUS_LOCKED
208 218 end
209 219
210 220 def activate
211 221 self.status = STATUS_ACTIVE
212 222 end
213 223
214 224 def register
215 225 self.status = STATUS_REGISTERED
216 226 end
217 227
218 228 def lock
219 229 self.status = STATUS_LOCKED
220 230 end
221 231
222 232 def activate!
223 233 update_attribute(:status, STATUS_ACTIVE)
224 234 end
225 235
226 236 def register!
227 237 update_attribute(:status, STATUS_REGISTERED)
228 238 end
229 239
230 240 def lock!
231 241 update_attribute(:status, STATUS_LOCKED)
232 242 end
233 243
234 244 # Returns true if +clear_password+ is the correct user's password, otherwise false
235 245 def check_password?(clear_password)
236 246 if auth_source_id.present?
237 247 auth_source.authenticate(self.login, clear_password)
238 248 else
239 249 User.hash_password("#{salt}#{User.hash_password clear_password}") == hashed_password
240 250 end
241 251 end
242 252
243 253 # Generates a random salt and computes hashed_password for +clear_password+
244 254 # The hashed password is stored in the following form: SHA1(salt + SHA1(password))
245 255 def salt_password(clear_password)
246 256 self.salt = User.generate_salt
247 257 self.hashed_password = User.hash_password("#{salt}#{User.hash_password clear_password}")
248 258 end
249 259
250 260 # Does the backend storage allow this user to change their password?
251 261 def change_password_allowed?
252 262 return true if auth_source_id.blank?
253 263 return auth_source.allow_password_changes?
254 264 end
255 265
256 266 # Generate and set a random password. Useful for automated user creation
257 267 # Based on Token#generate_token_value
258 268 #
259 269 def random_password
260 270 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
261 271 password = ''
262 272 40.times { |i| password << chars[rand(chars.size-1)] }
263 273 self.password = password
264 274 self.password_confirmation = password
265 275 self
266 276 end
267 277
268 278 def pref
269 279 self.preference ||= UserPreference.new(:user => self)
270 280 end
271 281
272 282 def time_zone
273 283 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
274 284 end
275 285
276 286 def wants_comments_in_reverse_order?
277 287 self.pref[:comments_sorting] == 'desc'
278 288 end
279 289
280 290 # Return user's RSS key (a 40 chars long string), used to access feeds
281 291 def rss_key
282 292 token = self.rss_token || Token.create(:user => self, :action => 'feeds')
283 293 token.value
284 294 end
285 295
286 296 # Return user's API key (a 40 chars long string), used to access the API
287 297 def api_key
288 298 token = self.api_token || self.create_api_token(:action => 'api')
289 299 token.value
290 300 end
291 301
292 302 # Return an array of project ids for which the user has explicitly turned mail notifications on
293 303 def notified_projects_ids
294 304 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
295 305 end
296 306
297 307 def notified_project_ids=(ids)
298 308 Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
299 309 Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
300 310 @notified_projects_ids = nil
301 311 notified_projects_ids
302 312 end
303 313
304 314 def valid_notification_options
305 315 self.class.valid_notification_options(self)
306 316 end
307 317
308 318 # Only users that belong to more than 1 project can select projects for which they are notified
309 319 def self.valid_notification_options(user=nil)
310 320 # Note that @user.membership.size would fail since AR ignores
311 321 # :include association option when doing a count
312 322 if user.nil? || user.memberships.length < 1
313 323 MAIL_NOTIFICATION_OPTIONS.reject {|option| option.first == 'selected'}
314 324 else
315 325 MAIL_NOTIFICATION_OPTIONS
316 326 end
317 327 end
318 328
319 329 # Find a user account by matching the exact login and then a case-insensitive
320 330 # version. Exact matches will be given priority.
321 331 def self.find_by_login(login)
322 332 # force string comparison to be case sensitive on MySQL
323 333 type_cast = (ActiveRecord::Base.connection.adapter_name == 'MySQL') ? 'BINARY' : ''
324 334
325 335 # First look for an exact match
326 336 user = first(:conditions => ["#{type_cast} login = ?", login])
327 337 # Fail over to case-insensitive if none was found
328 338 user ||= first(:conditions => ["#{type_cast} LOWER(login) = ?", login.to_s.downcase])
329 339 end
330 340
331 341 def self.find_by_rss_key(key)
332 342 token = Token.find_by_value(key)
333 343 token && token.user.active? ? token.user : nil
334 344 end
335 345
336 346 def self.find_by_api_key(key)
337 347 token = Token.find_by_action_and_value('api', key)
338 348 token && token.user.active? ? token.user : nil
339 349 end
340 350
341 351 # Makes find_by_mail case-insensitive
342 352 def self.find_by_mail(mail)
343 353 find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
344 354 end
345 355
346 356 def to_s
347 357 name
348 358 end
349 359
350 360 # Returns the current day according to user's time zone
351 361 def today
352 362 if time_zone.nil?
353 363 Date.today
354 364 else
355 365 Time.now.in_time_zone(time_zone).to_date
356 366 end
357 367 end
358 368
359 369 def logged?
360 370 true
361 371 end
362 372
363 373 def anonymous?
364 374 !logged?
365 375 end
366 376
367 377 # Return user's roles for project
368 378 def roles_for_project(project)
369 379 roles = []
370 380 # No role on archived projects
371 381 return roles unless project && project.active?
372 382 if logged?
373 383 # Find project membership
374 384 membership = memberships.detect {|m| m.project_id == project.id}
375 385 if membership
376 386 roles = membership.roles
377 387 else
378 388 @role_non_member ||= Role.non_member
379 389 roles << @role_non_member
380 390 end
381 391 else
382 392 @role_anonymous ||= Role.anonymous
383 393 roles << @role_anonymous
384 394 end
385 395 roles
386 396 end
387 397
388 398 # Return true if the user is a member of project
389 399 def member_of?(project)
390 400 !roles_for_project(project).detect {|role| role.member?}.nil?
391 401 end
392 402
393 403 # Returns a hash of user's projects grouped by roles
394 404 def projects_by_role
395 405 return @projects_by_role if @projects_by_role
396 406
397 407 @projects_by_role = Hash.new {|h,k| h[k]=[]}
398 408 memberships.each do |membership|
399 409 membership.roles.each do |role|
400 410 @projects_by_role[role] << membership.project if membership.project
401 411 end
402 412 end
403 413 @projects_by_role.each do |role, projects|
404 414 projects.uniq!
405 415 end
406 416
407 417 @projects_by_role
408 418 end
409 419
410 420 # Returns true if user is arg or belongs to arg
411 421 def is_or_belongs_to?(arg)
412 422 if arg.is_a?(User)
413 423 self == arg
414 424 elsif arg.is_a?(Group)
415 425 arg.users.include?(self)
416 426 else
417 427 false
418 428 end
419 429 end
420 430
421 431 # Return true if the user is allowed to do the specified action on a specific context
422 432 # Action can be:
423 433 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
424 434 # * a permission Symbol (eg. :edit_project)
425 435 # Context can be:
426 436 # * a project : returns true if user is allowed to do the specified action on this project
427 437 # * an array of projects : returns true if user is allowed on every project
428 438 # * nil with options[:global] set : check if user has at least one role allowed for this action,
429 439 # or falls back to Non Member / Anonymous permissions depending if the user is logged
430 440 def allowed_to?(action, context, options={}, &block)
431 441 if context && context.is_a?(Project)
432 442 # No action allowed on archived projects
433 443 return false unless context.active?
434 444 # No action allowed on disabled modules
435 445 return false unless context.allows_to?(action)
436 446 # Admin users are authorized for anything else
437 447 return true if admin?
438 448
439 449 roles = roles_for_project(context)
440 450 return false unless roles
441 451 roles.detect {|role|
442 452 (context.is_public? || role.member?) &&
443 453 role.allowed_to?(action) &&
444 454 (block_given? ? yield(role, self) : true)
445 455 }
446 456 elsif context && context.is_a?(Array)
447 457 # Authorize if user is authorized on every element of the array
448 458 context.map do |project|
449 459 allowed_to?(action, project, options, &block)
450 460 end.inject do |memo,allowed|
451 461 memo && allowed
452 462 end
453 463 elsif options[:global]
454 464 # Admin users are always authorized
455 465 return true if admin?
456 466
457 467 # authorize if user has at least one role that has this permission
458 468 roles = memberships.collect {|m| m.roles}.flatten.uniq
459 469 roles << (self.logged? ? Role.non_member : Role.anonymous)
460 470 roles.detect {|role|
461 471 role.allowed_to?(action) &&
462 472 (block_given? ? yield(role, self) : true)
463 473 }
464 474 else
465 475 false
466 476 end
467 477 end
468 478
469 479 # Is the user allowed to do the specified action on any project?
470 480 # See allowed_to? for the actions and valid options.
471 481 def allowed_to_globally?(action, options, &block)
472 482 allowed_to?(action, nil, options.reverse_merge(:global => true), &block)
473 483 end
474 484
475 485 safe_attributes 'login',
476 486 'firstname',
477 487 'lastname',
478 488 'mail',
479 489 'mail_notification',
480 490 'language',
481 491 'custom_field_values',
482 492 'custom_fields',
483 493 'identity_url'
484 494
485 495 safe_attributes 'status',
486 496 'auth_source_id',
487 497 :if => lambda {|user, current_user| current_user.admin?}
488 498
489 499 safe_attributes 'group_ids',
490 500 :if => lambda {|user, current_user| current_user.admin? && !user.new_record?}
491 501
492 502 # Utility method to help check if a user should be notified about an
493 503 # event.
494 504 #
495 505 # TODO: only supports Issue events currently
496 506 def notify_about?(object)
497 507 case mail_notification
498 508 when 'all'
499 509 true
500 510 when 'selected'
501 511 # user receives notifications for created/assigned issues on unselected projects
502 512 if object.is_a?(Issue) && (object.author == self || is_or_belongs_to?(object.assigned_to))
503 513 true
504 514 else
505 515 false
506 516 end
507 517 when 'none'
508 518 false
509 519 when 'only_my_events'
510 520 if object.is_a?(Issue) && (object.author == self || is_or_belongs_to?(object.assigned_to))
511 521 true
512 522 else
513 523 false
514 524 end
515 525 when 'only_assigned'
516 526 if object.is_a?(Issue) && is_or_belongs_to?(object.assigned_to)
517 527 true
518 528 else
519 529 false
520 530 end
521 531 when 'only_owner'
522 532 if object.is_a?(Issue) && object.author == self
523 533 true
524 534 else
525 535 false
526 536 end
527 537 else
528 538 false
529 539 end
530 540 end
531 541
532 542 def self.current=(user)
533 543 @current_user = user
534 544 end
535 545
536 546 def self.current
537 547 @current_user ||= User.anonymous
538 548 end
539 549
540 550 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
541 551 # one anonymous user per database.
542 552 def self.anonymous
543 553 anonymous_user = AnonymousUser.find(:first)
544 554 if anonymous_user.nil?
545 555 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
546 556 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
547 557 end
548 558 anonymous_user
549 559 end
550 560
551 561 # Salts all existing unsalted passwords
552 562 # It changes password storage scheme from SHA1(password) to SHA1(salt + SHA1(password))
553 563 # This method is used in the SaltPasswords migration and is to be kept as is
554 564 def self.salt_unsalted_passwords!
555 565 transaction do
556 566 User.find_each(:conditions => "salt IS NULL OR salt = ''") do |user|
557 567 next if user.hashed_password.blank?
558 568 salt = User.generate_salt
559 569 hashed_password = User.hash_password("#{salt}#{user.hashed_password}")
560 570 User.update_all("salt = '#{salt}', hashed_password = '#{hashed_password}'", ["id = ?", user.id] )
561 571 end
562 572 end
563 573 end
564 574
565 575 protected
566 576
567 577 def validate_password_length
568 578 # Password length validation based on setting
569 579 if !password.nil? && password.size < Setting.password_min_length.to_i
570 580 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
571 581 end
572 582 end
573 583
574 584 private
575 585
576 586 # Removes references that are not handled by associations
577 587 # Things that are not deleted are reassociated with the anonymous user
578 588 def remove_references_before_destroy
579 589 return if self.id.nil?
580 590
581 591 substitute = User.anonymous
582 592 Attachment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
583 593 Comment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
584 594 Issue.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
585 595 Issue.update_all 'assigned_to_id = NULL', ['assigned_to_id = ?', id]
586 596 Journal.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
587 597 JournalDetail.update_all ['old_value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND old_value = ?", id.to_s]
588 598 JournalDetail.update_all ['value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND value = ?", id.to_s]
589 599 Message.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
590 600 News.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
591 601 # Remove private queries and keep public ones
592 602 Query.delete_all ['user_id = ? AND is_public = ?', id, false]
593 603 Query.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
594 604 TimeEntry.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
595 605 Token.delete_all ['user_id = ?', id]
596 606 Watcher.delete_all ['user_id = ?', id]
597 607 WikiContent.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
598 608 WikiContent::Version.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
599 609 end
600 610
601 611 # Return password digest
602 612 def self.hash_password(clear_password)
603 613 Digest::SHA1.hexdigest(clear_password || "")
604 614 end
605 615
606 616 # Returns a 128bits random salt as a hex string (32 chars long)
607 617 def self.generate_salt
608 618 ActiveSupport::SecureRandom.hex(16)
609 619 end
610 620
611 621 end
612 622
613 623 class AnonymousUser < User
614 624
615 625 def validate_on_create
616 626 # There should be only one AnonymousUser in the database
617 627 errors.add :base, 'An anonymous user already exists.' if AnonymousUser.find(:first)
618 628 end
619 629
620 630 def available_custom_fields
621 631 []
622 632 end
623 633
624 634 # Overrides a few properties
625 635 def logged?; false end
626 636 def admin; false end
627 637 def name(*args); I18n.t(:label_user_anonymous) end
628 638 def mail; nil end
629 639 def time_zone; nil end
630 640 def rss_key; nil end
631 641
632 642 # Anonymous user can not be destroyed
633 643 def destroy
634 644 false
635 645 end
636 646 end
@@ -1,312 +1,320
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__)
19 19 require 'users_controller'
20 20
21 21 # Re-raise errors caught by the controller.
22 22 class UsersController; def rescue_action(e) raise e end; end
23 23
24 24 class UsersControllerTest < ActionController::TestCase
25 25 include Redmine::I18n
26 26
27 27 fixtures :users, :projects, :members, :member_roles, :roles, :auth_sources, :custom_fields, :custom_values, :groups_users
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 def test_index_with_status_filter
53 get :index, :status => 3
54 assert_response :success
55 assert_template 'index'
56 assert_not_nil assigns(:users)
57 assert_equal [3], assigns(:users).map(&:status).uniq
58 end
59
52 60 def test_index_with_name_filter
53 61 get :index, :name => 'john'
54 62 assert_response :success
55 63 assert_template 'index'
56 64 users = assigns(:users)
57 65 assert_not_nil users
58 66 assert_equal 1, users.size
59 67 assert_equal 'John', users.first.firstname
60 68 end
61 69
62 70 def test_index_with_group_filter
63 71 get :index, :group_id => '10'
64 72 assert_response :success
65 73 assert_template 'index'
66 74 users = assigns(:users)
67 75 assert users.any?
68 76 assert_equal([], (users - Group.find(10).users))
69 77 end
70 78
71 79 def test_show
72 80 @request.session[:user_id] = nil
73 81 get :show, :id => 2
74 82 assert_response :success
75 83 assert_template 'show'
76 84 assert_not_nil assigns(:user)
77 85
78 86 assert_tag 'li', :content => /Phone number/
79 87 end
80 88
81 89 def test_show_should_not_display_hidden_custom_fields
82 90 @request.session[:user_id] = nil
83 91 UserCustomField.find_by_name('Phone number').update_attribute :visible, false
84 92 get :show, :id => 2
85 93 assert_response :success
86 94 assert_template 'show'
87 95 assert_not_nil assigns(:user)
88 96
89 97 assert_no_tag 'li', :content => /Phone number/
90 98 end
91 99
92 100 def test_show_should_not_fail_when_custom_values_are_nil
93 101 user = User.find(2)
94 102
95 103 # Create a custom field to illustrate the issue
96 104 custom_field = CustomField.create!(:name => 'Testing', :field_format => 'text')
97 105 custom_value = user.custom_values.build(:custom_field => custom_field).save!
98 106
99 107 get :show, :id => 2
100 108 assert_response :success
101 109 end
102 110
103 111 def test_show_inactive
104 112 @request.session[:user_id] = nil
105 113 get :show, :id => 5
106 114 assert_response 404
107 115 end
108 116
109 117 def test_show_should_not_reveal_users_with_no_visible_activity_or_project
110 118 @request.session[:user_id] = nil
111 119 get :show, :id => 9
112 120 assert_response 404
113 121 end
114 122
115 123 def test_show_inactive_by_admin
116 124 @request.session[:user_id] = 1
117 125 get :show, :id => 5
118 126 assert_response 200
119 127 assert_not_nil assigns(:user)
120 128 end
121 129
122 130 def test_show_displays_memberships_based_on_project_visibility
123 131 @request.session[:user_id] = 1
124 132 get :show, :id => 2
125 133 assert_response :success
126 134 memberships = assigns(:memberships)
127 135 assert_not_nil memberships
128 136 project_ids = memberships.map(&:project_id)
129 137 assert project_ids.include?(2) #private project admin can see
130 138 end
131 139
132 140 def test_show_current_should_require_authentication
133 141 @request.session[:user_id] = nil
134 142 get :show, :id => 'current'
135 143 assert_response 302
136 144 end
137 145
138 146 def test_show_current
139 147 @request.session[:user_id] = 2
140 148 get :show, :id => 'current'
141 149 assert_response :success
142 150 assert_template 'show'
143 151 assert_equal User.find(2), assigns(:user)
144 152 end
145 153
146 154 def test_new
147 155 get :new
148 156
149 157 assert_response :success
150 158 assert_template :new
151 159 assert assigns(:user)
152 160 end
153 161
154 162 def test_create
155 163 Setting.bcc_recipients = '1'
156 164
157 165 assert_difference 'User.count' do
158 166 assert_difference 'ActionMailer::Base.deliveries.size' do
159 167 post :create,
160 168 :user => {
161 169 :firstname => 'John',
162 170 :lastname => 'Doe',
163 171 :login => 'jdoe',
164 172 :password => 'secret',
165 173 :password_confirmation => 'secret',
166 174 :mail => 'jdoe@gmail.com',
167 175 :mail_notification => 'none'
168 176 },
169 177 :send_information => '1'
170 178 end
171 179 end
172 180
173 181 user = User.first(:order => 'id DESC')
174 182 assert_redirected_to :controller => 'users', :action => 'edit', :id => user.id
175 183
176 184 assert_equal 'John', user.firstname
177 185 assert_equal 'Doe', user.lastname
178 186 assert_equal 'jdoe', user.login
179 187 assert_equal 'jdoe@gmail.com', user.mail
180 188 assert_equal 'none', user.mail_notification
181 189 assert user.check_password?('secret')
182 190
183 191 mail = ActionMailer::Base.deliveries.last
184 192 assert_not_nil mail
185 193 assert_equal [user.mail], mail.bcc
186 194 assert mail.body.include?('secret')
187 195 end
188 196
189 197 def test_create_with_failure
190 198 assert_no_difference 'User.count' do
191 199 post :create, :user => {}
192 200 end
193 201
194 202 assert_response :success
195 203 assert_template 'new'
196 204 end
197 205
198 206 def test_edit
199 207 get :edit, :id => 2
200 208
201 209 assert_response :success
202 210 assert_template 'edit'
203 211 assert_equal User.find(2), assigns(:user)
204 212 end
205 213
206 214 def test_update
207 215 ActionMailer::Base.deliveries.clear
208 216 put :update, :id => 2, :user => {:firstname => 'Changed', :mail_notification => 'only_assigned'}, :pref => {:hide_mail => '1', :comments_sorting => 'desc'}
209 217
210 218 user = User.find(2)
211 219 assert_equal 'Changed', user.firstname
212 220 assert_equal 'only_assigned', user.mail_notification
213 221 assert_equal true, user.pref[:hide_mail]
214 222 assert_equal 'desc', user.pref[:comments_sorting]
215 223 assert ActionMailer::Base.deliveries.empty?
216 224 end
217 225
218 226 def test_update_with_failure
219 227 assert_no_difference 'User.count' do
220 228 put :update, :id => 2, :user => {:firstname => ''}
221 229 end
222 230
223 231 assert_response :success
224 232 assert_template 'edit'
225 233 end
226 234
227 235 def test_update_with_group_ids_should_assign_groups
228 236 put :update, :id => 2, :user => {:group_ids => ['10']}
229 237
230 238 user = User.find(2)
231 239 assert_equal [10], user.group_ids
232 240 end
233 241
234 242 def test_update_with_activation_should_send_a_notification
235 243 u = User.new(:firstname => 'Foo', :lastname => 'Bar', :mail => 'foo.bar@somenet.foo', :language => 'fr')
236 244 u.login = 'foo'
237 245 u.status = User::STATUS_REGISTERED
238 246 u.save!
239 247 ActionMailer::Base.deliveries.clear
240 248 Setting.bcc_recipients = '1'
241 249
242 250 put :update, :id => u.id, :user => {:status => User::STATUS_ACTIVE}
243 251 assert u.reload.active?
244 252 mail = ActionMailer::Base.deliveries.last
245 253 assert_not_nil mail
246 254 assert_equal ['foo.bar@somenet.foo'], mail.bcc
247 255 assert mail.body.include?(ll('fr', :notice_account_activated))
248 256 end
249 257
250 258 def test_update_with_password_change_should_send_a_notification
251 259 ActionMailer::Base.deliveries.clear
252 260 Setting.bcc_recipients = '1'
253 261
254 262 put :update, :id => 2, :user => {:password => 'newpass', :password_confirmation => 'newpass'}, :send_information => '1'
255 263 u = User.find(2)
256 264 assert u.check_password?('newpass')
257 265
258 266 mail = ActionMailer::Base.deliveries.last
259 267 assert_not_nil mail
260 268 assert_equal [u.mail], mail.bcc
261 269 assert mail.body.include?('newpass')
262 270 end
263 271
264 272 test "put :update with a password change to an AuthSource user switching to Internal authentication" do
265 273 # Configure as auth source
266 274 u = User.find(2)
267 275 u.auth_source = AuthSource.find(1)
268 276 u.save!
269 277
270 278 put :update, :id => u.id, :user => {:auth_source_id => '', :password => 'newpass'}, :password_confirmation => 'newpass'
271 279
272 280 assert_equal nil, u.reload.auth_source
273 281 assert u.check_password?('newpass')
274 282 end
275 283
276 284 def test_destroy
277 285 assert_difference 'User.count', -1 do
278 286 delete :destroy, :id => 2
279 287 end
280 288 assert_redirected_to '/users'
281 289 assert_nil User.find_by_id(2)
282 290 end
283 291
284 292 def test_destroy_should_not_accept_get_requests
285 293 assert_no_difference 'User.count' do
286 294 get :destroy, :id => 2
287 295 end
288 296 assert_response 405
289 297 end
290 298
291 299 def test_destroy_should_be_denied_for_non_admin_users
292 300 @request.session[:user_id] = 3
293 301
294 302 assert_no_difference 'User.count' do
295 303 get :destroy, :id => 2
296 304 end
297 305 assert_response 403
298 306 end
299 307
300 308 def test_edit_membership
301 309 post :edit_membership, :id => 2, :membership_id => 1,
302 310 :membership => { :role_ids => [2]}
303 311 assert_redirected_to :action => 'edit', :id => '2', :tab => 'memberships'
304 312 assert_equal [2], Member.find(1).role_ids
305 313 end
306 314
307 315 def test_destroy_membership
308 316 post :destroy_membership, :id => 2, :membership_id => 1
309 317 assert_redirected_to :action => 'edit', :id => '2', :tab => 'memberships'
310 318 assert_nil Member.find_by_id(1)
311 319 end
312 320 end
General Comments 0
You need to be logged in to leave comments. Login now