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