##// END OF EJS Templates
Workaround for timestamps rounding issues with Rails4.2 and mysql5.7 that may kill user session after password is changed (#17460)....
Jean-Philippe Lang -
r13629:76e7025f0716
parent child
Show More
@@ -1,204 +1,204
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2015 Jean-Philippe Lang
2 # Copyright (C) 2006-2015 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 MyController < ApplicationController
18 class MyController < ApplicationController
19 before_filter :require_login
19 before_filter :require_login
20 # let user change user's password when user has to
20 # let user change user's password when user has to
21 skip_before_filter :check_password_change, :only => :password
21 skip_before_filter :check_password_change, :only => :password
22
22
23 helper :issues
23 helper :issues
24 helper :users
24 helper :users
25 helper :custom_fields
25 helper :custom_fields
26
26
27 BLOCKS = { 'issuesassignedtome' => :label_assigned_to_me_issues,
27 BLOCKS = { 'issuesassignedtome' => :label_assigned_to_me_issues,
28 'issuesreportedbyme' => :label_reported_issues,
28 'issuesreportedbyme' => :label_reported_issues,
29 'issueswatched' => :label_watched_issues,
29 'issueswatched' => :label_watched_issues,
30 'news' => :label_news_latest,
30 'news' => :label_news_latest,
31 'calendar' => :label_calendar,
31 'calendar' => :label_calendar,
32 'documents' => :label_document_plural,
32 'documents' => :label_document_plural,
33 'timelog' => :label_spent_time
33 'timelog' => :label_spent_time
34 }.merge(Redmine::Views::MyPage::Block.additional_blocks).freeze
34 }.merge(Redmine::Views::MyPage::Block.additional_blocks).freeze
35
35
36 DEFAULT_LAYOUT = { 'left' => ['issuesassignedtome'],
36 DEFAULT_LAYOUT = { 'left' => ['issuesassignedtome'],
37 'right' => ['issuesreportedbyme']
37 'right' => ['issuesreportedbyme']
38 }.freeze
38 }.freeze
39
39
40 def index
40 def index
41 page
41 page
42 render :action => 'page'
42 render :action => 'page'
43 end
43 end
44
44
45 # Show user's page
45 # Show user's page
46 def page
46 def page
47 @user = User.current
47 @user = User.current
48 @blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT
48 @blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT
49 end
49 end
50
50
51 # Edit user's account
51 # Edit user's account
52 def account
52 def account
53 @user = User.current
53 @user = User.current
54 @pref = @user.pref
54 @pref = @user.pref
55 if request.post?
55 if request.post?
56 @user.safe_attributes = params[:user] if params[:user]
56 @user.safe_attributes = params[:user] if params[:user]
57 @user.pref.attributes = params[:pref] if params[:pref]
57 @user.pref.attributes = params[:pref] if params[:pref]
58 if @user.save
58 if @user.save
59 @user.pref.save
59 @user.pref.save
60 set_language_if_valid @user.language
60 set_language_if_valid @user.language
61 flash[:notice] = l(:notice_account_updated)
61 flash[:notice] = l(:notice_account_updated)
62 redirect_to my_account_path
62 redirect_to my_account_path
63 return
63 return
64 end
64 end
65 end
65 end
66 end
66 end
67
67
68 # Destroys user's account
68 # Destroys user's account
69 def destroy
69 def destroy
70 @user = User.current
70 @user = User.current
71 unless @user.own_account_deletable?
71 unless @user.own_account_deletable?
72 redirect_to my_account_path
72 redirect_to my_account_path
73 return
73 return
74 end
74 end
75
75
76 if request.post? && params[:confirm]
76 if request.post? && params[:confirm]
77 @user.destroy
77 @user.destroy
78 if @user.destroyed?
78 if @user.destroyed?
79 logout_user
79 logout_user
80 flash[:notice] = l(:notice_account_deleted)
80 flash[:notice] = l(:notice_account_deleted)
81 end
81 end
82 redirect_to home_path
82 redirect_to home_path
83 end
83 end
84 end
84 end
85
85
86 # Manage user's password
86 # Manage user's password
87 def password
87 def password
88 @user = User.current
88 @user = User.current
89 unless @user.change_password_allowed?
89 unless @user.change_password_allowed?
90 flash[:error] = l(:notice_can_t_change_password)
90 flash[:error] = l(:notice_can_t_change_password)
91 redirect_to my_account_path
91 redirect_to my_account_path
92 return
92 return
93 end
93 end
94 if request.post?
94 if request.post?
95 if !@user.check_password?(params[:password])
95 if !@user.check_password?(params[:password])
96 flash.now[:error] = l(:notice_account_wrong_password)
96 flash.now[:error] = l(:notice_account_wrong_password)
97 elsif params[:password] == params[:new_password]
97 elsif params[:password] == params[:new_password]
98 flash.now[:error] = l(:notice_new_password_must_be_different)
98 flash.now[:error] = l(:notice_new_password_must_be_different)
99 else
99 else
100 @user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
100 @user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
101 @user.must_change_passwd = false
101 @user.must_change_passwd = false
102 if @user.save
102 if @user.save
103 # Reset the session creation time to not log out this session on next
103 # Reset the session creation time to not log out this session on next
104 # request due to ApplicationController#force_logout_if_password_changed
104 # request due to ApplicationController#force_logout_if_password_changed
105 session[:ctime] = Time.now.utc.to_i
105 session[:ctime] = User.current.passwd_changed_on.utc.to_i
106 flash[:notice] = l(:notice_account_password_updated)
106 flash[:notice] = l(:notice_account_password_updated)
107 redirect_to my_account_path
107 redirect_to my_account_path
108 end
108 end
109 end
109 end
110 end
110 end
111 end
111 end
112
112
113 # Create a new feeds key
113 # Create a new feeds key
114 def reset_rss_key
114 def reset_rss_key
115 if request.post?
115 if request.post?
116 if User.current.rss_token
116 if User.current.rss_token
117 User.current.rss_token.destroy
117 User.current.rss_token.destroy
118 User.current.reload
118 User.current.reload
119 end
119 end
120 User.current.rss_key
120 User.current.rss_key
121 flash[:notice] = l(:notice_feeds_access_key_reseted)
121 flash[:notice] = l(:notice_feeds_access_key_reseted)
122 end
122 end
123 redirect_to my_account_path
123 redirect_to my_account_path
124 end
124 end
125
125
126 # Create a new API key
126 # Create a new API key
127 def reset_api_key
127 def reset_api_key
128 if request.post?
128 if request.post?
129 if User.current.api_token
129 if User.current.api_token
130 User.current.api_token.destroy
130 User.current.api_token.destroy
131 User.current.reload
131 User.current.reload
132 end
132 end
133 User.current.api_key
133 User.current.api_key
134 flash[:notice] = l(:notice_api_access_key_reseted)
134 flash[:notice] = l(:notice_api_access_key_reseted)
135 end
135 end
136 redirect_to my_account_path
136 redirect_to my_account_path
137 end
137 end
138
138
139 # User's page layout configuration
139 # User's page layout configuration
140 def page_layout
140 def page_layout
141 @user = User.current
141 @user = User.current
142 @blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT.dup
142 @blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT.dup
143 @block_options = []
143 @block_options = []
144 BLOCKS.each do |k, v|
144 BLOCKS.each do |k, v|
145 unless @blocks.values.flatten.include?(k)
145 unless @blocks.values.flatten.include?(k)
146 @block_options << [l("my.blocks.#{v}", :default => [v, v.to_s.humanize]), k.dasherize]
146 @block_options << [l("my.blocks.#{v}", :default => [v, v.to_s.humanize]), k.dasherize]
147 end
147 end
148 end
148 end
149 end
149 end
150
150
151 # Add a block to user's page
151 # Add a block to user's page
152 # The block is added on top of the page
152 # The block is added on top of the page
153 # params[:block] : id of the block to add
153 # params[:block] : id of the block to add
154 def add_block
154 def add_block
155 block = params[:block].to_s.underscore
155 block = params[:block].to_s.underscore
156 if block.present? && BLOCKS.key?(block)
156 if block.present? && BLOCKS.key?(block)
157 @user = User.current
157 @user = User.current
158 layout = @user.pref[:my_page_layout] || {}
158 layout = @user.pref[:my_page_layout] || {}
159 # remove if already present in a group
159 # remove if already present in a group
160 %w(top left right).each {|f| (layout[f] ||= []).delete block }
160 %w(top left right).each {|f| (layout[f] ||= []).delete block }
161 # add it on top
161 # add it on top
162 layout['top'].unshift block
162 layout['top'].unshift block
163 @user.pref[:my_page_layout] = layout
163 @user.pref[:my_page_layout] = layout
164 @user.pref.save
164 @user.pref.save
165 end
165 end
166 redirect_to my_page_layout_path
166 redirect_to my_page_layout_path
167 end
167 end
168
168
169 # Remove a block to user's page
169 # Remove a block to user's page
170 # params[:block] : id of the block to remove
170 # params[:block] : id of the block to remove
171 def remove_block
171 def remove_block
172 block = params[:block].to_s.underscore
172 block = params[:block].to_s.underscore
173 @user = User.current
173 @user = User.current
174 # remove block in all groups
174 # remove block in all groups
175 layout = @user.pref[:my_page_layout] || {}
175 layout = @user.pref[:my_page_layout] || {}
176 %w(top left right).each {|f| (layout[f] ||= []).delete block }
176 %w(top left right).each {|f| (layout[f] ||= []).delete block }
177 @user.pref[:my_page_layout] = layout
177 @user.pref[:my_page_layout] = layout
178 @user.pref.save
178 @user.pref.save
179 redirect_to my_page_layout_path
179 redirect_to my_page_layout_path
180 end
180 end
181
181
182 # Change blocks order on user's page
182 # Change blocks order on user's page
183 # params[:group] : group to order (top, left or right)
183 # params[:group] : group to order (top, left or right)
184 # params[:list-(top|left|right)] : array of block ids of the group
184 # params[:list-(top|left|right)] : array of block ids of the group
185 def order_blocks
185 def order_blocks
186 group = params[:group]
186 group = params[:group]
187 @user = User.current
187 @user = User.current
188 if group.is_a?(String)
188 if group.is_a?(String)
189 group_items = (params["blocks"] || []).collect(&:underscore)
189 group_items = (params["blocks"] || []).collect(&:underscore)
190 group_items.each {|s| s.sub!(/^block_/, '')}
190 group_items.each {|s| s.sub!(/^block_/, '')}
191 if group_items and group_items.is_a? Array
191 if group_items and group_items.is_a? Array
192 layout = @user.pref[:my_page_layout] || {}
192 layout = @user.pref[:my_page_layout] || {}
193 # remove group blocks if they are presents in other groups
193 # remove group blocks if they are presents in other groups
194 %w(top left right).each {|f|
194 %w(top left right).each {|f|
195 layout[f] = (layout[f] || []) - group_items
195 layout[f] = (layout[f] || []) - group_items
196 }
196 }
197 layout[group] = group_items
197 layout[group] = group_items
198 @user.pref[:my_page_layout] = layout
198 @user.pref[:my_page_layout] = layout
199 @user.pref.save
199 @user.pref.save
200 end
200 end
201 end
201 end
202 render :nothing => true
202 render :nothing => true
203 end
203 end
204 end
204 end
@@ -1,835 +1,835
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2015 Jean-Philippe Lang
2 # Copyright (C) 2006-2015 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 :lastname_coma_firstname => {
50 :lastname_coma_firstname => {
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 => {
55 :lastname => {
56 :string => '#{lastname}',
56 :string => '#{lastname}',
57 :order => %w(lastname id),
57 :order => %w(lastname id),
58 :setting_order => 6
58 :setting_order => 6
59 },
59 },
60 :username => {
60 :username => {
61 :string => '#{login}',
61 :string => '#{login}',
62 :order => %w(login id),
62 :order => %w(login id),
63 :setting_order => 7
63 :setting_order => 7
64 },
64 },
65 }
65 }
66
66
67 MAIL_NOTIFICATION_OPTIONS = [
67 MAIL_NOTIFICATION_OPTIONS = [
68 ['all', :label_user_mail_option_all],
68 ['all', :label_user_mail_option_all],
69 ['selected', :label_user_mail_option_selected],
69 ['selected', :label_user_mail_option_selected],
70 ['only_my_events', :label_user_mail_option_only_my_events],
70 ['only_my_events', :label_user_mail_option_only_my_events],
71 ['only_assigned', :label_user_mail_option_only_assigned],
71 ['only_assigned', :label_user_mail_option_only_assigned],
72 ['only_owner', :label_user_mail_option_only_owner],
72 ['only_owner', :label_user_mail_option_only_owner],
73 ['none', :label_user_mail_option_none]
73 ['none', :label_user_mail_option_none]
74 ]
74 ]
75
75
76 has_and_belongs_to_many :groups,
76 has_and_belongs_to_many :groups,
77 :join_table => "#{table_name_prefix}groups_users#{table_name_suffix}",
77 :join_table => "#{table_name_prefix}groups_users#{table_name_suffix}",
78 :after_add => Proc.new {|user, group| group.user_added(user)},
78 :after_add => Proc.new {|user, group| group.user_added(user)},
79 :after_remove => Proc.new {|user, group| group.user_removed(user)}
79 :after_remove => Proc.new {|user, group| group.user_removed(user)}
80 has_many :changesets, :dependent => :nullify
80 has_many :changesets, :dependent => :nullify
81 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
81 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
82 has_one :rss_token, lambda {where "action='feeds'"}, :class_name => 'Token'
82 has_one :rss_token, lambda {where "action='feeds'"}, :class_name => 'Token'
83 has_one :api_token, lambda {where "action='api'"}, :class_name => 'Token'
83 has_one :api_token, lambda {where "action='api'"}, :class_name => 'Token'
84 has_one :email_address, lambda {where :is_default => true}, :autosave => true
84 has_one :email_address, lambda {where :is_default => true}, :autosave => true
85 has_many :email_addresses, :dependent => :delete_all
85 has_many :email_addresses, :dependent => :delete_all
86 belongs_to :auth_source
86 belongs_to :auth_source
87
87
88 scope :logged, lambda { where("#{User.table_name}.status <> #{STATUS_ANONYMOUS}") }
88 scope :logged, lambda { where("#{User.table_name}.status <> #{STATUS_ANONYMOUS}") }
89 scope :status, lambda {|arg| where(arg.blank? ? nil : {:status => arg.to_i}) }
89 scope :status, lambda {|arg| where(arg.blank? ? nil : {:status => arg.to_i}) }
90
90
91 acts_as_customizable
91 acts_as_customizable
92
92
93 attr_accessor :password, :password_confirmation, :generate_password
93 attr_accessor :password, :password_confirmation, :generate_password
94 attr_accessor :last_before_login_on
94 attr_accessor :last_before_login_on
95 # Prevents unauthorized assignments
95 # Prevents unauthorized assignments
96 attr_protected :login, :admin, :password, :password_confirmation, :hashed_password
96 attr_protected :login, :admin, :password, :password_confirmation, :hashed_password
97
97
98 LOGIN_LENGTH_LIMIT = 60
98 LOGIN_LENGTH_LIMIT = 60
99 MAIL_LENGTH_LIMIT = 60
99 MAIL_LENGTH_LIMIT = 60
100
100
101 validates_presence_of :login, :firstname, :lastname, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
101 validates_presence_of :login, :firstname, :lastname, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
102 validates_uniqueness_of :login, :if => Proc.new { |user| user.login_changed? && user.login.present? }, :case_sensitive => false
102 validates_uniqueness_of :login, :if => Proc.new { |user| user.login_changed? && user.login.present? }, :case_sensitive => false
103 # Login must contain letters, numbers, underscores only
103 # Login must contain letters, numbers, underscores only
104 validates_format_of :login, :with => /\A[a-z0-9_\-@\.]*\z/i
104 validates_format_of :login, :with => /\A[a-z0-9_\-@\.]*\z/i
105 validates_length_of :login, :maximum => LOGIN_LENGTH_LIMIT
105 validates_length_of :login, :maximum => LOGIN_LENGTH_LIMIT
106 validates_length_of :firstname, :lastname, :maximum => 30
106 validates_length_of :firstname, :lastname, :maximum => 30
107 validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true
107 validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true
108 validate :validate_password_length
108 validate :validate_password_length
109 validate do
109 validate do
110 if password_confirmation && password != password_confirmation
110 if password_confirmation && password != password_confirmation
111 errors.add(:password, :confirmation)
111 errors.add(:password, :confirmation)
112 end
112 end
113 end
113 end
114
114
115 before_validation :instantiate_email_address
115 before_validation :instantiate_email_address
116 before_create :set_mail_notification
116 before_create :set_mail_notification
117 before_save :generate_password_if_needed, :update_hashed_password
117 before_save :generate_password_if_needed, :update_hashed_password
118 before_destroy :remove_references_before_destroy
118 before_destroy :remove_references_before_destroy
119 after_save :update_notified_project_ids, :destroy_tokens
119 after_save :update_notified_project_ids, :destroy_tokens
120
120
121 scope :in_group, lambda {|group|
121 scope :in_group, lambda {|group|
122 group_id = group.is_a?(Group) ? group.id : group.to_i
122 group_id = group.is_a?(Group) ? group.id : group.to_i
123 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)
123 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)
124 }
124 }
125 scope :not_in_group, lambda {|group|
125 scope :not_in_group, lambda {|group|
126 group_id = group.is_a?(Group) ? group.id : group.to_i
126 group_id = group.is_a?(Group) ? group.id : group.to_i
127 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)
127 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)
128 }
128 }
129 scope :sorted, lambda { order(*User.fields_for_order_statement)}
129 scope :sorted, lambda { order(*User.fields_for_order_statement)}
130 scope :having_mail, lambda {|arg|
130 scope :having_mail, lambda {|arg|
131 addresses = Array.wrap(arg).map {|a| a.to_s.downcase}
131 addresses = Array.wrap(arg).map {|a| a.to_s.downcase}
132 if addresses.any?
132 if addresses.any?
133 joins(:email_addresses).where("LOWER(address) IN (?)", addresses).uniq
133 joins(:email_addresses).where("LOWER(address) IN (?)", addresses).uniq
134 else
134 else
135 none
135 none
136 end
136 end
137 }
137 }
138
138
139 def set_mail_notification
139 def set_mail_notification
140 self.mail_notification = Setting.default_notification_option if self.mail_notification.blank?
140 self.mail_notification = Setting.default_notification_option if self.mail_notification.blank?
141 true
141 true
142 end
142 end
143
143
144 def update_hashed_password
144 def update_hashed_password
145 # update hashed_password if password was set
145 # update hashed_password if password was set
146 if self.password && self.auth_source_id.blank?
146 if self.password && self.auth_source_id.blank?
147 salt_password(password)
147 salt_password(password)
148 end
148 end
149 end
149 end
150
150
151 alias :base_reload :reload
151 alias :base_reload :reload
152 def reload(*args)
152 def reload(*args)
153 @name = nil
153 @name = nil
154 @projects_by_role = nil
154 @projects_by_role = nil
155 @membership_by_project_id = nil
155 @membership_by_project_id = nil
156 @notified_projects_ids = nil
156 @notified_projects_ids = nil
157 @notified_projects_ids_changed = false
157 @notified_projects_ids_changed = false
158 @builtin_role = nil
158 @builtin_role = nil
159 @visible_project_ids = nil
159 @visible_project_ids = nil
160 base_reload(*args)
160 base_reload(*args)
161 end
161 end
162
162
163 def mail
163 def mail
164 email_address.try(:address)
164 email_address.try(:address)
165 end
165 end
166
166
167 def mail=(arg)
167 def mail=(arg)
168 email = email_address || build_email_address
168 email = email_address || build_email_address
169 email.address = arg
169 email.address = arg
170 end
170 end
171
171
172 def mail_changed?
172 def mail_changed?
173 email_address.try(:address_changed?)
173 email_address.try(:address_changed?)
174 end
174 end
175
175
176 def mails
176 def mails
177 email_addresses.pluck(:address)
177 email_addresses.pluck(:address)
178 end
178 end
179
179
180 def self.find_or_initialize_by_identity_url(url)
180 def self.find_or_initialize_by_identity_url(url)
181 user = where(:identity_url => url).first
181 user = where(:identity_url => url).first
182 unless user
182 unless user
183 user = User.new
183 user = User.new
184 user.identity_url = url
184 user.identity_url = url
185 end
185 end
186 user
186 user
187 end
187 end
188
188
189 def identity_url=(url)
189 def identity_url=(url)
190 if url.blank?
190 if url.blank?
191 write_attribute(:identity_url, '')
191 write_attribute(:identity_url, '')
192 else
192 else
193 begin
193 begin
194 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
194 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
195 rescue OpenIdAuthentication::InvalidOpenId
195 rescue OpenIdAuthentication::InvalidOpenId
196 # Invalid url, don't save
196 # Invalid url, don't save
197 end
197 end
198 end
198 end
199 self.read_attribute(:identity_url)
199 self.read_attribute(:identity_url)
200 end
200 end
201
201
202 # Returns the user that matches provided login and password, or nil
202 # Returns the user that matches provided login and password, or nil
203 def self.try_to_login(login, password, active_only=true)
203 def self.try_to_login(login, password, active_only=true)
204 login = login.to_s
204 login = login.to_s
205 password = password.to_s
205 password = password.to_s
206
206
207 # Make sure no one can sign in with an empty login or password
207 # Make sure no one can sign in with an empty login or password
208 return nil if login.empty? || password.empty?
208 return nil if login.empty? || password.empty?
209 user = find_by_login(login)
209 user = find_by_login(login)
210 if user
210 if user
211 # user is already in local database
211 # user is already in local database
212 return nil unless user.check_password?(password)
212 return nil unless user.check_password?(password)
213 return nil if !user.active? && active_only
213 return nil if !user.active? && active_only
214 else
214 else
215 # user is not yet registered, try to authenticate with available sources
215 # user is not yet registered, try to authenticate with available sources
216 attrs = AuthSource.authenticate(login, password)
216 attrs = AuthSource.authenticate(login, password)
217 if attrs
217 if attrs
218 user = new(attrs)
218 user = new(attrs)
219 user.login = login
219 user.login = login
220 user.language = Setting.default_language
220 user.language = Setting.default_language
221 if user.save
221 if user.save
222 user.reload
222 user.reload
223 logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source
223 logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source
224 end
224 end
225 end
225 end
226 end
226 end
227 user.update_column(:last_login_on, Time.now) if user && !user.new_record? && user.active?
227 user.update_column(:last_login_on, Time.now) if user && !user.new_record? && user.active?
228 user
228 user
229 rescue => text
229 rescue => text
230 raise text
230 raise text
231 end
231 end
232
232
233 # Returns the user who matches the given autologin +key+ or nil
233 # Returns the user who matches the given autologin +key+ or nil
234 def self.try_to_autologin(key)
234 def self.try_to_autologin(key)
235 user = Token.find_active_user('autologin', key, Setting.autologin.to_i)
235 user = Token.find_active_user('autologin', key, Setting.autologin.to_i)
236 if user
236 if user
237 user.update_column(:last_login_on, Time.now)
237 user.update_column(:last_login_on, Time.now)
238 user
238 user
239 end
239 end
240 end
240 end
241
241
242 def self.name_formatter(formatter = nil)
242 def self.name_formatter(formatter = nil)
243 USER_FORMATS[formatter || Setting.user_format] || USER_FORMATS[:firstname_lastname]
243 USER_FORMATS[formatter || Setting.user_format] || USER_FORMATS[:firstname_lastname]
244 end
244 end
245
245
246 # Returns an array of fields names than can be used to make an order statement for users
246 # Returns an array of fields names than can be used to make an order statement for users
247 # according to how user names are displayed
247 # according to how user names are displayed
248 # Examples:
248 # Examples:
249 #
249 #
250 # User.fields_for_order_statement => ['users.login', 'users.id']
250 # User.fields_for_order_statement => ['users.login', 'users.id']
251 # User.fields_for_order_statement('authors') => ['authors.login', 'authors.id']
251 # User.fields_for_order_statement('authors') => ['authors.login', 'authors.id']
252 def self.fields_for_order_statement(table=nil)
252 def self.fields_for_order_statement(table=nil)
253 table ||= table_name
253 table ||= table_name
254 name_formatter[:order].map {|field| "#{table}.#{field}"}
254 name_formatter[:order].map {|field| "#{table}.#{field}"}
255 end
255 end
256
256
257 # Return user's full name for display
257 # Return user's full name for display
258 def name(formatter = nil)
258 def name(formatter = nil)
259 f = self.class.name_formatter(formatter)
259 f = self.class.name_formatter(formatter)
260 if formatter
260 if formatter
261 eval('"' + f[:string] + '"')
261 eval('"' + f[:string] + '"')
262 else
262 else
263 @name ||= eval('"' + f[:string] + '"')
263 @name ||= eval('"' + f[:string] + '"')
264 end
264 end
265 end
265 end
266
266
267 def active?
267 def active?
268 self.status == STATUS_ACTIVE
268 self.status == STATUS_ACTIVE
269 end
269 end
270
270
271 def registered?
271 def registered?
272 self.status == STATUS_REGISTERED
272 self.status == STATUS_REGISTERED
273 end
273 end
274
274
275 def locked?
275 def locked?
276 self.status == STATUS_LOCKED
276 self.status == STATUS_LOCKED
277 end
277 end
278
278
279 def activate
279 def activate
280 self.status = STATUS_ACTIVE
280 self.status = STATUS_ACTIVE
281 end
281 end
282
282
283 def register
283 def register
284 self.status = STATUS_REGISTERED
284 self.status = STATUS_REGISTERED
285 end
285 end
286
286
287 def lock
287 def lock
288 self.status = STATUS_LOCKED
288 self.status = STATUS_LOCKED
289 end
289 end
290
290
291 def activate!
291 def activate!
292 update_attribute(:status, STATUS_ACTIVE)
292 update_attribute(:status, STATUS_ACTIVE)
293 end
293 end
294
294
295 def register!
295 def register!
296 update_attribute(:status, STATUS_REGISTERED)
296 update_attribute(:status, STATUS_REGISTERED)
297 end
297 end
298
298
299 def lock!
299 def lock!
300 update_attribute(:status, STATUS_LOCKED)
300 update_attribute(:status, STATUS_LOCKED)
301 end
301 end
302
302
303 # Returns true if +clear_password+ is the correct user's password, otherwise false
303 # Returns true if +clear_password+ is the correct user's password, otherwise false
304 def check_password?(clear_password)
304 def check_password?(clear_password)
305 if auth_source_id.present?
305 if auth_source_id.present?
306 auth_source.authenticate(self.login, clear_password)
306 auth_source.authenticate(self.login, clear_password)
307 else
307 else
308 User.hash_password("#{salt}#{User.hash_password clear_password}") == hashed_password
308 User.hash_password("#{salt}#{User.hash_password clear_password}") == hashed_password
309 end
309 end
310 end
310 end
311
311
312 # Generates a random salt and computes hashed_password for +clear_password+
312 # Generates a random salt and computes hashed_password for +clear_password+
313 # The hashed password is stored in the following form: SHA1(salt + SHA1(password))
313 # The hashed password is stored in the following form: SHA1(salt + SHA1(password))
314 def salt_password(clear_password)
314 def salt_password(clear_password)
315 self.salt = User.generate_salt
315 self.salt = User.generate_salt
316 self.hashed_password = User.hash_password("#{salt}#{User.hash_password clear_password}")
316 self.hashed_password = User.hash_password("#{salt}#{User.hash_password clear_password}")
317 self.passwd_changed_on = Time.now
317 self.passwd_changed_on = Time.now.change(:usec => 0)
318 end
318 end
319
319
320 # Does the backend storage allow this user to change their password?
320 # Does the backend storage allow this user to change their password?
321 def change_password_allowed?
321 def change_password_allowed?
322 return true if auth_source.nil?
322 return true if auth_source.nil?
323 return auth_source.allow_password_changes?
323 return auth_source.allow_password_changes?
324 end
324 end
325
325
326 def must_change_password?
326 def must_change_password?
327 must_change_passwd? && change_password_allowed?
327 must_change_passwd? && change_password_allowed?
328 end
328 end
329
329
330 def generate_password?
330 def generate_password?
331 generate_password == '1' || generate_password == true
331 generate_password == '1' || generate_password == true
332 end
332 end
333
333
334 # Generate and set a random password on given length
334 # Generate and set a random password on given length
335 def random_password(length=40)
335 def random_password(length=40)
336 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
336 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
337 chars -= %w(0 O 1 l)
337 chars -= %w(0 O 1 l)
338 password = ''
338 password = ''
339 length.times {|i| password << chars[SecureRandom.random_number(chars.size)] }
339 length.times {|i| password << chars[SecureRandom.random_number(chars.size)] }
340 self.password = password
340 self.password = password
341 self.password_confirmation = password
341 self.password_confirmation = password
342 self
342 self
343 end
343 end
344
344
345 def pref
345 def pref
346 self.preference ||= UserPreference.new(:user => self)
346 self.preference ||= UserPreference.new(:user => self)
347 end
347 end
348
348
349 def time_zone
349 def time_zone
350 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
350 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
351 end
351 end
352
352
353 def force_default_language?
353 def force_default_language?
354 Setting.force_default_language_for_loggedin?
354 Setting.force_default_language_for_loggedin?
355 end
355 end
356
356
357 def language
357 def language
358 if force_default_language?
358 if force_default_language?
359 Setting.default_language
359 Setting.default_language
360 else
360 else
361 super
361 super
362 end
362 end
363 end
363 end
364
364
365 def wants_comments_in_reverse_order?
365 def wants_comments_in_reverse_order?
366 self.pref[:comments_sorting] == 'desc'
366 self.pref[:comments_sorting] == 'desc'
367 end
367 end
368
368
369 # Return user's RSS key (a 40 chars long string), used to access feeds
369 # Return user's RSS key (a 40 chars long string), used to access feeds
370 def rss_key
370 def rss_key
371 if rss_token.nil?
371 if rss_token.nil?
372 create_rss_token(:action => 'feeds')
372 create_rss_token(:action => 'feeds')
373 end
373 end
374 rss_token.value
374 rss_token.value
375 end
375 end
376
376
377 # Return user's API key (a 40 chars long string), used to access the API
377 # Return user's API key (a 40 chars long string), used to access the API
378 def api_key
378 def api_key
379 if api_token.nil?
379 if api_token.nil?
380 create_api_token(:action => 'api')
380 create_api_token(:action => 'api')
381 end
381 end
382 api_token.value
382 api_token.value
383 end
383 end
384
384
385 # Return an array of project ids for which the user has explicitly turned mail notifications on
385 # Return an array of project ids for which the user has explicitly turned mail notifications on
386 def notified_projects_ids
386 def notified_projects_ids
387 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
387 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
388 end
388 end
389
389
390 def notified_project_ids=(ids)
390 def notified_project_ids=(ids)
391 @notified_projects_ids_changed = true
391 @notified_projects_ids_changed = true
392 @notified_projects_ids = ids.map(&:to_i).uniq.select {|n| n > 0}
392 @notified_projects_ids = ids.map(&:to_i).uniq.select {|n| n > 0}
393 end
393 end
394
394
395 # Updates per project notifications (after_save callback)
395 # Updates per project notifications (after_save callback)
396 def update_notified_project_ids
396 def update_notified_project_ids
397 if @notified_projects_ids_changed
397 if @notified_projects_ids_changed
398 ids = (mail_notification == 'selected' ? Array.wrap(notified_projects_ids).reject(&:blank?) : [])
398 ids = (mail_notification == 'selected' ? Array.wrap(notified_projects_ids).reject(&:blank?) : [])
399 members.update_all(:mail_notification => false)
399 members.update_all(:mail_notification => false)
400 members.where(:project_id => ids).update_all(:mail_notification => true) if ids.any?
400 members.where(:project_id => ids).update_all(:mail_notification => true) if ids.any?
401 end
401 end
402 end
402 end
403 private :update_notified_project_ids
403 private :update_notified_project_ids
404
404
405 def valid_notification_options
405 def valid_notification_options
406 self.class.valid_notification_options(self)
406 self.class.valid_notification_options(self)
407 end
407 end
408
408
409 # Only users that belong to more than 1 project can select projects for which they are notified
409 # Only users that belong to more than 1 project can select projects for which they are notified
410 def self.valid_notification_options(user=nil)
410 def self.valid_notification_options(user=nil)
411 # Note that @user.membership.size would fail since AR ignores
411 # Note that @user.membership.size would fail since AR ignores
412 # :include association option when doing a count
412 # :include association option when doing a count
413 if user.nil? || user.memberships.length < 1
413 if user.nil? || user.memberships.length < 1
414 MAIL_NOTIFICATION_OPTIONS.reject {|option| option.first == 'selected'}
414 MAIL_NOTIFICATION_OPTIONS.reject {|option| option.first == 'selected'}
415 else
415 else
416 MAIL_NOTIFICATION_OPTIONS
416 MAIL_NOTIFICATION_OPTIONS
417 end
417 end
418 end
418 end
419
419
420 # Find a user account by matching the exact login and then a case-insensitive
420 # Find a user account by matching the exact login and then a case-insensitive
421 # version. Exact matches will be given priority.
421 # version. Exact matches will be given priority.
422 def self.find_by_login(login)
422 def self.find_by_login(login)
423 login = Redmine::CodesetUtil.replace_invalid_utf8(login.to_s)
423 login = Redmine::CodesetUtil.replace_invalid_utf8(login.to_s)
424 if login.present?
424 if login.present?
425 # First look for an exact match
425 # First look for an exact match
426 user = where(:login => login).detect {|u| u.login == login}
426 user = where(:login => login).detect {|u| u.login == login}
427 unless user
427 unless user
428 # Fail over to case-insensitive if none was found
428 # Fail over to case-insensitive if none was found
429 user = where("LOWER(login) = ?", login.downcase).first
429 user = where("LOWER(login) = ?", login.downcase).first
430 end
430 end
431 user
431 user
432 end
432 end
433 end
433 end
434
434
435 def self.find_by_rss_key(key)
435 def self.find_by_rss_key(key)
436 Token.find_active_user('feeds', key)
436 Token.find_active_user('feeds', key)
437 end
437 end
438
438
439 def self.find_by_api_key(key)
439 def self.find_by_api_key(key)
440 Token.find_active_user('api', key)
440 Token.find_active_user('api', key)
441 end
441 end
442
442
443 # Makes find_by_mail case-insensitive
443 # Makes find_by_mail case-insensitive
444 def self.find_by_mail(mail)
444 def self.find_by_mail(mail)
445 having_mail(mail).first
445 having_mail(mail).first
446 end
446 end
447
447
448 # Returns true if the default admin account can no longer be used
448 # Returns true if the default admin account can no longer be used
449 def self.default_admin_account_changed?
449 def self.default_admin_account_changed?
450 !User.active.find_by_login("admin").try(:check_password?, "admin")
450 !User.active.find_by_login("admin").try(:check_password?, "admin")
451 end
451 end
452
452
453 def to_s
453 def to_s
454 name
454 name
455 end
455 end
456
456
457 CSS_CLASS_BY_STATUS = {
457 CSS_CLASS_BY_STATUS = {
458 STATUS_ANONYMOUS => 'anon',
458 STATUS_ANONYMOUS => 'anon',
459 STATUS_ACTIVE => 'active',
459 STATUS_ACTIVE => 'active',
460 STATUS_REGISTERED => 'registered',
460 STATUS_REGISTERED => 'registered',
461 STATUS_LOCKED => 'locked'
461 STATUS_LOCKED => 'locked'
462 }
462 }
463
463
464 def css_classes
464 def css_classes
465 "user #{CSS_CLASS_BY_STATUS[status]}"
465 "user #{CSS_CLASS_BY_STATUS[status]}"
466 end
466 end
467
467
468 # Returns the current day according to user's time zone
468 # Returns the current day according to user's time zone
469 def today
469 def today
470 if time_zone.nil?
470 if time_zone.nil?
471 Date.today
471 Date.today
472 else
472 else
473 Time.now.in_time_zone(time_zone).to_date
473 Time.now.in_time_zone(time_zone).to_date
474 end
474 end
475 end
475 end
476
476
477 # Returns the day of +time+ according to user's time zone
477 # Returns the day of +time+ according to user's time zone
478 def time_to_date(time)
478 def time_to_date(time)
479 if time_zone.nil?
479 if time_zone.nil?
480 time.to_date
480 time.to_date
481 else
481 else
482 time.in_time_zone(time_zone).to_date
482 time.in_time_zone(time_zone).to_date
483 end
483 end
484 end
484 end
485
485
486 def logged?
486 def logged?
487 true
487 true
488 end
488 end
489
489
490 def anonymous?
490 def anonymous?
491 !logged?
491 !logged?
492 end
492 end
493
493
494 # Returns user's membership for the given project
494 # Returns user's membership for the given project
495 # or nil if the user is not a member of project
495 # or nil if the user is not a member of project
496 def membership(project)
496 def membership(project)
497 project_id = project.is_a?(Project) ? project.id : project
497 project_id = project.is_a?(Project) ? project.id : project
498
498
499 @membership_by_project_id ||= Hash.new {|h, project_id|
499 @membership_by_project_id ||= Hash.new {|h, project_id|
500 h[project_id] = memberships.where(:project_id => project_id).first
500 h[project_id] = memberships.where(:project_id => project_id).first
501 }
501 }
502 @membership_by_project_id[project_id]
502 @membership_by_project_id[project_id]
503 end
503 end
504
504
505 # Returns the user's bult-in role
505 # Returns the user's bult-in role
506 def builtin_role
506 def builtin_role
507 @builtin_role ||= Role.non_member
507 @builtin_role ||= Role.non_member
508 end
508 end
509
509
510 # Return user's roles for project
510 # Return user's roles for project
511 def roles_for_project(project)
511 def roles_for_project(project)
512 # No role on archived projects
512 # No role on archived projects
513 return [] if project.nil? || project.archived?
513 return [] if project.nil? || project.archived?
514 if membership = membership(project)
514 if membership = membership(project)
515 membership.roles.dup
515 membership.roles.dup
516 elsif project.is_public?
516 elsif project.is_public?
517 project.override_roles(builtin_role)
517 project.override_roles(builtin_role)
518 else
518 else
519 []
519 []
520 end
520 end
521 end
521 end
522
522
523 # Returns a hash of user's projects grouped by roles
523 # Returns a hash of user's projects grouped by roles
524 def projects_by_role
524 def projects_by_role
525 return @projects_by_role if @projects_by_role
525 return @projects_by_role if @projects_by_role
526
526
527 hash = Hash.new([])
527 hash = Hash.new([])
528
528
529 group_class = anonymous? ? GroupAnonymous : GroupNonMember
529 group_class = anonymous? ? GroupAnonymous : GroupNonMember
530 members = Member.joins(:project, :principal).
530 members = Member.joins(:project, :principal).
531 where("#{Project.table_name}.status <> 9").
531 where("#{Project.table_name}.status <> 9").
532 where("#{Member.table_name}.user_id = ? OR (#{Project.table_name}.is_public = ? AND #{Principal.table_name}.type = ?)", self.id, true, group_class.name).
532 where("#{Member.table_name}.user_id = ? OR (#{Project.table_name}.is_public = ? AND #{Principal.table_name}.type = ?)", self.id, true, group_class.name).
533 preload(:project, :roles).
533 preload(:project, :roles).
534 to_a
534 to_a
535
535
536 members.reject! {|member| member.user_id != id && project_ids.include?(member.project_id)}
536 members.reject! {|member| member.user_id != id && project_ids.include?(member.project_id)}
537 members.each do |member|
537 members.each do |member|
538 if member.project
538 if member.project
539 member.roles.each do |role|
539 member.roles.each do |role|
540 hash[role] = [] unless hash.key?(role)
540 hash[role] = [] unless hash.key?(role)
541 hash[role] << member.project
541 hash[role] << member.project
542 end
542 end
543 end
543 end
544 end
544 end
545
545
546 hash.each do |role, projects|
546 hash.each do |role, projects|
547 projects.uniq!
547 projects.uniq!
548 end
548 end
549
549
550 @projects_by_role = hash
550 @projects_by_role = hash
551 end
551 end
552
552
553 # Returns the ids of visible projects
553 # Returns the ids of visible projects
554 def visible_project_ids
554 def visible_project_ids
555 @visible_project_ids ||= Project.visible(self).pluck(:id)
555 @visible_project_ids ||= Project.visible(self).pluck(:id)
556 end
556 end
557
557
558 # Returns true if user is arg or belongs to arg
558 # Returns true if user is arg or belongs to arg
559 def is_or_belongs_to?(arg)
559 def is_or_belongs_to?(arg)
560 if arg.is_a?(User)
560 if arg.is_a?(User)
561 self == arg
561 self == arg
562 elsif arg.is_a?(Group)
562 elsif arg.is_a?(Group)
563 arg.users.include?(self)
563 arg.users.include?(self)
564 else
564 else
565 false
565 false
566 end
566 end
567 end
567 end
568
568
569 # Return true if the user is allowed to do the specified action on a specific context
569 # Return true if the user is allowed to do the specified action on a specific context
570 # Action can be:
570 # Action can be:
571 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
571 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
572 # * a permission Symbol (eg. :edit_project)
572 # * a permission Symbol (eg. :edit_project)
573 # Context can be:
573 # Context can be:
574 # * a project : returns true if user is allowed to do the specified action on this project
574 # * a project : returns true if user is allowed to do the specified action on this project
575 # * an array of projects : returns true if user is allowed on every project
575 # * an array of projects : returns true if user is allowed on every project
576 # * nil with options[:global] set : check if user has at least one role allowed for this action,
576 # * nil with options[:global] set : check if user has at least one role allowed for this action,
577 # or falls back to Non Member / Anonymous permissions depending if the user is logged
577 # or falls back to Non Member / Anonymous permissions depending if the user is logged
578 def allowed_to?(action, context, options={}, &block)
578 def allowed_to?(action, context, options={}, &block)
579 if context && context.is_a?(Project)
579 if context && context.is_a?(Project)
580 return false unless context.allows_to?(action)
580 return false unless context.allows_to?(action)
581 # Admin users are authorized for anything else
581 # Admin users are authorized for anything else
582 return true if admin?
582 return true if admin?
583
583
584 roles = roles_for_project(context)
584 roles = roles_for_project(context)
585 return false unless roles
585 return false unless roles
586 roles.any? {|role|
586 roles.any? {|role|
587 (context.is_public? || role.member?) &&
587 (context.is_public? || role.member?) &&
588 role.allowed_to?(action) &&
588 role.allowed_to?(action) &&
589 (block_given? ? yield(role, self) : true)
589 (block_given? ? yield(role, self) : true)
590 }
590 }
591 elsif context && context.is_a?(Array)
591 elsif context && context.is_a?(Array)
592 if context.empty?
592 if context.empty?
593 false
593 false
594 else
594 else
595 # Authorize if user is authorized on every element of the array
595 # Authorize if user is authorized on every element of the array
596 context.map {|project| allowed_to?(action, project, options, &block)}.reduce(:&)
596 context.map {|project| allowed_to?(action, project, options, &block)}.reduce(:&)
597 end
597 end
598 elsif context
598 elsif context
599 raise ArgumentError.new("#allowed_to? context argument must be a Project, an Array of projects or nil")
599 raise ArgumentError.new("#allowed_to? context argument must be a Project, an Array of projects or nil")
600 elsif options[:global]
600 elsif options[:global]
601 # Admin users are always authorized
601 # Admin users are always authorized
602 return true if admin?
602 return true if admin?
603
603
604 # authorize if user has at least one role that has this permission
604 # authorize if user has at least one role that has this permission
605 roles = memberships.collect {|m| m.roles}.flatten.uniq
605 roles = memberships.collect {|m| m.roles}.flatten.uniq
606 roles << (self.logged? ? Role.non_member : Role.anonymous)
606 roles << (self.logged? ? Role.non_member : Role.anonymous)
607 roles.any? {|role|
607 roles.any? {|role|
608 role.allowed_to?(action) &&
608 role.allowed_to?(action) &&
609 (block_given? ? yield(role, self) : true)
609 (block_given? ? yield(role, self) : true)
610 }
610 }
611 else
611 else
612 false
612 false
613 end
613 end
614 end
614 end
615
615
616 # Is the user allowed to do the specified action on any project?
616 # Is the user allowed to do the specified action on any project?
617 # See allowed_to? for the actions and valid options.
617 # See allowed_to? for the actions and valid options.
618 #
618 #
619 # NB: this method is not used anywhere in the core codebase as of
619 # NB: this method is not used anywhere in the core codebase as of
620 # 2.5.2, but it's used by many plugins so if we ever want to remove
620 # 2.5.2, but it's used by many plugins so if we ever want to remove
621 # it it has to be carefully deprecated for a version or two.
621 # it it has to be carefully deprecated for a version or two.
622 def allowed_to_globally?(action, options={}, &block)
622 def allowed_to_globally?(action, options={}, &block)
623 allowed_to?(action, nil, options.reverse_merge(:global => true), &block)
623 allowed_to?(action, nil, options.reverse_merge(:global => true), &block)
624 end
624 end
625
625
626 # Returns true if the user is allowed to delete the user's own account
626 # Returns true if the user is allowed to delete the user's own account
627 def own_account_deletable?
627 def own_account_deletable?
628 Setting.unsubscribe? &&
628 Setting.unsubscribe? &&
629 (!admin? || User.active.where("admin = ? AND id <> ?", true, id).exists?)
629 (!admin? || User.active.where("admin = ? AND id <> ?", true, id).exists?)
630 end
630 end
631
631
632 safe_attributes 'login',
632 safe_attributes 'login',
633 'firstname',
633 'firstname',
634 'lastname',
634 'lastname',
635 'mail',
635 'mail',
636 'mail_notification',
636 'mail_notification',
637 'notified_project_ids',
637 'notified_project_ids',
638 'language',
638 'language',
639 'custom_field_values',
639 'custom_field_values',
640 'custom_fields',
640 'custom_fields',
641 'identity_url'
641 'identity_url'
642
642
643 safe_attributes 'status',
643 safe_attributes 'status',
644 'auth_source_id',
644 'auth_source_id',
645 'generate_password',
645 'generate_password',
646 'must_change_passwd',
646 'must_change_passwd',
647 :if => lambda {|user, current_user| current_user.admin?}
647 :if => lambda {|user, current_user| current_user.admin?}
648
648
649 safe_attributes 'group_ids',
649 safe_attributes 'group_ids',
650 :if => lambda {|user, current_user| current_user.admin? && !user.new_record?}
650 :if => lambda {|user, current_user| current_user.admin? && !user.new_record?}
651
651
652 # Utility method to help check if a user should be notified about an
652 # Utility method to help check if a user should be notified about an
653 # event.
653 # event.
654 #
654 #
655 # TODO: only supports Issue events currently
655 # TODO: only supports Issue events currently
656 def notify_about?(object)
656 def notify_about?(object)
657 if mail_notification == 'all'
657 if mail_notification == 'all'
658 true
658 true
659 elsif mail_notification.blank? || mail_notification == 'none'
659 elsif mail_notification.blank? || mail_notification == 'none'
660 false
660 false
661 else
661 else
662 case object
662 case object
663 when Issue
663 when Issue
664 case mail_notification
664 case mail_notification
665 when 'selected', 'only_my_events'
665 when 'selected', 'only_my_events'
666 # user receives notifications for created/assigned issues on unselected projects
666 # user receives notifications for created/assigned issues on unselected projects
667 object.author == self || is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was)
667 object.author == self || is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was)
668 when 'only_assigned'
668 when 'only_assigned'
669 is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was)
669 is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was)
670 when 'only_owner'
670 when 'only_owner'
671 object.author == self
671 object.author == self
672 end
672 end
673 when News
673 when News
674 # always send to project members except when mail_notification is set to 'none'
674 # always send to project members except when mail_notification is set to 'none'
675 true
675 true
676 end
676 end
677 end
677 end
678 end
678 end
679
679
680 def self.current=(user)
680 def self.current=(user)
681 RequestStore.store[:current_user] = user
681 RequestStore.store[:current_user] = user
682 end
682 end
683
683
684 def self.current
684 def self.current
685 RequestStore.store[:current_user] ||= User.anonymous
685 RequestStore.store[:current_user] ||= User.anonymous
686 end
686 end
687
687
688 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
688 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
689 # one anonymous user per database.
689 # one anonymous user per database.
690 def self.anonymous
690 def self.anonymous
691 anonymous_user = AnonymousUser.first
691 anonymous_user = AnonymousUser.first
692 if anonymous_user.nil?
692 if anonymous_user.nil?
693 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :login => '', :status => 0)
693 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :login => '', :status => 0)
694 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
694 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
695 end
695 end
696 anonymous_user
696 anonymous_user
697 end
697 end
698
698
699 # Salts all existing unsalted passwords
699 # Salts all existing unsalted passwords
700 # It changes password storage scheme from SHA1(password) to SHA1(salt + SHA1(password))
700 # It changes password storage scheme from SHA1(password) to SHA1(salt + SHA1(password))
701 # This method is used in the SaltPasswords migration and is to be kept as is
701 # This method is used in the SaltPasswords migration and is to be kept as is
702 def self.salt_unsalted_passwords!
702 def self.salt_unsalted_passwords!
703 transaction do
703 transaction do
704 User.where("salt IS NULL OR salt = ''").find_each do |user|
704 User.where("salt IS NULL OR salt = ''").find_each do |user|
705 next if user.hashed_password.blank?
705 next if user.hashed_password.blank?
706 salt = User.generate_salt
706 salt = User.generate_salt
707 hashed_password = User.hash_password("#{salt}#{user.hashed_password}")
707 hashed_password = User.hash_password("#{salt}#{user.hashed_password}")
708 User.where(:id => user.id).update_all(:salt => salt, :hashed_password => hashed_password)
708 User.where(:id => user.id).update_all(:salt => salt, :hashed_password => hashed_password)
709 end
709 end
710 end
710 end
711 end
711 end
712
712
713 protected
713 protected
714
714
715 def validate_password_length
715 def validate_password_length
716 return if password.blank? && generate_password?
716 return if password.blank? && generate_password?
717 # Password length validation based on setting
717 # Password length validation based on setting
718 if !password.nil? && password.size < Setting.password_min_length.to_i
718 if !password.nil? && password.size < Setting.password_min_length.to_i
719 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
719 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
720 end
720 end
721 end
721 end
722
722
723 def instantiate_email_address
723 def instantiate_email_address
724 email_address || build_email_address
724 email_address || build_email_address
725 end
725 end
726
726
727 private
727 private
728
728
729 def generate_password_if_needed
729 def generate_password_if_needed
730 if generate_password? && auth_source.nil?
730 if generate_password? && auth_source.nil?
731 length = [Setting.password_min_length.to_i + 2, 10].max
731 length = [Setting.password_min_length.to_i + 2, 10].max
732 random_password(length)
732 random_password(length)
733 end
733 end
734 end
734 end
735
735
736 # Delete all outstanding password reset tokens on password change.
736 # Delete all outstanding password reset tokens on password change.
737 # Delete the autologin tokens on password change to prohibit session leakage.
737 # Delete the autologin tokens on password change to prohibit session leakage.
738 # This helps to keep the account secure in case the associated email account
738 # This helps to keep the account secure in case the associated email account
739 # was compromised.
739 # was compromised.
740 def destroy_tokens
740 def destroy_tokens
741 if hashed_password_changed?
741 if hashed_password_changed?
742 tokens = ['recovery', 'autologin']
742 tokens = ['recovery', 'autologin']
743 Token.where(:user_id => id, :action => tokens).delete_all
743 Token.where(:user_id => id, :action => tokens).delete_all
744 end
744 end
745 end
745 end
746
746
747 # Removes references that are not handled by associations
747 # Removes references that are not handled by associations
748 # Things that are not deleted are reassociated with the anonymous user
748 # Things that are not deleted are reassociated with the anonymous user
749 def remove_references_before_destroy
749 def remove_references_before_destroy
750 return if self.id.nil?
750 return if self.id.nil?
751
751
752 substitute = User.anonymous
752 substitute = User.anonymous
753 Attachment.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
753 Attachment.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
754 Comment.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
754 Comment.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
755 Issue.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
755 Issue.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
756 Issue.where(['assigned_to_id = ?', id]).update_all('assigned_to_id = NULL')
756 Issue.where(['assigned_to_id = ?', id]).update_all('assigned_to_id = NULL')
757 Journal.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id])
757 Journal.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id])
758 JournalDetail.
758 JournalDetail.
759 where(["property = 'attr' AND prop_key = 'assigned_to_id' AND old_value = ?", id.to_s]).
759 where(["property = 'attr' AND prop_key = 'assigned_to_id' AND old_value = ?", id.to_s]).
760 update_all(['old_value = ?', substitute.id.to_s])
760 update_all(['old_value = ?', substitute.id.to_s])
761 JournalDetail.
761 JournalDetail.
762 where(["property = 'attr' AND prop_key = 'assigned_to_id' AND value = ?", id.to_s]).
762 where(["property = 'attr' AND prop_key = 'assigned_to_id' AND value = ?", id.to_s]).
763 update_all(['value = ?', substitute.id.to_s])
763 update_all(['value = ?', substitute.id.to_s])
764 Message.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
764 Message.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
765 News.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
765 News.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
766 # Remove private queries and keep public ones
766 # Remove private queries and keep public ones
767 ::Query.delete_all ['user_id = ? AND visibility = ?', id, ::Query::VISIBILITY_PRIVATE]
767 ::Query.delete_all ['user_id = ? AND visibility = ?', id, ::Query::VISIBILITY_PRIVATE]
768 ::Query.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id])
768 ::Query.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id])
769 TimeEntry.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id])
769 TimeEntry.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id])
770 Token.delete_all ['user_id = ?', id]
770 Token.delete_all ['user_id = ?', id]
771 Watcher.delete_all ['user_id = ?', id]
771 Watcher.delete_all ['user_id = ?', id]
772 WikiContent.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
772 WikiContent.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
773 WikiContent::Version.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
773 WikiContent::Version.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
774 end
774 end
775
775
776 # Return password digest
776 # Return password digest
777 def self.hash_password(clear_password)
777 def self.hash_password(clear_password)
778 Digest::SHA1.hexdigest(clear_password || "")
778 Digest::SHA1.hexdigest(clear_password || "")
779 end
779 end
780
780
781 # Returns a 128bits random salt as a hex string (32 chars long)
781 # Returns a 128bits random salt as a hex string (32 chars long)
782 def self.generate_salt
782 def self.generate_salt
783 Redmine::Utils.random_hex(16)
783 Redmine::Utils.random_hex(16)
784 end
784 end
785
785
786 end
786 end
787
787
788 class AnonymousUser < User
788 class AnonymousUser < User
789 validate :validate_anonymous_uniqueness, :on => :create
789 validate :validate_anonymous_uniqueness, :on => :create
790
790
791 def validate_anonymous_uniqueness
791 def validate_anonymous_uniqueness
792 # There should be only one AnonymousUser in the database
792 # There should be only one AnonymousUser in the database
793 errors.add :base, 'An anonymous user already exists.' if AnonymousUser.exists?
793 errors.add :base, 'An anonymous user already exists.' if AnonymousUser.exists?
794 end
794 end
795
795
796 def available_custom_fields
796 def available_custom_fields
797 []
797 []
798 end
798 end
799
799
800 # Overrides a few properties
800 # Overrides a few properties
801 def logged?; false end
801 def logged?; false end
802 def admin; false end
802 def admin; false end
803 def name(*args); I18n.t(:label_user_anonymous) end
803 def name(*args); I18n.t(:label_user_anonymous) end
804 def mail=(*args); nil end
804 def mail=(*args); nil end
805 def mail; nil end
805 def mail; nil end
806 def time_zone; nil end
806 def time_zone; nil end
807 def rss_key; nil end
807 def rss_key; nil end
808
808
809 def pref
809 def pref
810 UserPreference.new(:user => self)
810 UserPreference.new(:user => self)
811 end
811 end
812
812
813 # Returns the user's bult-in role
813 # Returns the user's bult-in role
814 def builtin_role
814 def builtin_role
815 @builtin_role ||= Role.anonymous
815 @builtin_role ||= Role.anonymous
816 end
816 end
817
817
818 def membership(*args)
818 def membership(*args)
819 nil
819 nil
820 end
820 end
821
821
822 def member_of?(*args)
822 def member_of?(*args)
823 false
823 false
824 end
824 end
825
825
826 # Anonymous user can not be destroyed
826 # Anonymous user can not be destroyed
827 def destroy
827 def destroy
828 false
828 false
829 end
829 end
830
830
831 protected
831 protected
832
832
833 def instantiate_email_address
833 def instantiate_email_address
834 end
834 end
835 end
835 end
General Comments 0
You need to be logged in to leave comments. Login now