##// END OF EJS Templates
Use .unscoped when querying and creating builtin objects (#24156)....
Jean-Philippe Lang -
r15667:3e86a2ab52bc
parent child
Show More
@@ -1,56 +1,56
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 GroupBuiltin < Group
18 class GroupBuiltin < Group
19 validate :validate_uniqueness, :on => :create
19 validate :validate_uniqueness, :on => :create
20
20
21 def validate_uniqueness
21 def validate_uniqueness
22 errors.add :base, 'The builtin group already exists.' if self.class.exists?
22 errors.add :base, 'The builtin group already exists.' if self.class.exists?
23 end
23 end
24
24
25 def builtin?
25 def builtin?
26 true
26 true
27 end
27 end
28
28
29 def destroy
29 def destroy
30 false
30 false
31 end
31 end
32
32
33 def user_added(user)
33 def user_added(user)
34 raise 'Cannot add users to a builtin group'
34 raise 'Cannot add users to a builtin group'
35 end
35 end
36
36
37 class << self
37 class << self
38 def load_instance
38 def load_instance
39 return nil if self == GroupBuiltin
39 return nil if self == GroupBuiltin
40 instance = order('id').first || create_instance
40 instance = unscoped.order('id').first || create_instance
41 end
41 end
42
42
43 def create_instance
43 def create_instance
44 raise 'The builtin group already exists.' if exists?
44 raise 'The builtin group already exists.' if exists?
45 instance = new
45 instance = unscoped.new
46 instance.lastname = name
46 instance.lastname = name
47 instance.save :validate => false
47 instance.save :validate => false
48 raise 'Unable to create builtin group.' if instance.new_record?
48 raise 'Unable to create builtin group.' if instance.new_record?
49 instance
49 instance
50 end
50 end
51 private :create_instance
51 private :create_instance
52 end
52 end
53 end
53 end
54
54
55 require_dependency "group_anonymous"
55 require_dependency "group_anonymous"
56 require_dependency "group_non_member"
56 require_dependency "group_non_member"
@@ -1,306 +1,306
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 Role < ActiveRecord::Base
18 class Role < ActiveRecord::Base
19 include Redmine::SafeAttributes
19 include Redmine::SafeAttributes
20
20
21 # Custom coder for the permissions attribute that should be an
21 # Custom coder for the permissions attribute that should be an
22 # array of symbols. Rails 3 uses Psych which can be *unbelievably*
22 # array of symbols. Rails 3 uses Psych which can be *unbelievably*
23 # slow on some platforms (eg. mingw32).
23 # slow on some platforms (eg. mingw32).
24 class PermissionsAttributeCoder
24 class PermissionsAttributeCoder
25 def self.load(str)
25 def self.load(str)
26 str.to_s.scan(/:([a-z0-9_]+)/).flatten.map(&:to_sym)
26 str.to_s.scan(/:([a-z0-9_]+)/).flatten.map(&:to_sym)
27 end
27 end
28
28
29 def self.dump(value)
29 def self.dump(value)
30 YAML.dump(value)
30 YAML.dump(value)
31 end
31 end
32 end
32 end
33
33
34 # Built-in roles
34 # Built-in roles
35 BUILTIN_NON_MEMBER = 1
35 BUILTIN_NON_MEMBER = 1
36 BUILTIN_ANONYMOUS = 2
36 BUILTIN_ANONYMOUS = 2
37
37
38 ISSUES_VISIBILITY_OPTIONS = [
38 ISSUES_VISIBILITY_OPTIONS = [
39 ['all', :label_issues_visibility_all],
39 ['all', :label_issues_visibility_all],
40 ['default', :label_issues_visibility_public],
40 ['default', :label_issues_visibility_public],
41 ['own', :label_issues_visibility_own]
41 ['own', :label_issues_visibility_own]
42 ]
42 ]
43
43
44 TIME_ENTRIES_VISIBILITY_OPTIONS = [
44 TIME_ENTRIES_VISIBILITY_OPTIONS = [
45 ['all', :label_time_entries_visibility_all],
45 ['all', :label_time_entries_visibility_all],
46 ['own', :label_time_entries_visibility_own]
46 ['own', :label_time_entries_visibility_own]
47 ]
47 ]
48
48
49 USERS_VISIBILITY_OPTIONS = [
49 USERS_VISIBILITY_OPTIONS = [
50 ['all', :label_users_visibility_all],
50 ['all', :label_users_visibility_all],
51 ['members_of_visible_projects', :label_users_visibility_members_of_visible_projects]
51 ['members_of_visible_projects', :label_users_visibility_members_of_visible_projects]
52 ]
52 ]
53
53
54 scope :sorted, lambda { order(:builtin, :position) }
54 scope :sorted, lambda { order(:builtin, :position) }
55 scope :givable, lambda { order(:position).where(:builtin => 0) }
55 scope :givable, lambda { order(:position).where(:builtin => 0) }
56 scope :builtin, lambda { |*args|
56 scope :builtin, lambda { |*args|
57 compare = (args.first == true ? 'not' : '')
57 compare = (args.first == true ? 'not' : '')
58 where("#{compare} builtin = 0")
58 where("#{compare} builtin = 0")
59 }
59 }
60
60
61 before_destroy :check_deletable
61 before_destroy :check_deletable
62 has_many :workflow_rules, :dependent => :delete_all do
62 has_many :workflow_rules, :dependent => :delete_all do
63 def copy(source_role)
63 def copy(source_role)
64 WorkflowRule.copy(nil, source_role, nil, proxy_association.owner)
64 WorkflowRule.copy(nil, source_role, nil, proxy_association.owner)
65 end
65 end
66 end
66 end
67 has_and_belongs_to_many :custom_fields, :join_table => "#{table_name_prefix}custom_fields_roles#{table_name_suffix}", :foreign_key => "role_id"
67 has_and_belongs_to_many :custom_fields, :join_table => "#{table_name_prefix}custom_fields_roles#{table_name_suffix}", :foreign_key => "role_id"
68
68
69 has_and_belongs_to_many :managed_roles, :class_name => 'Role',
69 has_and_belongs_to_many :managed_roles, :class_name => 'Role',
70 :join_table => "#{table_name_prefix}roles_managed_roles#{table_name_suffix}",
70 :join_table => "#{table_name_prefix}roles_managed_roles#{table_name_suffix}",
71 :association_foreign_key => "managed_role_id"
71 :association_foreign_key => "managed_role_id"
72
72
73 has_many :member_roles, :dependent => :destroy
73 has_many :member_roles, :dependent => :destroy
74 has_many :members, :through => :member_roles
74 has_many :members, :through => :member_roles
75 acts_as_positioned :scope => :builtin
75 acts_as_positioned :scope => :builtin
76
76
77 serialize :permissions, ::Role::PermissionsAttributeCoder
77 serialize :permissions, ::Role::PermissionsAttributeCoder
78 store :settings, :accessors => [:permissions_all_trackers, :permissions_tracker_ids]
78 store :settings, :accessors => [:permissions_all_trackers, :permissions_tracker_ids]
79 attr_protected :builtin
79 attr_protected :builtin
80
80
81 validates_presence_of :name
81 validates_presence_of :name
82 validates_uniqueness_of :name
82 validates_uniqueness_of :name
83 validates_length_of :name, :maximum => 30
83 validates_length_of :name, :maximum => 30
84 validates_inclusion_of :issues_visibility,
84 validates_inclusion_of :issues_visibility,
85 :in => ISSUES_VISIBILITY_OPTIONS.collect(&:first),
85 :in => ISSUES_VISIBILITY_OPTIONS.collect(&:first),
86 :if => lambda {|role| role.respond_to?(:issues_visibility) && role.issues_visibility_changed?}
86 :if => lambda {|role| role.respond_to?(:issues_visibility) && role.issues_visibility_changed?}
87 validates_inclusion_of :users_visibility,
87 validates_inclusion_of :users_visibility,
88 :in => USERS_VISIBILITY_OPTIONS.collect(&:first),
88 :in => USERS_VISIBILITY_OPTIONS.collect(&:first),
89 :if => lambda {|role| role.respond_to?(:users_visibility) && role.users_visibility_changed?}
89 :if => lambda {|role| role.respond_to?(:users_visibility) && role.users_visibility_changed?}
90 validates_inclusion_of :time_entries_visibility,
90 validates_inclusion_of :time_entries_visibility,
91 :in => TIME_ENTRIES_VISIBILITY_OPTIONS.collect(&:first),
91 :in => TIME_ENTRIES_VISIBILITY_OPTIONS.collect(&:first),
92 :if => lambda {|role| role.respond_to?(:time_entries_visibility) && role.time_entries_visibility_changed?}
92 :if => lambda {|role| role.respond_to?(:time_entries_visibility) && role.time_entries_visibility_changed?}
93
93
94 safe_attributes 'name',
94 safe_attributes 'name',
95 'assignable',
95 'assignable',
96 'position',
96 'position',
97 'issues_visibility',
97 'issues_visibility',
98 'users_visibility',
98 'users_visibility',
99 'time_entries_visibility',
99 'time_entries_visibility',
100 'all_roles_managed',
100 'all_roles_managed',
101 'managed_role_ids',
101 'managed_role_ids',
102 'permissions',
102 'permissions',
103 'permissions_all_trackers',
103 'permissions_all_trackers',
104 'permissions_tracker_ids'
104 'permissions_tracker_ids'
105
105
106 # Copies attributes from another role, arg can be an id or a Role
106 # Copies attributes from another role, arg can be an id or a Role
107 def copy_from(arg, options={})
107 def copy_from(arg, options={})
108 return unless arg.present?
108 return unless arg.present?
109 role = arg.is_a?(Role) ? arg : Role.find_by_id(arg.to_s)
109 role = arg.is_a?(Role) ? arg : Role.find_by_id(arg.to_s)
110 self.attributes = role.attributes.dup.except("id", "name", "position", "builtin", "permissions")
110 self.attributes = role.attributes.dup.except("id", "name", "position", "builtin", "permissions")
111 self.permissions = role.permissions.dup
111 self.permissions = role.permissions.dup
112 self.managed_role_ids = role.managed_role_ids.dup
112 self.managed_role_ids = role.managed_role_ids.dup
113 self
113 self
114 end
114 end
115
115
116 def permissions=(perms)
116 def permissions=(perms)
117 perms = perms.collect {|p| p.to_sym unless p.blank? }.compact.uniq if perms
117 perms = perms.collect {|p| p.to_sym unless p.blank? }.compact.uniq if perms
118 write_attribute(:permissions, perms)
118 write_attribute(:permissions, perms)
119 end
119 end
120
120
121 def add_permission!(*perms)
121 def add_permission!(*perms)
122 self.permissions = [] unless permissions.is_a?(Array)
122 self.permissions = [] unless permissions.is_a?(Array)
123
123
124 permissions_will_change!
124 permissions_will_change!
125 perms.each do |p|
125 perms.each do |p|
126 p = p.to_sym
126 p = p.to_sym
127 permissions << p unless permissions.include?(p)
127 permissions << p unless permissions.include?(p)
128 end
128 end
129 save!
129 save!
130 end
130 end
131
131
132 def remove_permission!(*perms)
132 def remove_permission!(*perms)
133 return unless permissions.is_a?(Array)
133 return unless permissions.is_a?(Array)
134 permissions_will_change!
134 permissions_will_change!
135 perms.each { |p| permissions.delete(p.to_sym) }
135 perms.each { |p| permissions.delete(p.to_sym) }
136 save!
136 save!
137 end
137 end
138
138
139 # Returns true if the role has the given permission
139 # Returns true if the role has the given permission
140 def has_permission?(perm)
140 def has_permission?(perm)
141 !permissions.nil? && permissions.include?(perm.to_sym)
141 !permissions.nil? && permissions.include?(perm.to_sym)
142 end
142 end
143
143
144 def consider_workflow?
144 def consider_workflow?
145 has_permission?(:add_issues) || has_permission?(:edit_issues)
145 has_permission?(:add_issues) || has_permission?(:edit_issues)
146 end
146 end
147
147
148 def <=>(role)
148 def <=>(role)
149 if role
149 if role
150 if builtin == role.builtin
150 if builtin == role.builtin
151 position <=> role.position
151 position <=> role.position
152 else
152 else
153 builtin <=> role.builtin
153 builtin <=> role.builtin
154 end
154 end
155 else
155 else
156 -1
156 -1
157 end
157 end
158 end
158 end
159
159
160 def to_s
160 def to_s
161 name
161 name
162 end
162 end
163
163
164 def name
164 def name
165 case builtin
165 case builtin
166 when 1; l(:label_role_non_member, :default => read_attribute(:name))
166 when 1; l(:label_role_non_member, :default => read_attribute(:name))
167 when 2; l(:label_role_anonymous, :default => read_attribute(:name))
167 when 2; l(:label_role_anonymous, :default => read_attribute(:name))
168 else; read_attribute(:name)
168 else; read_attribute(:name)
169 end
169 end
170 end
170 end
171
171
172 # Return true if the role is a builtin role
172 # Return true if the role is a builtin role
173 def builtin?
173 def builtin?
174 self.builtin != 0
174 self.builtin != 0
175 end
175 end
176
176
177 # Return true if the role is the anonymous role
177 # Return true if the role is the anonymous role
178 def anonymous?
178 def anonymous?
179 builtin == 2
179 builtin == 2
180 end
180 end
181
181
182 # Return true if the role is a project member role
182 # Return true if the role is a project member role
183 def member?
183 def member?
184 !self.builtin?
184 !self.builtin?
185 end
185 end
186
186
187 # Return true if role is allowed to do the specified action
187 # Return true if role is allowed to do the specified action
188 # action can be:
188 # action can be:
189 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
189 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
190 # * a permission Symbol (eg. :edit_project)
190 # * a permission Symbol (eg. :edit_project)
191 def allowed_to?(action)
191 def allowed_to?(action)
192 if action.is_a? Hash
192 if action.is_a? Hash
193 allowed_actions.include? "#{action[:controller]}/#{action[:action]}"
193 allowed_actions.include? "#{action[:controller]}/#{action[:action]}"
194 else
194 else
195 allowed_permissions.include? action
195 allowed_permissions.include? action
196 end
196 end
197 end
197 end
198
198
199 # Return all the permissions that can be given to the role
199 # Return all the permissions that can be given to the role
200 def setable_permissions
200 def setable_permissions
201 setable_permissions = Redmine::AccessControl.permissions - Redmine::AccessControl.public_permissions
201 setable_permissions = Redmine::AccessControl.permissions - Redmine::AccessControl.public_permissions
202 setable_permissions -= Redmine::AccessControl.members_only_permissions if self.builtin == BUILTIN_NON_MEMBER
202 setable_permissions -= Redmine::AccessControl.members_only_permissions if self.builtin == BUILTIN_NON_MEMBER
203 setable_permissions -= Redmine::AccessControl.loggedin_only_permissions if self.builtin == BUILTIN_ANONYMOUS
203 setable_permissions -= Redmine::AccessControl.loggedin_only_permissions if self.builtin == BUILTIN_ANONYMOUS
204 setable_permissions
204 setable_permissions
205 end
205 end
206
206
207 def permissions_tracker_ids(*args)
207 def permissions_tracker_ids(*args)
208 if args.any?
208 if args.any?
209 Array(permissions_tracker_ids[args.first.to_s]).map(&:to_i)
209 Array(permissions_tracker_ids[args.first.to_s]).map(&:to_i)
210 else
210 else
211 super || {}
211 super || {}
212 end
212 end
213 end
213 end
214
214
215 def permissions_tracker_ids=(arg)
215 def permissions_tracker_ids=(arg)
216 h = arg.to_hash
216 h = arg.to_hash
217 h.values.each {|v| v.reject!(&:blank?)}
217 h.values.each {|v| v.reject!(&:blank?)}
218 super(h)
218 super(h)
219 end
219 end
220
220
221 # Returns true if tracker_id belongs to the list of
221 # Returns true if tracker_id belongs to the list of
222 # trackers for which permission is given
222 # trackers for which permission is given
223 def permissions_tracker_ids?(permission, tracker_id)
223 def permissions_tracker_ids?(permission, tracker_id)
224 permissions_tracker_ids(permission).include?(tracker_id)
224 permissions_tracker_ids(permission).include?(tracker_id)
225 end
225 end
226
226
227 def permissions_all_trackers
227 def permissions_all_trackers
228 super || {}
228 super || {}
229 end
229 end
230
230
231 def permissions_all_trackers=(arg)
231 def permissions_all_trackers=(arg)
232 super(arg.to_hash)
232 super(arg.to_hash)
233 end
233 end
234
234
235 # Returns true if permission is given for all trackers
235 # Returns true if permission is given for all trackers
236 def permissions_all_trackers?(permission)
236 def permissions_all_trackers?(permission)
237 permissions_all_trackers[permission.to_s].to_s != '0'
237 permissions_all_trackers[permission.to_s].to_s != '0'
238 end
238 end
239
239
240 # Returns true if permission is given for the tracker
240 # Returns true if permission is given for the tracker
241 # (explicitly or for all trackers)
241 # (explicitly or for all trackers)
242 def permissions_tracker?(permission, tracker)
242 def permissions_tracker?(permission, tracker)
243 permissions_all_trackers?(permission) ||
243 permissions_all_trackers?(permission) ||
244 permissions_tracker_ids?(permission, tracker.try(:id))
244 permissions_tracker_ids?(permission, tracker.try(:id))
245 end
245 end
246
246
247 # Sets the trackers that are allowed for a permission.
247 # Sets the trackers that are allowed for a permission.
248 # tracker_ids can be an array of tracker ids or :all for
248 # tracker_ids can be an array of tracker ids or :all for
249 # no restrictions.
249 # no restrictions.
250 #
250 #
251 # Examples:
251 # Examples:
252 # role.set_permission_trackers :add_issues, [1, 3]
252 # role.set_permission_trackers :add_issues, [1, 3]
253 # role.set_permission_trackers :add_issues, :all
253 # role.set_permission_trackers :add_issues, :all
254 def set_permission_trackers(permission, tracker_ids)
254 def set_permission_trackers(permission, tracker_ids)
255 h = {permission.to_s => (tracker_ids == :all ? '1' : '0')}
255 h = {permission.to_s => (tracker_ids == :all ? '1' : '0')}
256 self.permissions_all_trackers = permissions_all_trackers.merge(h)
256 self.permissions_all_trackers = permissions_all_trackers.merge(h)
257
257
258 h = {permission.to_s => (tracker_ids == :all ? [] : tracker_ids)}
258 h = {permission.to_s => (tracker_ids == :all ? [] : tracker_ids)}
259 self.permissions_tracker_ids = permissions_tracker_ids.merge(h)
259 self.permissions_tracker_ids = permissions_tracker_ids.merge(h)
260
260
261 self
261 self
262 end
262 end
263
263
264 # Find all the roles that can be given to a project member
264 # Find all the roles that can be given to a project member
265 def self.find_all_givable
265 def self.find_all_givable
266 Role.givable.to_a
266 Role.givable.to_a
267 end
267 end
268
268
269 # Return the builtin 'non member' role. If the role doesn't exist,
269 # Return the builtin 'non member' role. If the role doesn't exist,
270 # it will be created on the fly.
270 # it will be created on the fly.
271 def self.non_member
271 def self.non_member
272 find_or_create_system_role(BUILTIN_NON_MEMBER, 'Non member')
272 find_or_create_system_role(BUILTIN_NON_MEMBER, 'Non member')
273 end
273 end
274
274
275 # Return the builtin 'anonymous' role. If the role doesn't exist,
275 # Return the builtin 'anonymous' role. If the role doesn't exist,
276 # it will be created on the fly.
276 # it will be created on the fly.
277 def self.anonymous
277 def self.anonymous
278 find_or_create_system_role(BUILTIN_ANONYMOUS, 'Anonymous')
278 find_or_create_system_role(BUILTIN_ANONYMOUS, 'Anonymous')
279 end
279 end
280
280
281 private
281 private
282
282
283 def allowed_permissions
283 def allowed_permissions
284 @allowed_permissions ||= permissions + Redmine::AccessControl.public_permissions.collect {|p| p.name}
284 @allowed_permissions ||= permissions + Redmine::AccessControl.public_permissions.collect {|p| p.name}
285 end
285 end
286
286
287 def allowed_actions
287 def allowed_actions
288 @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten
288 @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten
289 end
289 end
290
290
291 def check_deletable
291 def check_deletable
292 raise "Cannot delete role" if members.any?
292 raise "Cannot delete role" if members.any?
293 raise "Cannot delete builtin role" if builtin?
293 raise "Cannot delete builtin role" if builtin?
294 end
294 end
295
295
296 def self.find_or_create_system_role(builtin, name)
296 def self.find_or_create_system_role(builtin, name)
297 role = where(:builtin => builtin).first
297 role = unscoped.where(:builtin => builtin).first
298 if role.nil?
298 if role.nil?
299 role = create(:name => name) do |r|
299 role = unscoped.create(:name => name) do |r|
300 r.builtin = builtin
300 r.builtin = builtin
301 end
301 end
302 raise "Unable to create the #{name} role (#{role.errors.full_messages.join(',')})." if role.new_record?
302 raise "Unable to create the #{name} role (#{role.errors.full_messages.join(',')})." if role.new_record?
303 end
303 end
304 role
304 role
305 end
305 end
306 end
306 end
@@ -1,929 +1,929
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 @membership_by_project_id = nil
166 @membership_by_project_id = nil
167 @notified_projects_ids = nil
167 @notified_projects_ids = nil
168 @notified_projects_ids_changed = false
168 @notified_projects_ids_changed = false
169 @builtin_role = nil
169 @builtin_role = nil
170 @visible_project_ids = nil
170 @visible_project_ids = nil
171 @managed_roles = nil
171 @managed_roles = nil
172 base_reload(*args)
172 base_reload(*args)
173 end
173 end
174
174
175 def mail
175 def mail
176 email_address.try(:address)
176 email_address.try(:address)
177 end
177 end
178
178
179 def mail=(arg)
179 def mail=(arg)
180 email = email_address || build_email_address
180 email = email_address || build_email_address
181 email.address = arg
181 email.address = arg
182 end
182 end
183
183
184 def mail_changed?
184 def mail_changed?
185 email_address.try(:address_changed?)
185 email_address.try(:address_changed?)
186 end
186 end
187
187
188 def mails
188 def mails
189 email_addresses.pluck(:address)
189 email_addresses.pluck(:address)
190 end
190 end
191
191
192 def self.find_or_initialize_by_identity_url(url)
192 def self.find_or_initialize_by_identity_url(url)
193 user = where(:identity_url => url).first
193 user = where(:identity_url => url).first
194 unless user
194 unless user
195 user = User.new
195 user = User.new
196 user.identity_url = url
196 user.identity_url = url
197 end
197 end
198 user
198 user
199 end
199 end
200
200
201 def identity_url=(url)
201 def identity_url=(url)
202 if url.blank?
202 if url.blank?
203 write_attribute(:identity_url, '')
203 write_attribute(:identity_url, '')
204 else
204 else
205 begin
205 begin
206 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
206 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
207 rescue OpenIdAuthentication::InvalidOpenId
207 rescue OpenIdAuthentication::InvalidOpenId
208 # Invalid url, don't save
208 # Invalid url, don't save
209 end
209 end
210 end
210 end
211 self.read_attribute(:identity_url)
211 self.read_attribute(:identity_url)
212 end
212 end
213
213
214 # Returns the user that matches provided login and password, or nil
214 # Returns the user that matches provided login and password, or nil
215 def self.try_to_login(login, password, active_only=true)
215 def self.try_to_login(login, password, active_only=true)
216 login = login.to_s
216 login = login.to_s
217 password = password.to_s
217 password = password.to_s
218
218
219 # Make sure no one can sign in with an empty login or password
219 # Make sure no one can sign in with an empty login or password
220 return nil if login.empty? || password.empty?
220 return nil if login.empty? || password.empty?
221 user = find_by_login(login)
221 user = find_by_login(login)
222 if user
222 if user
223 # user is already in local database
223 # user is already in local database
224 return nil unless user.check_password?(password)
224 return nil unless user.check_password?(password)
225 return nil if !user.active? && active_only
225 return nil if !user.active? && active_only
226 else
226 else
227 # user is not yet registered, try to authenticate with available sources
227 # user is not yet registered, try to authenticate with available sources
228 attrs = AuthSource.authenticate(login, password)
228 attrs = AuthSource.authenticate(login, password)
229 if attrs
229 if attrs
230 user = new(attrs)
230 user = new(attrs)
231 user.login = login
231 user.login = login
232 user.language = Setting.default_language
232 user.language = Setting.default_language
233 if user.save
233 if user.save
234 user.reload
234 user.reload
235 logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source
235 logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source
236 end
236 end
237 end
237 end
238 end
238 end
239 user.update_column(:last_login_on, Time.now) if user && !user.new_record? && user.active?
239 user.update_column(:last_login_on, Time.now) if user && !user.new_record? && user.active?
240 user
240 user
241 rescue => text
241 rescue => text
242 raise text
242 raise text
243 end
243 end
244
244
245 # Returns the user who matches the given autologin +key+ or nil
245 # Returns the user who matches the given autologin +key+ or nil
246 def self.try_to_autologin(key)
246 def self.try_to_autologin(key)
247 user = Token.find_active_user('autologin', key, Setting.autologin.to_i)
247 user = Token.find_active_user('autologin', key, Setting.autologin.to_i)
248 if user
248 if user
249 user.update_column(:last_login_on, Time.now)
249 user.update_column(:last_login_on, Time.now)
250 user
250 user
251 end
251 end
252 end
252 end
253
253
254 def self.name_formatter(formatter = nil)
254 def self.name_formatter(formatter = nil)
255 USER_FORMATS[formatter || Setting.user_format] || USER_FORMATS[:firstname_lastname]
255 USER_FORMATS[formatter || Setting.user_format] || USER_FORMATS[:firstname_lastname]
256 end
256 end
257
257
258 # Returns an array of fields names than can be used to make an order statement for users
258 # Returns an array of fields names than can be used to make an order statement for users
259 # according to how user names are displayed
259 # according to how user names are displayed
260 # Examples:
260 # Examples:
261 #
261 #
262 # User.fields_for_order_statement => ['users.login', 'users.id']
262 # User.fields_for_order_statement => ['users.login', 'users.id']
263 # User.fields_for_order_statement('authors') => ['authors.login', 'authors.id']
263 # User.fields_for_order_statement('authors') => ['authors.login', 'authors.id']
264 def self.fields_for_order_statement(table=nil)
264 def self.fields_for_order_statement(table=nil)
265 table ||= table_name
265 table ||= table_name
266 name_formatter[:order].map {|field| "#{table}.#{field}"}
266 name_formatter[:order].map {|field| "#{table}.#{field}"}
267 end
267 end
268
268
269 # Return user's full name for display
269 # Return user's full name for display
270 def name(formatter = nil)
270 def name(formatter = nil)
271 f = self.class.name_formatter(formatter)
271 f = self.class.name_formatter(formatter)
272 if formatter
272 if formatter
273 eval('"' + f[:string] + '"')
273 eval('"' + f[:string] + '"')
274 else
274 else
275 @name ||= eval('"' + f[:string] + '"')
275 @name ||= eval('"' + f[:string] + '"')
276 end
276 end
277 end
277 end
278
278
279 def active?
279 def active?
280 self.status == STATUS_ACTIVE
280 self.status == STATUS_ACTIVE
281 end
281 end
282
282
283 def registered?
283 def registered?
284 self.status == STATUS_REGISTERED
284 self.status == STATUS_REGISTERED
285 end
285 end
286
286
287 def locked?
287 def locked?
288 self.status == STATUS_LOCKED
288 self.status == STATUS_LOCKED
289 end
289 end
290
290
291 def activate
291 def activate
292 self.status = STATUS_ACTIVE
292 self.status = STATUS_ACTIVE
293 end
293 end
294
294
295 def register
295 def register
296 self.status = STATUS_REGISTERED
296 self.status = STATUS_REGISTERED
297 end
297 end
298
298
299 def lock
299 def lock
300 self.status = STATUS_LOCKED
300 self.status = STATUS_LOCKED
301 end
301 end
302
302
303 def activate!
303 def activate!
304 update_attribute(:status, STATUS_ACTIVE)
304 update_attribute(:status, STATUS_ACTIVE)
305 end
305 end
306
306
307 def register!
307 def register!
308 update_attribute(:status, STATUS_REGISTERED)
308 update_attribute(:status, STATUS_REGISTERED)
309 end
309 end
310
310
311 def lock!
311 def lock!
312 update_attribute(:status, STATUS_LOCKED)
312 update_attribute(:status, STATUS_LOCKED)
313 end
313 end
314
314
315 # Returns true if +clear_password+ is the correct user's password, otherwise false
315 # Returns true if +clear_password+ is the correct user's password, otherwise false
316 def check_password?(clear_password)
316 def check_password?(clear_password)
317 if auth_source_id.present?
317 if auth_source_id.present?
318 auth_source.authenticate(self.login, clear_password)
318 auth_source.authenticate(self.login, clear_password)
319 else
319 else
320 User.hash_password("#{salt}#{User.hash_password clear_password}") == hashed_password
320 User.hash_password("#{salt}#{User.hash_password clear_password}") == hashed_password
321 end
321 end
322 end
322 end
323
323
324 # Generates a random salt and computes hashed_password for +clear_password+
324 # Generates a random salt and computes hashed_password for +clear_password+
325 # The hashed password is stored in the following form: SHA1(salt + SHA1(password))
325 # The hashed password is stored in the following form: SHA1(salt + SHA1(password))
326 def salt_password(clear_password)
326 def salt_password(clear_password)
327 self.salt = User.generate_salt
327 self.salt = User.generate_salt
328 self.hashed_password = User.hash_password("#{salt}#{User.hash_password clear_password}")
328 self.hashed_password = User.hash_password("#{salt}#{User.hash_password clear_password}")
329 self.passwd_changed_on = Time.now.change(:usec => 0)
329 self.passwd_changed_on = Time.now.change(:usec => 0)
330 end
330 end
331
331
332 # Does the backend storage allow this user to change their password?
332 # Does the backend storage allow this user to change their password?
333 def change_password_allowed?
333 def change_password_allowed?
334 return true if auth_source.nil?
334 return true if auth_source.nil?
335 return auth_source.allow_password_changes?
335 return auth_source.allow_password_changes?
336 end
336 end
337
337
338 # Returns true if the user password has expired
338 # Returns true if the user password has expired
339 def password_expired?
339 def password_expired?
340 period = Setting.password_max_age.to_i
340 period = Setting.password_max_age.to_i
341 if period.zero?
341 if period.zero?
342 false
342 false
343 else
343 else
344 changed_on = self.passwd_changed_on || Time.at(0)
344 changed_on = self.passwd_changed_on || Time.at(0)
345 changed_on < period.days.ago
345 changed_on < period.days.ago
346 end
346 end
347 end
347 end
348
348
349 def must_change_password?
349 def must_change_password?
350 (must_change_passwd? || password_expired?) && change_password_allowed?
350 (must_change_passwd? || password_expired?) && change_password_allowed?
351 end
351 end
352
352
353 def generate_password?
353 def generate_password?
354 generate_password == '1' || generate_password == true
354 generate_password == '1' || generate_password == true
355 end
355 end
356
356
357 # Generate and set a random password on given length
357 # Generate and set a random password on given length
358 def random_password(length=40)
358 def random_password(length=40)
359 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
359 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
360 chars -= %w(0 O 1 l)
360 chars -= %w(0 O 1 l)
361 password = ''
361 password = ''
362 length.times {|i| password << chars[SecureRandom.random_number(chars.size)] }
362 length.times {|i| password << chars[SecureRandom.random_number(chars.size)] }
363 self.password = password
363 self.password = password
364 self.password_confirmation = password
364 self.password_confirmation = password
365 self
365 self
366 end
366 end
367
367
368 def pref
368 def pref
369 self.preference ||= UserPreference.new(:user => self)
369 self.preference ||= UserPreference.new(:user => self)
370 end
370 end
371
371
372 def time_zone
372 def time_zone
373 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
373 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
374 end
374 end
375
375
376 def force_default_language?
376 def force_default_language?
377 Setting.force_default_language_for_loggedin?
377 Setting.force_default_language_for_loggedin?
378 end
378 end
379
379
380 def language
380 def language
381 if force_default_language?
381 if force_default_language?
382 Setting.default_language
382 Setting.default_language
383 else
383 else
384 super
384 super
385 end
385 end
386 end
386 end
387
387
388 def wants_comments_in_reverse_order?
388 def wants_comments_in_reverse_order?
389 self.pref[:comments_sorting] == 'desc'
389 self.pref[:comments_sorting] == 'desc'
390 end
390 end
391
391
392 # Return user's RSS key (a 40 chars long string), used to access feeds
392 # Return user's RSS key (a 40 chars long string), used to access feeds
393 def rss_key
393 def rss_key
394 if rss_token.nil?
394 if rss_token.nil?
395 create_rss_token(:action => 'feeds')
395 create_rss_token(:action => 'feeds')
396 end
396 end
397 rss_token.value
397 rss_token.value
398 end
398 end
399
399
400 # Return user's API key (a 40 chars long string), used to access the API
400 # Return user's API key (a 40 chars long string), used to access the API
401 def api_key
401 def api_key
402 if api_token.nil?
402 if api_token.nil?
403 create_api_token(:action => 'api')
403 create_api_token(:action => 'api')
404 end
404 end
405 api_token.value
405 api_token.value
406 end
406 end
407
407
408 # Generates a new session token and returns its value
408 # Generates a new session token and returns its value
409 def generate_session_token
409 def generate_session_token
410 token = Token.create!(:user_id => id, :action => 'session')
410 token = Token.create!(:user_id => id, :action => 'session')
411 token.value
411 token.value
412 end
412 end
413
413
414 # Returns true if token is a valid session token for the user whose id is user_id
414 # Returns true if token is a valid session token for the user whose id is user_id
415 def self.verify_session_token(user_id, token)
415 def self.verify_session_token(user_id, token)
416 return false if user_id.blank? || token.blank?
416 return false if user_id.blank? || token.blank?
417
417
418 scope = Token.where(:user_id => user_id, :value => token.to_s, :action => 'session')
418 scope = Token.where(:user_id => user_id, :value => token.to_s, :action => 'session')
419 if Setting.session_lifetime?
419 if Setting.session_lifetime?
420 scope = scope.where("created_on > ?", Setting.session_lifetime.to_i.minutes.ago)
420 scope = scope.where("created_on > ?", Setting.session_lifetime.to_i.minutes.ago)
421 end
421 end
422 if Setting.session_timeout?
422 if Setting.session_timeout?
423 scope = scope.where("updated_on > ?", Setting.session_timeout.to_i.minutes.ago)
423 scope = scope.where("updated_on > ?", Setting.session_timeout.to_i.minutes.ago)
424 end
424 end
425 scope.update_all(:updated_on => Time.now) == 1
425 scope.update_all(:updated_on => Time.now) == 1
426 end
426 end
427
427
428 # Return an array of project ids for which the user has explicitly turned mail notifications on
428 # Return an array of project ids for which the user has explicitly turned mail notifications on
429 def notified_projects_ids
429 def notified_projects_ids
430 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
430 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
431 end
431 end
432
432
433 def notified_project_ids=(ids)
433 def notified_project_ids=(ids)
434 @notified_projects_ids_changed = true
434 @notified_projects_ids_changed = true
435 @notified_projects_ids = ids.map(&:to_i).uniq.select {|n| n > 0}
435 @notified_projects_ids = ids.map(&:to_i).uniq.select {|n| n > 0}
436 end
436 end
437
437
438 # Updates per project notifications (after_save callback)
438 # Updates per project notifications (after_save callback)
439 def update_notified_project_ids
439 def update_notified_project_ids
440 if @notified_projects_ids_changed
440 if @notified_projects_ids_changed
441 ids = (mail_notification == 'selected' ? Array.wrap(notified_projects_ids).reject(&:blank?) : [])
441 ids = (mail_notification == 'selected' ? Array.wrap(notified_projects_ids).reject(&:blank?) : [])
442 members.update_all(:mail_notification => false)
442 members.update_all(:mail_notification => false)
443 members.where(:project_id => ids).update_all(:mail_notification => true) if ids.any?
443 members.where(:project_id => ids).update_all(:mail_notification => true) if ids.any?
444 end
444 end
445 end
445 end
446 private :update_notified_project_ids
446 private :update_notified_project_ids
447
447
448 def valid_notification_options
448 def valid_notification_options
449 self.class.valid_notification_options(self)
449 self.class.valid_notification_options(self)
450 end
450 end
451
451
452 # Only users that belong to more than 1 project can select projects for which they are notified
452 # Only users that belong to more than 1 project can select projects for which they are notified
453 def self.valid_notification_options(user=nil)
453 def self.valid_notification_options(user=nil)
454 # Note that @user.membership.size would fail since AR ignores
454 # Note that @user.membership.size would fail since AR ignores
455 # :include association option when doing a count
455 # :include association option when doing a count
456 if user.nil? || user.memberships.length < 1
456 if user.nil? || user.memberships.length < 1
457 MAIL_NOTIFICATION_OPTIONS.reject {|option| option.first == 'selected'}
457 MAIL_NOTIFICATION_OPTIONS.reject {|option| option.first == 'selected'}
458 else
458 else
459 MAIL_NOTIFICATION_OPTIONS
459 MAIL_NOTIFICATION_OPTIONS
460 end
460 end
461 end
461 end
462
462
463 # Find a user account by matching the exact login and then a case-insensitive
463 # Find a user account by matching the exact login and then a case-insensitive
464 # version. Exact matches will be given priority.
464 # version. Exact matches will be given priority.
465 def self.find_by_login(login)
465 def self.find_by_login(login)
466 login = Redmine::CodesetUtil.replace_invalid_utf8(login.to_s)
466 login = Redmine::CodesetUtil.replace_invalid_utf8(login.to_s)
467 if login.present?
467 if login.present?
468 # First look for an exact match
468 # First look for an exact match
469 user = where(:login => login).detect {|u| u.login == login}
469 user = where(:login => login).detect {|u| u.login == login}
470 unless user
470 unless user
471 # Fail over to case-insensitive if none was found
471 # Fail over to case-insensitive if none was found
472 user = where("LOWER(login) = ?", login.downcase).first
472 user = where("LOWER(login) = ?", login.downcase).first
473 end
473 end
474 user
474 user
475 end
475 end
476 end
476 end
477
477
478 def self.find_by_rss_key(key)
478 def self.find_by_rss_key(key)
479 Token.find_active_user('feeds', key)
479 Token.find_active_user('feeds', key)
480 end
480 end
481
481
482 def self.find_by_api_key(key)
482 def self.find_by_api_key(key)
483 Token.find_active_user('api', key)
483 Token.find_active_user('api', key)
484 end
484 end
485
485
486 # Makes find_by_mail case-insensitive
486 # Makes find_by_mail case-insensitive
487 def self.find_by_mail(mail)
487 def self.find_by_mail(mail)
488 having_mail(mail).first
488 having_mail(mail).first
489 end
489 end
490
490
491 # Returns true if the default admin account can no longer be used
491 # Returns true if the default admin account can no longer be used
492 def self.default_admin_account_changed?
492 def self.default_admin_account_changed?
493 !User.active.find_by_login("admin").try(:check_password?, "admin")
493 !User.active.find_by_login("admin").try(:check_password?, "admin")
494 end
494 end
495
495
496 def to_s
496 def to_s
497 name
497 name
498 end
498 end
499
499
500 CSS_CLASS_BY_STATUS = {
500 CSS_CLASS_BY_STATUS = {
501 STATUS_ANONYMOUS => 'anon',
501 STATUS_ANONYMOUS => 'anon',
502 STATUS_ACTIVE => 'active',
502 STATUS_ACTIVE => 'active',
503 STATUS_REGISTERED => 'registered',
503 STATUS_REGISTERED => 'registered',
504 STATUS_LOCKED => 'locked'
504 STATUS_LOCKED => 'locked'
505 }
505 }
506
506
507 def css_classes
507 def css_classes
508 "user #{CSS_CLASS_BY_STATUS[status]}"
508 "user #{CSS_CLASS_BY_STATUS[status]}"
509 end
509 end
510
510
511 # Returns the current day according to user's time zone
511 # Returns the current day according to user's time zone
512 def today
512 def today
513 if time_zone.nil?
513 if time_zone.nil?
514 Date.today
514 Date.today
515 else
515 else
516 time_zone.today
516 time_zone.today
517 end
517 end
518 end
518 end
519
519
520 # Returns the day of +time+ according to user's time zone
520 # Returns the day of +time+ according to user's time zone
521 def time_to_date(time)
521 def time_to_date(time)
522 if time_zone.nil?
522 if time_zone.nil?
523 time.to_date
523 time.to_date
524 else
524 else
525 time.in_time_zone(time_zone).to_date
525 time.in_time_zone(time_zone).to_date
526 end
526 end
527 end
527 end
528
528
529 def logged?
529 def logged?
530 true
530 true
531 end
531 end
532
532
533 def anonymous?
533 def anonymous?
534 !logged?
534 !logged?
535 end
535 end
536
536
537 # Returns user's membership for the given project
537 # Returns user's membership for the given project
538 # or nil if the user is not a member of project
538 # or nil if the user is not a member of project
539 def membership(project)
539 def membership(project)
540 project_id = project.is_a?(Project) ? project.id : project
540 project_id = project.is_a?(Project) ? project.id : project
541
541
542 @membership_by_project_id ||= Hash.new {|h, project_id|
542 @membership_by_project_id ||= Hash.new {|h, project_id|
543 h[project_id] = memberships.where(:project_id => project_id).first
543 h[project_id] = memberships.where(:project_id => project_id).first
544 }
544 }
545 @membership_by_project_id[project_id]
545 @membership_by_project_id[project_id]
546 end
546 end
547
547
548 # Returns the user's bult-in role
548 # Returns the user's bult-in role
549 def builtin_role
549 def builtin_role
550 @builtin_role ||= Role.non_member
550 @builtin_role ||= Role.non_member
551 end
551 end
552
552
553 # Return user's roles for project
553 # Return user's roles for project
554 def roles_for_project(project)
554 def roles_for_project(project)
555 # No role on archived projects
555 # No role on archived projects
556 return [] if project.nil? || project.archived?
556 return [] if project.nil? || project.archived?
557 if membership = membership(project)
557 if membership = membership(project)
558 membership.roles.to_a
558 membership.roles.to_a
559 elsif project.is_public?
559 elsif project.is_public?
560 project.override_roles(builtin_role)
560 project.override_roles(builtin_role)
561 else
561 else
562 []
562 []
563 end
563 end
564 end
564 end
565
565
566 # Returns a hash of user's projects grouped by roles
566 # Returns a hash of user's projects grouped by roles
567 def projects_by_role
567 def projects_by_role
568 return @projects_by_role if @projects_by_role
568 return @projects_by_role if @projects_by_role
569
569
570 hash = Hash.new([])
570 hash = Hash.new([])
571
571
572 group_class = anonymous? ? GroupAnonymous : GroupNonMember
572 group_class = anonymous? ? GroupAnonymous : GroupNonMember
573 members = Member.joins(:project, :principal).
573 members = Member.joins(:project, :principal).
574 where("#{Project.table_name}.status <> 9").
574 where("#{Project.table_name}.status <> 9").
575 where("#{Member.table_name}.user_id = ? OR (#{Project.table_name}.is_public = ? AND #{Principal.table_name}.type = ?)", self.id, true, group_class.name).
575 where("#{Member.table_name}.user_id = ? OR (#{Project.table_name}.is_public = ? AND #{Principal.table_name}.type = ?)", self.id, true, group_class.name).
576 preload(:project, :roles).
576 preload(:project, :roles).
577 to_a
577 to_a
578
578
579 members.reject! {|member| member.user_id != id && project_ids.include?(member.project_id)}
579 members.reject! {|member| member.user_id != id && project_ids.include?(member.project_id)}
580 members.each do |member|
580 members.each do |member|
581 if member.project
581 if member.project
582 member.roles.each do |role|
582 member.roles.each do |role|
583 hash[role] = [] unless hash.key?(role)
583 hash[role] = [] unless hash.key?(role)
584 hash[role] << member.project
584 hash[role] << member.project
585 end
585 end
586 end
586 end
587 end
587 end
588
588
589 hash.each do |role, projects|
589 hash.each do |role, projects|
590 projects.uniq!
590 projects.uniq!
591 end
591 end
592
592
593 @projects_by_role = hash
593 @projects_by_role = hash
594 end
594 end
595
595
596 # Returns the ids of visible projects
596 # Returns the ids of visible projects
597 def visible_project_ids
597 def visible_project_ids
598 @visible_project_ids ||= Project.visible(self).pluck(:id)
598 @visible_project_ids ||= Project.visible(self).pluck(:id)
599 end
599 end
600
600
601 # Returns the roles that the user is allowed to manage for the given project
601 # Returns the roles that the user is allowed to manage for the given project
602 def managed_roles(project)
602 def managed_roles(project)
603 if admin?
603 if admin?
604 @managed_roles ||= Role.givable.to_a
604 @managed_roles ||= Role.givable.to_a
605 else
605 else
606 membership(project).try(:managed_roles) || []
606 membership(project).try(:managed_roles) || []
607 end
607 end
608 end
608 end
609
609
610 # Returns true if user is arg or belongs to arg
610 # Returns true if user is arg or belongs to arg
611 def is_or_belongs_to?(arg)
611 def is_or_belongs_to?(arg)
612 if arg.is_a?(User)
612 if arg.is_a?(User)
613 self == arg
613 self == arg
614 elsif arg.is_a?(Group)
614 elsif arg.is_a?(Group)
615 arg.users.include?(self)
615 arg.users.include?(self)
616 else
616 else
617 false
617 false
618 end
618 end
619 end
619 end
620
620
621 # Return true if the user is allowed to do the specified action on a specific context
621 # Return true if the user is allowed to do the specified action on a specific context
622 # Action can be:
622 # Action can be:
623 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
623 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
624 # * a permission Symbol (eg. :edit_project)
624 # * a permission Symbol (eg. :edit_project)
625 # Context can be:
625 # Context can be:
626 # * a project : returns true if user is allowed to do the specified action on this project
626 # * a project : returns true if user is allowed to do the specified action on this project
627 # * an array of projects : returns true if user is allowed on every project
627 # * an array of projects : returns true if user is allowed on every project
628 # * nil with options[:global] set : check if user has at least one role allowed for this action,
628 # * nil with options[:global] set : check if user has at least one role allowed for this action,
629 # or falls back to Non Member / Anonymous permissions depending if the user is logged
629 # or falls back to Non Member / Anonymous permissions depending if the user is logged
630 def allowed_to?(action, context, options={}, &block)
630 def allowed_to?(action, context, options={}, &block)
631 if context && context.is_a?(Project)
631 if context && context.is_a?(Project)
632 return false unless context.allows_to?(action)
632 return false unless context.allows_to?(action)
633 # Admin users are authorized for anything else
633 # Admin users are authorized for anything else
634 return true if admin?
634 return true if admin?
635
635
636 roles = roles_for_project(context)
636 roles = roles_for_project(context)
637 return false unless roles
637 return false unless roles
638 roles.any? {|role|
638 roles.any? {|role|
639 (context.is_public? || role.member?) &&
639 (context.is_public? || role.member?) &&
640 role.allowed_to?(action) &&
640 role.allowed_to?(action) &&
641 (block_given? ? yield(role, self) : true)
641 (block_given? ? yield(role, self) : true)
642 }
642 }
643 elsif context && context.is_a?(Array)
643 elsif context && context.is_a?(Array)
644 if context.empty?
644 if context.empty?
645 false
645 false
646 else
646 else
647 # Authorize if user is authorized on every element of the array
647 # Authorize if user is authorized on every element of the array
648 context.map {|project| allowed_to?(action, project, options, &block)}.reduce(:&)
648 context.map {|project| allowed_to?(action, project, options, &block)}.reduce(:&)
649 end
649 end
650 elsif context
650 elsif context
651 raise ArgumentError.new("#allowed_to? context argument must be a Project, an Array of projects or nil")
651 raise ArgumentError.new("#allowed_to? context argument must be a Project, an Array of projects or nil")
652 elsif options[:global]
652 elsif options[:global]
653 # Admin users are always authorized
653 # Admin users are always authorized
654 return true if admin?
654 return true if admin?
655
655
656 # authorize if user has at least one role that has this permission
656 # authorize if user has at least one role that has this permission
657 roles = memberships.collect {|m| m.roles}.flatten.uniq
657 roles = memberships.collect {|m| m.roles}.flatten.uniq
658 roles << (self.logged? ? Role.non_member : Role.anonymous)
658 roles << (self.logged? ? Role.non_member : Role.anonymous)
659 roles.any? {|role|
659 roles.any? {|role|
660 role.allowed_to?(action) &&
660 role.allowed_to?(action) &&
661 (block_given? ? yield(role, self) : true)
661 (block_given? ? yield(role, self) : true)
662 }
662 }
663 else
663 else
664 false
664 false
665 end
665 end
666 end
666 end
667
667
668 # Is the user allowed to do the specified action on any project?
668 # Is the user allowed to do the specified action on any project?
669 # See allowed_to? for the actions and valid options.
669 # See allowed_to? for the actions and valid options.
670 #
670 #
671 # NB: this method is not used anywhere in the core codebase as of
671 # NB: this method is not used anywhere in the core codebase as of
672 # 2.5.2, but it's used by many plugins so if we ever want to remove
672 # 2.5.2, but it's used by many plugins so if we ever want to remove
673 # it it has to be carefully deprecated for a version or two.
673 # it it has to be carefully deprecated for a version or two.
674 def allowed_to_globally?(action, options={}, &block)
674 def allowed_to_globally?(action, options={}, &block)
675 allowed_to?(action, nil, options.reverse_merge(:global => true), &block)
675 allowed_to?(action, nil, options.reverse_merge(:global => true), &block)
676 end
676 end
677
677
678 def allowed_to_view_all_time_entries?(context)
678 def allowed_to_view_all_time_entries?(context)
679 allowed_to?(:view_time_entries, context) do |role, user|
679 allowed_to?(:view_time_entries, context) do |role, user|
680 role.time_entries_visibility == 'all'
680 role.time_entries_visibility == 'all'
681 end
681 end
682 end
682 end
683
683
684 # Returns true if the user is allowed to delete the user's own account
684 # Returns true if the user is allowed to delete the user's own account
685 def own_account_deletable?
685 def own_account_deletable?
686 Setting.unsubscribe? &&
686 Setting.unsubscribe? &&
687 (!admin? || User.active.where("admin = ? AND id <> ?", true, id).exists?)
687 (!admin? || User.active.where("admin = ? AND id <> ?", true, id).exists?)
688 end
688 end
689
689
690 safe_attributes 'firstname',
690 safe_attributes 'firstname',
691 'lastname',
691 'lastname',
692 'mail',
692 'mail',
693 'mail_notification',
693 'mail_notification',
694 'notified_project_ids',
694 'notified_project_ids',
695 'language',
695 'language',
696 'custom_field_values',
696 'custom_field_values',
697 'custom_fields',
697 'custom_fields',
698 'identity_url'
698 'identity_url'
699
699
700 safe_attributes 'login',
700 safe_attributes 'login',
701 :if => lambda {|user, current_user| user.new_record?}
701 :if => lambda {|user, current_user| user.new_record?}
702
702
703 safe_attributes 'status',
703 safe_attributes 'status',
704 'auth_source_id',
704 'auth_source_id',
705 'generate_password',
705 'generate_password',
706 'must_change_passwd',
706 'must_change_passwd',
707 'login',
707 'login',
708 'admin',
708 'admin',
709 :if => lambda {|user, current_user| current_user.admin?}
709 :if => lambda {|user, current_user| current_user.admin?}
710
710
711 safe_attributes 'group_ids',
711 safe_attributes 'group_ids',
712 :if => lambda {|user, current_user| current_user.admin? && !user.new_record?}
712 :if => lambda {|user, current_user| current_user.admin? && !user.new_record?}
713
713
714 # Utility method to help check if a user should be notified about an
714 # Utility method to help check if a user should be notified about an
715 # event.
715 # event.
716 #
716 #
717 # TODO: only supports Issue events currently
717 # TODO: only supports Issue events currently
718 def notify_about?(object)
718 def notify_about?(object)
719 if mail_notification == 'all'
719 if mail_notification == 'all'
720 true
720 true
721 elsif mail_notification.blank? || mail_notification == 'none'
721 elsif mail_notification.blank? || mail_notification == 'none'
722 false
722 false
723 else
723 else
724 case object
724 case object
725 when Issue
725 when Issue
726 case mail_notification
726 case mail_notification
727 when 'selected', 'only_my_events'
727 when 'selected', 'only_my_events'
728 # user receives notifications for created/assigned issues on unselected projects
728 # user receives notifications for created/assigned issues on unselected projects
729 object.author == self || is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was)
729 object.author == self || is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was)
730 when 'only_assigned'
730 when 'only_assigned'
731 is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was)
731 is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was)
732 when 'only_owner'
732 when 'only_owner'
733 object.author == self
733 object.author == self
734 end
734 end
735 when News
735 when News
736 # always send to project members except when mail_notification is set to 'none'
736 # always send to project members except when mail_notification is set to 'none'
737 true
737 true
738 end
738 end
739 end
739 end
740 end
740 end
741
741
742 def self.current=(user)
742 def self.current=(user)
743 RequestStore.store[:current_user] = user
743 RequestStore.store[:current_user] = user
744 end
744 end
745
745
746 def self.current
746 def self.current
747 RequestStore.store[:current_user] ||= User.anonymous
747 RequestStore.store[:current_user] ||= User.anonymous
748 end
748 end
749
749
750 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
750 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
751 # one anonymous user per database.
751 # one anonymous user per database.
752 def self.anonymous
752 def self.anonymous
753 anonymous_user = AnonymousUser.first
753 anonymous_user = AnonymousUser.unscoped.first
754 if anonymous_user.nil?
754 if anonymous_user.nil?
755 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :login => '', :status => 0)
755 anonymous_user = AnonymousUser.unscoped.create(:lastname => 'Anonymous', :firstname => '', :login => '', :status => 0)
756 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
756 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
757 end
757 end
758 anonymous_user
758 anonymous_user
759 end
759 end
760
760
761 # Salts all existing unsalted passwords
761 # Salts all existing unsalted passwords
762 # It changes password storage scheme from SHA1(password) to SHA1(salt + SHA1(password))
762 # It changes password storage scheme from SHA1(password) to SHA1(salt + SHA1(password))
763 # This method is used in the SaltPasswords migration and is to be kept as is
763 # This method is used in the SaltPasswords migration and is to be kept as is
764 def self.salt_unsalted_passwords!
764 def self.salt_unsalted_passwords!
765 transaction do
765 transaction do
766 User.where("salt IS NULL OR salt = ''").find_each do |user|
766 User.where("salt IS NULL OR salt = ''").find_each do |user|
767 next if user.hashed_password.blank?
767 next if user.hashed_password.blank?
768 salt = User.generate_salt
768 salt = User.generate_salt
769 hashed_password = User.hash_password("#{salt}#{user.hashed_password}")
769 hashed_password = User.hash_password("#{salt}#{user.hashed_password}")
770 User.where(:id => user.id).update_all(:salt => salt, :hashed_password => hashed_password)
770 User.where(:id => user.id).update_all(:salt => salt, :hashed_password => hashed_password)
771 end
771 end
772 end
772 end
773 end
773 end
774
774
775 protected
775 protected
776
776
777 def validate_password_length
777 def validate_password_length
778 return if password.blank? && generate_password?
778 return if password.blank? && generate_password?
779 # Password length validation based on setting
779 # Password length validation based on setting
780 if !password.nil? && password.size < Setting.password_min_length.to_i
780 if !password.nil? && password.size < Setting.password_min_length.to_i
781 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
781 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
782 end
782 end
783 end
783 end
784
784
785 def instantiate_email_address
785 def instantiate_email_address
786 email_address || build_email_address
786 email_address || build_email_address
787 end
787 end
788
788
789 private
789 private
790
790
791 def generate_password_if_needed
791 def generate_password_if_needed
792 if generate_password? && auth_source.nil?
792 if generate_password? && auth_source.nil?
793 length = [Setting.password_min_length.to_i + 2, 10].max
793 length = [Setting.password_min_length.to_i + 2, 10].max
794 random_password(length)
794 random_password(length)
795 end
795 end
796 end
796 end
797
797
798 # Delete all outstanding password reset tokens on password change.
798 # Delete all outstanding password reset tokens on password change.
799 # Delete the autologin tokens on password change to prohibit session leakage.
799 # Delete the autologin tokens on password change to prohibit session leakage.
800 # This helps to keep the account secure in case the associated email account
800 # This helps to keep the account secure in case the associated email account
801 # was compromised.
801 # was compromised.
802 def destroy_tokens
802 def destroy_tokens
803 if hashed_password_changed? || (status_changed? && !active?)
803 if hashed_password_changed? || (status_changed? && !active?)
804 tokens = ['recovery', 'autologin', 'session']
804 tokens = ['recovery', 'autologin', 'session']
805 Token.where(:user_id => id, :action => tokens).delete_all
805 Token.where(:user_id => id, :action => tokens).delete_all
806 end
806 end
807 end
807 end
808
808
809 # Removes references that are not handled by associations
809 # Removes references that are not handled by associations
810 # Things that are not deleted are reassociated with the anonymous user
810 # Things that are not deleted are reassociated with the anonymous user
811 def remove_references_before_destroy
811 def remove_references_before_destroy
812 return if self.id.nil?
812 return if self.id.nil?
813
813
814 substitute = User.anonymous
814 substitute = User.anonymous
815 Attachment.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
815 Attachment.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
816 Comment.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
816 Comment.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
817 Issue.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
817 Issue.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
818 Issue.where(['assigned_to_id = ?', id]).update_all('assigned_to_id = NULL')
818 Issue.where(['assigned_to_id = ?', id]).update_all('assigned_to_id = NULL')
819 Journal.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id])
819 Journal.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id])
820 JournalDetail.
820 JournalDetail.
821 where(["property = 'attr' AND prop_key = 'assigned_to_id' AND old_value = ?", id.to_s]).
821 where(["property = 'attr' AND prop_key = 'assigned_to_id' AND old_value = ?", id.to_s]).
822 update_all(['old_value = ?', substitute.id.to_s])
822 update_all(['old_value = ?', substitute.id.to_s])
823 JournalDetail.
823 JournalDetail.
824 where(["property = 'attr' AND prop_key = 'assigned_to_id' AND value = ?", id.to_s]).
824 where(["property = 'attr' AND prop_key = 'assigned_to_id' AND value = ?", id.to_s]).
825 update_all(['value = ?', substitute.id.to_s])
825 update_all(['value = ?', substitute.id.to_s])
826 Message.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
826 Message.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
827 News.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
827 News.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
828 # Remove private queries and keep public ones
828 # Remove private queries and keep public ones
829 ::Query.where('user_id = ? AND visibility = ?', id, ::Query::VISIBILITY_PRIVATE).delete_all
829 ::Query.where('user_id = ? AND visibility = ?', id, ::Query::VISIBILITY_PRIVATE).delete_all
830 ::Query.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id])
830 ::Query.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id])
831 TimeEntry.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id])
831 TimeEntry.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id])
832 Token.where('user_id = ?', id).delete_all
832 Token.where('user_id = ?', id).delete_all
833 Watcher.where('user_id = ?', id).delete_all
833 Watcher.where('user_id = ?', id).delete_all
834 WikiContent.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
834 WikiContent.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
835 WikiContent::Version.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
835 WikiContent::Version.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
836 end
836 end
837
837
838 # Return password digest
838 # Return password digest
839 def self.hash_password(clear_password)
839 def self.hash_password(clear_password)
840 Digest::SHA1.hexdigest(clear_password || "")
840 Digest::SHA1.hexdigest(clear_password || "")
841 end
841 end
842
842
843 # Returns a 128bits random salt as a hex string (32 chars long)
843 # Returns a 128bits random salt as a hex string (32 chars long)
844 def self.generate_salt
844 def self.generate_salt
845 Redmine::Utils.random_hex(16)
845 Redmine::Utils.random_hex(16)
846 end
846 end
847
847
848 # Send a security notification to all admins if the user has gained/lost admin privileges
848 # Send a security notification to all admins if the user has gained/lost admin privileges
849 def deliver_security_notification
849 def deliver_security_notification
850 options = {
850 options = {
851 field: :field_admin,
851 field: :field_admin,
852 value: login,
852 value: login,
853 title: :label_user_plural,
853 title: :label_user_plural,
854 url: {controller: 'users', action: 'index'}
854 url: {controller: 'users', action: 'index'}
855 }
855 }
856
856
857 deliver = false
857 deliver = false
858 if (admin? && id_changed? && active?) || # newly created admin
858 if (admin? && id_changed? && active?) || # newly created admin
859 (admin? && admin_changed? && active?) || # regular user became admin
859 (admin? && admin_changed? && active?) || # regular user became admin
860 (admin? && status_changed? && active?) # locked admin became active again
860 (admin? && status_changed? && active?) # locked admin became active again
861
861
862 deliver = true
862 deliver = true
863 options[:message] = :mail_body_security_notification_add
863 options[:message] = :mail_body_security_notification_add
864
864
865 elsif (admin? && destroyed? && active?) || # active admin user was deleted
865 elsif (admin? && destroyed? && active?) || # active admin user was deleted
866 (!admin? && admin_changed? && active?) || # admin is no longer admin
866 (!admin? && admin_changed? && active?) || # admin is no longer admin
867 (admin? && status_changed? && !active?) # admin was locked
867 (admin? && status_changed? && !active?) # admin was locked
868
868
869 deliver = true
869 deliver = true
870 options[:message] = :mail_body_security_notification_remove
870 options[:message] = :mail_body_security_notification_remove
871 end
871 end
872
872
873 if deliver
873 if deliver
874 users = User.active.where(admin: true).to_a
874 users = User.active.where(admin: true).to_a
875 Mailer.security_notification(users, options).deliver
875 Mailer.security_notification(users, options).deliver
876 end
876 end
877 end
877 end
878 end
878 end
879
879
880 class AnonymousUser < User
880 class AnonymousUser < User
881 validate :validate_anonymous_uniqueness, :on => :create
881 validate :validate_anonymous_uniqueness, :on => :create
882
882
883 self.valid_statuses = [STATUS_ANONYMOUS]
883 self.valid_statuses = [STATUS_ANONYMOUS]
884
884
885 def validate_anonymous_uniqueness
885 def validate_anonymous_uniqueness
886 # There should be only one AnonymousUser in the database
886 # There should be only one AnonymousUser in the database
887 errors.add :base, 'An anonymous user already exists.' if AnonymousUser.exists?
887 errors.add :base, 'An anonymous user already exists.' if AnonymousUser.exists?
888 end
888 end
889
889
890 def available_custom_fields
890 def available_custom_fields
891 []
891 []
892 end
892 end
893
893
894 # Overrides a few properties
894 # Overrides a few properties
895 def logged?; false end
895 def logged?; false end
896 def admin; false end
896 def admin; false end
897 def name(*args); I18n.t(:label_user_anonymous) end
897 def name(*args); I18n.t(:label_user_anonymous) end
898 def mail=(*args); nil end
898 def mail=(*args); nil end
899 def mail; nil end
899 def mail; nil end
900 def time_zone; nil end
900 def time_zone; nil end
901 def rss_key; nil end
901 def rss_key; nil end
902
902
903 def pref
903 def pref
904 UserPreference.new(:user => self)
904 UserPreference.new(:user => self)
905 end
905 end
906
906
907 # Returns the user's bult-in role
907 # Returns the user's bult-in role
908 def builtin_role
908 def builtin_role
909 @builtin_role ||= Role.anonymous
909 @builtin_role ||= Role.anonymous
910 end
910 end
911
911
912 def membership(*args)
912 def membership(*args)
913 nil
913 nil
914 end
914 end
915
915
916 def member_of?(*args)
916 def member_of?(*args)
917 false
917 false
918 end
918 end
919
919
920 # Anonymous user can not be destroyed
920 # Anonymous user can not be destroyed
921 def destroy
921 def destroy
922 false
922 false
923 end
923 end
924
924
925 protected
925 protected
926
926
927 def instantiate_email_address
927 def instantiate_email_address
928 end
928 end
929 end
929 end
General Comments 0
You need to be logged in to leave comments. Login now