##// END OF EJS Templates
Don't preload projects and roles on Principal#memberships association (#23519)....
Jean-Philippe Lang -
r15771:3deb70d4aab9
parent child
Show More
@@ -1,187 +1,187
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class UsersController < ApplicationController
18 class UsersController < ApplicationController
19 layout 'admin'
19 layout 'admin'
20 self.main_menu = false
20 self.main_menu = false
21
21
22 before_action :require_admin, :except => :show
22 before_action :require_admin, :except => :show
23 before_action :find_user, :only => [:show, :edit, :update, :destroy]
23 before_action :find_user, :only => [:show, :edit, :update, :destroy]
24 accept_api_auth :index, :show, :create, :update, :destroy
24 accept_api_auth :index, :show, :create, :update, :destroy
25
25
26 helper :sort
26 helper :sort
27 include SortHelper
27 include SortHelper
28 helper :custom_fields
28 helper :custom_fields
29 include CustomFieldsHelper
29 include CustomFieldsHelper
30 helper :principal_memberships
30 helper :principal_memberships
31
31
32 require_sudo_mode :create, :update, :destroy
32 require_sudo_mode :create, :update, :destroy
33
33
34 def index
34 def index
35 sort_init 'login', 'asc'
35 sort_init 'login', 'asc'
36 sort_update %w(login firstname lastname admin created_on last_login_on)
36 sort_update %w(login firstname lastname admin created_on last_login_on)
37
37
38 case params[:format]
38 case params[:format]
39 when 'xml', 'json'
39 when 'xml', 'json'
40 @offset, @limit = api_offset_and_limit
40 @offset, @limit = api_offset_and_limit
41 else
41 else
42 @limit = per_page_option
42 @limit = per_page_option
43 end
43 end
44
44
45 @status = params[:status] || 1
45 @status = params[:status] || 1
46
46
47 scope = User.logged.status(@status).preload(:email_address)
47 scope = User.logged.status(@status).preload(:email_address)
48 scope = scope.like(params[:name]) if params[:name].present?
48 scope = scope.like(params[:name]) if params[:name].present?
49 scope = scope.in_group(params[:group_id]) if params[:group_id].present?
49 scope = scope.in_group(params[:group_id]) if params[:group_id].present?
50
50
51 @user_count = scope.count
51 @user_count = scope.count
52 @user_pages = Paginator.new @user_count, @limit, params['page']
52 @user_pages = Paginator.new @user_count, @limit, params['page']
53 @offset ||= @user_pages.offset
53 @offset ||= @user_pages.offset
54 @users = scope.order(sort_clause).limit(@limit).offset(@offset).to_a
54 @users = scope.order(sort_clause).limit(@limit).offset(@offset).to_a
55
55
56 respond_to do |format|
56 respond_to do |format|
57 format.html {
57 format.html {
58 @groups = Group.givable.sort
58 @groups = Group.givable.sort
59 render :layout => !request.xhr?
59 render :layout => !request.xhr?
60 }
60 }
61 format.api
61 format.api
62 end
62 end
63 end
63 end
64
64
65 def show
65 def show
66 unless @user.visible?
66 unless @user.visible?
67 render_404
67 render_404
68 return
68 return
69 end
69 end
70
70
71 # show projects based on current user visibility
71 # show projects based on current user visibility
72 @memberships = @user.memberships.where(Project.visible_condition(User.current)).to_a
72 @memberships = @user.memberships.preload(:roles, :project).where(Project.visible_condition(User.current)).to_a
73
73
74 respond_to do |format|
74 respond_to do |format|
75 format.html {
75 format.html {
76 events = Redmine::Activity::Fetcher.new(User.current, :author => @user).events(nil, nil, :limit => 10)
76 events = Redmine::Activity::Fetcher.new(User.current, :author => @user).events(nil, nil, :limit => 10)
77 @events_by_day = events.group_by {|event| User.current.time_to_date(event.event_datetime)}
77 @events_by_day = events.group_by {|event| User.current.time_to_date(event.event_datetime)}
78 render :layout => 'base'
78 render :layout => 'base'
79 }
79 }
80 format.api
80 format.api
81 end
81 end
82 end
82 end
83
83
84 def new
84 def new
85 @user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option)
85 @user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option)
86 @user.safe_attributes = params[:user]
86 @user.safe_attributes = params[:user]
87 @auth_sources = AuthSource.all
87 @auth_sources = AuthSource.all
88 end
88 end
89
89
90 def create
90 def create
91 @user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option, :admin => false)
91 @user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option, :admin => false)
92 @user.safe_attributes = params[:user]
92 @user.safe_attributes = params[:user]
93 @user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation] unless @user.auth_source_id
93 @user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation] unless @user.auth_source_id
94 @user.pref.safe_attributes = params[:pref]
94 @user.pref.safe_attributes = params[:pref]
95
95
96 if @user.save
96 if @user.save
97 Mailer.account_information(@user, @user.password).deliver if params[:send_information]
97 Mailer.account_information(@user, @user.password).deliver if params[:send_information]
98
98
99 respond_to do |format|
99 respond_to do |format|
100 format.html {
100 format.html {
101 flash[:notice] = l(:notice_user_successful_create, :id => view_context.link_to(@user.login, user_path(@user)))
101 flash[:notice] = l(:notice_user_successful_create, :id => view_context.link_to(@user.login, user_path(@user)))
102 if params[:continue]
102 if params[:continue]
103 attrs = params[:user].slice(:generate_password)
103 attrs = params[:user].slice(:generate_password)
104 redirect_to new_user_path(:user => attrs)
104 redirect_to new_user_path(:user => attrs)
105 else
105 else
106 redirect_to edit_user_path(@user)
106 redirect_to edit_user_path(@user)
107 end
107 end
108 }
108 }
109 format.api { render :action => 'show', :status => :created, :location => user_url(@user) }
109 format.api { render :action => 'show', :status => :created, :location => user_url(@user) }
110 end
110 end
111 else
111 else
112 @auth_sources = AuthSource.all
112 @auth_sources = AuthSource.all
113 # Clear password input
113 # Clear password input
114 @user.password = @user.password_confirmation = nil
114 @user.password = @user.password_confirmation = nil
115
115
116 respond_to do |format|
116 respond_to do |format|
117 format.html { render :action => 'new' }
117 format.html { render :action => 'new' }
118 format.api { render_validation_errors(@user) }
118 format.api { render_validation_errors(@user) }
119 end
119 end
120 end
120 end
121 end
121 end
122
122
123 def edit
123 def edit
124 @auth_sources = AuthSource.all
124 @auth_sources = AuthSource.all
125 @membership ||= Member.new
125 @membership ||= Member.new
126 end
126 end
127
127
128 def update
128 def update
129 if params[:user][:password].present? && (@user.auth_source_id.nil? || params[:user][:auth_source_id].blank?)
129 if params[:user][:password].present? && (@user.auth_source_id.nil? || params[:user][:auth_source_id].blank?)
130 @user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation]
130 @user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation]
131 end
131 end
132 @user.safe_attributes = params[:user]
132 @user.safe_attributes = params[:user]
133 # Was the account actived ? (do it before User#save clears the change)
133 # Was the account actived ? (do it before User#save clears the change)
134 was_activated = (@user.status_change == [User::STATUS_REGISTERED, User::STATUS_ACTIVE])
134 was_activated = (@user.status_change == [User::STATUS_REGISTERED, User::STATUS_ACTIVE])
135 # TODO: Similar to My#account
135 # TODO: Similar to My#account
136 @user.pref.safe_attributes = params[:pref]
136 @user.pref.safe_attributes = params[:pref]
137
137
138 if @user.save
138 if @user.save
139 @user.pref.save
139 @user.pref.save
140
140
141 if was_activated
141 if was_activated
142 Mailer.account_activated(@user).deliver
142 Mailer.account_activated(@user).deliver
143 elsif @user.active? && params[:send_information] && @user.password.present? && @user.auth_source_id.nil? && @user != User.current
143 elsif @user.active? && params[:send_information] && @user.password.present? && @user.auth_source_id.nil? && @user != User.current
144 Mailer.account_information(@user, @user.password).deliver
144 Mailer.account_information(@user, @user.password).deliver
145 end
145 end
146
146
147 respond_to do |format|
147 respond_to do |format|
148 format.html {
148 format.html {
149 flash[:notice] = l(:notice_successful_update)
149 flash[:notice] = l(:notice_successful_update)
150 redirect_to_referer_or edit_user_path(@user)
150 redirect_to_referer_or edit_user_path(@user)
151 }
151 }
152 format.api { render_api_ok }
152 format.api { render_api_ok }
153 end
153 end
154 else
154 else
155 @auth_sources = AuthSource.all
155 @auth_sources = AuthSource.all
156 @membership ||= Member.new
156 @membership ||= Member.new
157 # Clear password input
157 # Clear password input
158 @user.password = @user.password_confirmation = nil
158 @user.password = @user.password_confirmation = nil
159
159
160 respond_to do |format|
160 respond_to do |format|
161 format.html { render :action => :edit }
161 format.html { render :action => :edit }
162 format.api { render_validation_errors(@user) }
162 format.api { render_validation_errors(@user) }
163 end
163 end
164 end
164 end
165 end
165 end
166
166
167 def destroy
167 def destroy
168 @user.destroy
168 @user.destroy
169 respond_to do |format|
169 respond_to do |format|
170 format.html { redirect_back_or_default(users_path) }
170 format.html { redirect_back_or_default(users_path) }
171 format.api { render_api_ok }
171 format.api { render_api_ok }
172 end
172 end
173 end
173 end
174
174
175 private
175 private
176
176
177 def find_user
177 def find_user
178 if params[:id] == 'current'
178 if params[:id] == 'current'
179 require_login || return
179 require_login || return
180 @user = User.current
180 @user = User.current
181 else
181 else
182 @user = User.find(params[:id])
182 @user = User.find(params[:id])
183 end
183 end
184 rescue ActiveRecord::RecordNotFound
184 rescue ActiveRecord::RecordNotFound
185 render_404
185 render_404
186 end
186 end
187 end
187 end
@@ -1,206 +1,205
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
2 # Copyright (C) 2006-2016 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 Principal < ActiveRecord::Base
18 class Principal < ActiveRecord::Base
19 self.table_name = "#{table_name_prefix}users#{table_name_suffix}"
19 self.table_name = "#{table_name_prefix}users#{table_name_suffix}"
20
20
21 # Account statuses
21 # Account statuses
22 STATUS_ANONYMOUS = 0
22 STATUS_ANONYMOUS = 0
23 STATUS_ACTIVE = 1
23 STATUS_ACTIVE = 1
24 STATUS_REGISTERED = 2
24 STATUS_REGISTERED = 2
25 STATUS_LOCKED = 3
25 STATUS_LOCKED = 3
26
26
27 class_attribute :valid_statuses
27 class_attribute :valid_statuses
28
28
29 has_many :members, :foreign_key => 'user_id', :dependent => :destroy
29 has_many :members, :foreign_key => 'user_id', :dependent => :destroy
30 has_many :memberships,
30 has_many :memberships,
31 lambda {preload(:project, :roles).
31 lambda {joins(:project).
32 joins(:project).
33 where("#{Project.table_name}.status<>#{Project::STATUS_ARCHIVED}")},
32 where("#{Project.table_name}.status<>#{Project::STATUS_ARCHIVED}")},
34 :class_name => 'Member',
33 :class_name => 'Member',
35 :foreign_key => 'user_id'
34 :foreign_key => 'user_id'
36 has_many :projects, :through => :memberships
35 has_many :projects, :through => :memberships
37 has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
36 has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
38
37
39 validate :validate_status
38 validate :validate_status
40
39
41 # Groups and active users
40 # Groups and active users
42 scope :active, lambda { where(:status => STATUS_ACTIVE) }
41 scope :active, lambda { where(:status => STATUS_ACTIVE) }
43
42
44 scope :visible, lambda {|*args|
43 scope :visible, lambda {|*args|
45 user = args.first || User.current
44 user = args.first || User.current
46
45
47 if user.admin?
46 if user.admin?
48 all
47 all
49 else
48 else
50 view_all_active = false
49 view_all_active = false
51 if user.memberships.to_a.any?
50 if user.memberships.to_a.any?
52 view_all_active = user.memberships.any? {|m| m.roles.any? {|r| r.users_visibility == 'all'}}
51 view_all_active = user.memberships.any? {|m| m.roles.any? {|r| r.users_visibility == 'all'}}
53 else
52 else
54 view_all_active = user.builtin_role.users_visibility == 'all'
53 view_all_active = user.builtin_role.users_visibility == 'all'
55 end
54 end
56
55
57 if view_all_active
56 if view_all_active
58 active
57 active
59 else
58 else
60 # self and members of visible projects
59 # self and members of visible projects
61 active.where("#{table_name}.id = ? OR #{table_name}.id IN (SELECT user_id FROM #{Member.table_name} WHERE project_id IN (?))",
60 active.where("#{table_name}.id = ? OR #{table_name}.id IN (SELECT user_id FROM #{Member.table_name} WHERE project_id IN (?))",
62 user.id, user.visible_project_ids
61 user.id, user.visible_project_ids
63 )
62 )
64 end
63 end
65 end
64 end
66 }
65 }
67
66
68 scope :like, lambda {|q|
67 scope :like, lambda {|q|
69 q = q.to_s
68 q = q.to_s
70 if q.blank?
69 if q.blank?
71 where({})
70 where({})
72 else
71 else
73 pattern = "%#{q}%"
72 pattern = "%#{q}%"
74 sql = %w(login firstname lastname).map {|column| "LOWER(#{table_name}.#{column}) LIKE LOWER(:p)"}.join(" OR ")
73 sql = %w(login firstname lastname).map {|column| "LOWER(#{table_name}.#{column}) LIKE LOWER(:p)"}.join(" OR ")
75 sql << " OR #{table_name}.id IN (SELECT user_id FROM #{EmailAddress.table_name} WHERE LOWER(address) LIKE LOWER(:p))"
74 sql << " OR #{table_name}.id IN (SELECT user_id FROM #{EmailAddress.table_name} WHERE LOWER(address) LIKE LOWER(:p))"
76 params = {:p => pattern}
75 params = {:p => pattern}
77 if q =~ /^(.+)\s+(.+)$/
76 if q =~ /^(.+)\s+(.+)$/
78 a, b = "#{$1}%", "#{$2}%"
77 a, b = "#{$1}%", "#{$2}%"
79 sql << " OR (LOWER(#{table_name}.firstname) LIKE LOWER(:a) AND LOWER(#{table_name}.lastname) LIKE LOWER(:b))"
78 sql << " OR (LOWER(#{table_name}.firstname) LIKE LOWER(:a) AND LOWER(#{table_name}.lastname) LIKE LOWER(:b))"
80 sql << " OR (LOWER(#{table_name}.firstname) LIKE LOWER(:b) AND LOWER(#{table_name}.lastname) LIKE LOWER(:a))"
79 sql << " OR (LOWER(#{table_name}.firstname) LIKE LOWER(:b) AND LOWER(#{table_name}.lastname) LIKE LOWER(:a))"
81 params.merge!(:a => a, :b => b)
80 params.merge!(:a => a, :b => b)
82 end
81 end
83 where(sql, params)
82 where(sql, params)
84 end
83 end
85 }
84 }
86
85
87 # Principals that are members of a collection of projects
86 # Principals that are members of a collection of projects
88 scope :member_of, lambda {|projects|
87 scope :member_of, lambda {|projects|
89 projects = [projects] if projects.is_a?(Project)
88 projects = [projects] if projects.is_a?(Project)
90 if projects.blank?
89 if projects.blank?
91 where("1=0")
90 where("1=0")
92 else
91 else
93 ids = projects.map(&:id)
92 ids = projects.map(&:id)
94 active.where("#{Principal.table_name}.id IN (SELECT DISTINCT user_id FROM #{Member.table_name} WHERE project_id IN (?))", ids)
93 active.where("#{Principal.table_name}.id IN (SELECT DISTINCT user_id FROM #{Member.table_name} WHERE project_id IN (?))", ids)
95 end
94 end
96 }
95 }
97 # Principals that are not members of projects
96 # Principals that are not members of projects
98 scope :not_member_of, lambda {|projects|
97 scope :not_member_of, lambda {|projects|
99 projects = [projects] unless projects.is_a?(Array)
98 projects = [projects] unless projects.is_a?(Array)
100 if projects.empty?
99 if projects.empty?
101 where("1=0")
100 where("1=0")
102 else
101 else
103 ids = projects.map(&:id)
102 ids = projects.map(&:id)
104 where("#{Principal.table_name}.id NOT IN (SELECT DISTINCT user_id FROM #{Member.table_name} WHERE project_id IN (?))", ids)
103 where("#{Principal.table_name}.id NOT IN (SELECT DISTINCT user_id FROM #{Member.table_name} WHERE project_id IN (?))", ids)
105 end
104 end
106 }
105 }
107 scope :sorted, lambda { order(*Principal.fields_for_order_statement)}
106 scope :sorted, lambda { order(*Principal.fields_for_order_statement)}
108
107
109 before_create :set_default_empty_values
108 before_create :set_default_empty_values
110
109
111 def reload(*args)
110 def reload(*args)
112 @project_ids = nil
111 @project_ids = nil
113 super
112 super
114 end
113 end
115
114
116 def name(formatter = nil)
115 def name(formatter = nil)
117 to_s
116 to_s
118 end
117 end
119
118
120 def mail=(*args)
119 def mail=(*args)
121 nil
120 nil
122 end
121 end
123
122
124 def mail
123 def mail
125 nil
124 nil
126 end
125 end
127
126
128 def visible?(user=User.current)
127 def visible?(user=User.current)
129 Principal.visible(user).where(:id => id).first == self
128 Principal.visible(user).where(:id => id).first == self
130 end
129 end
131
130
132 # Returns true if the principal is a member of project
131 # Returns true if the principal is a member of project
133 def member_of?(project)
132 def member_of?(project)
134 project.is_a?(Project) && project_ids.include?(project.id)
133 project.is_a?(Project) && project_ids.include?(project.id)
135 end
134 end
136
135
137 # Returns an array of the project ids that the principal is a member of
136 # Returns an array of the project ids that the principal is a member of
138 def project_ids
137 def project_ids
139 @project_ids ||= super.freeze
138 @project_ids ||= super.freeze
140 end
139 end
141
140
142 def <=>(principal)
141 def <=>(principal)
143 if principal.nil?
142 if principal.nil?
144 -1
143 -1
145 elsif self.class.name == principal.class.name
144 elsif self.class.name == principal.class.name
146 self.to_s.casecmp(principal.to_s)
145 self.to_s.casecmp(principal.to_s)
147 else
146 else
148 # groups after users
147 # groups after users
149 principal.class.name <=> self.class.name
148 principal.class.name <=> self.class.name
150 end
149 end
151 end
150 end
152
151
153 # Returns an array of fields names than can be used to make an order statement for principals.
152 # Returns an array of fields names than can be used to make an order statement for principals.
154 # Users are sorted before Groups.
153 # Users are sorted before Groups.
155 # Examples:
154 # Examples:
156 def self.fields_for_order_statement(table=nil)
155 def self.fields_for_order_statement(table=nil)
157 table ||= table_name
156 table ||= table_name
158 columns = ['type DESC'] + (User.name_formatter[:order] - ['id']) + ['lastname', 'id']
157 columns = ['type DESC'] + (User.name_formatter[:order] - ['id']) + ['lastname', 'id']
159 columns.uniq.map {|field| "#{table}.#{field}"}
158 columns.uniq.map {|field| "#{table}.#{field}"}
160 end
159 end
161
160
162 # Returns the principal that matches the keyword among principals
161 # Returns the principal that matches the keyword among principals
163 def self.detect_by_keyword(principals, keyword)
162 def self.detect_by_keyword(principals, keyword)
164 keyword = keyword.to_s
163 keyword = keyword.to_s
165 return nil if keyword.blank?
164 return nil if keyword.blank?
166
165
167 principal = nil
166 principal = nil
168 principal ||= principals.detect {|a| keyword.casecmp(a.login.to_s) == 0}
167 principal ||= principals.detect {|a| keyword.casecmp(a.login.to_s) == 0}
169 principal ||= principals.detect {|a| keyword.casecmp(a.mail.to_s) == 0}
168 principal ||= principals.detect {|a| keyword.casecmp(a.mail.to_s) == 0}
170
169
171 if principal.nil? && keyword.match(/ /)
170 if principal.nil? && keyword.match(/ /)
172 firstname, lastname = *(keyword.split) # "First Last Throwaway"
171 firstname, lastname = *(keyword.split) # "First Last Throwaway"
173 principal ||= principals.detect {|a|
172 principal ||= principals.detect {|a|
174 a.is_a?(User) &&
173 a.is_a?(User) &&
175 firstname.casecmp(a.firstname.to_s) == 0 &&
174 firstname.casecmp(a.firstname.to_s) == 0 &&
176 lastname.casecmp(a.lastname.to_s) == 0
175 lastname.casecmp(a.lastname.to_s) == 0
177 }
176 }
178 end
177 end
179 if principal.nil?
178 if principal.nil?
180 principal ||= principals.detect {|a| keyword.casecmp(a.name) == 0}
179 principal ||= principals.detect {|a| keyword.casecmp(a.name) == 0}
181 end
180 end
182 principal
181 principal
183 end
182 end
184
183
185 protected
184 protected
186
185
187 # Make sure we don't try to insert NULL values (see #4632)
186 # Make sure we don't try to insert NULL values (see #4632)
188 def set_default_empty_values
187 def set_default_empty_values
189 self.login ||= ''
188 self.login ||= ''
190 self.hashed_password ||= ''
189 self.hashed_password ||= ''
191 self.firstname ||= ''
190 self.firstname ||= ''
192 self.lastname ||= ''
191 self.lastname ||= ''
193 true
192 true
194 end
193 end
195
194
196 def validate_status
195 def validate_status
197 if status_changed? && self.class.valid_statuses.present?
196 if status_changed? && self.class.valid_statuses.present?
198 unless self.class.valid_statuses.include?(status)
197 unless self.class.valid_statuses.include?(status)
199 errors.add :status, :invalid
198 errors.add :status, :invalid
200 end
199 end
201 end
200 end
202 end
201 end
203 end
202 end
204
203
205 require_dependency "user"
204 require_dependency "user"
206 require_dependency "group"
205 require_dependency "group"
@@ -1,948 +1,952
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
2 # Copyright (C) 2006-2016 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 # Different ways of displaying/sorting users
23 # Different ways of displaying/sorting users
24 USER_FORMATS = {
24 USER_FORMATS = {
25 :firstname_lastname => {
25 :firstname_lastname => {
26 :string => '#{firstname} #{lastname}',
26 :string => '#{firstname} #{lastname}',
27 :order => %w(firstname lastname id),
27 :order => %w(firstname lastname id),
28 :setting_order => 1
28 :setting_order => 1
29 },
29 },
30 :firstname_lastinitial => {
30 :firstname_lastinitial => {
31 :string => '#{firstname} #{lastname.to_s.chars.first}.',
31 :string => '#{firstname} #{lastname.to_s.chars.first}.',
32 :order => %w(firstname lastname id),
32 :order => %w(firstname lastname id),
33 :setting_order => 2
33 :setting_order => 2
34 },
34 },
35 :firstinitial_lastname => {
35 :firstinitial_lastname => {
36 :string => '#{firstname.to_s.gsub(/(([[:alpha:]])[[:alpha:]]*\.?)/, \'\2.\')} #{lastname}',
36 :string => '#{firstname.to_s.gsub(/(([[:alpha:]])[[:alpha:]]*\.?)/, \'\2.\')} #{lastname}',
37 :order => %w(firstname lastname id),
37 :order => %w(firstname lastname id),
38 :setting_order => 2
38 :setting_order => 2
39 },
39 },
40 :firstname => {
40 :firstname => {
41 :string => '#{firstname}',
41 :string => '#{firstname}',
42 :order => %w(firstname id),
42 :order => %w(firstname id),
43 :setting_order => 3
43 :setting_order => 3
44 },
44 },
45 :lastname_firstname => {
45 :lastname_firstname => {
46 :string => '#{lastname} #{firstname}',
46 :string => '#{lastname} #{firstname}',
47 :order => %w(lastname firstname id),
47 :order => %w(lastname firstname id),
48 :setting_order => 4
48 :setting_order => 4
49 },
49 },
50 :lastnamefirstname => {
50 :lastnamefirstname => {
51 :string => '#{lastname}#{firstname}',
51 :string => '#{lastname}#{firstname}',
52 :order => %w(lastname firstname id),
52 :order => %w(lastname firstname id),
53 :setting_order => 5
53 :setting_order => 5
54 },
54 },
55 :lastname_comma_firstname => {
55 :lastname_comma_firstname => {
56 :string => '#{lastname}, #{firstname}',
56 :string => '#{lastname}, #{firstname}',
57 :order => %w(lastname firstname id),
57 :order => %w(lastname firstname id),
58 :setting_order => 6
58 :setting_order => 6
59 },
59 },
60 :lastname => {
60 :lastname => {
61 :string => '#{lastname}',
61 :string => '#{lastname}',
62 :order => %w(lastname id),
62 :order => %w(lastname id),
63 :setting_order => 7
63 :setting_order => 7
64 },
64 },
65 :username => {
65 :username => {
66 :string => '#{login}',
66 :string => '#{login}',
67 :order => %w(login id),
67 :order => %w(login id),
68 :setting_order => 8
68 :setting_order => 8
69 },
69 },
70 }
70 }
71
71
72 MAIL_NOTIFICATION_OPTIONS = [
72 MAIL_NOTIFICATION_OPTIONS = [
73 ['all', :label_user_mail_option_all],
73 ['all', :label_user_mail_option_all],
74 ['selected', :label_user_mail_option_selected],
74 ['selected', :label_user_mail_option_selected],
75 ['only_my_events', :label_user_mail_option_only_my_events],
75 ['only_my_events', :label_user_mail_option_only_my_events],
76 ['only_assigned', :label_user_mail_option_only_assigned],
76 ['only_assigned', :label_user_mail_option_only_assigned],
77 ['only_owner', :label_user_mail_option_only_owner],
77 ['only_owner', :label_user_mail_option_only_owner],
78 ['none', :label_user_mail_option_none]
78 ['none', :label_user_mail_option_none]
79 ]
79 ]
80
80
81 has_and_belongs_to_many :groups,
81 has_and_belongs_to_many :groups,
82 :join_table => "#{table_name_prefix}groups_users#{table_name_suffix}",
82 :join_table => "#{table_name_prefix}groups_users#{table_name_suffix}",
83 :after_add => Proc.new {|user, group| group.user_added(user)},
83 :after_add => Proc.new {|user, group| group.user_added(user)},
84 :after_remove => Proc.new {|user, group| group.user_removed(user)}
84 :after_remove => Proc.new {|user, group| group.user_removed(user)}
85 has_many :changesets, :dependent => :nullify
85 has_many :changesets, :dependent => :nullify
86 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
86 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
87 has_one :rss_token, lambda {where "action='feeds'"}, :class_name => 'Token'
87 has_one :rss_token, lambda {where "action='feeds'"}, :class_name => 'Token'
88 has_one :api_token, lambda {where "action='api'"}, :class_name => 'Token'
88 has_one :api_token, lambda {where "action='api'"}, :class_name => 'Token'
89 has_one :email_address, lambda {where :is_default => true}, :autosave => true
89 has_one :email_address, lambda {where :is_default => true}, :autosave => true
90 has_many :email_addresses, :dependent => :delete_all
90 has_many :email_addresses, :dependent => :delete_all
91 belongs_to :auth_source
91 belongs_to :auth_source
92
92
93 scope :logged, lambda { where("#{User.table_name}.status <> #{STATUS_ANONYMOUS}") }
93 scope :logged, lambda { where("#{User.table_name}.status <> #{STATUS_ANONYMOUS}") }
94 scope :status, lambda {|arg| where(arg.blank? ? nil : {:status => arg.to_i}) }
94 scope :status, lambda {|arg| where(arg.blank? ? nil : {:status => arg.to_i}) }
95
95
96 acts_as_customizable
96 acts_as_customizable
97
97
98 attr_accessor :password, :password_confirmation, :generate_password
98 attr_accessor :password, :password_confirmation, :generate_password
99 attr_accessor :last_before_login_on
99 attr_accessor :last_before_login_on
100 attr_accessor :remote_ip
100 attr_accessor :remote_ip
101
101
102 # Prevents unauthorized assignments
102 # Prevents unauthorized assignments
103 attr_protected :password, :password_confirmation, :hashed_password
103 attr_protected :password, :password_confirmation, :hashed_password
104
104
105 LOGIN_LENGTH_LIMIT = 60
105 LOGIN_LENGTH_LIMIT = 60
106 MAIL_LENGTH_LIMIT = 60
106 MAIL_LENGTH_LIMIT = 60
107
107
108 validates_presence_of :login, :firstname, :lastname, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
108 validates_presence_of :login, :firstname, :lastname, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
109 validates_uniqueness_of :login, :if => Proc.new { |user| user.login_changed? && user.login.present? }, :case_sensitive => false
109 validates_uniqueness_of :login, :if => Proc.new { |user| user.login_changed? && user.login.present? }, :case_sensitive => false
110 # Login must contain letters, numbers, underscores only
110 # Login must contain letters, numbers, underscores only
111 validates_format_of :login, :with => /\A[a-z0-9_\-@\.]*\z/i
111 validates_format_of :login, :with => /\A[a-z0-9_\-@\.]*\z/i
112 validates_length_of :login, :maximum => LOGIN_LENGTH_LIMIT
112 validates_length_of :login, :maximum => LOGIN_LENGTH_LIMIT
113 validates_length_of :firstname, :lastname, :maximum => 30
113 validates_length_of :firstname, :lastname, :maximum => 30
114 validates_length_of :identity_url, maximum: 255
114 validates_length_of :identity_url, maximum: 255
115 validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true
115 validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true
116 validate :validate_password_length
116 validate :validate_password_length
117 validate do
117 validate do
118 if password_confirmation && password != password_confirmation
118 if password_confirmation && password != password_confirmation
119 errors.add(:password, :confirmation)
119 errors.add(:password, :confirmation)
120 end
120 end
121 end
121 end
122
122
123 self.valid_statuses = [STATUS_ACTIVE, STATUS_REGISTERED, STATUS_LOCKED]
123 self.valid_statuses = [STATUS_ACTIVE, STATUS_REGISTERED, STATUS_LOCKED]
124
124
125 before_validation :instantiate_email_address
125 before_validation :instantiate_email_address
126 before_create :set_mail_notification
126 before_create :set_mail_notification
127 before_save :generate_password_if_needed, :update_hashed_password
127 before_save :generate_password_if_needed, :update_hashed_password
128 before_destroy :remove_references_before_destroy
128 before_destroy :remove_references_before_destroy
129 after_save :update_notified_project_ids, :destroy_tokens, :deliver_security_notification
129 after_save :update_notified_project_ids, :destroy_tokens, :deliver_security_notification
130 after_destroy :deliver_security_notification
130 after_destroy :deliver_security_notification
131
131
132 scope :in_group, lambda {|group|
132 scope :in_group, lambda {|group|
133 group_id = group.is_a?(Group) ? group.id : group.to_i
133 group_id = group.is_a?(Group) ? group.id : group.to_i
134 where("#{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)
134 where("#{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)
135 }
135 }
136 scope :not_in_group, lambda {|group|
136 scope :not_in_group, lambda {|group|
137 group_id = group.is_a?(Group) ? group.id : group.to_i
137 group_id = group.is_a?(Group) ? group.id : group.to_i
138 where("#{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)
138 where("#{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)
139 }
139 }
140 scope :sorted, lambda { order(*User.fields_for_order_statement)}
140 scope :sorted, lambda { order(*User.fields_for_order_statement)}
141 scope :having_mail, lambda {|arg|
141 scope :having_mail, lambda {|arg|
142 addresses = Array.wrap(arg).map {|a| a.to_s.downcase}
142 addresses = Array.wrap(arg).map {|a| a.to_s.downcase}
143 if addresses.any?
143 if addresses.any?
144 joins(:email_addresses).where("LOWER(#{EmailAddress.table_name}.address) IN (?)", addresses).distinct
144 joins(:email_addresses).where("LOWER(#{EmailAddress.table_name}.address) IN (?)", addresses).distinct
145 else
145 else
146 none
146 none
147 end
147 end
148 }
148 }
149
149
150 def set_mail_notification
150 def set_mail_notification
151 self.mail_notification = Setting.default_notification_option if self.mail_notification.blank?
151 self.mail_notification = Setting.default_notification_option if self.mail_notification.blank?
152 true
152 true
153 end
153 end
154
154
155 def update_hashed_password
155 def update_hashed_password
156 # update hashed_password if password was set
156 # update hashed_password if password was set
157 if self.password && self.auth_source_id.blank?
157 if self.password && self.auth_source_id.blank?
158 salt_password(password)
158 salt_password(password)
159 end
159 end
160 end
160 end
161
161
162 alias :base_reload :reload
162 alias :base_reload :reload
163 def reload(*args)
163 def reload(*args)
164 @name = nil
164 @name = nil
165 @projects_by_role = nil
165 @projects_by_role = nil
166 @project_ids_by_role = nil
166 @project_ids_by_role = nil
167 @membership_by_project_id = nil
167 @membership_by_project_id = nil
168 @notified_projects_ids = nil
168 @notified_projects_ids = nil
169 @notified_projects_ids_changed = false
169 @notified_projects_ids_changed = false
170 @builtin_role = nil
170 @builtin_role = nil
171 @visible_project_ids = nil
171 @visible_project_ids = nil
172 @managed_roles = nil
172 @managed_roles = nil
173 base_reload(*args)
173 base_reload(*args)
174 end
174 end
175
175
176 def mail
176 def mail
177 email_address.try(:address)
177 email_address.try(:address)
178 end
178 end
179
179
180 def mail=(arg)
180 def mail=(arg)
181 email = email_address || build_email_address
181 email = email_address || build_email_address
182 email.address = arg
182 email.address = arg
183 end
183 end
184
184
185 def mail_changed?
185 def mail_changed?
186 email_address.try(:address_changed?)
186 email_address.try(:address_changed?)
187 end
187 end
188
188
189 def mails
189 def mails
190 email_addresses.pluck(:address)
190 email_addresses.pluck(:address)
191 end
191 end
192
192
193 def self.find_or_initialize_by_identity_url(url)
193 def self.find_or_initialize_by_identity_url(url)
194 user = where(:identity_url => url).first
194 user = where(:identity_url => url).first
195 unless user
195 unless user
196 user = User.new
196 user = User.new
197 user.identity_url = url
197 user.identity_url = url
198 end
198 end
199 user
199 user
200 end
200 end
201
201
202 def identity_url=(url)
202 def identity_url=(url)
203 if url.blank?
203 if url.blank?
204 write_attribute(:identity_url, '')
204 write_attribute(:identity_url, '')
205 else
205 else
206 begin
206 begin
207 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
207 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
208 rescue OpenIdAuthentication::InvalidOpenId
208 rescue OpenIdAuthentication::InvalidOpenId
209 # Invalid url, don't save
209 # Invalid url, don't save
210 end
210 end
211 end
211 end
212 self.read_attribute(:identity_url)
212 self.read_attribute(:identity_url)
213 end
213 end
214
214
215 # Returns the user that matches provided login and password, or nil
215 # Returns the user that matches provided login and password, or nil
216 def self.try_to_login(login, password, active_only=true)
216 def self.try_to_login(login, password, active_only=true)
217 login = login.to_s
217 login = login.to_s
218 password = password.to_s
218 password = password.to_s
219
219
220 # Make sure no one can sign in with an empty login or password
220 # Make sure no one can sign in with an empty login or password
221 return nil if login.empty? || password.empty?
221 return nil if login.empty? || password.empty?
222 user = find_by_login(login)
222 user = find_by_login(login)
223 if user
223 if user
224 # user is already in local database
224 # user is already in local database
225 return nil unless user.check_password?(password)
225 return nil unless user.check_password?(password)
226 return nil if !user.active? && active_only
226 return nil if !user.active? && active_only
227 else
227 else
228 # user is not yet registered, try to authenticate with available sources
228 # user is not yet registered, try to authenticate with available sources
229 attrs = AuthSource.authenticate(login, password)
229 attrs = AuthSource.authenticate(login, password)
230 if attrs
230 if attrs
231 user = new(attrs)
231 user = new(attrs)
232 user.login = login
232 user.login = login
233 user.language = Setting.default_language
233 user.language = Setting.default_language
234 if user.save
234 if user.save
235 user.reload
235 user.reload
236 logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source
236 logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source
237 end
237 end
238 end
238 end
239 end
239 end
240 user.update_column(:last_login_on, Time.now) if user && !user.new_record? && user.active?
240 user.update_column(:last_login_on, Time.now) if user && !user.new_record? && user.active?
241 user
241 user
242 rescue => text
242 rescue => text
243 raise text
243 raise text
244 end
244 end
245
245
246 # Returns the user who matches the given autologin +key+ or nil
246 # Returns the user who matches the given autologin +key+ or nil
247 def self.try_to_autologin(key)
247 def self.try_to_autologin(key)
248 user = Token.find_active_user('autologin', key, Setting.autologin.to_i)
248 user = Token.find_active_user('autologin', key, Setting.autologin.to_i)
249 if user
249 if user
250 user.update_column(:last_login_on, Time.now)
250 user.update_column(:last_login_on, Time.now)
251 user
251 user
252 end
252 end
253 end
253 end
254
254
255 def self.name_formatter(formatter = nil)
255 def self.name_formatter(formatter = nil)
256 USER_FORMATS[formatter || Setting.user_format] || USER_FORMATS[:firstname_lastname]
256 USER_FORMATS[formatter || Setting.user_format] || USER_FORMATS[:firstname_lastname]
257 end
257 end
258
258
259 # Returns an array of fields names than can be used to make an order statement for users
259 # Returns an array of fields names than can be used to make an order statement for users
260 # according to how user names are displayed
260 # according to how user names are displayed
261 # Examples:
261 # Examples:
262 #
262 #
263 # User.fields_for_order_statement => ['users.login', 'users.id']
263 # User.fields_for_order_statement => ['users.login', 'users.id']
264 # User.fields_for_order_statement('authors') => ['authors.login', 'authors.id']
264 # User.fields_for_order_statement('authors') => ['authors.login', 'authors.id']
265 def self.fields_for_order_statement(table=nil)
265 def self.fields_for_order_statement(table=nil)
266 table ||= table_name
266 table ||= table_name
267 name_formatter[:order].map {|field| "#{table}.#{field}"}
267 name_formatter[:order].map {|field| "#{table}.#{field}"}
268 end
268 end
269
269
270 # Return user's full name for display
270 # Return user's full name for display
271 def name(formatter = nil)
271 def name(formatter = nil)
272 f = self.class.name_formatter(formatter)
272 f = self.class.name_formatter(formatter)
273 if formatter
273 if formatter
274 eval('"' + f[:string] + '"')
274 eval('"' + f[:string] + '"')
275 else
275 else
276 @name ||= eval('"' + f[:string] + '"')
276 @name ||= eval('"' + f[:string] + '"')
277 end
277 end
278 end
278 end
279
279
280 def active?
280 def active?
281 self.status == STATUS_ACTIVE
281 self.status == STATUS_ACTIVE
282 end
282 end
283
283
284 def registered?
284 def registered?
285 self.status == STATUS_REGISTERED
285 self.status == STATUS_REGISTERED
286 end
286 end
287
287
288 def locked?
288 def locked?
289 self.status == STATUS_LOCKED
289 self.status == STATUS_LOCKED
290 end
290 end
291
291
292 def activate
292 def activate
293 self.status = STATUS_ACTIVE
293 self.status = STATUS_ACTIVE
294 end
294 end
295
295
296 def register
296 def register
297 self.status = STATUS_REGISTERED
297 self.status = STATUS_REGISTERED
298 end
298 end
299
299
300 def lock
300 def lock
301 self.status = STATUS_LOCKED
301 self.status = STATUS_LOCKED
302 end
302 end
303
303
304 def activate!
304 def activate!
305 update_attribute(:status, STATUS_ACTIVE)
305 update_attribute(:status, STATUS_ACTIVE)
306 end
306 end
307
307
308 def register!
308 def register!
309 update_attribute(:status, STATUS_REGISTERED)
309 update_attribute(:status, STATUS_REGISTERED)
310 end
310 end
311
311
312 def lock!
312 def lock!
313 update_attribute(:status, STATUS_LOCKED)
313 update_attribute(:status, STATUS_LOCKED)
314 end
314 end
315
315
316 # Returns true if +clear_password+ is the correct user's password, otherwise false
316 # Returns true if +clear_password+ is the correct user's password, otherwise false
317 def check_password?(clear_password)
317 def check_password?(clear_password)
318 if auth_source_id.present?
318 if auth_source_id.present?
319 auth_source.authenticate(self.login, clear_password)
319 auth_source.authenticate(self.login, clear_password)
320 else
320 else
321 User.hash_password("#{salt}#{User.hash_password clear_password}") == hashed_password
321 User.hash_password("#{salt}#{User.hash_password clear_password}") == hashed_password
322 end
322 end
323 end
323 end
324
324
325 # Generates a random salt and computes hashed_password for +clear_password+
325 # Generates a random salt and computes hashed_password for +clear_password+
326 # The hashed password is stored in the following form: SHA1(salt + SHA1(password))
326 # The hashed password is stored in the following form: SHA1(salt + SHA1(password))
327 def salt_password(clear_password)
327 def salt_password(clear_password)
328 self.salt = User.generate_salt
328 self.salt = User.generate_salt
329 self.hashed_password = User.hash_password("#{salt}#{User.hash_password clear_password}")
329 self.hashed_password = User.hash_password("#{salt}#{User.hash_password clear_password}")
330 self.passwd_changed_on = Time.now.change(:usec => 0)
330 self.passwd_changed_on = Time.now.change(:usec => 0)
331 end
331 end
332
332
333 # Does the backend storage allow this user to change their password?
333 # Does the backend storage allow this user to change their password?
334 def change_password_allowed?
334 def change_password_allowed?
335 return true if auth_source.nil?
335 return true if auth_source.nil?
336 return auth_source.allow_password_changes?
336 return auth_source.allow_password_changes?
337 end
337 end
338
338
339 # Returns true if the user password has expired
339 # Returns true if the user password has expired
340 def password_expired?
340 def password_expired?
341 period = Setting.password_max_age.to_i
341 period = Setting.password_max_age.to_i
342 if period.zero?
342 if period.zero?
343 false
343 false
344 else
344 else
345 changed_on = self.passwd_changed_on || Time.at(0)
345 changed_on = self.passwd_changed_on || Time.at(0)
346 changed_on < period.days.ago
346 changed_on < period.days.ago
347 end
347 end
348 end
348 end
349
349
350 def must_change_password?
350 def must_change_password?
351 (must_change_passwd? || password_expired?) && change_password_allowed?
351 (must_change_passwd? || password_expired?) && change_password_allowed?
352 end
352 end
353
353
354 def generate_password?
354 def generate_password?
355 generate_password == '1' || generate_password == true
355 generate_password == '1' || generate_password == true
356 end
356 end
357
357
358 # Generate and set a random password on given length
358 # Generate and set a random password on given length
359 def random_password(length=40)
359 def random_password(length=40)
360 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
360 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
361 chars -= %w(0 O 1 l)
361 chars -= %w(0 O 1 l)
362 password = ''
362 password = ''
363 length.times {|i| password << chars[SecureRandom.random_number(chars.size)] }
363 length.times {|i| password << chars[SecureRandom.random_number(chars.size)] }
364 self.password = password
364 self.password = password
365 self.password_confirmation = password
365 self.password_confirmation = password
366 self
366 self
367 end
367 end
368
368
369 def pref
369 def pref
370 self.preference ||= UserPreference.new(:user => self)
370 self.preference ||= UserPreference.new(:user => self)
371 end
371 end
372
372
373 def time_zone
373 def time_zone
374 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
374 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
375 end
375 end
376
376
377 def force_default_language?
377 def force_default_language?
378 Setting.force_default_language_for_loggedin?
378 Setting.force_default_language_for_loggedin?
379 end
379 end
380
380
381 def language
381 def language
382 if force_default_language?
382 if force_default_language?
383 Setting.default_language
383 Setting.default_language
384 else
384 else
385 super
385 super
386 end
386 end
387 end
387 end
388
388
389 def wants_comments_in_reverse_order?
389 def wants_comments_in_reverse_order?
390 self.pref[:comments_sorting] == 'desc'
390 self.pref[:comments_sorting] == 'desc'
391 end
391 end
392
392
393 # Return user's RSS key (a 40 chars long string), used to access feeds
393 # Return user's RSS key (a 40 chars long string), used to access feeds
394 def rss_key
394 def rss_key
395 if rss_token.nil?
395 if rss_token.nil?
396 create_rss_token(:action => 'feeds')
396 create_rss_token(:action => 'feeds')
397 end
397 end
398 rss_token.value
398 rss_token.value
399 end
399 end
400
400
401 # Return user's API key (a 40 chars long string), used to access the API
401 # Return user's API key (a 40 chars long string), used to access the API
402 def api_key
402 def api_key
403 if api_token.nil?
403 if api_token.nil?
404 create_api_token(:action => 'api')
404 create_api_token(:action => 'api')
405 end
405 end
406 api_token.value
406 api_token.value
407 end
407 end
408
408
409 # Generates a new session token and returns its value
409 # Generates a new session token and returns its value
410 def generate_session_token
410 def generate_session_token
411 token = Token.create!(:user_id => id, :action => 'session')
411 token = Token.create!(:user_id => id, :action => 'session')
412 token.value
412 token.value
413 end
413 end
414
414
415 # Returns true if token is a valid session token for the user whose id is user_id
415 # Returns true if token is a valid session token for the user whose id is user_id
416 def self.verify_session_token(user_id, token)
416 def self.verify_session_token(user_id, token)
417 return false if user_id.blank? || token.blank?
417 return false if user_id.blank? || token.blank?
418
418
419 scope = Token.where(:user_id => user_id, :value => token.to_s, :action => 'session')
419 scope = Token.where(:user_id => user_id, :value => token.to_s, :action => 'session')
420 if Setting.session_lifetime?
420 if Setting.session_lifetime?
421 scope = scope.where("created_on > ?", Setting.session_lifetime.to_i.minutes.ago)
421 scope = scope.where("created_on > ?", Setting.session_lifetime.to_i.minutes.ago)
422 end
422 end
423 if Setting.session_timeout?
423 if Setting.session_timeout?
424 scope = scope.where("updated_on > ?", Setting.session_timeout.to_i.minutes.ago)
424 scope = scope.where("updated_on > ?", Setting.session_timeout.to_i.minutes.ago)
425 end
425 end
426 scope.update_all(:updated_on => Time.now) == 1
426 scope.update_all(:updated_on => Time.now) == 1
427 end
427 end
428
428
429 # Return an array of project ids for which the user has explicitly turned mail notifications on
429 # Return an array of project ids for which the user has explicitly turned mail notifications on
430 def notified_projects_ids
430 def notified_projects_ids
431 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
431 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
432 end
432 end
433
433
434 def notified_project_ids=(ids)
434 def notified_project_ids=(ids)
435 @notified_projects_ids_changed = true
435 @notified_projects_ids_changed = true
436 @notified_projects_ids = ids.map(&:to_i).uniq.select {|n| n > 0}
436 @notified_projects_ids = ids.map(&:to_i).uniq.select {|n| n > 0}
437 end
437 end
438
438
439 # Updates per project notifications (after_save callback)
439 # Updates per project notifications (after_save callback)
440 def update_notified_project_ids
440 def update_notified_project_ids
441 if @notified_projects_ids_changed
441 if @notified_projects_ids_changed
442 ids = (mail_notification == 'selected' ? Array.wrap(notified_projects_ids).reject(&:blank?) : [])
442 ids = (mail_notification == 'selected' ? Array.wrap(notified_projects_ids).reject(&:blank?) : [])
443 members.update_all(:mail_notification => false)
443 members.update_all(:mail_notification => false)
444 members.where(:project_id => ids).update_all(:mail_notification => true) if ids.any?
444 members.where(:project_id => ids).update_all(:mail_notification => true) if ids.any?
445 end
445 end
446 end
446 end
447 private :update_notified_project_ids
447 private :update_notified_project_ids
448
448
449 def valid_notification_options
449 def valid_notification_options
450 self.class.valid_notification_options(self)
450 self.class.valid_notification_options(self)
451 end
451 end
452
452
453 # Only users that belong to more than 1 project can select projects for which they are notified
453 # Only users that belong to more than 1 project can select projects for which they are notified
454 def self.valid_notification_options(user=nil)
454 def self.valid_notification_options(user=nil)
455 # Note that @user.membership.size would fail since AR ignores
455 # Note that @user.membership.size would fail since AR ignores
456 # :include association option when doing a count
456 # :include association option when doing a count
457 if user.nil? || user.memberships.length < 1
457 if user.nil? || user.memberships.length < 1
458 MAIL_NOTIFICATION_OPTIONS.reject {|option| option.first == 'selected'}
458 MAIL_NOTIFICATION_OPTIONS.reject {|option| option.first == 'selected'}
459 else
459 else
460 MAIL_NOTIFICATION_OPTIONS
460 MAIL_NOTIFICATION_OPTIONS
461 end
461 end
462 end
462 end
463
463
464 # Find a user account by matching the exact login and then a case-insensitive
464 # Find a user account by matching the exact login and then a case-insensitive
465 # version. Exact matches will be given priority.
465 # version. Exact matches will be given priority.
466 def self.find_by_login(login)
466 def self.find_by_login(login)
467 login = Redmine::CodesetUtil.replace_invalid_utf8(login.to_s)
467 login = Redmine::CodesetUtil.replace_invalid_utf8(login.to_s)
468 if login.present?
468 if login.present?
469 # First look for an exact match
469 # First look for an exact match
470 user = where(:login => login).detect {|u| u.login == login}
470 user = where(:login => login).detect {|u| u.login == login}
471 unless user
471 unless user
472 # Fail over to case-insensitive if none was found
472 # Fail over to case-insensitive if none was found
473 user = where("LOWER(login) = ?", login.downcase).first
473 user = where("LOWER(login) = ?", login.downcase).first
474 end
474 end
475 user
475 user
476 end
476 end
477 end
477 end
478
478
479 def self.find_by_rss_key(key)
479 def self.find_by_rss_key(key)
480 Token.find_active_user('feeds', key)
480 Token.find_active_user('feeds', key)
481 end
481 end
482
482
483 def self.find_by_api_key(key)
483 def self.find_by_api_key(key)
484 Token.find_active_user('api', key)
484 Token.find_active_user('api', key)
485 end
485 end
486
486
487 # Makes find_by_mail case-insensitive
487 # Makes find_by_mail case-insensitive
488 def self.find_by_mail(mail)
488 def self.find_by_mail(mail)
489 having_mail(mail).first
489 having_mail(mail).first
490 end
490 end
491
491
492 # Returns true if the default admin account can no longer be used
492 # Returns true if the default admin account can no longer be used
493 def self.default_admin_account_changed?
493 def self.default_admin_account_changed?
494 !User.active.find_by_login("admin").try(:check_password?, "admin")
494 !User.active.find_by_login("admin").try(:check_password?, "admin")
495 end
495 end
496
496
497 def to_s
497 def to_s
498 name
498 name
499 end
499 end
500
500
501 CSS_CLASS_BY_STATUS = {
501 CSS_CLASS_BY_STATUS = {
502 STATUS_ANONYMOUS => 'anon',
502 STATUS_ANONYMOUS => 'anon',
503 STATUS_ACTIVE => 'active',
503 STATUS_ACTIVE => 'active',
504 STATUS_REGISTERED => 'registered',
504 STATUS_REGISTERED => 'registered',
505 STATUS_LOCKED => 'locked'
505 STATUS_LOCKED => 'locked'
506 }
506 }
507
507
508 def css_classes
508 def css_classes
509 "user #{CSS_CLASS_BY_STATUS[status]}"
509 "user #{CSS_CLASS_BY_STATUS[status]}"
510 end
510 end
511
511
512 # Returns the current day according to user's time zone
512 # Returns the current day according to user's time zone
513 def today
513 def today
514 if time_zone.nil?
514 if time_zone.nil?
515 Date.today
515 Date.today
516 else
516 else
517 time_zone.today
517 time_zone.today
518 end
518 end
519 end
519 end
520
520
521 # Returns the day of +time+ according to user's time zone
521 # Returns the day of +time+ according to user's time zone
522 def time_to_date(time)
522 def time_to_date(time)
523 if time_zone.nil?
523 if time_zone.nil?
524 time.to_date
524 time.to_date
525 else
525 else
526 time.in_time_zone(time_zone).to_date
526 time.in_time_zone(time_zone).to_date
527 end
527 end
528 end
528 end
529
529
530 def logged?
530 def logged?
531 true
531 true
532 end
532 end
533
533
534 def anonymous?
534 def anonymous?
535 !logged?
535 !logged?
536 end
536 end
537
537
538 # Returns user's membership for the given project
538 # Returns user's membership for the given project
539 # or nil if the user is not a member of project
539 # or nil if the user is not a member of project
540 def membership(project)
540 def membership(project)
541 project_id = project.is_a?(Project) ? project.id : project
541 project_id = project.is_a?(Project) ? project.id : project
542
542
543 @membership_by_project_id ||= Hash.new {|h, project_id|
543 @membership_by_project_id ||= Hash.new {|h, project_id|
544 h[project_id] = memberships.where(:project_id => project_id).first
544 h[project_id] = memberships.where(:project_id => project_id).first
545 }
545 }
546 @membership_by_project_id[project_id]
546 @membership_by_project_id[project_id]
547 end
547 end
548
548
549 def roles
550 @roles ||= Role.joins(members: :project).where(["#{Project.table_name}.status <> ?", Project::STATUS_ARCHIVED]).where(Member.arel_table[:user_id].eq(id)).uniq
551 end
552
549 # Returns the user's bult-in role
553 # Returns the user's bult-in role
550 def builtin_role
554 def builtin_role
551 @builtin_role ||= Role.non_member
555 @builtin_role ||= Role.non_member
552 end
556 end
553
557
554 # Return user's roles for project
558 # Return user's roles for project
555 def roles_for_project(project)
559 def roles_for_project(project)
556 # No role on archived projects
560 # No role on archived projects
557 return [] if project.nil? || project.archived?
561 return [] if project.nil? || project.archived?
558 if membership = membership(project)
562 if membership = membership(project)
559 membership.roles.to_a
563 membership.roles.to_a
560 elsif project.is_public?
564 elsif project.is_public?
561 project.override_roles(builtin_role)
565 project.override_roles(builtin_role)
562 else
566 else
563 []
567 []
564 end
568 end
565 end
569 end
566
570
567 # Returns a hash of user's projects grouped by roles
571 # Returns a hash of user's projects grouped by roles
568 # TODO: No longer used, should be deprecated
572 # TODO: No longer used, should be deprecated
569 def projects_by_role
573 def projects_by_role
570 return @projects_by_role if @projects_by_role
574 return @projects_by_role if @projects_by_role
571
575
572 result = Hash.new([])
576 result = Hash.new([])
573 project_ids_by_role.each do |role, ids|
577 project_ids_by_role.each do |role, ids|
574 result[role] = Project.where(:id => ids).to_a
578 result[role] = Project.where(:id => ids).to_a
575 end
579 end
576 @projects_by_role = result
580 @projects_by_role = result
577 end
581 end
578
582
579 # Returns a hash of project ids grouped by roles.
583 # Returns a hash of project ids grouped by roles.
580 # Includes the projects that the user is a member of and the projects
584 # Includes the projects that the user is a member of and the projects
581 # that grant custom permissions to the builtin groups.
585 # that grant custom permissions to the builtin groups.
582 def project_ids_by_role
586 def project_ids_by_role
583 return @project_ids_by_role if @project_ids_by_role
587 return @project_ids_by_role if @project_ids_by_role
584
588
585 group_class = anonymous? ? GroupAnonymous : GroupNonMember
589 group_class = anonymous? ? GroupAnonymous : GroupNonMember
586 group_id = group_class.pluck(:id).first
590 group_id = group_class.pluck(:id).first
587
591
588 members = Member.joins(:project, :member_roles).
592 members = Member.joins(:project, :member_roles).
589 where("#{Project.table_name}.status <> 9").
593 where("#{Project.table_name}.status <> 9").
590 where("#{Member.table_name}.user_id = ? OR (#{Project.table_name}.is_public = ? AND #{Member.table_name}.user_id = ?)", self.id, true, group_id).
594 where("#{Member.table_name}.user_id = ? OR (#{Project.table_name}.is_public = ? AND #{Member.table_name}.user_id = ?)", self.id, true, group_id).
591 pluck(:user_id, :role_id, :project_id)
595 pluck(:user_id, :role_id, :project_id)
592
596
593 hash = {}
597 hash = {}
594 members.each do |user_id, role_id, project_id|
598 members.each do |user_id, role_id, project_id|
595 # Ignore the roles of the builtin group if the user is a member of the project
599 # Ignore the roles of the builtin group if the user is a member of the project
596 next if user_id != id && project_ids.include?(project_id)
600 next if user_id != id && project_ids.include?(project_id)
597
601
598 hash[role_id] ||= []
602 hash[role_id] ||= []
599 hash[role_id] << project_id
603 hash[role_id] << project_id
600 end
604 end
601
605
602 result = Hash.new([])
606 result = Hash.new([])
603 if hash.present?
607 if hash.present?
604 roles = Role.where(:id => hash.keys).to_a
608 roles = Role.where(:id => hash.keys).to_a
605 hash.each do |role_id, proj_ids|
609 hash.each do |role_id, proj_ids|
606 role = roles.detect {|r| r.id == role_id}
610 role = roles.detect {|r| r.id == role_id}
607 if role
611 if role
608 result[role] = proj_ids.uniq
612 result[role] = proj_ids.uniq
609 end
613 end
610 end
614 end
611 end
615 end
612 @project_ids_by_role = result
616 @project_ids_by_role = result
613 end
617 end
614
618
615 # Returns the ids of visible projects
619 # Returns the ids of visible projects
616 def visible_project_ids
620 def visible_project_ids
617 @visible_project_ids ||= Project.visible(self).pluck(:id)
621 @visible_project_ids ||= Project.visible(self).pluck(:id)
618 end
622 end
619
623
620 # Returns the roles that the user is allowed to manage for the given project
624 # Returns the roles that the user is allowed to manage for the given project
621 def managed_roles(project)
625 def managed_roles(project)
622 if admin?
626 if admin?
623 @managed_roles ||= Role.givable.to_a
627 @managed_roles ||= Role.givable.to_a
624 else
628 else
625 membership(project).try(:managed_roles) || []
629 membership(project).try(:managed_roles) || []
626 end
630 end
627 end
631 end
628
632
629 # Returns true if user is arg or belongs to arg
633 # Returns true if user is arg or belongs to arg
630 def is_or_belongs_to?(arg)
634 def is_or_belongs_to?(arg)
631 if arg.is_a?(User)
635 if arg.is_a?(User)
632 self == arg
636 self == arg
633 elsif arg.is_a?(Group)
637 elsif arg.is_a?(Group)
634 arg.users.include?(self)
638 arg.users.include?(self)
635 else
639 else
636 false
640 false
637 end
641 end
638 end
642 end
639
643
640 # Return true if the user is allowed to do the specified action on a specific context
644 # Return true if the user is allowed to do the specified action on a specific context
641 # Action can be:
645 # Action can be:
642 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
646 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
643 # * a permission Symbol (eg. :edit_project)
647 # * a permission Symbol (eg. :edit_project)
644 # Context can be:
648 # Context can be:
645 # * a project : returns true if user is allowed to do the specified action on this project
649 # * a project : returns true if user is allowed to do the specified action on this project
646 # * an array of projects : returns true if user is allowed on every project
650 # * an array of projects : returns true if user is allowed on every project
647 # * nil with options[:global] set : check if user has at least one role allowed for this action,
651 # * nil with options[:global] set : check if user has at least one role allowed for this action,
648 # or falls back to Non Member / Anonymous permissions depending if the user is logged
652 # or falls back to Non Member / Anonymous permissions depending if the user is logged
649 def allowed_to?(action, context, options={}, &block)
653 def allowed_to?(action, context, options={}, &block)
650 if context && context.is_a?(Project)
654 if context && context.is_a?(Project)
651 return false unless context.allows_to?(action)
655 return false unless context.allows_to?(action)
652 # Admin users are authorized for anything else
656 # Admin users are authorized for anything else
653 return true if admin?
657 return true if admin?
654
658
655 roles = roles_for_project(context)
659 roles = roles_for_project(context)
656 return false unless roles
660 return false unless roles
657 roles.any? {|role|
661 roles.any? {|role|
658 (context.is_public? || role.member?) &&
662 (context.is_public? || role.member?) &&
659 role.allowed_to?(action) &&
663 role.allowed_to?(action) &&
660 (block_given? ? yield(role, self) : true)
664 (block_given? ? yield(role, self) : true)
661 }
665 }
662 elsif context && context.is_a?(Array)
666 elsif context && context.is_a?(Array)
663 if context.empty?
667 if context.empty?
664 false
668 false
665 else
669 else
666 # Authorize if user is authorized on every element of the array
670 # Authorize if user is authorized on every element of the array
667 context.map {|project| allowed_to?(action, project, options, &block)}.reduce(:&)
671 context.map {|project| allowed_to?(action, project, options, &block)}.reduce(:&)
668 end
672 end
669 elsif context
673 elsif context
670 raise ArgumentError.new("#allowed_to? context argument must be a Project, an Array of projects or nil")
674 raise ArgumentError.new("#allowed_to? context argument must be a Project, an Array of projects or nil")
671 elsif options[:global]
675 elsif options[:global]
672 # Admin users are always authorized
676 # Admin users are always authorized
673 return true if admin?
677 return true if admin?
674
678
675 # authorize if user has at least one role that has this permission
679 # authorize if user has at least one role that has this permission
676 roles = memberships.collect {|m| m.roles}.flatten.uniq
680 rls = self.roles.to_a
677 roles << (self.logged? ? Role.non_member : Role.anonymous)
681 rls << builtin_role
678 roles.any? {|role|
682 rls.any? {|role|
679 role.allowed_to?(action) &&
683 role.allowed_to?(action) &&
680 (block_given? ? yield(role, self) : true)
684 (block_given? ? yield(role, self) : true)
681 }
685 }
682 else
686 else
683 false
687 false
684 end
688 end
685 end
689 end
686
690
687 # Is the user allowed to do the specified action on any project?
691 # Is the user allowed to do the specified action on any project?
688 # See allowed_to? for the actions and valid options.
692 # See allowed_to? for the actions and valid options.
689 #
693 #
690 # NB: this method is not used anywhere in the core codebase as of
694 # NB: this method is not used anywhere in the core codebase as of
691 # 2.5.2, but it's used by many plugins so if we ever want to remove
695 # 2.5.2, but it's used by many plugins so if we ever want to remove
692 # it it has to be carefully deprecated for a version or two.
696 # it it has to be carefully deprecated for a version or two.
693 def allowed_to_globally?(action, options={}, &block)
697 def allowed_to_globally?(action, options={}, &block)
694 allowed_to?(action, nil, options.reverse_merge(:global => true), &block)
698 allowed_to?(action, nil, options.reverse_merge(:global => true), &block)
695 end
699 end
696
700
697 def allowed_to_view_all_time_entries?(context)
701 def allowed_to_view_all_time_entries?(context)
698 allowed_to?(:view_time_entries, context) do |role, user|
702 allowed_to?(:view_time_entries, context) do |role, user|
699 role.time_entries_visibility == 'all'
703 role.time_entries_visibility == 'all'
700 end
704 end
701 end
705 end
702
706
703 # Returns true if the user is allowed to delete the user's own account
707 # Returns true if the user is allowed to delete the user's own account
704 def own_account_deletable?
708 def own_account_deletable?
705 Setting.unsubscribe? &&
709 Setting.unsubscribe? &&
706 (!admin? || User.active.where("admin = ? AND id <> ?", true, id).exists?)
710 (!admin? || User.active.where("admin = ? AND id <> ?", true, id).exists?)
707 end
711 end
708
712
709 safe_attributes 'firstname',
713 safe_attributes 'firstname',
710 'lastname',
714 'lastname',
711 'mail',
715 'mail',
712 'mail_notification',
716 'mail_notification',
713 'notified_project_ids',
717 'notified_project_ids',
714 'language',
718 'language',
715 'custom_field_values',
719 'custom_field_values',
716 'custom_fields',
720 'custom_fields',
717 'identity_url'
721 'identity_url'
718
722
719 safe_attributes 'login',
723 safe_attributes 'login',
720 :if => lambda {|user, current_user| user.new_record?}
724 :if => lambda {|user, current_user| user.new_record?}
721
725
722 safe_attributes 'status',
726 safe_attributes 'status',
723 'auth_source_id',
727 'auth_source_id',
724 'generate_password',
728 'generate_password',
725 'must_change_passwd',
729 'must_change_passwd',
726 'login',
730 'login',
727 'admin',
731 'admin',
728 :if => lambda {|user, current_user| current_user.admin?}
732 :if => lambda {|user, current_user| current_user.admin?}
729
733
730 safe_attributes 'group_ids',
734 safe_attributes 'group_ids',
731 :if => lambda {|user, current_user| current_user.admin? && !user.new_record?}
735 :if => lambda {|user, current_user| current_user.admin? && !user.new_record?}
732
736
733 # Utility method to help check if a user should be notified about an
737 # Utility method to help check if a user should be notified about an
734 # event.
738 # event.
735 #
739 #
736 # TODO: only supports Issue events currently
740 # TODO: only supports Issue events currently
737 def notify_about?(object)
741 def notify_about?(object)
738 if mail_notification == 'all'
742 if mail_notification == 'all'
739 true
743 true
740 elsif mail_notification.blank? || mail_notification == 'none'
744 elsif mail_notification.blank? || mail_notification == 'none'
741 false
745 false
742 else
746 else
743 case object
747 case object
744 when Issue
748 when Issue
745 case mail_notification
749 case mail_notification
746 when 'selected', 'only_my_events'
750 when 'selected', 'only_my_events'
747 # user receives notifications for created/assigned issues on unselected projects
751 # user receives notifications for created/assigned issues on unselected projects
748 object.author == self || is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was)
752 object.author == self || is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was)
749 when 'only_assigned'
753 when 'only_assigned'
750 is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was)
754 is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was)
751 when 'only_owner'
755 when 'only_owner'
752 object.author == self
756 object.author == self
753 end
757 end
754 when News
758 when News
755 # always send to project members except when mail_notification is set to 'none'
759 # always send to project members except when mail_notification is set to 'none'
756 true
760 true
757 end
761 end
758 end
762 end
759 end
763 end
760
764
761 def self.current=(user)
765 def self.current=(user)
762 RequestStore.store[:current_user] = user
766 RequestStore.store[:current_user] = user
763 end
767 end
764
768
765 def self.current
769 def self.current
766 RequestStore.store[:current_user] ||= User.anonymous
770 RequestStore.store[:current_user] ||= User.anonymous
767 end
771 end
768
772
769 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
773 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
770 # one anonymous user per database.
774 # one anonymous user per database.
771 def self.anonymous
775 def self.anonymous
772 anonymous_user = AnonymousUser.unscoped.first
776 anonymous_user = AnonymousUser.unscoped.first
773 if anonymous_user.nil?
777 if anonymous_user.nil?
774 anonymous_user = AnonymousUser.unscoped.create(:lastname => 'Anonymous', :firstname => '', :login => '', :status => 0)
778 anonymous_user = AnonymousUser.unscoped.create(:lastname => 'Anonymous', :firstname => '', :login => '', :status => 0)
775 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
779 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
776 end
780 end
777 anonymous_user
781 anonymous_user
778 end
782 end
779
783
780 # Salts all existing unsalted passwords
784 # Salts all existing unsalted passwords
781 # It changes password storage scheme from SHA1(password) to SHA1(salt + SHA1(password))
785 # It changes password storage scheme from SHA1(password) to SHA1(salt + SHA1(password))
782 # This method is used in the SaltPasswords migration and is to be kept as is
786 # This method is used in the SaltPasswords migration and is to be kept as is
783 def self.salt_unsalted_passwords!
787 def self.salt_unsalted_passwords!
784 transaction do
788 transaction do
785 User.where("salt IS NULL OR salt = ''").find_each do |user|
789 User.where("salt IS NULL OR salt = ''").find_each do |user|
786 next if user.hashed_password.blank?
790 next if user.hashed_password.blank?
787 salt = User.generate_salt
791 salt = User.generate_salt
788 hashed_password = User.hash_password("#{salt}#{user.hashed_password}")
792 hashed_password = User.hash_password("#{salt}#{user.hashed_password}")
789 User.where(:id => user.id).update_all(:salt => salt, :hashed_password => hashed_password)
793 User.where(:id => user.id).update_all(:salt => salt, :hashed_password => hashed_password)
790 end
794 end
791 end
795 end
792 end
796 end
793
797
794 protected
798 protected
795
799
796 def validate_password_length
800 def validate_password_length
797 return if password.blank? && generate_password?
801 return if password.blank? && generate_password?
798 # Password length validation based on setting
802 # Password length validation based on setting
799 if !password.nil? && password.size < Setting.password_min_length.to_i
803 if !password.nil? && password.size < Setting.password_min_length.to_i
800 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
804 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
801 end
805 end
802 end
806 end
803
807
804 def instantiate_email_address
808 def instantiate_email_address
805 email_address || build_email_address
809 email_address || build_email_address
806 end
810 end
807
811
808 private
812 private
809
813
810 def generate_password_if_needed
814 def generate_password_if_needed
811 if generate_password? && auth_source.nil?
815 if generate_password? && auth_source.nil?
812 length = [Setting.password_min_length.to_i + 2, 10].max
816 length = [Setting.password_min_length.to_i + 2, 10].max
813 random_password(length)
817 random_password(length)
814 end
818 end
815 end
819 end
816
820
817 # Delete all outstanding password reset tokens on password change.
821 # Delete all outstanding password reset tokens on password change.
818 # Delete the autologin tokens on password change to prohibit session leakage.
822 # Delete the autologin tokens on password change to prohibit session leakage.
819 # This helps to keep the account secure in case the associated email account
823 # This helps to keep the account secure in case the associated email account
820 # was compromised.
824 # was compromised.
821 def destroy_tokens
825 def destroy_tokens
822 if hashed_password_changed? || (status_changed? && !active?)
826 if hashed_password_changed? || (status_changed? && !active?)
823 tokens = ['recovery', 'autologin', 'session']
827 tokens = ['recovery', 'autologin', 'session']
824 Token.where(:user_id => id, :action => tokens).delete_all
828 Token.where(:user_id => id, :action => tokens).delete_all
825 end
829 end
826 end
830 end
827
831
828 # Removes references that are not handled by associations
832 # Removes references that are not handled by associations
829 # Things that are not deleted are reassociated with the anonymous user
833 # Things that are not deleted are reassociated with the anonymous user
830 def remove_references_before_destroy
834 def remove_references_before_destroy
831 return if self.id.nil?
835 return if self.id.nil?
832
836
833 substitute = User.anonymous
837 substitute = User.anonymous
834 Attachment.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
838 Attachment.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
835 Comment.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
839 Comment.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
836 Issue.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
840 Issue.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
837 Issue.where(['assigned_to_id = ?', id]).update_all('assigned_to_id = NULL')
841 Issue.where(['assigned_to_id = ?', id]).update_all('assigned_to_id = NULL')
838 Journal.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id])
842 Journal.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id])
839 JournalDetail.
843 JournalDetail.
840 where(["property = 'attr' AND prop_key = 'assigned_to_id' AND old_value = ?", id.to_s]).
844 where(["property = 'attr' AND prop_key = 'assigned_to_id' AND old_value = ?", id.to_s]).
841 update_all(['old_value = ?', substitute.id.to_s])
845 update_all(['old_value = ?', substitute.id.to_s])
842 JournalDetail.
846 JournalDetail.
843 where(["property = 'attr' AND prop_key = 'assigned_to_id' AND value = ?", id.to_s]).
847 where(["property = 'attr' AND prop_key = 'assigned_to_id' AND value = ?", id.to_s]).
844 update_all(['value = ?', substitute.id.to_s])
848 update_all(['value = ?', substitute.id.to_s])
845 Message.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
849 Message.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
846 News.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
850 News.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
847 # Remove private queries and keep public ones
851 # Remove private queries and keep public ones
848 ::Query.where('user_id = ? AND visibility = ?', id, ::Query::VISIBILITY_PRIVATE).delete_all
852 ::Query.where('user_id = ? AND visibility = ?', id, ::Query::VISIBILITY_PRIVATE).delete_all
849 ::Query.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id])
853 ::Query.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id])
850 TimeEntry.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id])
854 TimeEntry.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id])
851 Token.where('user_id = ?', id).delete_all
855 Token.where('user_id = ?', id).delete_all
852 Watcher.where('user_id = ?', id).delete_all
856 Watcher.where('user_id = ?', id).delete_all
853 WikiContent.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
857 WikiContent.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
854 WikiContent::Version.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
858 WikiContent::Version.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
855 end
859 end
856
860
857 # Return password digest
861 # Return password digest
858 def self.hash_password(clear_password)
862 def self.hash_password(clear_password)
859 Digest::SHA1.hexdigest(clear_password || "")
863 Digest::SHA1.hexdigest(clear_password || "")
860 end
864 end
861
865
862 # Returns a 128bits random salt as a hex string (32 chars long)
866 # Returns a 128bits random salt as a hex string (32 chars long)
863 def self.generate_salt
867 def self.generate_salt
864 Redmine::Utils.random_hex(16)
868 Redmine::Utils.random_hex(16)
865 end
869 end
866
870
867 # Send a security notification to all admins if the user has gained/lost admin privileges
871 # Send a security notification to all admins if the user has gained/lost admin privileges
868 def deliver_security_notification
872 def deliver_security_notification
869 options = {
873 options = {
870 field: :field_admin,
874 field: :field_admin,
871 value: login,
875 value: login,
872 title: :label_user_plural,
876 title: :label_user_plural,
873 url: {controller: 'users', action: 'index'}
877 url: {controller: 'users', action: 'index'}
874 }
878 }
875
879
876 deliver = false
880 deliver = false
877 if (admin? && id_changed? && active?) || # newly created admin
881 if (admin? && id_changed? && active?) || # newly created admin
878 (admin? && admin_changed? && active?) || # regular user became admin
882 (admin? && admin_changed? && active?) || # regular user became admin
879 (admin? && status_changed? && active?) # locked admin became active again
883 (admin? && status_changed? && active?) # locked admin became active again
880
884
881 deliver = true
885 deliver = true
882 options[:message] = :mail_body_security_notification_add
886 options[:message] = :mail_body_security_notification_add
883
887
884 elsif (admin? && destroyed? && active?) || # active admin user was deleted
888 elsif (admin? && destroyed? && active?) || # active admin user was deleted
885 (!admin? && admin_changed? && active?) || # admin is no longer admin
889 (!admin? && admin_changed? && active?) || # admin is no longer admin
886 (admin? && status_changed? && !active?) # admin was locked
890 (admin? && status_changed? && !active?) # admin was locked
887
891
888 deliver = true
892 deliver = true
889 options[:message] = :mail_body_security_notification_remove
893 options[:message] = :mail_body_security_notification_remove
890 end
894 end
891
895
892 if deliver
896 if deliver
893 users = User.active.where(admin: true).to_a
897 users = User.active.where(admin: true).to_a
894 Mailer.security_notification(users, options).deliver
898 Mailer.security_notification(users, options).deliver
895 end
899 end
896 end
900 end
897 end
901 end
898
902
899 class AnonymousUser < User
903 class AnonymousUser < User
900 validate :validate_anonymous_uniqueness, :on => :create
904 validate :validate_anonymous_uniqueness, :on => :create
901
905
902 self.valid_statuses = [STATUS_ANONYMOUS]
906 self.valid_statuses = [STATUS_ANONYMOUS]
903
907
904 def validate_anonymous_uniqueness
908 def validate_anonymous_uniqueness
905 # There should be only one AnonymousUser in the database
909 # There should be only one AnonymousUser in the database
906 errors.add :base, 'An anonymous user already exists.' if AnonymousUser.exists?
910 errors.add :base, 'An anonymous user already exists.' if AnonymousUser.exists?
907 end
911 end
908
912
909 def available_custom_fields
913 def available_custom_fields
910 []
914 []
911 end
915 end
912
916
913 # Overrides a few properties
917 # Overrides a few properties
914 def logged?; false end
918 def logged?; false end
915 def admin; false end
919 def admin; false end
916 def name(*args); I18n.t(:label_user_anonymous) end
920 def name(*args); I18n.t(:label_user_anonymous) end
917 def mail=(*args); nil end
921 def mail=(*args); nil end
918 def mail; nil end
922 def mail; nil end
919 def time_zone; nil end
923 def time_zone; nil end
920 def rss_key; nil end
924 def rss_key; nil end
921
925
922 def pref
926 def pref
923 UserPreference.new(:user => self)
927 UserPreference.new(:user => self)
924 end
928 end
925
929
926 # Returns the user's bult-in role
930 # Returns the user's bult-in role
927 def builtin_role
931 def builtin_role
928 @builtin_role ||= Role.anonymous
932 @builtin_role ||= Role.anonymous
929 end
933 end
930
934
931 def membership(*args)
935 def membership(*args)
932 nil
936 nil
933 end
937 end
934
938
935 def member_of?(*args)
939 def member_of?(*args)
936 false
940 false
937 end
941 end
938
942
939 # Anonymous user can not be destroyed
943 # Anonymous user can not be destroyed
940 def destroy
944 def destroy
941 false
945 false
942 end
946 end
943
947
944 protected
948 protected
945
949
946 def instantiate_email_address
950 def instantiate_email_address
947 end
951 end
948 end
952 end
@@ -1,31 +1,31
1 api.group do
1 api.group do
2 api.id @group.id
2 api.id @group.id
3 api.name @group.lastname
3 api.name @group.lastname
4 api.builtin @group.builtin_type if @group.builtin_type
4 api.builtin @group.builtin_type if @group.builtin_type
5
5
6 render_api_custom_values @group.visible_custom_field_values, api
6 render_api_custom_values @group.visible_custom_field_values, api
7
7
8 api.array :users do
8 api.array :users do
9 @group.users.each do |user|
9 @group.users.each do |user|
10 api.user :id => user.id, :name => user.name
10 api.user :id => user.id, :name => user.name
11 end
11 end
12 end if include_in_api_response?('users') && !@group.builtin?
12 end if include_in_api_response?('users') && !@group.builtin?
13
13
14 api.array :memberships do
14 api.array :memberships do
15 @group.memberships.each do |membership|
15 @group.memberships.preload(:roles, :project).each do |membership|
16 api.membership do
16 api.membership do
17 api.id membership.id
17 api.id membership.id
18 api.project :id => membership.project.id, :name => membership.project.name
18 api.project :id => membership.project.id, :name => membership.project.name
19 api.array :roles do
19 api.array :roles do
20 membership.member_roles.each do |member_role|
20 membership.member_roles.each do |member_role|
21 if member_role.role
21 if member_role.role
22 attrs = {:id => member_role.role.id, :name => member_role.role.name}
22 attrs = {:id => member_role.role.id, :name => member_role.role.name}
23 attrs.merge!(:inherited => true) if member_role.inherited_from.present?
23 attrs.merge!(:inherited => true) if member_role.inherited_from.present?
24 api.role attrs
24 api.role attrs
25 end
25 end
26 end
26 end
27 end
27 end
28 end if membership.project
28 end if membership.project
29 end
29 end
30 end if include_in_api_response?('memberships')
30 end if include_in_api_response?('memberships')
31 end
31 end
General Comments 0
You need to be logged in to leave comments. Login now