##// END OF EJS Templates
Merged r16049 (#24156)....
Jean-Philippe Lang -
r15711:d5a943fd132c
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,291 +1,291
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 # Custom coder for the permissions attribute that should be an
20 20 # array of symbols. Rails 3 uses Psych which can be *unbelievably*
21 21 # slow on some platforms (eg. mingw32).
22 22 class PermissionsAttributeCoder
23 23 def self.load(str)
24 24 str.to_s.scan(/:([a-z0-9_]+)/).flatten.map(&:to_sym)
25 25 end
26 26
27 27 def self.dump(value)
28 28 YAML.dump(value)
29 29 end
30 30 end
31 31
32 32 # Built-in roles
33 33 BUILTIN_NON_MEMBER = 1
34 34 BUILTIN_ANONYMOUS = 2
35 35
36 36 ISSUES_VISIBILITY_OPTIONS = [
37 37 ['all', :label_issues_visibility_all],
38 38 ['default', :label_issues_visibility_public],
39 39 ['own', :label_issues_visibility_own]
40 40 ]
41 41
42 42 TIME_ENTRIES_VISIBILITY_OPTIONS = [
43 43 ['all', :label_time_entries_visibility_all],
44 44 ['own', :label_time_entries_visibility_own]
45 45 ]
46 46
47 47 USERS_VISIBILITY_OPTIONS = [
48 48 ['all', :label_users_visibility_all],
49 49 ['members_of_visible_projects', :label_users_visibility_members_of_visible_projects]
50 50 ]
51 51
52 52 scope :sorted, lambda { order(:builtin, :position) }
53 53 scope :givable, lambda { order(:position).where(:builtin => 0) }
54 54 scope :builtin, lambda { |*args|
55 55 compare = (args.first == true ? 'not' : '')
56 56 where("#{compare} builtin = 0")
57 57 }
58 58
59 59 before_destroy :check_deletable
60 60 has_many :workflow_rules, :dependent => :delete_all do
61 61 def copy(source_role)
62 62 WorkflowRule.copy(nil, source_role, nil, proxy_association.owner)
63 63 end
64 64 end
65 65 has_and_belongs_to_many :custom_fields, :join_table => "#{table_name_prefix}custom_fields_roles#{table_name_suffix}", :foreign_key => "role_id"
66 66
67 67 has_and_belongs_to_many :managed_roles, :class_name => 'Role',
68 68 :join_table => "#{table_name_prefix}roles_managed_roles#{table_name_suffix}",
69 69 :association_foreign_key => "managed_role_id"
70 70
71 71 has_many :member_roles, :dependent => :destroy
72 72 has_many :members, :through => :member_roles
73 73 acts_as_positioned :scope => :builtin
74 74
75 75 serialize :permissions, ::Role::PermissionsAttributeCoder
76 76 store :settings, :accessors => [:permissions_all_trackers, :permissions_tracker_ids]
77 77 attr_protected :builtin
78 78
79 79 validates_presence_of :name
80 80 validates_uniqueness_of :name
81 81 validates_length_of :name, :maximum => 30
82 82 validates_inclusion_of :issues_visibility,
83 83 :in => ISSUES_VISIBILITY_OPTIONS.collect(&:first),
84 84 :if => lambda {|role| role.respond_to?(:issues_visibility) && role.issues_visibility_changed?}
85 85 validates_inclusion_of :users_visibility,
86 86 :in => USERS_VISIBILITY_OPTIONS.collect(&:first),
87 87 :if => lambda {|role| role.respond_to?(:users_visibility) && role.users_visibility_changed?}
88 88 validates_inclusion_of :time_entries_visibility,
89 89 :in => TIME_ENTRIES_VISIBILITY_OPTIONS.collect(&:first),
90 90 :if => lambda {|role| role.respond_to?(:time_entries_visibility) && role.time_entries_visibility_changed?}
91 91
92 92 # Copies attributes from another role, arg can be an id or a Role
93 93 def copy_from(arg, options={})
94 94 return unless arg.present?
95 95 role = arg.is_a?(Role) ? arg : Role.find_by_id(arg.to_s)
96 96 self.attributes = role.attributes.dup.except("id", "name", "position", "builtin", "permissions")
97 97 self.permissions = role.permissions.dup
98 98 self
99 99 end
100 100
101 101 def permissions=(perms)
102 102 perms = perms.collect {|p| p.to_sym unless p.blank? }.compact.uniq if perms
103 103 write_attribute(:permissions, perms)
104 104 end
105 105
106 106 def add_permission!(*perms)
107 107 self.permissions = [] unless permissions.is_a?(Array)
108 108
109 109 permissions_will_change!
110 110 perms.each do |p|
111 111 p = p.to_sym
112 112 permissions << p unless permissions.include?(p)
113 113 end
114 114 save!
115 115 end
116 116
117 117 def remove_permission!(*perms)
118 118 return unless permissions.is_a?(Array)
119 119 permissions_will_change!
120 120 perms.each { |p| permissions.delete(p.to_sym) }
121 121 save!
122 122 end
123 123
124 124 # Returns true if the role has the given permission
125 125 def has_permission?(perm)
126 126 !permissions.nil? && permissions.include?(perm.to_sym)
127 127 end
128 128
129 129 def consider_workflow?
130 130 has_permission?(:add_issues) || has_permission?(:edit_issues)
131 131 end
132 132
133 133 def <=>(role)
134 134 if role
135 135 if builtin == role.builtin
136 136 position <=> role.position
137 137 else
138 138 builtin <=> role.builtin
139 139 end
140 140 else
141 141 -1
142 142 end
143 143 end
144 144
145 145 def to_s
146 146 name
147 147 end
148 148
149 149 def name
150 150 case builtin
151 151 when 1; l(:label_role_non_member, :default => read_attribute(:name))
152 152 when 2; l(:label_role_anonymous, :default => read_attribute(:name))
153 153 else; read_attribute(:name)
154 154 end
155 155 end
156 156
157 157 # Return true if the role is a builtin role
158 158 def builtin?
159 159 self.builtin != 0
160 160 end
161 161
162 162 # Return true if the role is the anonymous role
163 163 def anonymous?
164 164 builtin == 2
165 165 end
166 166
167 167 # Return true if the role is a project member role
168 168 def member?
169 169 !self.builtin?
170 170 end
171 171
172 172 # Return true if role is allowed to do the specified action
173 173 # action can be:
174 174 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
175 175 # * a permission Symbol (eg. :edit_project)
176 176 def allowed_to?(action)
177 177 if action.is_a? Hash
178 178 allowed_actions.include? "#{action[:controller]}/#{action[:action]}"
179 179 else
180 180 allowed_permissions.include? action
181 181 end
182 182 end
183 183
184 184 # Return all the permissions that can be given to the role
185 185 def setable_permissions
186 186 setable_permissions = Redmine::AccessControl.permissions - Redmine::AccessControl.public_permissions
187 187 setable_permissions -= Redmine::AccessControl.members_only_permissions if self.builtin == BUILTIN_NON_MEMBER
188 188 setable_permissions -= Redmine::AccessControl.loggedin_only_permissions if self.builtin == BUILTIN_ANONYMOUS
189 189 setable_permissions
190 190 end
191 191
192 192 def permissions_tracker_ids(*args)
193 193 if args.any?
194 194 Array(permissions_tracker_ids[args.first.to_s]).map(&:to_i)
195 195 else
196 196 super || {}
197 197 end
198 198 end
199 199
200 200 def permissions_tracker_ids=(arg)
201 201 h = arg.to_hash
202 202 h.values.each {|v| v.reject!(&:blank?)}
203 203 super(h)
204 204 end
205 205
206 206 # Returns true if tracker_id belongs to the list of
207 207 # trackers for which permission is given
208 208 def permissions_tracker_ids?(permission, tracker_id)
209 209 permissions_tracker_ids(permission).include?(tracker_id)
210 210 end
211 211
212 212 def permissions_all_trackers
213 213 super || {}
214 214 end
215 215
216 216 def permissions_all_trackers=(arg)
217 217 super(arg.to_hash)
218 218 end
219 219
220 220 # Returns true if permission is given for all trackers
221 221 def permissions_all_trackers?(permission)
222 222 permissions_all_trackers[permission.to_s].to_s != '0'
223 223 end
224 224
225 225 # Returns true if permission is given for the tracker
226 226 # (explicitly or for all trackers)
227 227 def permissions_tracker?(permission, tracker)
228 228 permissions_all_trackers?(permission) ||
229 229 permissions_tracker_ids?(permission, tracker.try(:id))
230 230 end
231 231
232 232 # Sets the trackers that are allowed for a permission.
233 233 # tracker_ids can be an array of tracker ids or :all for
234 234 # no restrictions.
235 235 #
236 236 # Examples:
237 237 # role.set_permission_trackers :add_issues, [1, 3]
238 238 # role.set_permission_trackers :add_issues, :all
239 239 def set_permission_trackers(permission, tracker_ids)
240 240 h = {permission.to_s => (tracker_ids == :all ? '1' : '0')}
241 241 self.permissions_all_trackers = permissions_all_trackers.merge(h)
242 242
243 243 h = {permission.to_s => (tracker_ids == :all ? [] : tracker_ids)}
244 244 self.permissions_tracker_ids = permissions_tracker_ids.merge(h)
245 245
246 246 self
247 247 end
248 248
249 249 # Find all the roles that can be given to a project member
250 250 def self.find_all_givable
251 251 Role.givable.to_a
252 252 end
253 253
254 254 # Return the builtin 'non member' role. If the role doesn't exist,
255 255 # it will be created on the fly.
256 256 def self.non_member
257 257 find_or_create_system_role(BUILTIN_NON_MEMBER, 'Non member')
258 258 end
259 259
260 260 # Return the builtin 'anonymous' role. If the role doesn't exist,
261 261 # it will be created on the fly.
262 262 def self.anonymous
263 263 find_or_create_system_role(BUILTIN_ANONYMOUS, 'Anonymous')
264 264 end
265 265
266 266 private
267 267
268 268 def allowed_permissions
269 269 @allowed_permissions ||= permissions + Redmine::AccessControl.public_permissions.collect {|p| p.name}
270 270 end
271 271
272 272 def allowed_actions
273 273 @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten
274 274 end
275 275
276 276 def check_deletable
277 277 raise "Cannot delete role" if members.any?
278 278 raise "Cannot delete builtin role" if builtin?
279 279 end
280 280
281 281 def self.find_or_create_system_role(builtin, name)
282 role = where(:builtin => builtin).first
282 role = unscoped.where(:builtin => builtin).first
283 283 if role.nil?
284 role = create(:name => name) do |r|
284 role = unscoped.create(:name => name) do |r|
285 285 r.builtin = builtin
286 286 end
287 287 raise "Unable to create the #{name} role (#{role.errors.full_messages.join(',')})." if role.new_record?
288 288 end
289 289 role
290 290 end
291 291 end
@@ -1,924 +1,924
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 :login, :admin, :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).uniq
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 'status',
701 701 'auth_source_id',
702 702 'generate_password',
703 703 'must_change_passwd',
704 704 :if => lambda {|user, current_user| current_user.admin?}
705 705
706 706 safe_attributes 'group_ids',
707 707 :if => lambda {|user, current_user| current_user.admin? && !user.new_record?}
708 708
709 709 # Utility method to help check if a user should be notified about an
710 710 # event.
711 711 #
712 712 # TODO: only supports Issue events currently
713 713 def notify_about?(object)
714 714 if mail_notification == 'all'
715 715 true
716 716 elsif mail_notification.blank? || mail_notification == 'none'
717 717 false
718 718 else
719 719 case object
720 720 when Issue
721 721 case mail_notification
722 722 when 'selected', 'only_my_events'
723 723 # user receives notifications for created/assigned issues on unselected projects
724 724 object.author == self || is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was)
725 725 when 'only_assigned'
726 726 is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was)
727 727 when 'only_owner'
728 728 object.author == self
729 729 end
730 730 when News
731 731 # always send to project members except when mail_notification is set to 'none'
732 732 true
733 733 end
734 734 end
735 735 end
736 736
737 737 def self.current=(user)
738 738 RequestStore.store[:current_user] = user
739 739 end
740 740
741 741 def self.current
742 742 RequestStore.store[:current_user] ||= User.anonymous
743 743 end
744 744
745 745 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
746 746 # one anonymous user per database.
747 747 def self.anonymous
748 anonymous_user = AnonymousUser.first
748 anonymous_user = AnonymousUser.unscoped.first
749 749 if anonymous_user.nil?
750 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :login => '', :status => 0)
750 anonymous_user = AnonymousUser.unscoped.create(:lastname => 'Anonymous', :firstname => '', :login => '', :status => 0)
751 751 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
752 752 end
753 753 anonymous_user
754 754 end
755 755
756 756 # Salts all existing unsalted passwords
757 757 # It changes password storage scheme from SHA1(password) to SHA1(salt + SHA1(password))
758 758 # This method is used in the SaltPasswords migration and is to be kept as is
759 759 def self.salt_unsalted_passwords!
760 760 transaction do
761 761 User.where("salt IS NULL OR salt = ''").find_each do |user|
762 762 next if user.hashed_password.blank?
763 763 salt = User.generate_salt
764 764 hashed_password = User.hash_password("#{salt}#{user.hashed_password}")
765 765 User.where(:id => user.id).update_all(:salt => salt, :hashed_password => hashed_password)
766 766 end
767 767 end
768 768 end
769 769
770 770 protected
771 771
772 772 def validate_password_length
773 773 return if password.blank? && generate_password?
774 774 # Password length validation based on setting
775 775 if !password.nil? && password.size < Setting.password_min_length.to_i
776 776 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
777 777 end
778 778 end
779 779
780 780 def instantiate_email_address
781 781 email_address || build_email_address
782 782 end
783 783
784 784 private
785 785
786 786 def generate_password_if_needed
787 787 if generate_password? && auth_source.nil?
788 788 length = [Setting.password_min_length.to_i + 2, 10].max
789 789 random_password(length)
790 790 end
791 791 end
792 792
793 793 # Delete all outstanding password reset tokens on password change.
794 794 # Delete the autologin tokens on password change to prohibit session leakage.
795 795 # This helps to keep the account secure in case the associated email account
796 796 # was compromised.
797 797 def destroy_tokens
798 798 if hashed_password_changed? || (status_changed? && !active?)
799 799 tokens = ['recovery', 'autologin', 'session']
800 800 Token.where(:user_id => id, :action => tokens).delete_all
801 801 end
802 802 end
803 803
804 804 # Removes references that are not handled by associations
805 805 # Things that are not deleted are reassociated with the anonymous user
806 806 def remove_references_before_destroy
807 807 return if self.id.nil?
808 808
809 809 substitute = User.anonymous
810 810 Attachment.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
811 811 Comment.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
812 812 Issue.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
813 813 Issue.where(['assigned_to_id = ?', id]).update_all('assigned_to_id = NULL')
814 814 Journal.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id])
815 815 JournalDetail.
816 816 where(["property = 'attr' AND prop_key = 'assigned_to_id' AND old_value = ?", id.to_s]).
817 817 update_all(['old_value = ?', substitute.id.to_s])
818 818 JournalDetail.
819 819 where(["property = 'attr' AND prop_key = 'assigned_to_id' AND value = ?", id.to_s]).
820 820 update_all(['value = ?', substitute.id.to_s])
821 821 Message.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
822 822 News.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
823 823 # Remove private queries and keep public ones
824 824 ::Query.delete_all ['user_id = ? AND visibility = ?', id, ::Query::VISIBILITY_PRIVATE]
825 825 ::Query.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id])
826 826 TimeEntry.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id])
827 827 Token.delete_all ['user_id = ?', id]
828 828 Watcher.delete_all ['user_id = ?', id]
829 829 WikiContent.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
830 830 WikiContent::Version.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
831 831 end
832 832
833 833 # Return password digest
834 834 def self.hash_password(clear_password)
835 835 Digest::SHA1.hexdigest(clear_password || "")
836 836 end
837 837
838 838 # Returns a 128bits random salt as a hex string (32 chars long)
839 839 def self.generate_salt
840 840 Redmine::Utils.random_hex(16)
841 841 end
842 842
843 843 # Send a security notification to all admins if the user has gained/lost admin privileges
844 844 def deliver_security_notification
845 845 options = {
846 846 field: :field_admin,
847 847 value: login,
848 848 title: :label_user_plural,
849 849 url: {controller: 'users', action: 'index'}
850 850 }
851 851
852 852 deliver = false
853 853 if (admin? && id_changed? && active?) || # newly created admin
854 854 (admin? && admin_changed? && active?) || # regular user became admin
855 855 (admin? && status_changed? && active?) # locked admin became active again
856 856
857 857 deliver = true
858 858 options[:message] = :mail_body_security_notification_add
859 859
860 860 elsif (admin? && destroyed? && active?) || # active admin user was deleted
861 861 (!admin? && admin_changed? && active?) || # admin is no longer admin
862 862 (admin? && status_changed? && !active?) # admin was locked
863 863
864 864 deliver = true
865 865 options[:message] = :mail_body_security_notification_remove
866 866 end
867 867
868 868 if deliver
869 869 users = User.active.where(admin: true).to_a
870 870 Mailer.security_notification(users, options).deliver
871 871 end
872 872 end
873 873 end
874 874
875 875 class AnonymousUser < User
876 876 validate :validate_anonymous_uniqueness, :on => :create
877 877
878 878 self.valid_statuses = [STATUS_ANONYMOUS]
879 879
880 880 def validate_anonymous_uniqueness
881 881 # There should be only one AnonymousUser in the database
882 882 errors.add :base, 'An anonymous user already exists.' if AnonymousUser.exists?
883 883 end
884 884
885 885 def available_custom_fields
886 886 []
887 887 end
888 888
889 889 # Overrides a few properties
890 890 def logged?; false end
891 891 def admin; false end
892 892 def name(*args); I18n.t(:label_user_anonymous) end
893 893 def mail=(*args); nil end
894 894 def mail; nil end
895 895 def time_zone; nil end
896 896 def rss_key; nil end
897 897
898 898 def pref
899 899 UserPreference.new(:user => self)
900 900 end
901 901
902 902 # Returns the user's bult-in role
903 903 def builtin_role
904 904 @builtin_role ||= Role.anonymous
905 905 end
906 906
907 907 def membership(*args)
908 908 nil
909 909 end
910 910
911 911 def member_of?(*args)
912 912 false
913 913 end
914 914
915 915 # Anonymous user can not be destroyed
916 916 def destroy
917 917 false
918 918 end
919 919
920 920 protected
921 921
922 922 def instantiate_email_address
923 923 end
924 924 end
General Comments 0
You need to be logged in to leave comments. Login now