##// END OF EJS Templates
Fixed: list of users for adding to a group may be empty if 100 first users have been added (#8029)....
Jean-Philippe Lang -
r5164:b972b5a647ca
parent child
Show More
@@ -1,170 +1,170
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 GroupsController < ApplicationController
18 class GroupsController < ApplicationController
19 layout 'admin'
19 layout 'admin'
20
20
21 before_filter :require_admin
21 before_filter :require_admin
22
22
23 helper :custom_fields
23 helper :custom_fields
24
24
25 # GET /groups
25 # GET /groups
26 # GET /groups.xml
26 # GET /groups.xml
27 def index
27 def index
28 @groups = Group.find(:all, :order => 'lastname')
28 @groups = Group.find(:all, :order => 'lastname')
29
29
30 respond_to do |format|
30 respond_to do |format|
31 format.html # index.html.erb
31 format.html # index.html.erb
32 format.xml { render :xml => @groups }
32 format.xml { render :xml => @groups }
33 end
33 end
34 end
34 end
35
35
36 # GET /groups/1
36 # GET /groups/1
37 # GET /groups/1.xml
37 # GET /groups/1.xml
38 def show
38 def show
39 @group = Group.find(params[:id])
39 @group = Group.find(params[:id])
40
40
41 respond_to do |format|
41 respond_to do |format|
42 format.html # show.html.erb
42 format.html # show.html.erb
43 format.xml { render :xml => @group }
43 format.xml { render :xml => @group }
44 end
44 end
45 end
45 end
46
46
47 # GET /groups/new
47 # GET /groups/new
48 # GET /groups/new.xml
48 # GET /groups/new.xml
49 def new
49 def new
50 @group = Group.new
50 @group = Group.new
51
51
52 respond_to do |format|
52 respond_to do |format|
53 format.html # new.html.erb
53 format.html # new.html.erb
54 format.xml { render :xml => @group }
54 format.xml { render :xml => @group }
55 end
55 end
56 end
56 end
57
57
58 # GET /groups/1/edit
58 # GET /groups/1/edit
59 def edit
59 def edit
60 @group = Group.find(params[:id], :include => :projects)
60 @group = Group.find(params[:id], :include => :projects)
61 end
61 end
62
62
63 # POST /groups
63 # POST /groups
64 # POST /groups.xml
64 # POST /groups.xml
65 def create
65 def create
66 @group = Group.new(params[:group])
66 @group = Group.new(params[:group])
67
67
68 respond_to do |format|
68 respond_to do |format|
69 if @group.save
69 if @group.save
70 flash[:notice] = l(:notice_successful_create)
70 flash[:notice] = l(:notice_successful_create)
71 format.html { redirect_to(groups_path) }
71 format.html { redirect_to(groups_path) }
72 format.xml { render :xml => @group, :status => :created, :location => @group }
72 format.xml { render :xml => @group, :status => :created, :location => @group }
73 else
73 else
74 format.html { render :action => "new" }
74 format.html { render :action => "new" }
75 format.xml { render :xml => @group.errors, :status => :unprocessable_entity }
75 format.xml { render :xml => @group.errors, :status => :unprocessable_entity }
76 end
76 end
77 end
77 end
78 end
78 end
79
79
80 # PUT /groups/1
80 # PUT /groups/1
81 # PUT /groups/1.xml
81 # PUT /groups/1.xml
82 def update
82 def update
83 @group = Group.find(params[:id])
83 @group = Group.find(params[:id])
84
84
85 respond_to do |format|
85 respond_to do |format|
86 if @group.update_attributes(params[:group])
86 if @group.update_attributes(params[:group])
87 flash[:notice] = l(:notice_successful_update)
87 flash[:notice] = l(:notice_successful_update)
88 format.html { redirect_to(groups_path) }
88 format.html { redirect_to(groups_path) }
89 format.xml { head :ok }
89 format.xml { head :ok }
90 else
90 else
91 format.html { render :action => "edit" }
91 format.html { render :action => "edit" }
92 format.xml { render :xml => @group.errors, :status => :unprocessable_entity }
92 format.xml { render :xml => @group.errors, :status => :unprocessable_entity }
93 end
93 end
94 end
94 end
95 end
95 end
96
96
97 # DELETE /groups/1
97 # DELETE /groups/1
98 # DELETE /groups/1.xml
98 # DELETE /groups/1.xml
99 def destroy
99 def destroy
100 @group = Group.find(params[:id])
100 @group = Group.find(params[:id])
101 @group.destroy
101 @group.destroy
102
102
103 respond_to do |format|
103 respond_to do |format|
104 format.html { redirect_to(groups_url) }
104 format.html { redirect_to(groups_url) }
105 format.xml { head :ok }
105 format.xml { head :ok }
106 end
106 end
107 end
107 end
108
108
109 def add_users
109 def add_users
110 @group = Group.find(params[:id])
110 @group = Group.find(params[:id])
111 users = User.find_all_by_id(params[:user_ids])
111 users = User.find_all_by_id(params[:user_ids])
112 @group.users << users if request.post?
112 @group.users << users if request.post?
113 respond_to do |format|
113 respond_to do |format|
114 format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'users' }
114 format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'users' }
115 format.js {
115 format.js {
116 render(:update) {|page|
116 render(:update) {|page|
117 page.replace_html "tab-content-users", :partial => 'groups/users'
117 page.replace_html "tab-content-users", :partial => 'groups/users'
118 users.each {|user| page.visual_effect(:highlight, "user-#{user.id}") }
118 users.each {|user| page.visual_effect(:highlight, "user-#{user.id}") }
119 }
119 }
120 }
120 }
121 end
121 end
122 end
122 end
123
123
124 def remove_user
124 def remove_user
125 @group = Group.find(params[:id])
125 @group = Group.find(params[:id])
126 @group.users.delete(User.find(params[:user_id])) if request.post?
126 @group.users.delete(User.find(params[:user_id])) if request.post?
127 respond_to do |format|
127 respond_to do |format|
128 format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'users' }
128 format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'users' }
129 format.js { render(:update) {|page| page.replace_html "tab-content-users", :partial => 'groups/users'} }
129 format.js { render(:update) {|page| page.replace_html "tab-content-users", :partial => 'groups/users'} }
130 end
130 end
131 end
131 end
132
132
133 def autocomplete_for_user
133 def autocomplete_for_user
134 @group = Group.find(params[:id])
134 @group = Group.find(params[:id])
135 @users = User.active.like(params[:q]).find(:all, :limit => 100) - @group.users
135 @users = User.active.not_in_group(@group).like(params[:q]).all(:limit => 100)
136 render :layout => false
136 render :layout => false
137 end
137 end
138
138
139 def edit_membership
139 def edit_membership
140 @group = Group.find(params[:id])
140 @group = Group.find(params[:id])
141 @membership = Member.edit_membership(params[:membership_id], params[:membership], @group)
141 @membership = Member.edit_membership(params[:membership_id], params[:membership], @group)
142 @membership.save if request.post?
142 @membership.save if request.post?
143 respond_to do |format|
143 respond_to do |format|
144 if @membership.valid?
144 if @membership.valid?
145 format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'memberships' }
145 format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'memberships' }
146 format.js {
146 format.js {
147 render(:update) {|page|
147 render(:update) {|page|
148 page.replace_html "tab-content-memberships", :partial => 'groups/memberships'
148 page.replace_html "tab-content-memberships", :partial => 'groups/memberships'
149 page.visual_effect(:highlight, "member-#{@membership.id}")
149 page.visual_effect(:highlight, "member-#{@membership.id}")
150 }
150 }
151 }
151 }
152 else
152 else
153 format.js {
153 format.js {
154 render(:update) {|page|
154 render(:update) {|page|
155 page.alert(l(:notice_failed_to_save_members, :errors => @membership.errors.full_messages.join(', ')))
155 page.alert(l(:notice_failed_to_save_members, :errors => @membership.errors.full_messages.join(', ')))
156 }
156 }
157 }
157 }
158 end
158 end
159 end
159 end
160 end
160 end
161
161
162 def destroy_membership
162 def destroy_membership
163 @group = Group.find(params[:id])
163 @group = Group.find(params[:id])
164 Member.find(params[:membership_id]).destroy if request.post?
164 Member.find(params[:membership_id]).destroy if request.post?
165 respond_to do |format|
165 respond_to do |format|
166 format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'memberships' }
166 format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'memberships' }
167 format.js { render(:update) {|page| page.replace_html "tab-content-memberships", :partial => 'groups/memberships'} }
167 format.js { render(:update) {|page| page.replace_html "tab-content-memberships", :partial => 'groups/memberships'} }
168 end
168 end
169 end
169 end
170 end
170 end
@@ -1,595 +1,599
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 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 include Redmine::SafeAttributes
21 include Redmine::SafeAttributes
22
22
23 # Account statuses
23 # Account statuses
24 STATUS_ANONYMOUS = 0
24 STATUS_ANONYMOUS = 0
25 STATUS_ACTIVE = 1
25 STATUS_ACTIVE = 1
26 STATUS_REGISTERED = 2
26 STATUS_REGISTERED = 2
27 STATUS_LOCKED = 3
27 STATUS_LOCKED = 3
28
28
29 USER_FORMATS = {
29 USER_FORMATS = {
30 :firstname_lastname => '#{firstname} #{lastname}',
30 :firstname_lastname => '#{firstname} #{lastname}',
31 :firstname => '#{firstname}',
31 :firstname => '#{firstname}',
32 :lastname_firstname => '#{lastname} #{firstname}',
32 :lastname_firstname => '#{lastname} #{firstname}',
33 :lastname_coma_firstname => '#{lastname}, #{firstname}',
33 :lastname_coma_firstname => '#{lastname}, #{firstname}',
34 :username => '#{login}'
34 :username => '#{login}'
35 }
35 }
36
36
37 MAIL_NOTIFICATION_OPTIONS = [
37 MAIL_NOTIFICATION_OPTIONS = [
38 ['all', :label_user_mail_option_all],
38 ['all', :label_user_mail_option_all],
39 ['selected', :label_user_mail_option_selected],
39 ['selected', :label_user_mail_option_selected],
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 ['none', :label_user_mail_option_none]
43 ['none', :label_user_mail_option_none]
44 ]
44 ]
45
45
46 has_and_belongs_to_many :groups, :after_add => Proc.new {|user, group| group.user_added(user)},
46 has_and_belongs_to_many :groups, :after_add => Proc.new {|user, group| group.user_added(user)},
47 :after_remove => Proc.new {|user, group| group.user_removed(user)}
47 :after_remove => Proc.new {|user, group| group.user_removed(user)}
48 has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
48 has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
49 has_many :changesets, :dependent => :nullify
49 has_many :changesets, :dependent => :nullify
50 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
50 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
51 has_one :rss_token, :class_name => 'Token', :conditions => "action='feeds'"
51 has_one :rss_token, :class_name => 'Token', :conditions => "action='feeds'"
52 has_one :api_token, :class_name => 'Token', :conditions => "action='api'"
52 has_one :api_token, :class_name => 'Token', :conditions => "action='api'"
53 belongs_to :auth_source
53 belongs_to :auth_source
54
54
55 # Active non-anonymous users scope
55 # Active non-anonymous users scope
56 named_scope :active, :conditions => "#{User.table_name}.status = #{STATUS_ACTIVE}"
56 named_scope :active, :conditions => "#{User.table_name}.status = #{STATUS_ACTIVE}"
57
57
58 acts_as_customizable
58 acts_as_customizable
59
59
60 attr_accessor :password, :password_confirmation
60 attr_accessor :password, :password_confirmation
61 attr_accessor :last_before_login_on
61 attr_accessor :last_before_login_on
62 # Prevents unauthorized assignments
62 # Prevents unauthorized assignments
63 attr_protected :login, :admin, :password, :password_confirmation, :hashed_password
63 attr_protected :login, :admin, :password, :password_confirmation, :hashed_password
64
64
65 validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
65 validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
66 validates_uniqueness_of :login, :if => Proc.new { |user| !user.login.blank? }, :case_sensitive => false
66 validates_uniqueness_of :login, :if => Proc.new { |user| !user.login.blank? }, :case_sensitive => false
67 validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }, :case_sensitive => false
67 validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }, :case_sensitive => false
68 # Login must contain lettres, numbers, underscores only
68 # Login must contain lettres, numbers, underscores only
69 validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
69 validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
70 validates_length_of :login, :maximum => 30
70 validates_length_of :login, :maximum => 30
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 validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true
75 validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true
76
76
77 before_destroy :remove_references_before_destroy
77 before_destroy :remove_references_before_destroy
78
78
79 named_scope :in_group, lambda {|group|
79 named_scope :in_group, lambda {|group|
80 group_id = group.is_a?(Group) ? group.id : group.to_i
80 group_id = group.is_a?(Group) ? group.id : group.to_i
81 { :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] }
81 { :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] }
82 }
82 }
83 named_scope :not_in_group, lambda {|group|
84 group_id = group.is_a?(Group) ? group.id : group.to_i
85 { :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] }
86 }
83
87
84 def before_create
88 def before_create
85 self.mail_notification = Setting.default_notification_option if self.mail_notification.blank?
89 self.mail_notification = Setting.default_notification_option if self.mail_notification.blank?
86 true
90 true
87 end
91 end
88
92
89 def before_save
93 def before_save
90 # update hashed_password if password was set
94 # update hashed_password if password was set
91 if self.password && self.auth_source_id.blank?
95 if self.password && self.auth_source_id.blank?
92 salt_password(password)
96 salt_password(password)
93 end
97 end
94 end
98 end
95
99
96 def reload(*args)
100 def reload(*args)
97 @name = nil
101 @name = nil
98 @projects_by_role = nil
102 @projects_by_role = nil
99 super
103 super
100 end
104 end
101
105
102 def mail=(arg)
106 def mail=(arg)
103 write_attribute(:mail, arg.to_s.strip)
107 write_attribute(:mail, arg.to_s.strip)
104 end
108 end
105
109
106 def identity_url=(url)
110 def identity_url=(url)
107 if url.blank?
111 if url.blank?
108 write_attribute(:identity_url, '')
112 write_attribute(:identity_url, '')
109 else
113 else
110 begin
114 begin
111 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
115 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
112 rescue OpenIdAuthentication::InvalidOpenId
116 rescue OpenIdAuthentication::InvalidOpenId
113 # Invlaid url, don't save
117 # Invlaid url, don't save
114 end
118 end
115 end
119 end
116 self.read_attribute(:identity_url)
120 self.read_attribute(:identity_url)
117 end
121 end
118
122
119 # Returns the user that matches provided login and password, or nil
123 # Returns the user that matches provided login and password, or nil
120 def self.try_to_login(login, password)
124 def self.try_to_login(login, password)
121 # Make sure no one can sign in with an empty password
125 # Make sure no one can sign in with an empty password
122 return nil if password.to_s.empty?
126 return nil if password.to_s.empty?
123 user = find_by_login(login)
127 user = find_by_login(login)
124 if user
128 if user
125 # user is already in local database
129 # user is already in local database
126 return nil if !user.active?
130 return nil if !user.active?
127 if user.auth_source
131 if user.auth_source
128 # user has an external authentication method
132 # user has an external authentication method
129 return nil unless user.auth_source.authenticate(login, password)
133 return nil unless user.auth_source.authenticate(login, password)
130 else
134 else
131 # authentication with local password
135 # authentication with local password
132 return nil unless user.check_password?(password)
136 return nil unless user.check_password?(password)
133 end
137 end
134 else
138 else
135 # user is not yet registered, try to authenticate with available sources
139 # user is not yet registered, try to authenticate with available sources
136 attrs = AuthSource.authenticate(login, password)
140 attrs = AuthSource.authenticate(login, password)
137 if attrs
141 if attrs
138 user = new(attrs)
142 user = new(attrs)
139 user.login = login
143 user.login = login
140 user.language = Setting.default_language
144 user.language = Setting.default_language
141 if user.save
145 if user.save
142 user.reload
146 user.reload
143 logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source
147 logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source
144 end
148 end
145 end
149 end
146 end
150 end
147 user.update_attribute(:last_login_on, Time.now) if user && !user.new_record?
151 user.update_attribute(:last_login_on, Time.now) if user && !user.new_record?
148 user
152 user
149 rescue => text
153 rescue => text
150 raise text
154 raise text
151 end
155 end
152
156
153 # Returns the user who matches the given autologin +key+ or nil
157 # Returns the user who matches the given autologin +key+ or nil
154 def self.try_to_autologin(key)
158 def self.try_to_autologin(key)
155 tokens = Token.find_all_by_action_and_value('autologin', key)
159 tokens = Token.find_all_by_action_and_value('autologin', key)
156 # Make sure there's only 1 token that matches the key
160 # Make sure there's only 1 token that matches the key
157 if tokens.size == 1
161 if tokens.size == 1
158 token = tokens.first
162 token = tokens.first
159 if (token.created_on > Setting.autologin.to_i.day.ago) && token.user && token.user.active?
163 if (token.created_on > Setting.autologin.to_i.day.ago) && token.user && token.user.active?
160 token.user.update_attribute(:last_login_on, Time.now)
164 token.user.update_attribute(:last_login_on, Time.now)
161 token.user
165 token.user
162 end
166 end
163 end
167 end
164 end
168 end
165
169
166 # Return user's full name for display
170 # Return user's full name for display
167 def name(formatter = nil)
171 def name(formatter = nil)
168 if formatter
172 if formatter
169 eval('"' + (USER_FORMATS[formatter] || USER_FORMATS[:firstname_lastname]) + '"')
173 eval('"' + (USER_FORMATS[formatter] || USER_FORMATS[:firstname_lastname]) + '"')
170 else
174 else
171 @name ||= eval('"' + (USER_FORMATS[Setting.user_format] || USER_FORMATS[:firstname_lastname]) + '"')
175 @name ||= eval('"' + (USER_FORMATS[Setting.user_format] || USER_FORMATS[:firstname_lastname]) + '"')
172 end
176 end
173 end
177 end
174
178
175 def active?
179 def active?
176 self.status == STATUS_ACTIVE
180 self.status == STATUS_ACTIVE
177 end
181 end
178
182
179 def registered?
183 def registered?
180 self.status == STATUS_REGISTERED
184 self.status == STATUS_REGISTERED
181 end
185 end
182
186
183 def locked?
187 def locked?
184 self.status == STATUS_LOCKED
188 self.status == STATUS_LOCKED
185 end
189 end
186
190
187 def activate
191 def activate
188 self.status = STATUS_ACTIVE
192 self.status = STATUS_ACTIVE
189 end
193 end
190
194
191 def register
195 def register
192 self.status = STATUS_REGISTERED
196 self.status = STATUS_REGISTERED
193 end
197 end
194
198
195 def lock
199 def lock
196 self.status = STATUS_LOCKED
200 self.status = STATUS_LOCKED
197 end
201 end
198
202
199 def activate!
203 def activate!
200 update_attribute(:status, STATUS_ACTIVE)
204 update_attribute(:status, STATUS_ACTIVE)
201 end
205 end
202
206
203 def register!
207 def register!
204 update_attribute(:status, STATUS_REGISTERED)
208 update_attribute(:status, STATUS_REGISTERED)
205 end
209 end
206
210
207 def lock!
211 def lock!
208 update_attribute(:status, STATUS_LOCKED)
212 update_attribute(:status, STATUS_LOCKED)
209 end
213 end
210
214
211 # Returns true if +clear_password+ is the correct user's password, otherwise false
215 # Returns true if +clear_password+ is the correct user's password, otherwise false
212 def check_password?(clear_password)
216 def check_password?(clear_password)
213 if auth_source_id.present?
217 if auth_source_id.present?
214 auth_source.authenticate(self.login, clear_password)
218 auth_source.authenticate(self.login, clear_password)
215 else
219 else
216 User.hash_password("#{salt}#{User.hash_password clear_password}") == hashed_password
220 User.hash_password("#{salt}#{User.hash_password clear_password}") == hashed_password
217 end
221 end
218 end
222 end
219
223
220 # Generates a random salt and computes hashed_password for +clear_password+
224 # Generates a random salt and computes hashed_password for +clear_password+
221 # The hashed password is stored in the following form: SHA1(salt + SHA1(password))
225 # The hashed password is stored in the following form: SHA1(salt + SHA1(password))
222 def salt_password(clear_password)
226 def salt_password(clear_password)
223 self.salt = User.generate_salt
227 self.salt = User.generate_salt
224 self.hashed_password = User.hash_password("#{salt}#{User.hash_password clear_password}")
228 self.hashed_password = User.hash_password("#{salt}#{User.hash_password clear_password}")
225 end
229 end
226
230
227 # Does the backend storage allow this user to change their password?
231 # Does the backend storage allow this user to change their password?
228 def change_password_allowed?
232 def change_password_allowed?
229 return true if auth_source_id.blank?
233 return true if auth_source_id.blank?
230 return auth_source.allow_password_changes?
234 return auth_source.allow_password_changes?
231 end
235 end
232
236
233 # Generate and set a random password. Useful for automated user creation
237 # Generate and set a random password. Useful for automated user creation
234 # Based on Token#generate_token_value
238 # Based on Token#generate_token_value
235 #
239 #
236 def random_password
240 def random_password
237 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
241 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
238 password = ''
242 password = ''
239 40.times { |i| password << chars[rand(chars.size-1)] }
243 40.times { |i| password << chars[rand(chars.size-1)] }
240 self.password = password
244 self.password = password
241 self.password_confirmation = password
245 self.password_confirmation = password
242 self
246 self
243 end
247 end
244
248
245 def pref
249 def pref
246 self.preference ||= UserPreference.new(:user => self)
250 self.preference ||= UserPreference.new(:user => self)
247 end
251 end
248
252
249 def time_zone
253 def time_zone
250 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
254 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
251 end
255 end
252
256
253 def wants_comments_in_reverse_order?
257 def wants_comments_in_reverse_order?
254 self.pref[:comments_sorting] == 'desc'
258 self.pref[:comments_sorting] == 'desc'
255 end
259 end
256
260
257 # Return user's RSS key (a 40 chars long string), used to access feeds
261 # Return user's RSS key (a 40 chars long string), used to access feeds
258 def rss_key
262 def rss_key
259 token = self.rss_token || Token.create(:user => self, :action => 'feeds')
263 token = self.rss_token || Token.create(:user => self, :action => 'feeds')
260 token.value
264 token.value
261 end
265 end
262
266
263 # Return user's API key (a 40 chars long string), used to access the API
267 # Return user's API key (a 40 chars long string), used to access the API
264 def api_key
268 def api_key
265 token = self.api_token || self.create_api_token(:action => 'api')
269 token = self.api_token || self.create_api_token(:action => 'api')
266 token.value
270 token.value
267 end
271 end
268
272
269 # Return an array of project ids for which the user has explicitly turned mail notifications on
273 # Return an array of project ids for which the user has explicitly turned mail notifications on
270 def notified_projects_ids
274 def notified_projects_ids
271 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
275 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
272 end
276 end
273
277
274 def notified_project_ids=(ids)
278 def notified_project_ids=(ids)
275 Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
279 Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
276 Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
280 Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
277 @notified_projects_ids = nil
281 @notified_projects_ids = nil
278 notified_projects_ids
282 notified_projects_ids
279 end
283 end
280
284
281 def valid_notification_options
285 def valid_notification_options
282 self.class.valid_notification_options(self)
286 self.class.valid_notification_options(self)
283 end
287 end
284
288
285 # Only users that belong to more than 1 project can select projects for which they are notified
289 # Only users that belong to more than 1 project can select projects for which they are notified
286 def self.valid_notification_options(user=nil)
290 def self.valid_notification_options(user=nil)
287 # Note that @user.membership.size would fail since AR ignores
291 # Note that @user.membership.size would fail since AR ignores
288 # :include association option when doing a count
292 # :include association option when doing a count
289 if user.nil? || user.memberships.length < 1
293 if user.nil? || user.memberships.length < 1
290 MAIL_NOTIFICATION_OPTIONS.reject {|option| option.first == 'selected'}
294 MAIL_NOTIFICATION_OPTIONS.reject {|option| option.first == 'selected'}
291 else
295 else
292 MAIL_NOTIFICATION_OPTIONS
296 MAIL_NOTIFICATION_OPTIONS
293 end
297 end
294 end
298 end
295
299
296 # Find a user account by matching the exact login and then a case-insensitive
300 # Find a user account by matching the exact login and then a case-insensitive
297 # version. Exact matches will be given priority.
301 # version. Exact matches will be given priority.
298 def self.find_by_login(login)
302 def self.find_by_login(login)
299 # force string comparison to be case sensitive on MySQL
303 # force string comparison to be case sensitive on MySQL
300 type_cast = (ActiveRecord::Base.connection.adapter_name == 'MySQL') ? 'BINARY' : ''
304 type_cast = (ActiveRecord::Base.connection.adapter_name == 'MySQL') ? 'BINARY' : ''
301
305
302 # First look for an exact match
306 # First look for an exact match
303 user = first(:conditions => ["#{type_cast} login = ?", login])
307 user = first(:conditions => ["#{type_cast} login = ?", login])
304 # Fail over to case-insensitive if none was found
308 # Fail over to case-insensitive if none was found
305 user ||= first(:conditions => ["#{type_cast} LOWER(login) = ?", login.to_s.downcase])
309 user ||= first(:conditions => ["#{type_cast} LOWER(login) = ?", login.to_s.downcase])
306 end
310 end
307
311
308 def self.find_by_rss_key(key)
312 def self.find_by_rss_key(key)
309 token = Token.find_by_value(key)
313 token = Token.find_by_value(key)
310 token && token.user.active? ? token.user : nil
314 token && token.user.active? ? token.user : nil
311 end
315 end
312
316
313 def self.find_by_api_key(key)
317 def self.find_by_api_key(key)
314 token = Token.find_by_action_and_value('api', key)
318 token = Token.find_by_action_and_value('api', key)
315 token && token.user.active? ? token.user : nil
319 token && token.user.active? ? token.user : nil
316 end
320 end
317
321
318 # Makes find_by_mail case-insensitive
322 # Makes find_by_mail case-insensitive
319 def self.find_by_mail(mail)
323 def self.find_by_mail(mail)
320 find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
324 find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
321 end
325 end
322
326
323 def to_s
327 def to_s
324 name
328 name
325 end
329 end
326
330
327 # Returns the current day according to user's time zone
331 # Returns the current day according to user's time zone
328 def today
332 def today
329 if time_zone.nil?
333 if time_zone.nil?
330 Date.today
334 Date.today
331 else
335 else
332 Time.now.in_time_zone(time_zone).to_date
336 Time.now.in_time_zone(time_zone).to_date
333 end
337 end
334 end
338 end
335
339
336 def logged?
340 def logged?
337 true
341 true
338 end
342 end
339
343
340 def anonymous?
344 def anonymous?
341 !logged?
345 !logged?
342 end
346 end
343
347
344 # Return user's roles for project
348 # Return user's roles for project
345 def roles_for_project(project)
349 def roles_for_project(project)
346 roles = []
350 roles = []
347 # No role on archived projects
351 # No role on archived projects
348 return roles unless project && project.active?
352 return roles unless project && project.active?
349 if logged?
353 if logged?
350 # Find project membership
354 # Find project membership
351 membership = memberships.detect {|m| m.project_id == project.id}
355 membership = memberships.detect {|m| m.project_id == project.id}
352 if membership
356 if membership
353 roles = membership.roles
357 roles = membership.roles
354 else
358 else
355 @role_non_member ||= Role.non_member
359 @role_non_member ||= Role.non_member
356 roles << @role_non_member
360 roles << @role_non_member
357 end
361 end
358 else
362 else
359 @role_anonymous ||= Role.anonymous
363 @role_anonymous ||= Role.anonymous
360 roles << @role_anonymous
364 roles << @role_anonymous
361 end
365 end
362 roles
366 roles
363 end
367 end
364
368
365 # Return true if the user is a member of project
369 # Return true if the user is a member of project
366 def member_of?(project)
370 def member_of?(project)
367 !roles_for_project(project).detect {|role| role.member?}.nil?
371 !roles_for_project(project).detect {|role| role.member?}.nil?
368 end
372 end
369
373
370 # Returns a hash of user's projects grouped by roles
374 # Returns a hash of user's projects grouped by roles
371 def projects_by_role
375 def projects_by_role
372 return @projects_by_role if @projects_by_role
376 return @projects_by_role if @projects_by_role
373
377
374 @projects_by_role = Hash.new {|h,k| h[k]=[]}
378 @projects_by_role = Hash.new {|h,k| h[k]=[]}
375 memberships.each do |membership|
379 memberships.each do |membership|
376 membership.roles.each do |role|
380 membership.roles.each do |role|
377 @projects_by_role[role] << membership.project if membership.project
381 @projects_by_role[role] << membership.project if membership.project
378 end
382 end
379 end
383 end
380 @projects_by_role.each do |role, projects|
384 @projects_by_role.each do |role, projects|
381 projects.uniq!
385 projects.uniq!
382 end
386 end
383
387
384 @projects_by_role
388 @projects_by_role
385 end
389 end
386
390
387 # Return true if the user is allowed to do the specified action on a specific context
391 # Return true if the user is allowed to do the specified action on a specific context
388 # Action can be:
392 # Action can be:
389 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
393 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
390 # * a permission Symbol (eg. :edit_project)
394 # * a permission Symbol (eg. :edit_project)
391 # Context can be:
395 # Context can be:
392 # * a project : returns true if user is allowed to do the specified action on this project
396 # * a project : returns true if user is allowed to do the specified action on this project
393 # * a group of projects : returns true if user is allowed on every project
397 # * a group of projects : returns true if user is allowed on every project
394 # * nil with options[:global] set : check if user has at least one role allowed for this action,
398 # * nil with options[:global] set : check if user has at least one role allowed for this action,
395 # or falls back to Non Member / Anonymous permissions depending if the user is logged
399 # or falls back to Non Member / Anonymous permissions depending if the user is logged
396 def allowed_to?(action, context, options={})
400 def allowed_to?(action, context, options={})
397 if context && context.is_a?(Project)
401 if context && context.is_a?(Project)
398 # No action allowed on archived projects
402 # No action allowed on archived projects
399 return false unless context.active?
403 return false unless context.active?
400 # No action allowed on disabled modules
404 # No action allowed on disabled modules
401 return false unless context.allows_to?(action)
405 return false unless context.allows_to?(action)
402 # Admin users are authorized for anything else
406 # Admin users are authorized for anything else
403 return true if admin?
407 return true if admin?
404
408
405 roles = roles_for_project(context)
409 roles = roles_for_project(context)
406 return false unless roles
410 return false unless roles
407 roles.detect {|role| (context.is_public? || role.member?) && role.allowed_to?(action)}
411 roles.detect {|role| (context.is_public? || role.member?) && role.allowed_to?(action)}
408
412
409 elsif context && context.is_a?(Array)
413 elsif context && context.is_a?(Array)
410 # Authorize if user is authorized on every element of the array
414 # Authorize if user is authorized on every element of the array
411 context.map do |project|
415 context.map do |project|
412 allowed_to?(action,project,options)
416 allowed_to?(action,project,options)
413 end.inject do |memo,allowed|
417 end.inject do |memo,allowed|
414 memo && allowed
418 memo && allowed
415 end
419 end
416 elsif options[:global]
420 elsif options[:global]
417 # Admin users are always authorized
421 # Admin users are always authorized
418 return true if admin?
422 return true if admin?
419
423
420 # authorize if user has at least one role that has this permission
424 # authorize if user has at least one role that has this permission
421 roles = memberships.collect {|m| m.roles}.flatten.uniq
425 roles = memberships.collect {|m| m.roles}.flatten.uniq
422 roles.detect {|r| r.allowed_to?(action)} || (self.logged? ? Role.non_member.allowed_to?(action) : Role.anonymous.allowed_to?(action))
426 roles.detect {|r| r.allowed_to?(action)} || (self.logged? ? Role.non_member.allowed_to?(action) : Role.anonymous.allowed_to?(action))
423 else
427 else
424 false
428 false
425 end
429 end
426 end
430 end
427
431
428 # Is the user allowed to do the specified action on any project?
432 # Is the user allowed to do the specified action on any project?
429 # See allowed_to? for the actions and valid options.
433 # See allowed_to? for the actions and valid options.
430 def allowed_to_globally?(action, options)
434 def allowed_to_globally?(action, options)
431 allowed_to?(action, nil, options.reverse_merge(:global => true))
435 allowed_to?(action, nil, options.reverse_merge(:global => true))
432 end
436 end
433
437
434 safe_attributes 'login',
438 safe_attributes 'login',
435 'firstname',
439 'firstname',
436 'lastname',
440 'lastname',
437 'mail',
441 'mail',
438 'mail_notification',
442 'mail_notification',
439 'language',
443 'language',
440 'custom_field_values',
444 'custom_field_values',
441 'custom_fields',
445 'custom_fields',
442 'identity_url'
446 'identity_url'
443
447
444 safe_attributes 'status',
448 safe_attributes 'status',
445 'auth_source_id',
449 'auth_source_id',
446 :if => lambda {|user, current_user| current_user.admin?}
450 :if => lambda {|user, current_user| current_user.admin?}
447
451
448 safe_attributes 'group_ids',
452 safe_attributes 'group_ids',
449 :if => lambda {|user, current_user| current_user.admin? && !user.new_record?}
453 :if => lambda {|user, current_user| current_user.admin? && !user.new_record?}
450
454
451 # Utility method to help check if a user should be notified about an
455 # Utility method to help check if a user should be notified about an
452 # event.
456 # event.
453 #
457 #
454 # TODO: only supports Issue events currently
458 # TODO: only supports Issue events currently
455 def notify_about?(object)
459 def notify_about?(object)
456 case mail_notification
460 case mail_notification
457 when 'all'
461 when 'all'
458 true
462 true
459 when 'selected'
463 when 'selected'
460 # user receives notifications for created/assigned issues on unselected projects
464 # user receives notifications for created/assigned issues on unselected projects
461 if object.is_a?(Issue) && (object.author == self || object.assigned_to == self)
465 if object.is_a?(Issue) && (object.author == self || object.assigned_to == self)
462 true
466 true
463 else
467 else
464 false
468 false
465 end
469 end
466 when 'none'
470 when 'none'
467 false
471 false
468 when 'only_my_events'
472 when 'only_my_events'
469 if object.is_a?(Issue) && (object.author == self || object.assigned_to == self)
473 if object.is_a?(Issue) && (object.author == self || object.assigned_to == self)
470 true
474 true
471 else
475 else
472 false
476 false
473 end
477 end
474 when 'only_assigned'
478 when 'only_assigned'
475 if object.is_a?(Issue) && object.assigned_to == self
479 if object.is_a?(Issue) && object.assigned_to == self
476 true
480 true
477 else
481 else
478 false
482 false
479 end
483 end
480 when 'only_owner'
484 when 'only_owner'
481 if object.is_a?(Issue) && object.author == self
485 if object.is_a?(Issue) && object.author == self
482 true
486 true
483 else
487 else
484 false
488 false
485 end
489 end
486 else
490 else
487 false
491 false
488 end
492 end
489 end
493 end
490
494
491 def self.current=(user)
495 def self.current=(user)
492 @current_user = user
496 @current_user = user
493 end
497 end
494
498
495 def self.current
499 def self.current
496 @current_user ||= User.anonymous
500 @current_user ||= User.anonymous
497 end
501 end
498
502
499 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
503 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
500 # one anonymous user per database.
504 # one anonymous user per database.
501 def self.anonymous
505 def self.anonymous
502 anonymous_user = AnonymousUser.find(:first)
506 anonymous_user = AnonymousUser.find(:first)
503 if anonymous_user.nil?
507 if anonymous_user.nil?
504 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
508 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
505 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
509 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
506 end
510 end
507 anonymous_user
511 anonymous_user
508 end
512 end
509
513
510 # Salts all existing unsalted passwords
514 # Salts all existing unsalted passwords
511 # It changes password storage scheme from SHA1(password) to SHA1(salt + SHA1(password))
515 # It changes password storage scheme from SHA1(password) to SHA1(salt + SHA1(password))
512 # This method is used in the SaltPasswords migration and is to be kept as is
516 # This method is used in the SaltPasswords migration and is to be kept as is
513 def self.salt_unsalted_passwords!
517 def self.salt_unsalted_passwords!
514 transaction do
518 transaction do
515 User.find_each(:conditions => "salt IS NULL OR salt = ''") do |user|
519 User.find_each(:conditions => "salt IS NULL OR salt = ''") do |user|
516 next if user.hashed_password.blank?
520 next if user.hashed_password.blank?
517 salt = User.generate_salt
521 salt = User.generate_salt
518 hashed_password = User.hash_password("#{salt}#{user.hashed_password}")
522 hashed_password = User.hash_password("#{salt}#{user.hashed_password}")
519 User.update_all("salt = '#{salt}', hashed_password = '#{hashed_password}'", ["id = ?", user.id] )
523 User.update_all("salt = '#{salt}', hashed_password = '#{hashed_password}'", ["id = ?", user.id] )
520 end
524 end
521 end
525 end
522 end
526 end
523
527
524 protected
528 protected
525
529
526 def validate
530 def validate
527 # Password length validation based on setting
531 # Password length validation based on setting
528 if !password.nil? && password.size < Setting.password_min_length.to_i
532 if !password.nil? && password.size < Setting.password_min_length.to_i
529 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
533 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
530 end
534 end
531 end
535 end
532
536
533 private
537 private
534
538
535 # Removes references that are not handled by associations
539 # Removes references that are not handled by associations
536 # Things that are not deleted are reassociated with the anonymous user
540 # Things that are not deleted are reassociated with the anonymous user
537 def remove_references_before_destroy
541 def remove_references_before_destroy
538 return if self.id.nil?
542 return if self.id.nil?
539
543
540 substitute = User.anonymous
544 substitute = User.anonymous
541 Attachment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
545 Attachment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
542 Comment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
546 Comment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
543 Issue.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
547 Issue.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
544 Issue.update_all 'assigned_to_id = NULL', ['assigned_to_id = ?', id]
548 Issue.update_all 'assigned_to_id = NULL', ['assigned_to_id = ?', id]
545 Journal.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
549 Journal.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
546 JournalDetail.update_all ['old_value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND old_value = ?", id.to_s]
550 JournalDetail.update_all ['old_value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND old_value = ?", id.to_s]
547 JournalDetail.update_all ['value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND value = ?", id.to_s]
551 JournalDetail.update_all ['value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND value = ?", id.to_s]
548 Message.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
552 Message.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
549 News.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
553 News.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
550 # Remove private queries and keep public ones
554 # Remove private queries and keep public ones
551 Query.delete_all ['user_id = ? AND is_public = ?', id, false]
555 Query.delete_all ['user_id = ? AND is_public = ?', id, false]
552 Query.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
556 Query.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
553 TimeEntry.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
557 TimeEntry.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
554 Token.delete_all ['user_id = ?', id]
558 Token.delete_all ['user_id = ?', id]
555 Watcher.delete_all ['user_id = ?', id]
559 Watcher.delete_all ['user_id = ?', id]
556 WikiContent.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
560 WikiContent.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
557 WikiContent::Version.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
561 WikiContent::Version.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
558 end
562 end
559
563
560 # Return password digest
564 # Return password digest
561 def self.hash_password(clear_password)
565 def self.hash_password(clear_password)
562 Digest::SHA1.hexdigest(clear_password || "")
566 Digest::SHA1.hexdigest(clear_password || "")
563 end
567 end
564
568
565 # Returns a 128bits random salt as a hex string (32 chars long)
569 # Returns a 128bits random salt as a hex string (32 chars long)
566 def self.generate_salt
570 def self.generate_salt
567 ActiveSupport::SecureRandom.hex(16)
571 ActiveSupport::SecureRandom.hex(16)
568 end
572 end
569
573
570 end
574 end
571
575
572 class AnonymousUser < User
576 class AnonymousUser < User
573
577
574 def validate_on_create
578 def validate_on_create
575 # There should be only one AnonymousUser in the database
579 # There should be only one AnonymousUser in the database
576 errors.add_to_base 'An anonymous user already exists.' if AnonymousUser.find(:first)
580 errors.add_to_base 'An anonymous user already exists.' if AnonymousUser.find(:first)
577 end
581 end
578
582
579 def available_custom_fields
583 def available_custom_fields
580 []
584 []
581 end
585 end
582
586
583 # Overrides a few properties
587 # Overrides a few properties
584 def logged?; false end
588 def logged?; false end
585 def admin; false end
589 def admin; false end
586 def name(*args); I18n.t(:label_user_anonymous) end
590 def name(*args); I18n.t(:label_user_anonymous) end
587 def mail; nil end
591 def mail; nil end
588 def time_zone; nil end
592 def time_zone; nil end
589 def rss_key; nil end
593 def rss_key; nil end
590
594
591 # Anonymous user can not be destroyed
595 # Anonymous user can not be destroyed
592 def destroy
596 def destroy
593 false
597 false
594 end
598 end
595 end
599 end
@@ -1,49 +1,49
1 <div class="splitcontentleft">
1 <div class="splitcontentleft">
2 <% if @group.users.any? %>
2 <% if @group.users.any? %>
3 <table class="list users">
3 <table class="list users">
4 <thead><tr>
4 <thead><tr>
5 <th><%= l(:label_user) %></th>
5 <th><%= l(:label_user) %></th>
6 <th style="width:15%"></th>
6 <th style="width:15%"></th>
7 </tr></thead>
7 </tr></thead>
8 <tbody>
8 <tbody>
9 <% @group.users.sort.each do |user| %>
9 <% @group.users.sort.each do |user| %>
10 <tr id="user-<%= user.id %>" class="<%= cycle 'odd', 'even' %>">
10 <tr id="user-<%= user.id %>" class="<%= cycle 'odd', 'even' %>">
11 <td class="user"><%= link_to_user user %></td>
11 <td class="user"><%= link_to_user user %></td>
12 <td class="buttons">
12 <td class="buttons">
13 <%= link_to_remote l(:button_delete), { :url => { :controller => 'groups', :action => 'remove_user', :id => @group, :user_id => user },
13 <%= link_to_remote l(:button_delete), { :url => { :controller => 'groups', :action => 'remove_user', :id => @group, :user_id => user },
14 :method => :post },
14 :method => :post },
15 :class => 'icon icon-del' %>
15 :class => 'icon icon-del' %>
16 </td>
16 </td>
17 </tr>
17 </tr>
18 <% end %>
18 <% end %>
19 </tbody>
19 </tbody>
20 </table>
20 </table>
21 <% else %>
21 <% else %>
22 <p class="nodata"><%= l(:label_no_data) %></p>
22 <p class="nodata"><%= l(:label_no_data) %></p>
23 <% end %>
23 <% end %>
24 </div>
24 </div>
25
25
26 <div class="splitcontentright">
26 <div class="splitcontentright">
27 <% users = User.active.find(:all, :limit => 100) - @group.users %>
27 <% users = User.active.not_in_group(@group).all(:limit => 100) %>
28 <% if users.any? %>
28 <% if users.any? %>
29 <% remote_form_for(:group, @group, :url => {:controller => 'groups', :action => 'add_users', :id => @group}, :method => :post) do |f| %>
29 <% remote_form_for(:group, @group, :url => {:controller => 'groups', :action => 'add_users', :id => @group}, :method => :post) do |f| %>
30 <fieldset><legend><%=l(:label_user_new)%></legend>
30 <fieldset><legend><%=l(:label_user_new)%></legend>
31
31
32 <p><%= label_tag "user_search", l(:label_user_search) %><%= text_field_tag 'user_search', nil %></p>
32 <p><%= label_tag "user_search", l(:label_user_search) %><%= text_field_tag 'user_search', nil %></p>
33 <%= observe_field(:user_search,
33 <%= observe_field(:user_search,
34 :frequency => 0.5,
34 :frequency => 0.5,
35 :update => :users,
35 :update => :users,
36 :url => { :controller => 'groups', :action => 'autocomplete_for_user', :id => @group },
36 :url => { :controller => 'groups', :action => 'autocomplete_for_user', :id => @group },
37 :with => 'q')
37 :with => 'q')
38 %>
38 %>
39
39
40 <div id="users">
40 <div id="users">
41 <%= principals_check_box_tags 'user_ids[]', users %>
41 <%= principals_check_box_tags 'user_ids[]', users %>
42 </div>
42 </div>
43
43
44 <p><%= submit_tag l(:button_add) %></p>
44 <p><%= submit_tag l(:button_add) %></p>
45 </fieldset>
45 </fieldset>
46 <% end %>
46 <% end %>
47 <% end %>
47 <% end %>
48
48
49 </div>
49 </div>
@@ -1,107 +1,116
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 File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19 require 'groups_controller'
19 require 'groups_controller'
20
20
21 # Re-raise errors caught by the controller.
21 # Re-raise errors caught by the controller.
22 class GroupsController; def rescue_action(e) raise e end; end
22 class GroupsController; def rescue_action(e) raise e end; end
23
23
24 class GroupsControllerTest < ActionController::TestCase
24 class GroupsControllerTest < ActionController::TestCase
25 fixtures :projects, :users, :members, :member_roles, :groups_users
25 fixtures :projects, :users, :members, :member_roles, :groups_users
26
26
27 def setup
27 def setup
28 @controller = GroupsController.new
28 @controller = GroupsController.new
29 @request = ActionController::TestRequest.new
29 @request = ActionController::TestRequest.new
30 @response = ActionController::TestResponse.new
30 @response = ActionController::TestResponse.new
31 User.current = nil
31 User.current = nil
32 @request.session[:user_id] = 1
32 @request.session[:user_id] = 1
33 end
33 end
34
34
35 def test_index
35 def test_index
36 get :index
36 get :index
37 assert_response :success
37 assert_response :success
38 assert_template 'index'
38 assert_template 'index'
39 end
39 end
40
40
41 def test_show
41 def test_show
42 get :show, :id => 10
42 get :show, :id => 10
43 assert_response :success
43 assert_response :success
44 assert_template 'show'
44 assert_template 'show'
45 end
45 end
46
46
47 def test_new
47 def test_new
48 get :new
48 get :new
49 assert_response :success
49 assert_response :success
50 assert_template 'new'
50 assert_template 'new'
51 end
51 end
52
52
53 def test_create
53 def test_create
54 assert_difference 'Group.count' do
54 assert_difference 'Group.count' do
55 post :create, :group => {:lastname => 'New group'}
55 post :create, :group => {:lastname => 'New group'}
56 end
56 end
57 assert_redirected_to '/groups'
57 assert_redirected_to '/groups'
58 end
58 end
59
59
60 def test_edit
60 def test_edit
61 get :edit, :id => 10
61 get :edit, :id => 10
62 assert_response :success
62 assert_response :success
63 assert_template 'edit'
63 assert_template 'edit'
64 end
64 end
65
65
66 def test_update
66 def test_update
67 post :update, :id => 10
67 post :update, :id => 10
68 assert_redirected_to '/groups'
68 assert_redirected_to '/groups'
69 end
69 end
70
70
71 def test_destroy
71 def test_destroy
72 assert_difference 'Group.count', -1 do
72 assert_difference 'Group.count', -1 do
73 post :destroy, :id => 10
73 post :destroy, :id => 10
74 end
74 end
75 assert_redirected_to '/groups'
75 assert_redirected_to '/groups'
76 end
76 end
77
77
78 def test_add_users
78 def test_add_users
79 assert_difference 'Group.find(10).users.count', 2 do
79 assert_difference 'Group.find(10).users.count', 2 do
80 post :add_users, :id => 10, :user_ids => ['2', '3']
80 post :add_users, :id => 10, :user_ids => ['2', '3']
81 end
81 end
82 end
82 end
83
83
84 def test_remove_user
84 def test_remove_user
85 assert_difference 'Group.find(10).users.count', -1 do
85 assert_difference 'Group.find(10).users.count', -1 do
86 post :remove_user, :id => 10, :user_id => '8'
86 post :remove_user, :id => 10, :user_id => '8'
87 end
87 end
88 end
88 end
89
89
90 def test_new_membership
90 def test_new_membership
91 assert_difference 'Group.find(10).members.count' do
91 assert_difference 'Group.find(10).members.count' do
92 post :edit_membership, :id => 10, :membership => { :project_id => 2, :role_ids => ['1', '2']}
92 post :edit_membership, :id => 10, :membership => { :project_id => 2, :role_ids => ['1', '2']}
93 end
93 end
94 end
94 end
95
95
96 def test_edit_membership
96 def test_edit_membership
97 assert_no_difference 'Group.find(10).members.count' do
97 assert_no_difference 'Group.find(10).members.count' do
98 post :edit_membership, :id => 10, :membership_id => 6, :membership => { :role_ids => ['1', '3']}
98 post :edit_membership, :id => 10, :membership_id => 6, :membership => { :role_ids => ['1', '3']}
99 end
99 end
100 end
100 end
101
101
102 def test_destroy_membership
102 def test_destroy_membership
103 assert_difference 'Group.find(10).members.count', -1 do
103 assert_difference 'Group.find(10).members.count', -1 do
104 post :destroy_membership, :id => 10, :membership_id => 6
104 post :destroy_membership, :id => 10, :membership_id => 6
105 end
105 end
106 end
106 end
107
108 def test_autocomplete_for_user
109 get :autocomplete_for_user, :id => 10, :q => 'mis'
110 assert_response :success
111 users = assigns(:users)
112 assert_not_nil users
113 assert users.any?
114 assert !users.include?(Group.find(10).users.first)
115 end
107 end
116 end
General Comments 0
You need to be logged in to leave comments. Login now