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