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