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