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