@@ -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 { |
|
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 |
r |
|
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