##// END OF EJS Templates
explicitly define fix has_and_belongs_to_many join_table at Group and User...
Toshi MARUYAMA -
r12196:e9470e55857a
parent child
Show More
@@ -1,90 +1,92
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2013 Jean-Philippe Lang
2 # Copyright (C) 2006-2013 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class Group < Principal
18 class Group < Principal
19 include Redmine::SafeAttributes
19 include Redmine::SafeAttributes
20
20
21 has_and_belongs_to_many :users, :after_add => :user_added,
21 has_and_belongs_to_many :users,
22 :after_remove => :user_removed
22 :join_table => "#{table_name_prefix}groups_users#{table_name_suffix}",
23 :after_add => :user_added,
24 :after_remove => :user_removed
23
25
24 acts_as_customizable
26 acts_as_customizable
25
27
26 validates_presence_of :lastname
28 validates_presence_of :lastname
27 validates_uniqueness_of :lastname, :case_sensitive => false
29 validates_uniqueness_of :lastname, :case_sensitive => false
28 validates_length_of :lastname, :maximum => 255
30 validates_length_of :lastname, :maximum => 255
29
31
30 before_destroy :remove_references_before_destroy
32 before_destroy :remove_references_before_destroy
31
33
32 scope :sorted, lambda { order("#{table_name}.lastname ASC") }
34 scope :sorted, lambda { order("#{table_name}.lastname ASC") }
33 scope :named, lambda {|arg| where("LOWER(#{table_name}.lastname) = LOWER(?)", arg.to_s.strip)}
35 scope :named, lambda {|arg| where("LOWER(#{table_name}.lastname) = LOWER(?)", arg.to_s.strip)}
34
36
35 safe_attributes 'name',
37 safe_attributes 'name',
36 'user_ids',
38 'user_ids',
37 'custom_field_values',
39 'custom_field_values',
38 'custom_fields',
40 'custom_fields',
39 :if => lambda {|group, user| user.admin?}
41 :if => lambda {|group, user| user.admin?}
40
42
41 def to_s
43 def to_s
42 lastname.to_s
44 lastname.to_s
43 end
45 end
44
46
45 def name
47 def name
46 lastname
48 lastname
47 end
49 end
48
50
49 def name=(arg)
51 def name=(arg)
50 self.lastname = arg
52 self.lastname = arg
51 end
53 end
52
54
53 def user_added(user)
55 def user_added(user)
54 members.each do |member|
56 members.each do |member|
55 next if member.project.nil?
57 next if member.project.nil?
56 user_member = Member.find_by_project_id_and_user_id(member.project_id, user.id) || Member.new(:project_id => member.project_id, :user_id => user.id)
58 user_member = Member.find_by_project_id_and_user_id(member.project_id, user.id) || Member.new(:project_id => member.project_id, :user_id => user.id)
57 member.member_roles.each do |member_role|
59 member.member_roles.each do |member_role|
58 user_member.member_roles << MemberRole.new(:role => member_role.role, :inherited_from => member_role.id)
60 user_member.member_roles << MemberRole.new(:role => member_role.role, :inherited_from => member_role.id)
59 end
61 end
60 user_member.save!
62 user_member.save!
61 end
63 end
62 end
64 end
63
65
64 def user_removed(user)
66 def user_removed(user)
65 members.each do |member|
67 members.each do |member|
66 MemberRole.
68 MemberRole.
67 includes(:member).
69 includes(:member).
68 where("#{Member.table_name}.user_id = ? AND #{MemberRole.table_name}.inherited_from IN (?)", user.id, member.member_role_ids).
70 where("#{Member.table_name}.user_id = ? AND #{MemberRole.table_name}.inherited_from IN (?)", user.id, member.member_role_ids).
69 all.
71 all.
70 each(&:destroy)
72 each(&:destroy)
71 end
73 end
72 end
74 end
73
75
74 def self.human_attribute_name(attribute_key_name, *args)
76 def self.human_attribute_name(attribute_key_name, *args)
75 attr_name = attribute_key_name.to_s
77 attr_name = attribute_key_name.to_s
76 if attr_name == 'lastname'
78 if attr_name == 'lastname'
77 attr_name = "name"
79 attr_name = "name"
78 end
80 end
79 super(attr_name, *args)
81 super(attr_name, *args)
80 end
82 end
81
83
82 private
84 private
83
85
84 # Removes references that are not handled by associations
86 # Removes references that are not handled by associations
85 def remove_references_before_destroy
87 def remove_references_before_destroy
86 return if self.id.nil?
88 return if self.id.nil?
87
89
88 Issue.update_all 'assigned_to_id = NULL', ['assigned_to_id = ?', id]
90 Issue.update_all 'assigned_to_id = NULL', ['assigned_to_id = ?', id]
89 end
91 end
90 end
92 end
@@ -1,739 +1,741
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2013 Jean-Philippe Lang
2 # Copyright (C) 2006-2013 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require "digest/sha1"
18 require "digest/sha1"
19
19
20 class User < Principal
20 class User < Principal
21 include Redmine::SafeAttributes
21 include Redmine::SafeAttributes
22
22
23 # Different ways of displaying/sorting users
23 # Different ways of displaying/sorting users
24 USER_FORMATS = {
24 USER_FORMATS = {
25 :firstname_lastname => {
25 :firstname_lastname => {
26 :string => '#{firstname} #{lastname}',
26 :string => '#{firstname} #{lastname}',
27 :order => %w(firstname lastname id),
27 :order => %w(firstname lastname id),
28 :setting_order => 1
28 :setting_order => 1
29 },
29 },
30 :firstname_lastinitial => {
30 :firstname_lastinitial => {
31 :string => '#{firstname} #{lastname.to_s.chars.first}.',
31 :string => '#{firstname} #{lastname.to_s.chars.first}.',
32 :order => %w(firstname lastname id),
32 :order => %w(firstname lastname id),
33 :setting_order => 2
33 :setting_order => 2
34 },
34 },
35 :firstname => {
35 :firstname => {
36 :string => '#{firstname}',
36 :string => '#{firstname}',
37 :order => %w(firstname id),
37 :order => %w(firstname id),
38 :setting_order => 3
38 :setting_order => 3
39 },
39 },
40 :lastname_firstname => {
40 :lastname_firstname => {
41 :string => '#{lastname} #{firstname}',
41 :string => '#{lastname} #{firstname}',
42 :order => %w(lastname firstname id),
42 :order => %w(lastname firstname id),
43 :setting_order => 4
43 :setting_order => 4
44 },
44 },
45 :lastname_coma_firstname => {
45 :lastname_coma_firstname => {
46 :string => '#{lastname}, #{firstname}',
46 :string => '#{lastname}, #{firstname}',
47 :order => %w(lastname firstname id),
47 :order => %w(lastname firstname id),
48 :setting_order => 5
48 :setting_order => 5
49 },
49 },
50 :lastname => {
50 :lastname => {
51 :string => '#{lastname}',
51 :string => '#{lastname}',
52 :order => %w(lastname id),
52 :order => %w(lastname id),
53 :setting_order => 6
53 :setting_order => 6
54 },
54 },
55 :username => {
55 :username => {
56 :string => '#{login}',
56 :string => '#{login}',
57 :order => %w(login id),
57 :order => %w(login id),
58 :setting_order => 7
58 :setting_order => 7
59 },
59 },
60 }
60 }
61
61
62 MAIL_NOTIFICATION_OPTIONS = [
62 MAIL_NOTIFICATION_OPTIONS = [
63 ['all', :label_user_mail_option_all],
63 ['all', :label_user_mail_option_all],
64 ['selected', :label_user_mail_option_selected],
64 ['selected', :label_user_mail_option_selected],
65 ['only_my_events', :label_user_mail_option_only_my_events],
65 ['only_my_events', :label_user_mail_option_only_my_events],
66 ['only_assigned', :label_user_mail_option_only_assigned],
66 ['only_assigned', :label_user_mail_option_only_assigned],
67 ['only_owner', :label_user_mail_option_only_owner],
67 ['only_owner', :label_user_mail_option_only_owner],
68 ['none', :label_user_mail_option_none]
68 ['none', :label_user_mail_option_none]
69 ]
69 ]
70
70
71 has_and_belongs_to_many :groups, :after_add => Proc.new {|user, group| group.user_added(user)},
71 has_and_belongs_to_many :groups,
72 :after_remove => Proc.new {|user, group| group.user_removed(user)}
72 :join_table => "#{table_name_prefix}groups_users#{table_name_suffix}",
73 :after_add => Proc.new {|user, group| group.user_added(user)},
74 :after_remove => Proc.new {|user, group| group.user_removed(user)}
73 has_many :changesets, :dependent => :nullify
75 has_many :changesets, :dependent => :nullify
74 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
76 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
75 has_one :rss_token, :class_name => 'Token', :conditions => "action='feeds'"
77 has_one :rss_token, :class_name => 'Token', :conditions => "action='feeds'"
76 has_one :api_token, :class_name => 'Token', :conditions => "action='api'"
78 has_one :api_token, :class_name => 'Token', :conditions => "action='api'"
77 belongs_to :auth_source
79 belongs_to :auth_source
78
80
79 scope :logged, lambda { where("#{User.table_name}.status <> #{STATUS_ANONYMOUS}") }
81 scope :logged, lambda { where("#{User.table_name}.status <> #{STATUS_ANONYMOUS}") }
80 scope :status, lambda {|arg| where(arg.blank? ? nil : {:status => arg.to_i}) }
82 scope :status, lambda {|arg| where(arg.blank? ? nil : {:status => arg.to_i}) }
81
83
82 acts_as_customizable
84 acts_as_customizable
83
85
84 attr_accessor :password, :password_confirmation, :generate_password
86 attr_accessor :password, :password_confirmation, :generate_password
85 attr_accessor :last_before_login_on
87 attr_accessor :last_before_login_on
86 # Prevents unauthorized assignments
88 # Prevents unauthorized assignments
87 attr_protected :login, :admin, :password, :password_confirmation, :hashed_password
89 attr_protected :login, :admin, :password, :password_confirmation, :hashed_password
88
90
89 LOGIN_LENGTH_LIMIT = 60
91 LOGIN_LENGTH_LIMIT = 60
90 MAIL_LENGTH_LIMIT = 60
92 MAIL_LENGTH_LIMIT = 60
91
93
92 validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
94 validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
93 validates_uniqueness_of :login, :if => Proc.new { |user| user.login_changed? && user.login.present? }, :case_sensitive => false
95 validates_uniqueness_of :login, :if => Proc.new { |user| user.login_changed? && user.login.present? }, :case_sensitive => false
94 validates_uniqueness_of :mail, :if => Proc.new { |user| user.mail_changed? && user.mail.present? }, :case_sensitive => false
96 validates_uniqueness_of :mail, :if => Proc.new { |user| user.mail_changed? && user.mail.present? }, :case_sensitive => false
95 # Login must contain letters, numbers, underscores only
97 # Login must contain letters, numbers, underscores only
96 validates_format_of :login, :with => /\A[a-z0-9_\-@\.]*\z/i
98 validates_format_of :login, :with => /\A[a-z0-9_\-@\.]*\z/i
97 validates_length_of :login, :maximum => LOGIN_LENGTH_LIMIT
99 validates_length_of :login, :maximum => LOGIN_LENGTH_LIMIT
98 validates_length_of :firstname, :lastname, :maximum => 30
100 validates_length_of :firstname, :lastname, :maximum => 30
99 validates_format_of :mail, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, :allow_blank => true
101 validates_format_of :mail, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, :allow_blank => true
100 validates_length_of :mail, :maximum => MAIL_LENGTH_LIMIT, :allow_nil => true
102 validates_length_of :mail, :maximum => MAIL_LENGTH_LIMIT, :allow_nil => true
101 validates_confirmation_of :password, :allow_nil => true
103 validates_confirmation_of :password, :allow_nil => true
102 validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true
104 validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true
103 validate :validate_password_length
105 validate :validate_password_length
104
106
105 before_create :set_mail_notification
107 before_create :set_mail_notification
106 before_save :generate_password_if_needed, :update_hashed_password
108 before_save :generate_password_if_needed, :update_hashed_password
107 before_destroy :remove_references_before_destroy
109 before_destroy :remove_references_before_destroy
108 after_save :update_notified_project_ids
110 after_save :update_notified_project_ids
109
111
110 scope :in_group, lambda {|group|
112 scope :in_group, lambda {|group|
111 group_id = group.is_a?(Group) ? group.id : group.to_i
113 group_id = group.is_a?(Group) ? group.id : group.to_i
112 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)
114 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)
113 }
115 }
114 scope :not_in_group, lambda {|group|
116 scope :not_in_group, lambda {|group|
115 group_id = group.is_a?(Group) ? group.id : group.to_i
117 group_id = group.is_a?(Group) ? group.id : group.to_i
116 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)
118 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)
117 }
119 }
118 scope :sorted, lambda { order(*User.fields_for_order_statement)}
120 scope :sorted, lambda { order(*User.fields_for_order_statement)}
119
121
120 def set_mail_notification
122 def set_mail_notification
121 self.mail_notification = Setting.default_notification_option if self.mail_notification.blank?
123 self.mail_notification = Setting.default_notification_option if self.mail_notification.blank?
122 true
124 true
123 end
125 end
124
126
125 def update_hashed_password
127 def update_hashed_password
126 # update hashed_password if password was set
128 # update hashed_password if password was set
127 if self.password && self.auth_source_id.blank?
129 if self.password && self.auth_source_id.blank?
128 salt_password(password)
130 salt_password(password)
129 end
131 end
130 end
132 end
131
133
132 alias :base_reload :reload
134 alias :base_reload :reload
133 def reload(*args)
135 def reload(*args)
134 @name = nil
136 @name = nil
135 @projects_by_role = nil
137 @projects_by_role = nil
136 @membership_by_project_id = nil
138 @membership_by_project_id = nil
137 @notified_projects_ids = nil
139 @notified_projects_ids = nil
138 @notified_projects_ids_changed = false
140 @notified_projects_ids_changed = false
139 @builtin_role = nil
141 @builtin_role = nil
140 base_reload(*args)
142 base_reload(*args)
141 end
143 end
142
144
143 def mail=(arg)
145 def mail=(arg)
144 write_attribute(:mail, arg.to_s.strip)
146 write_attribute(:mail, arg.to_s.strip)
145 end
147 end
146
148
147 def identity_url=(url)
149 def identity_url=(url)
148 if url.blank?
150 if url.blank?
149 write_attribute(:identity_url, '')
151 write_attribute(:identity_url, '')
150 else
152 else
151 begin
153 begin
152 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
154 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
153 rescue OpenIdAuthentication::InvalidOpenId
155 rescue OpenIdAuthentication::InvalidOpenId
154 # Invalid url, don't save
156 # Invalid url, don't save
155 end
157 end
156 end
158 end
157 self.read_attribute(:identity_url)
159 self.read_attribute(:identity_url)
158 end
160 end
159
161
160 # Returns the user that matches provided login and password, or nil
162 # Returns the user that matches provided login and password, or nil
161 def self.try_to_login(login, password, active_only=true)
163 def self.try_to_login(login, password, active_only=true)
162 login = login.to_s
164 login = login.to_s
163 password = password.to_s
165 password = password.to_s
164
166
165 # Make sure no one can sign in with an empty login or password
167 # Make sure no one can sign in with an empty login or password
166 return nil if login.empty? || password.empty?
168 return nil if login.empty? || password.empty?
167 user = find_by_login(login)
169 user = find_by_login(login)
168 if user
170 if user
169 # user is already in local database
171 # user is already in local database
170 return nil unless user.check_password?(password)
172 return nil unless user.check_password?(password)
171 return nil if !user.active? && active_only
173 return nil if !user.active? && active_only
172 else
174 else
173 # user is not yet registered, try to authenticate with available sources
175 # user is not yet registered, try to authenticate with available sources
174 attrs = AuthSource.authenticate(login, password)
176 attrs = AuthSource.authenticate(login, password)
175 if attrs
177 if attrs
176 user = new(attrs)
178 user = new(attrs)
177 user.login = login
179 user.login = login
178 user.language = Setting.default_language
180 user.language = Setting.default_language
179 if user.save
181 if user.save
180 user.reload
182 user.reload
181 logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source
183 logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source
182 end
184 end
183 end
185 end
184 end
186 end
185 user.update_column(:last_login_on, Time.now) if user && !user.new_record? && user.active?
187 user.update_column(:last_login_on, Time.now) if user && !user.new_record? && user.active?
186 user
188 user
187 rescue => text
189 rescue => text
188 raise text
190 raise text
189 end
191 end
190
192
191 # Returns the user who matches the given autologin +key+ or nil
193 # Returns the user who matches the given autologin +key+ or nil
192 def self.try_to_autologin(key)
194 def self.try_to_autologin(key)
193 user = Token.find_active_user('autologin', key, Setting.autologin.to_i)
195 user = Token.find_active_user('autologin', key, Setting.autologin.to_i)
194 if user
196 if user
195 user.update_column(:last_login_on, Time.now)
197 user.update_column(:last_login_on, Time.now)
196 user
198 user
197 end
199 end
198 end
200 end
199
201
200 def self.name_formatter(formatter = nil)
202 def self.name_formatter(formatter = nil)
201 USER_FORMATS[formatter || Setting.user_format] || USER_FORMATS[:firstname_lastname]
203 USER_FORMATS[formatter || Setting.user_format] || USER_FORMATS[:firstname_lastname]
202 end
204 end
203
205
204 # Returns an array of fields names than can be used to make an order statement for users
206 # Returns an array of fields names than can be used to make an order statement for users
205 # according to how user names are displayed
207 # according to how user names are displayed
206 # Examples:
208 # Examples:
207 #
209 #
208 # User.fields_for_order_statement => ['users.login', 'users.id']
210 # User.fields_for_order_statement => ['users.login', 'users.id']
209 # User.fields_for_order_statement('authors') => ['authors.login', 'authors.id']
211 # User.fields_for_order_statement('authors') => ['authors.login', 'authors.id']
210 def self.fields_for_order_statement(table=nil)
212 def self.fields_for_order_statement(table=nil)
211 table ||= table_name
213 table ||= table_name
212 name_formatter[:order].map {|field| "#{table}.#{field}"}
214 name_formatter[:order].map {|field| "#{table}.#{field}"}
213 end
215 end
214
216
215 # Return user's full name for display
217 # Return user's full name for display
216 def name(formatter = nil)
218 def name(formatter = nil)
217 f = self.class.name_formatter(formatter)
219 f = self.class.name_formatter(formatter)
218 if formatter
220 if formatter
219 eval('"' + f[:string] + '"')
221 eval('"' + f[:string] + '"')
220 else
222 else
221 @name ||= eval('"' + f[:string] + '"')
223 @name ||= eval('"' + f[:string] + '"')
222 end
224 end
223 end
225 end
224
226
225 def active?
227 def active?
226 self.status == STATUS_ACTIVE
228 self.status == STATUS_ACTIVE
227 end
229 end
228
230
229 def registered?
231 def registered?
230 self.status == STATUS_REGISTERED
232 self.status == STATUS_REGISTERED
231 end
233 end
232
234
233 def locked?
235 def locked?
234 self.status == STATUS_LOCKED
236 self.status == STATUS_LOCKED
235 end
237 end
236
238
237 def activate
239 def activate
238 self.status = STATUS_ACTIVE
240 self.status = STATUS_ACTIVE
239 end
241 end
240
242
241 def register
243 def register
242 self.status = STATUS_REGISTERED
244 self.status = STATUS_REGISTERED
243 end
245 end
244
246
245 def lock
247 def lock
246 self.status = STATUS_LOCKED
248 self.status = STATUS_LOCKED
247 end
249 end
248
250
249 def activate!
251 def activate!
250 update_attribute(:status, STATUS_ACTIVE)
252 update_attribute(:status, STATUS_ACTIVE)
251 end
253 end
252
254
253 def register!
255 def register!
254 update_attribute(:status, STATUS_REGISTERED)
256 update_attribute(:status, STATUS_REGISTERED)
255 end
257 end
256
258
257 def lock!
259 def lock!
258 update_attribute(:status, STATUS_LOCKED)
260 update_attribute(:status, STATUS_LOCKED)
259 end
261 end
260
262
261 # Returns true if +clear_password+ is the correct user's password, otherwise false
263 # Returns true if +clear_password+ is the correct user's password, otherwise false
262 def check_password?(clear_password)
264 def check_password?(clear_password)
263 if auth_source_id.present?
265 if auth_source_id.present?
264 auth_source.authenticate(self.login, clear_password)
266 auth_source.authenticate(self.login, clear_password)
265 else
267 else
266 User.hash_password("#{salt}#{User.hash_password clear_password}") == hashed_password
268 User.hash_password("#{salt}#{User.hash_password clear_password}") == hashed_password
267 end
269 end
268 end
270 end
269
271
270 # Generates a random salt and computes hashed_password for +clear_password+
272 # Generates a random salt and computes hashed_password for +clear_password+
271 # The hashed password is stored in the following form: SHA1(salt + SHA1(password))
273 # The hashed password is stored in the following form: SHA1(salt + SHA1(password))
272 def salt_password(clear_password)
274 def salt_password(clear_password)
273 self.salt = User.generate_salt
275 self.salt = User.generate_salt
274 self.hashed_password = User.hash_password("#{salt}#{User.hash_password clear_password}")
276 self.hashed_password = User.hash_password("#{salt}#{User.hash_password clear_password}")
275 end
277 end
276
278
277 # Does the backend storage allow this user to change their password?
279 # Does the backend storage allow this user to change their password?
278 def change_password_allowed?
280 def change_password_allowed?
279 return true if auth_source.nil?
281 return true if auth_source.nil?
280 return auth_source.allow_password_changes?
282 return auth_source.allow_password_changes?
281 end
283 end
282
284
283 def must_change_password?
285 def must_change_password?
284 must_change_passwd? && change_password_allowed?
286 must_change_passwd? && change_password_allowed?
285 end
287 end
286
288
287 def generate_password?
289 def generate_password?
288 generate_password == '1' || generate_password == true
290 generate_password == '1' || generate_password == true
289 end
291 end
290
292
291 # Generate and set a random password on given length
293 # Generate and set a random password on given length
292 def random_password(length=40)
294 def random_password(length=40)
293 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
295 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
294 chars -= %w(0 O 1 l)
296 chars -= %w(0 O 1 l)
295 password = ''
297 password = ''
296 length.times {|i| password << chars[SecureRandom.random_number(chars.size)] }
298 length.times {|i| password << chars[SecureRandom.random_number(chars.size)] }
297 self.password = password
299 self.password = password
298 self.password_confirmation = password
300 self.password_confirmation = password
299 self
301 self
300 end
302 end
301
303
302 def pref
304 def pref
303 self.preference ||= UserPreference.new(:user => self)
305 self.preference ||= UserPreference.new(:user => self)
304 end
306 end
305
307
306 def time_zone
308 def time_zone
307 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
309 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
308 end
310 end
309
311
310 def wants_comments_in_reverse_order?
312 def wants_comments_in_reverse_order?
311 self.pref[:comments_sorting] == 'desc'
313 self.pref[:comments_sorting] == 'desc'
312 end
314 end
313
315
314 # Return user's RSS key (a 40 chars long string), used to access feeds
316 # Return user's RSS key (a 40 chars long string), used to access feeds
315 def rss_key
317 def rss_key
316 if rss_token.nil?
318 if rss_token.nil?
317 create_rss_token(:action => 'feeds')
319 create_rss_token(:action => 'feeds')
318 end
320 end
319 rss_token.value
321 rss_token.value
320 end
322 end
321
323
322 # Return user's API key (a 40 chars long string), used to access the API
324 # Return user's API key (a 40 chars long string), used to access the API
323 def api_key
325 def api_key
324 if api_token.nil?
326 if api_token.nil?
325 create_api_token(:action => 'api')
327 create_api_token(:action => 'api')
326 end
328 end
327 api_token.value
329 api_token.value
328 end
330 end
329
331
330 # Return an array of project ids for which the user has explicitly turned mail notifications on
332 # Return an array of project ids for which the user has explicitly turned mail notifications on
331 def notified_projects_ids
333 def notified_projects_ids
332 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
334 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
333 end
335 end
334
336
335 def notified_project_ids=(ids)
337 def notified_project_ids=(ids)
336 @notified_projects_ids_changed = true
338 @notified_projects_ids_changed = true
337 @notified_projects_ids = ids
339 @notified_projects_ids = ids
338 end
340 end
339
341
340 # Updates per project notifications (after_save callback)
342 # Updates per project notifications (after_save callback)
341 def update_notified_project_ids
343 def update_notified_project_ids
342 if @notified_projects_ids_changed
344 if @notified_projects_ids_changed
343 ids = (mail_notification == 'selected' ? Array.wrap(notified_projects_ids).reject(&:blank?) : [])
345 ids = (mail_notification == 'selected' ? Array.wrap(notified_projects_ids).reject(&:blank?) : [])
344 members.update_all(:mail_notification => false)
346 members.update_all(:mail_notification => false)
345 members.where(:project_id => ids).update_all(:mail_notification => true) if ids.any?
347 members.where(:project_id => ids).update_all(:mail_notification => true) if ids.any?
346 end
348 end
347 end
349 end
348 private :update_notified_project_ids
350 private :update_notified_project_ids
349
351
350 def valid_notification_options
352 def valid_notification_options
351 self.class.valid_notification_options(self)
353 self.class.valid_notification_options(self)
352 end
354 end
353
355
354 # Only users that belong to more than 1 project can select projects for which they are notified
356 # Only users that belong to more than 1 project can select projects for which they are notified
355 def self.valid_notification_options(user=nil)
357 def self.valid_notification_options(user=nil)
356 # Note that @user.membership.size would fail since AR ignores
358 # Note that @user.membership.size would fail since AR ignores
357 # :include association option when doing a count
359 # :include association option when doing a count
358 if user.nil? || user.memberships.length < 1
360 if user.nil? || user.memberships.length < 1
359 MAIL_NOTIFICATION_OPTIONS.reject {|option| option.first == 'selected'}
361 MAIL_NOTIFICATION_OPTIONS.reject {|option| option.first == 'selected'}
360 else
362 else
361 MAIL_NOTIFICATION_OPTIONS
363 MAIL_NOTIFICATION_OPTIONS
362 end
364 end
363 end
365 end
364
366
365 # Find a user account by matching the exact login and then a case-insensitive
367 # Find a user account by matching the exact login and then a case-insensitive
366 # version. Exact matches will be given priority.
368 # version. Exact matches will be given priority.
367 def self.find_by_login(login)
369 def self.find_by_login(login)
368 if login.present?
370 if login.present?
369 login = login.to_s
371 login = login.to_s
370 # First look for an exact match
372 # First look for an exact match
371 user = where(:login => login).all.detect {|u| u.login == login}
373 user = where(:login => login).all.detect {|u| u.login == login}
372 unless user
374 unless user
373 # Fail over to case-insensitive if none was found
375 # Fail over to case-insensitive if none was found
374 user = where("LOWER(login) = ?", login.downcase).first
376 user = where("LOWER(login) = ?", login.downcase).first
375 end
377 end
376 user
378 user
377 end
379 end
378 end
380 end
379
381
380 def self.find_by_rss_key(key)
382 def self.find_by_rss_key(key)
381 Token.find_active_user('feeds', key)
383 Token.find_active_user('feeds', key)
382 end
384 end
383
385
384 def self.find_by_api_key(key)
386 def self.find_by_api_key(key)
385 Token.find_active_user('api', key)
387 Token.find_active_user('api', key)
386 end
388 end
387
389
388 # Makes find_by_mail case-insensitive
390 # Makes find_by_mail case-insensitive
389 def self.find_by_mail(mail)
391 def self.find_by_mail(mail)
390 where("LOWER(mail) = ?", mail.to_s.downcase).first
392 where("LOWER(mail) = ?", mail.to_s.downcase).first
391 end
393 end
392
394
393 # Returns true if the default admin account can no longer be used
395 # Returns true if the default admin account can no longer be used
394 def self.default_admin_account_changed?
396 def self.default_admin_account_changed?
395 !User.active.find_by_login("admin").try(:check_password?, "admin")
397 !User.active.find_by_login("admin").try(:check_password?, "admin")
396 end
398 end
397
399
398 def to_s
400 def to_s
399 name
401 name
400 end
402 end
401
403
402 CSS_CLASS_BY_STATUS = {
404 CSS_CLASS_BY_STATUS = {
403 STATUS_ANONYMOUS => 'anon',
405 STATUS_ANONYMOUS => 'anon',
404 STATUS_ACTIVE => 'active',
406 STATUS_ACTIVE => 'active',
405 STATUS_REGISTERED => 'registered',
407 STATUS_REGISTERED => 'registered',
406 STATUS_LOCKED => 'locked'
408 STATUS_LOCKED => 'locked'
407 }
409 }
408
410
409 def css_classes
411 def css_classes
410 "user #{CSS_CLASS_BY_STATUS[status]}"
412 "user #{CSS_CLASS_BY_STATUS[status]}"
411 end
413 end
412
414
413 # Returns the current day according to user's time zone
415 # Returns the current day according to user's time zone
414 def today
416 def today
415 if time_zone.nil?
417 if time_zone.nil?
416 Date.today
418 Date.today
417 else
419 else
418 Time.now.in_time_zone(time_zone).to_date
420 Time.now.in_time_zone(time_zone).to_date
419 end
421 end
420 end
422 end
421
423
422 # Returns the day of +time+ according to user's time zone
424 # Returns the day of +time+ according to user's time zone
423 def time_to_date(time)
425 def time_to_date(time)
424 if time_zone.nil?
426 if time_zone.nil?
425 time.to_date
427 time.to_date
426 else
428 else
427 time.in_time_zone(time_zone).to_date
429 time.in_time_zone(time_zone).to_date
428 end
430 end
429 end
431 end
430
432
431 def logged?
433 def logged?
432 true
434 true
433 end
435 end
434
436
435 def anonymous?
437 def anonymous?
436 !logged?
438 !logged?
437 end
439 end
438
440
439 # Returns user's membership for the given project
441 # Returns user's membership for the given project
440 # or nil if the user is not a member of project
442 # or nil if the user is not a member of project
441 def membership(project)
443 def membership(project)
442 project_id = project.is_a?(Project) ? project.id : project
444 project_id = project.is_a?(Project) ? project.id : project
443
445
444 @membership_by_project_id ||= Hash.new {|h, project_id|
446 @membership_by_project_id ||= Hash.new {|h, project_id|
445 h[project_id] = memberships.where(:project_id => project_id).first
447 h[project_id] = memberships.where(:project_id => project_id).first
446 }
448 }
447 @membership_by_project_id[project_id]
449 @membership_by_project_id[project_id]
448 end
450 end
449
451
450 # Returns the user's bult-in role
452 # Returns the user's bult-in role
451 def builtin_role
453 def builtin_role
452 @builtin_role ||= Role.non_member
454 @builtin_role ||= Role.non_member
453 end
455 end
454
456
455 # Return user's roles for project
457 # Return user's roles for project
456 def roles_for_project(project)
458 def roles_for_project(project)
457 roles = []
459 roles = []
458 # No role on archived projects
460 # No role on archived projects
459 return roles if project.nil? || project.archived?
461 return roles if project.nil? || project.archived?
460 if membership = membership(project)
462 if membership = membership(project)
461 roles = membership.roles
463 roles = membership.roles
462 else
464 else
463 roles << builtin_role
465 roles << builtin_role
464 end
466 end
465 roles
467 roles
466 end
468 end
467
469
468 # Return true if the user is a member of project
470 # Return true if the user is a member of project
469 def member_of?(project)
471 def member_of?(project)
470 projects.to_a.include?(project)
472 projects.to_a.include?(project)
471 end
473 end
472
474
473 # Returns a hash of user's projects grouped by roles
475 # Returns a hash of user's projects grouped by roles
474 def projects_by_role
476 def projects_by_role
475 return @projects_by_role if @projects_by_role
477 return @projects_by_role if @projects_by_role
476
478
477 @projects_by_role = Hash.new([])
479 @projects_by_role = Hash.new([])
478 memberships.each do |membership|
480 memberships.each do |membership|
479 if membership.project
481 if membership.project
480 membership.roles.each do |role|
482 membership.roles.each do |role|
481 @projects_by_role[role] = [] unless @projects_by_role.key?(role)
483 @projects_by_role[role] = [] unless @projects_by_role.key?(role)
482 @projects_by_role[role] << membership.project
484 @projects_by_role[role] << membership.project
483 end
485 end
484 end
486 end
485 end
487 end
486 @projects_by_role.each do |role, projects|
488 @projects_by_role.each do |role, projects|
487 projects.uniq!
489 projects.uniq!
488 end
490 end
489
491
490 @projects_by_role
492 @projects_by_role
491 end
493 end
492
494
493 # Returns true if user is arg or belongs to arg
495 # Returns true if user is arg or belongs to arg
494 def is_or_belongs_to?(arg)
496 def is_or_belongs_to?(arg)
495 if arg.is_a?(User)
497 if arg.is_a?(User)
496 self == arg
498 self == arg
497 elsif arg.is_a?(Group)
499 elsif arg.is_a?(Group)
498 arg.users.include?(self)
500 arg.users.include?(self)
499 else
501 else
500 false
502 false
501 end
503 end
502 end
504 end
503
505
504 # Return true if the user is allowed to do the specified action on a specific context
506 # Return true if the user is allowed to do the specified action on a specific context
505 # Action can be:
507 # Action can be:
506 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
508 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
507 # * a permission Symbol (eg. :edit_project)
509 # * a permission Symbol (eg. :edit_project)
508 # Context can be:
510 # Context can be:
509 # * a project : returns true if user is allowed to do the specified action on this project
511 # * a project : returns true if user is allowed to do the specified action on this project
510 # * an array of projects : returns true if user is allowed on every project
512 # * an array of projects : returns true if user is allowed on every project
511 # * nil with options[:global] set : check if user has at least one role allowed for this action,
513 # * nil with options[:global] set : check if user has at least one role allowed for this action,
512 # or falls back to Non Member / Anonymous permissions depending if the user is logged
514 # or falls back to Non Member / Anonymous permissions depending if the user is logged
513 def allowed_to?(action, context, options={}, &block)
515 def allowed_to?(action, context, options={}, &block)
514 if context && context.is_a?(Project)
516 if context && context.is_a?(Project)
515 return false unless context.allows_to?(action)
517 return false unless context.allows_to?(action)
516 # Admin users are authorized for anything else
518 # Admin users are authorized for anything else
517 return true if admin?
519 return true if admin?
518
520
519 roles = roles_for_project(context)
521 roles = roles_for_project(context)
520 return false unless roles
522 return false unless roles
521 roles.any? {|role|
523 roles.any? {|role|
522 (context.is_public? || role.member?) &&
524 (context.is_public? || role.member?) &&
523 role.allowed_to?(action) &&
525 role.allowed_to?(action) &&
524 (block_given? ? yield(role, self) : true)
526 (block_given? ? yield(role, self) : true)
525 }
527 }
526 elsif context && context.is_a?(Array)
528 elsif context && context.is_a?(Array)
527 if context.empty?
529 if context.empty?
528 false
530 false
529 else
531 else
530 # Authorize if user is authorized on every element of the array
532 # Authorize if user is authorized on every element of the array
531 context.map {|project| allowed_to?(action, project, options, &block)}.reduce(:&)
533 context.map {|project| allowed_to?(action, project, options, &block)}.reduce(:&)
532 end
534 end
533 elsif options[:global]
535 elsif options[:global]
534 # Admin users are always authorized
536 # Admin users are always authorized
535 return true if admin?
537 return true if admin?
536
538
537 # authorize if user has at least one role that has this permission
539 # authorize if user has at least one role that has this permission
538 roles = memberships.collect {|m| m.roles}.flatten.uniq
540 roles = memberships.collect {|m| m.roles}.flatten.uniq
539 roles << (self.logged? ? Role.non_member : Role.anonymous)
541 roles << (self.logged? ? Role.non_member : Role.anonymous)
540 roles.any? {|role|
542 roles.any? {|role|
541 role.allowed_to?(action) &&
543 role.allowed_to?(action) &&
542 (block_given? ? yield(role, self) : true)
544 (block_given? ? yield(role, self) : true)
543 }
545 }
544 else
546 else
545 false
547 false
546 end
548 end
547 end
549 end
548
550
549 # Is the user allowed to do the specified action on any project?
551 # Is the user allowed to do the specified action on any project?
550 # See allowed_to? for the actions and valid options.
552 # See allowed_to? for the actions and valid options.
551 def allowed_to_globally?(action, options, &block)
553 def allowed_to_globally?(action, options, &block)
552 allowed_to?(action, nil, options.reverse_merge(:global => true), &block)
554 allowed_to?(action, nil, options.reverse_merge(:global => true), &block)
553 end
555 end
554
556
555 # Returns true if the user is allowed to delete the user's own account
557 # Returns true if the user is allowed to delete the user's own account
556 def own_account_deletable?
558 def own_account_deletable?
557 Setting.unsubscribe? &&
559 Setting.unsubscribe? &&
558 (!admin? || User.active.where("admin = ? AND id <> ?", true, id).exists?)
560 (!admin? || User.active.where("admin = ? AND id <> ?", true, id).exists?)
559 end
561 end
560
562
561 safe_attributes 'login',
563 safe_attributes 'login',
562 'firstname',
564 'firstname',
563 'lastname',
565 'lastname',
564 'mail',
566 'mail',
565 'mail_notification',
567 'mail_notification',
566 'notified_project_ids',
568 'notified_project_ids',
567 'language',
569 'language',
568 'custom_field_values',
570 'custom_field_values',
569 'custom_fields',
571 'custom_fields',
570 'identity_url'
572 'identity_url'
571
573
572 safe_attributes 'status',
574 safe_attributes 'status',
573 'auth_source_id',
575 'auth_source_id',
574 'generate_password',
576 'generate_password',
575 'must_change_passwd',
577 'must_change_passwd',
576 :if => lambda {|user, current_user| current_user.admin?}
578 :if => lambda {|user, current_user| current_user.admin?}
577
579
578 safe_attributes 'group_ids',
580 safe_attributes 'group_ids',
579 :if => lambda {|user, current_user| current_user.admin? && !user.new_record?}
581 :if => lambda {|user, current_user| current_user.admin? && !user.new_record?}
580
582
581 # Utility method to help check if a user should be notified about an
583 # Utility method to help check if a user should be notified about an
582 # event.
584 # event.
583 #
585 #
584 # TODO: only supports Issue events currently
586 # TODO: only supports Issue events currently
585 def notify_about?(object)
587 def notify_about?(object)
586 if mail_notification == 'all'
588 if mail_notification == 'all'
587 true
589 true
588 elsif mail_notification.blank? || mail_notification == 'none'
590 elsif mail_notification.blank? || mail_notification == 'none'
589 false
591 false
590 else
592 else
591 case object
593 case object
592 when Issue
594 when Issue
593 case mail_notification
595 case mail_notification
594 when 'selected', 'only_my_events'
596 when 'selected', 'only_my_events'
595 # user receives notifications for created/assigned issues on unselected projects
597 # user receives notifications for created/assigned issues on unselected projects
596 object.author == self || is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was)
598 object.author == self || is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was)
597 when 'only_assigned'
599 when 'only_assigned'
598 is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was)
600 is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was)
599 when 'only_owner'
601 when 'only_owner'
600 object.author == self
602 object.author == self
601 end
603 end
602 when News
604 when News
603 # always send to project members except when mail_notification is set to 'none'
605 # always send to project members except when mail_notification is set to 'none'
604 true
606 true
605 end
607 end
606 end
608 end
607 end
609 end
608
610
609 def self.current=(user)
611 def self.current=(user)
610 Thread.current[:current_user] = user
612 Thread.current[:current_user] = user
611 end
613 end
612
614
613 def self.current
615 def self.current
614 Thread.current[:current_user] ||= User.anonymous
616 Thread.current[:current_user] ||= User.anonymous
615 end
617 end
616
618
617 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
619 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
618 # one anonymous user per database.
620 # one anonymous user per database.
619 def self.anonymous
621 def self.anonymous
620 anonymous_user = AnonymousUser.first
622 anonymous_user = AnonymousUser.first
621 if anonymous_user.nil?
623 if anonymous_user.nil?
622 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
624 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
623 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
625 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
624 end
626 end
625 anonymous_user
627 anonymous_user
626 end
628 end
627
629
628 # Salts all existing unsalted passwords
630 # Salts all existing unsalted passwords
629 # It changes password storage scheme from SHA1(password) to SHA1(salt + SHA1(password))
631 # It changes password storage scheme from SHA1(password) to SHA1(salt + SHA1(password))
630 # This method is used in the SaltPasswords migration and is to be kept as is
632 # This method is used in the SaltPasswords migration and is to be kept as is
631 def self.salt_unsalted_passwords!
633 def self.salt_unsalted_passwords!
632 transaction do
634 transaction do
633 User.where("salt IS NULL OR salt = ''").find_each do |user|
635 User.where("salt IS NULL OR salt = ''").find_each do |user|
634 next if user.hashed_password.blank?
636 next if user.hashed_password.blank?
635 salt = User.generate_salt
637 salt = User.generate_salt
636 hashed_password = User.hash_password("#{salt}#{user.hashed_password}")
638 hashed_password = User.hash_password("#{salt}#{user.hashed_password}")
637 User.where(:id => user.id).update_all(:salt => salt, :hashed_password => hashed_password)
639 User.where(:id => user.id).update_all(:salt => salt, :hashed_password => hashed_password)
638 end
640 end
639 end
641 end
640 end
642 end
641
643
642 protected
644 protected
643
645
644 def validate_password_length
646 def validate_password_length
645 return if password.blank? && generate_password?
647 return if password.blank? && generate_password?
646 # Password length validation based on setting
648 # Password length validation based on setting
647 if !password.nil? && password.size < Setting.password_min_length.to_i
649 if !password.nil? && password.size < Setting.password_min_length.to_i
648 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
650 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
649 end
651 end
650 end
652 end
651
653
652 private
654 private
653
655
654 def generate_password_if_needed
656 def generate_password_if_needed
655 if generate_password? && auth_source.nil?
657 if generate_password? && auth_source.nil?
656 length = [Setting.password_min_length.to_i + 2, 10].max
658 length = [Setting.password_min_length.to_i + 2, 10].max
657 random_password(length)
659 random_password(length)
658 end
660 end
659 end
661 end
660
662
661 # Removes references that are not handled by associations
663 # Removes references that are not handled by associations
662 # Things that are not deleted are reassociated with the anonymous user
664 # Things that are not deleted are reassociated with the anonymous user
663 def remove_references_before_destroy
665 def remove_references_before_destroy
664 return if self.id.nil?
666 return if self.id.nil?
665
667
666 substitute = User.anonymous
668 substitute = User.anonymous
667 Attachment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
669 Attachment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
668 Comment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
670 Comment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
669 Issue.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
671 Issue.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
670 Issue.update_all 'assigned_to_id = NULL', ['assigned_to_id = ?', id]
672 Issue.update_all 'assigned_to_id = NULL', ['assigned_to_id = ?', id]
671 Journal.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
673 Journal.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
672 JournalDetail.update_all ['old_value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND old_value = ?", id.to_s]
674 JournalDetail.update_all ['old_value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND old_value = ?", id.to_s]
673 JournalDetail.update_all ['value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND value = ?", id.to_s]
675 JournalDetail.update_all ['value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND value = ?", id.to_s]
674 Message.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
676 Message.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
675 News.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
677 News.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
676 # Remove private queries and keep public ones
678 # Remove private queries and keep public ones
677 ::Query.delete_all ['user_id = ? AND visibility = ?', id, ::Query::VISIBILITY_PRIVATE]
679 ::Query.delete_all ['user_id = ? AND visibility = ?', id, ::Query::VISIBILITY_PRIVATE]
678 ::Query.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
680 ::Query.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
679 TimeEntry.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
681 TimeEntry.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
680 Token.delete_all ['user_id = ?', id]
682 Token.delete_all ['user_id = ?', id]
681 Watcher.delete_all ['user_id = ?', id]
683 Watcher.delete_all ['user_id = ?', id]
682 WikiContent.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
684 WikiContent.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
683 WikiContent::Version.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
685 WikiContent::Version.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
684 end
686 end
685
687
686 # Return password digest
688 # Return password digest
687 def self.hash_password(clear_password)
689 def self.hash_password(clear_password)
688 Digest::SHA1.hexdigest(clear_password || "")
690 Digest::SHA1.hexdigest(clear_password || "")
689 end
691 end
690
692
691 # Returns a 128bits random salt as a hex string (32 chars long)
693 # Returns a 128bits random salt as a hex string (32 chars long)
692 def self.generate_salt
694 def self.generate_salt
693 Redmine::Utils.random_hex(16)
695 Redmine::Utils.random_hex(16)
694 end
696 end
695
697
696 end
698 end
697
699
698 class AnonymousUser < User
700 class AnonymousUser < User
699 validate :validate_anonymous_uniqueness, :on => :create
701 validate :validate_anonymous_uniqueness, :on => :create
700
702
701 def validate_anonymous_uniqueness
703 def validate_anonymous_uniqueness
702 # There should be only one AnonymousUser in the database
704 # There should be only one AnonymousUser in the database
703 errors.add :base, 'An anonymous user already exists.' if AnonymousUser.exists?
705 errors.add :base, 'An anonymous user already exists.' if AnonymousUser.exists?
704 end
706 end
705
707
706 def available_custom_fields
708 def available_custom_fields
707 []
709 []
708 end
710 end
709
711
710 # Overrides a few properties
712 # Overrides a few properties
711 def logged?; false end
713 def logged?; false end
712 def admin; false end
714 def admin; false end
713 def name(*args); I18n.t(:label_user_anonymous) end
715 def name(*args); I18n.t(:label_user_anonymous) end
714 def mail; nil end
716 def mail; nil end
715 def time_zone; nil end
717 def time_zone; nil end
716 def rss_key; nil end
718 def rss_key; nil end
717
719
718 def pref
720 def pref
719 UserPreference.new(:user => self)
721 UserPreference.new(:user => self)
720 end
722 end
721
723
722 # Returns the user's bult-in role
724 # Returns the user's bult-in role
723 def builtin_role
725 def builtin_role
724 @builtin_role ||= Role.anonymous
726 @builtin_role ||= Role.anonymous
725 end
727 end
726
728
727 def membership(*args)
729 def membership(*args)
728 nil
730 nil
729 end
731 end
730
732
731 def member_of?(*args)
733 def member_of?(*args)
732 false
734 false
733 end
735 end
734
736
735 # Anonymous user can not be destroyed
737 # Anonymous user can not be destroyed
736 def destroy
738 def destroy
737 false
739 false
738 end
740 end
739 end
741 end
General Comments 0
You need to be logged in to leave comments. Login now