##// END OF EJS Templates
Validate status of users and groups....
Jean-Philippe Lang -
r14938:97a647c1e5d4
parent child
Show More
@@ -1,117 +1,119
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class Group < Principal
19 19 include Redmine::SafeAttributes
20 20
21 21 has_and_belongs_to_many :users,
22 22 :join_table => "#{table_name_prefix}groups_users#{table_name_suffix}",
23 23 :after_add => :user_added,
24 24 :after_remove => :user_removed
25 25
26 26 acts_as_customizable
27 27
28 28 validates_presence_of :lastname
29 29 validates_uniqueness_of :lastname, :case_sensitive => false
30 30 validates_length_of :lastname, :maximum => 255
31 31 attr_protected :id
32 32
33 self.valid_statuses = [STATUS_ACTIVE]
34
33 35 before_destroy :remove_references_before_destroy
34 36
35 37 scope :sorted, lambda { order(:type => :asc, :lastname => :asc) }
36 38 scope :named, lambda {|arg| where("LOWER(#{table_name}.lastname) = LOWER(?)", arg.to_s.strip)}
37 39 scope :givable, lambda {where(:type => 'Group')}
38 40
39 41 safe_attributes 'name',
40 42 'user_ids',
41 43 'custom_field_values',
42 44 'custom_fields',
43 45 :if => lambda {|group, user| user.admin? && !group.builtin?}
44 46
45 47 def to_s
46 48 name.to_s
47 49 end
48 50
49 51 def name
50 52 lastname
51 53 end
52 54
53 55 def name=(arg)
54 56 self.lastname = arg
55 57 end
56 58
57 59 def builtin_type
58 60 nil
59 61 end
60 62
61 63 # Return true if the group is a builtin group
62 64 def builtin?
63 65 false
64 66 end
65 67
66 68 # Returns true if the group can be given to a user
67 69 def givable?
68 70 !builtin?
69 71 end
70 72
71 73 def user_added(user)
72 74 members.each do |member|
73 75 next if member.project.nil?
74 76 user_member = Member.find_by_project_id_and_user_id(member.project_id, user.id) || Member.new(:project_id => member.project_id, :user_id => user.id)
75 77 member.member_roles.each do |member_role|
76 78 user_member.member_roles << MemberRole.new(:role => member_role.role, :inherited_from => member_role.id)
77 79 end
78 80 user_member.save!
79 81 end
80 82 end
81 83
82 84 def user_removed(user)
83 85 members.each do |member|
84 86 MemberRole.
85 87 joins(:member).
86 88 where("#{Member.table_name}.user_id = ? AND #{MemberRole.table_name}.inherited_from IN (?)", user.id, member.member_role_ids).
87 89 each(&:destroy)
88 90 end
89 91 end
90 92
91 93 def self.human_attribute_name(attribute_key_name, *args)
92 94 attr_name = attribute_key_name.to_s
93 95 if attr_name == 'lastname'
94 96 attr_name = "name"
95 97 end
96 98 super(attr_name, *args)
97 99 end
98 100
99 101 def self.anonymous
100 102 GroupAnonymous.load_instance
101 103 end
102 104
103 105 def self.non_member
104 106 GroupNonMember.load_instance
105 107 end
106 108
107 109 private
108 110
109 111 # Removes references that are not handled by associations
110 112 def remove_references_before_destroy
111 113 return if self.id.nil?
112 114
113 115 Issue.where(['assigned_to_id = ?', id]).update_all('assigned_to_id = NULL')
114 116 end
115 117 end
116 118
117 119 require_dependency "group_builtin"
@@ -1,184 +1,196
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class Principal < ActiveRecord::Base
19 19 self.table_name = "#{table_name_prefix}users#{table_name_suffix}"
20 20
21 21 # Account statuses
22 22 STATUS_ANONYMOUS = 0
23 23 STATUS_ACTIVE = 1
24 24 STATUS_REGISTERED = 2
25 25 STATUS_LOCKED = 3
26 26
27 class_attribute :valid_statuses
28
27 29 has_many :members, :foreign_key => 'user_id', :dependent => :destroy
28 30 has_many :memberships,
29 31 lambda {preload(:project, :roles).
30 32 joins(:project).
31 33 where("#{Project.table_name}.status<>#{Project::STATUS_ARCHIVED}")},
32 34 :class_name => 'Member',
33 35 :foreign_key => 'user_id'
34 36 has_many :projects, :through => :memberships
35 37 has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
36 38
39 validate :validate_status
40
37 41 # Groups and active users
38 42 scope :active, lambda { where(:status => STATUS_ACTIVE) }
39 43
40 44 scope :visible, lambda {|*args|
41 45 user = args.first || User.current
42 46
43 47 if user.admin?
44 48 all
45 49 else
46 50 view_all_active = false
47 51 if user.memberships.to_a.any?
48 52 view_all_active = user.memberships.any? {|m| m.roles.any? {|r| r.users_visibility == 'all'}}
49 53 else
50 54 view_all_active = user.builtin_role.users_visibility == 'all'
51 55 end
52 56
53 57 if view_all_active
54 58 active
55 59 else
56 60 # self and members of visible projects
57 61 active.where("#{table_name}.id = ? OR #{table_name}.id IN (SELECT user_id FROM #{Member.table_name} WHERE project_id IN (?))",
58 62 user.id, user.visible_project_ids
59 63 )
60 64 end
61 65 end
62 66 }
63 67
64 68 scope :like, lambda {|q|
65 69 q = q.to_s
66 70 if q.blank?
67 71 where({})
68 72 else
69 73 pattern = "%#{q}%"
70 74 sql = %w(login firstname lastname).map {|column| "LOWER(#{table_name}.#{column}) LIKE LOWER(:p)"}.join(" OR ")
71 75 sql << " OR #{table_name}.id IN (SELECT user_id FROM #{EmailAddress.table_name} WHERE LOWER(address) LIKE LOWER(:p))"
72 76 params = {:p => pattern}
73 77 if q =~ /^(.+)\s+(.+)$/
74 78 a, b = "#{$1}%", "#{$2}%"
75 79 sql << " OR (LOWER(#{table_name}.firstname) LIKE LOWER(:a) AND LOWER(#{table_name}.lastname) LIKE LOWER(:b))"
76 80 sql << " OR (LOWER(#{table_name}.firstname) LIKE LOWER(:b) AND LOWER(#{table_name}.lastname) LIKE LOWER(:a))"
77 81 params.merge!(:a => a, :b => b)
78 82 end
79 83 where(sql, params)
80 84 end
81 85 }
82 86
83 87 # Principals that are members of a collection of projects
84 88 scope :member_of, lambda {|projects|
85 89 projects = [projects] if projects.is_a?(Project)
86 90 if projects.blank?
87 91 where("1=0")
88 92 else
89 93 ids = projects.map(&:id)
90 94 active.where("#{Principal.table_name}.id IN (SELECT DISTINCT user_id FROM #{Member.table_name} WHERE project_id IN (?))", ids)
91 95 end
92 96 }
93 97 # Principals that are not members of projects
94 98 scope :not_member_of, lambda {|projects|
95 99 projects = [projects] unless projects.is_a?(Array)
96 100 if projects.empty?
97 101 where("1=0")
98 102 else
99 103 ids = projects.map(&:id)
100 104 where("#{Principal.table_name}.id NOT IN (SELECT DISTINCT user_id FROM #{Member.table_name} WHERE project_id IN (?))", ids)
101 105 end
102 106 }
103 107 scope :sorted, lambda { order(*Principal.fields_for_order_statement)}
104 108
105 109 before_create :set_default_empty_values
106 110
107 111 def name(formatter = nil)
108 112 to_s
109 113 end
110 114
111 115 def mail=(*args)
112 116 nil
113 117 end
114 118
115 119 def mail
116 120 nil
117 121 end
118 122
119 123 def visible?(user=User.current)
120 124 Principal.visible(user).where(:id => id).first == self
121 125 end
122 126
123 127 # Return true if the principal is a member of project
124 128 def member_of?(project)
125 129 projects.to_a.include?(project)
126 130 end
127 131
128 132 def <=>(principal)
129 133 if principal.nil?
130 134 -1
131 135 elsif self.class.name == principal.class.name
132 136 self.to_s.casecmp(principal.to_s)
133 137 else
134 138 # groups after users
135 139 principal.class.name <=> self.class.name
136 140 end
137 141 end
138 142
139 143 # Returns an array of fields names than can be used to make an order statement for principals.
140 144 # Users are sorted before Groups.
141 145 # Examples:
142 146 def self.fields_for_order_statement(table=nil)
143 147 table ||= table_name
144 148 columns = ['type DESC'] + (User.name_formatter[:order] - ['id']) + ['lastname', 'id']
145 149 columns.uniq.map {|field| "#{table}.#{field}"}
146 150 end
147 151
148 152 # Returns the principal that matches the keyword among principals
149 153 def self.detect_by_keyword(principals, keyword)
150 154 keyword = keyword.to_s
151 155 return nil if keyword.blank?
152 156
153 157 principal = nil
154 158 principal ||= principals.detect {|a| keyword.casecmp(a.login.to_s) == 0}
155 159 principal ||= principals.detect {|a| keyword.casecmp(a.mail.to_s) == 0}
156 160
157 161 if principal.nil? && keyword.match(/ /)
158 162 firstname, lastname = *(keyword.split) # "First Last Throwaway"
159 163 principal ||= principals.detect {|a|
160 164 a.is_a?(User) &&
161 165 firstname.casecmp(a.firstname.to_s) == 0 &&
162 166 lastname.casecmp(a.lastname.to_s) == 0
163 167 }
164 168 end
165 169 if principal.nil?
166 170 principal ||= principals.detect {|a| keyword.casecmp(a.name) == 0}
167 171 end
168 172 principal
169 173 end
170 174
171 175 protected
172 176
173 177 # Make sure we don't try to insert NULL values (see #4632)
174 178 def set_default_empty_values
175 179 self.login ||= ''
176 180 self.hashed_password ||= ''
177 181 self.firstname ||= ''
178 182 self.lastname ||= ''
179 183 true
180 184 end
185
186 def validate_status
187 if status_changed? && self.class.valid_statuses.present?
188 unless self.class.valid_statuses.include?(status)
189 errors.add :status, :invalid
190 end
191 end
192 end
181 193 end
182 194
183 195 require_dependency "user"
184 196 require_dependency "group"
@@ -1,919 +1,923
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require "digest/sha1"
19 19
20 20 class User < Principal
21 21 include Redmine::SafeAttributes
22 22
23 23 # Different ways of displaying/sorting users
24 24 USER_FORMATS = {
25 25 :firstname_lastname => {
26 26 :string => '#{firstname} #{lastname}',
27 27 :order => %w(firstname lastname id),
28 28 :setting_order => 1
29 29 },
30 30 :firstname_lastinitial => {
31 31 :string => '#{firstname} #{lastname.to_s.chars.first}.',
32 32 :order => %w(firstname lastname id),
33 33 :setting_order => 2
34 34 },
35 35 :firstinitial_lastname => {
36 36 :string => '#{firstname.to_s.gsub(/(([[:alpha:]])[[:alpha:]]*\.?)/, \'\2.\')} #{lastname}',
37 37 :order => %w(firstname lastname id),
38 38 :setting_order => 2
39 39 },
40 40 :firstname => {
41 41 :string => '#{firstname}',
42 42 :order => %w(firstname id),
43 43 :setting_order => 3
44 44 },
45 45 :lastname_firstname => {
46 46 :string => '#{lastname} #{firstname}',
47 47 :order => %w(lastname firstname id),
48 48 :setting_order => 4
49 49 },
50 50 :lastnamefirstname => {
51 51 :string => '#{lastname}#{firstname}',
52 52 :order => %w(lastname firstname id),
53 53 :setting_order => 5
54 54 },
55 55 :lastname_comma_firstname => {
56 56 :string => '#{lastname}, #{firstname}',
57 57 :order => %w(lastname firstname id),
58 58 :setting_order => 6
59 59 },
60 60 :lastname => {
61 61 :string => '#{lastname}',
62 62 :order => %w(lastname id),
63 63 :setting_order => 7
64 64 },
65 65 :username => {
66 66 :string => '#{login}',
67 67 :order => %w(login id),
68 68 :setting_order => 8
69 69 },
70 70 }
71 71
72 72 MAIL_NOTIFICATION_OPTIONS = [
73 73 ['all', :label_user_mail_option_all],
74 74 ['selected', :label_user_mail_option_selected],
75 75 ['only_my_events', :label_user_mail_option_only_my_events],
76 76 ['only_assigned', :label_user_mail_option_only_assigned],
77 77 ['only_owner', :label_user_mail_option_only_owner],
78 78 ['none', :label_user_mail_option_none]
79 79 ]
80 80
81 81 has_and_belongs_to_many :groups,
82 82 :join_table => "#{table_name_prefix}groups_users#{table_name_suffix}",
83 83 :after_add => Proc.new {|user, group| group.user_added(user)},
84 84 :after_remove => Proc.new {|user, group| group.user_removed(user)}
85 85 has_many :changesets, :dependent => :nullify
86 86 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
87 87 has_one :rss_token, lambda {where "action='feeds'"}, :class_name => 'Token'
88 88 has_one :api_token, lambda {where "action='api'"}, :class_name => 'Token'
89 89 has_one :email_address, lambda {where :is_default => true}, :autosave => true
90 90 has_many :email_addresses, :dependent => :delete_all
91 91 belongs_to :auth_source
92 92
93 93 scope :logged, lambda { where("#{User.table_name}.status <> #{STATUS_ANONYMOUS}") }
94 94 scope :status, lambda {|arg| where(arg.blank? ? nil : {:status => arg.to_i}) }
95 95
96 96 acts_as_customizable
97 97
98 98 attr_accessor :password, :password_confirmation, :generate_password
99 99 attr_accessor :last_before_login_on
100 100 attr_accessor :remote_ip
101 101
102 102 # Prevents unauthorized assignments
103 103 attr_protected :login, :admin, :password, :password_confirmation, :hashed_password
104 104
105 105 LOGIN_LENGTH_LIMIT = 60
106 106 MAIL_LENGTH_LIMIT = 60
107 107
108 108 validates_presence_of :login, :firstname, :lastname, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
109 109 validates_uniqueness_of :login, :if => Proc.new { |user| user.login_changed? && user.login.present? }, :case_sensitive => false
110 110 # Login must contain letters, numbers, underscores only
111 111 validates_format_of :login, :with => /\A[a-z0-9_\-@\.]*\z/i
112 112 validates_length_of :login, :maximum => LOGIN_LENGTH_LIMIT
113 113 validates_length_of :firstname, :lastname, :maximum => 30
114 114 validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true
115 115 validate :validate_password_length
116 116 validate do
117 117 if password_confirmation && password != password_confirmation
118 118 errors.add(:password, :confirmation)
119 119 end
120 120 end
121 121
122 self.valid_statuses = [STATUS_ACTIVE, STATUS_REGISTERED, STATUS_LOCKED]
123
122 124 before_validation :instantiate_email_address
123 125 before_create :set_mail_notification
124 126 before_save :generate_password_if_needed, :update_hashed_password
125 127 before_destroy :remove_references_before_destroy
126 128 after_save :update_notified_project_ids, :destroy_tokens, :deliver_security_notification
127 129 after_destroy :deliver_security_notification
128 130
129 131 scope :in_group, lambda {|group|
130 132 group_id = group.is_a?(Group) ? group.id : group.to_i
131 133 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)
132 134 }
133 135 scope :not_in_group, lambda {|group|
134 136 group_id = group.is_a?(Group) ? group.id : group.to_i
135 137 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)
136 138 }
137 139 scope :sorted, lambda { order(*User.fields_for_order_statement)}
138 140 scope :having_mail, lambda {|arg|
139 141 addresses = Array.wrap(arg).map {|a| a.to_s.downcase}
140 142 if addresses.any?
141 143 joins(:email_addresses).where("LOWER(#{EmailAddress.table_name}.address) IN (?)", addresses).uniq
142 144 else
143 145 none
144 146 end
145 147 }
146 148
147 149 def set_mail_notification
148 150 self.mail_notification = Setting.default_notification_option if self.mail_notification.blank?
149 151 true
150 152 end
151 153
152 154 def update_hashed_password
153 155 # update hashed_password if password was set
154 156 if self.password && self.auth_source_id.blank?
155 157 salt_password(password)
156 158 end
157 159 end
158 160
159 161 alias :base_reload :reload
160 162 def reload(*args)
161 163 @name = nil
162 164 @projects_by_role = nil
163 165 @membership_by_project_id = nil
164 166 @notified_projects_ids = nil
165 167 @notified_projects_ids_changed = false
166 168 @builtin_role = nil
167 169 @visible_project_ids = nil
168 170 @managed_roles = nil
169 171 base_reload(*args)
170 172 end
171 173
172 174 def mail
173 175 email_address.try(:address)
174 176 end
175 177
176 178 def mail=(arg)
177 179 email = email_address || build_email_address
178 180 email.address = arg
179 181 end
180 182
181 183 def mail_changed?
182 184 email_address.try(:address_changed?)
183 185 end
184 186
185 187 def mails
186 188 email_addresses.pluck(:address)
187 189 end
188 190
189 191 def self.find_or_initialize_by_identity_url(url)
190 192 user = where(:identity_url => url).first
191 193 unless user
192 194 user = User.new
193 195 user.identity_url = url
194 196 end
195 197 user
196 198 end
197 199
198 200 def identity_url=(url)
199 201 if url.blank?
200 202 write_attribute(:identity_url, '')
201 203 else
202 204 begin
203 205 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
204 206 rescue OpenIdAuthentication::InvalidOpenId
205 207 # Invalid url, don't save
206 208 end
207 209 end
208 210 self.read_attribute(:identity_url)
209 211 end
210 212
211 213 # Returns the user that matches provided login and password, or nil
212 214 def self.try_to_login(login, password, active_only=true)
213 215 login = login.to_s
214 216 password = password.to_s
215 217
216 218 # Make sure no one can sign in with an empty login or password
217 219 return nil if login.empty? || password.empty?
218 220 user = find_by_login(login)
219 221 if user
220 222 # user is already in local database
221 223 return nil unless user.check_password?(password)
222 224 return nil if !user.active? && active_only
223 225 else
224 226 # user is not yet registered, try to authenticate with available sources
225 227 attrs = AuthSource.authenticate(login, password)
226 228 if attrs
227 229 user = new(attrs)
228 230 user.login = login
229 231 user.language = Setting.default_language
230 232 if user.save
231 233 user.reload
232 234 logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source
233 235 end
234 236 end
235 237 end
236 238 user.update_column(:last_login_on, Time.now) if user && !user.new_record? && user.active?
237 239 user
238 240 rescue => text
239 241 raise text
240 242 end
241 243
242 244 # Returns the user who matches the given autologin +key+ or nil
243 245 def self.try_to_autologin(key)
244 246 user = Token.find_active_user('autologin', key, Setting.autologin.to_i)
245 247 if user
246 248 user.update_column(:last_login_on, Time.now)
247 249 user
248 250 end
249 251 end
250 252
251 253 def self.name_formatter(formatter = nil)
252 254 USER_FORMATS[formatter || Setting.user_format] || USER_FORMATS[:firstname_lastname]
253 255 end
254 256
255 257 # Returns an array of fields names than can be used to make an order statement for users
256 258 # according to how user names are displayed
257 259 # Examples:
258 260 #
259 261 # User.fields_for_order_statement => ['users.login', 'users.id']
260 262 # User.fields_for_order_statement('authors') => ['authors.login', 'authors.id']
261 263 def self.fields_for_order_statement(table=nil)
262 264 table ||= table_name
263 265 name_formatter[:order].map {|field| "#{table}.#{field}"}
264 266 end
265 267
266 268 # Return user's full name for display
267 269 def name(formatter = nil)
268 270 f = self.class.name_formatter(formatter)
269 271 if formatter
270 272 eval('"' + f[:string] + '"')
271 273 else
272 274 @name ||= eval('"' + f[:string] + '"')
273 275 end
274 276 end
275 277
276 278 def active?
277 279 self.status == STATUS_ACTIVE
278 280 end
279 281
280 282 def registered?
281 283 self.status == STATUS_REGISTERED
282 284 end
283 285
284 286 def locked?
285 287 self.status == STATUS_LOCKED
286 288 end
287 289
288 290 def activate
289 291 self.status = STATUS_ACTIVE
290 292 end
291 293
292 294 def register
293 295 self.status = STATUS_REGISTERED
294 296 end
295 297
296 298 def lock
297 299 self.status = STATUS_LOCKED
298 300 end
299 301
300 302 def activate!
301 303 update_attribute(:status, STATUS_ACTIVE)
302 304 end
303 305
304 306 def register!
305 307 update_attribute(:status, STATUS_REGISTERED)
306 308 end
307 309
308 310 def lock!
309 311 update_attribute(:status, STATUS_LOCKED)
310 312 end
311 313
312 314 # Returns true if +clear_password+ is the correct user's password, otherwise false
313 315 def check_password?(clear_password)
314 316 if auth_source_id.present?
315 317 auth_source.authenticate(self.login, clear_password)
316 318 else
317 319 User.hash_password("#{salt}#{User.hash_password clear_password}") == hashed_password
318 320 end
319 321 end
320 322
321 323 # Generates a random salt and computes hashed_password for +clear_password+
322 324 # The hashed password is stored in the following form: SHA1(salt + SHA1(password))
323 325 def salt_password(clear_password)
324 326 self.salt = User.generate_salt
325 327 self.hashed_password = User.hash_password("#{salt}#{User.hash_password clear_password}")
326 328 self.passwd_changed_on = Time.now.change(:usec => 0)
327 329 end
328 330
329 331 # Does the backend storage allow this user to change their password?
330 332 def change_password_allowed?
331 333 return true if auth_source.nil?
332 334 return auth_source.allow_password_changes?
333 335 end
334 336
335 337 # Returns true if the user password has expired
336 338 def password_expired?
337 339 period = Setting.password_max_age.to_i
338 340 if period.zero?
339 341 false
340 342 else
341 343 changed_on = self.passwd_changed_on || Time.at(0)
342 344 changed_on < period.days.ago
343 345 end
344 346 end
345 347
346 348 def must_change_password?
347 349 (must_change_passwd? || password_expired?) && change_password_allowed?
348 350 end
349 351
350 352 def generate_password?
351 353 generate_password == '1' || generate_password == true
352 354 end
353 355
354 356 # Generate and set a random password on given length
355 357 def random_password(length=40)
356 358 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
357 359 chars -= %w(0 O 1 l)
358 360 password = ''
359 361 length.times {|i| password << chars[SecureRandom.random_number(chars.size)] }
360 362 self.password = password
361 363 self.password_confirmation = password
362 364 self
363 365 end
364 366
365 367 def pref
366 368 self.preference ||= UserPreference.new(:user => self)
367 369 end
368 370
369 371 def time_zone
370 372 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
371 373 end
372 374
373 375 def force_default_language?
374 376 Setting.force_default_language_for_loggedin?
375 377 end
376 378
377 379 def language
378 380 if force_default_language?
379 381 Setting.default_language
380 382 else
381 383 super
382 384 end
383 385 end
384 386
385 387 def wants_comments_in_reverse_order?
386 388 self.pref[:comments_sorting] == 'desc'
387 389 end
388 390
389 391 # Return user's RSS key (a 40 chars long string), used to access feeds
390 392 def rss_key
391 393 if rss_token.nil?
392 394 create_rss_token(:action => 'feeds')
393 395 end
394 396 rss_token.value
395 397 end
396 398
397 399 # Return user's API key (a 40 chars long string), used to access the API
398 400 def api_key
399 401 if api_token.nil?
400 402 create_api_token(:action => 'api')
401 403 end
402 404 api_token.value
403 405 end
404 406
405 407 # Generates a new session token and returns its value
406 408 def generate_session_token
407 409 token = Token.create!(:user_id => id, :action => 'session')
408 410 token.value
409 411 end
410 412
411 413 # Returns true if token is a valid session token for the user whose id is user_id
412 414 def self.verify_session_token(user_id, token)
413 415 return false if user_id.blank? || token.blank?
414 416
415 417 scope = Token.where(:user_id => user_id, :value => token.to_s, :action => 'session')
416 418 if Setting.session_lifetime?
417 419 scope = scope.where("created_on > ?", Setting.session_lifetime.to_i.minutes.ago)
418 420 end
419 421 if Setting.session_timeout?
420 422 scope = scope.where("updated_on > ?", Setting.session_timeout.to_i.minutes.ago)
421 423 end
422 424 scope.update_all(:updated_on => Time.now) == 1
423 425 end
424 426
425 427 # Return an array of project ids for which the user has explicitly turned mail notifications on
426 428 def notified_projects_ids
427 429 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
428 430 end
429 431
430 432 def notified_project_ids=(ids)
431 433 @notified_projects_ids_changed = true
432 434 @notified_projects_ids = ids.map(&:to_i).uniq.select {|n| n > 0}
433 435 end
434 436
435 437 # Updates per project notifications (after_save callback)
436 438 def update_notified_project_ids
437 439 if @notified_projects_ids_changed
438 440 ids = (mail_notification == 'selected' ? Array.wrap(notified_projects_ids).reject(&:blank?) : [])
439 441 members.update_all(:mail_notification => false)
440 442 members.where(:project_id => ids).update_all(:mail_notification => true) if ids.any?
441 443 end
442 444 end
443 445 private :update_notified_project_ids
444 446
445 447 def valid_notification_options
446 448 self.class.valid_notification_options(self)
447 449 end
448 450
449 451 # Only users that belong to more than 1 project can select projects for which they are notified
450 452 def self.valid_notification_options(user=nil)
451 453 # Note that @user.membership.size would fail since AR ignores
452 454 # :include association option when doing a count
453 455 if user.nil? || user.memberships.length < 1
454 456 MAIL_NOTIFICATION_OPTIONS.reject {|option| option.first == 'selected'}
455 457 else
456 458 MAIL_NOTIFICATION_OPTIONS
457 459 end
458 460 end
459 461
460 462 # Find a user account by matching the exact login and then a case-insensitive
461 463 # version. Exact matches will be given priority.
462 464 def self.find_by_login(login)
463 465 login = Redmine::CodesetUtil.replace_invalid_utf8(login.to_s)
464 466 if login.present?
465 467 # First look for an exact match
466 468 user = where(:login => login).detect {|u| u.login == login}
467 469 unless user
468 470 # Fail over to case-insensitive if none was found
469 471 user = where("LOWER(login) = ?", login.downcase).first
470 472 end
471 473 user
472 474 end
473 475 end
474 476
475 477 def self.find_by_rss_key(key)
476 478 Token.find_active_user('feeds', key)
477 479 end
478 480
479 481 def self.find_by_api_key(key)
480 482 Token.find_active_user('api', key)
481 483 end
482 484
483 485 # Makes find_by_mail case-insensitive
484 486 def self.find_by_mail(mail)
485 487 having_mail(mail).first
486 488 end
487 489
488 490 # Returns true if the default admin account can no longer be used
489 491 def self.default_admin_account_changed?
490 492 !User.active.find_by_login("admin").try(:check_password?, "admin")
491 493 end
492 494
493 495 def to_s
494 496 name
495 497 end
496 498
497 499 CSS_CLASS_BY_STATUS = {
498 500 STATUS_ANONYMOUS => 'anon',
499 501 STATUS_ACTIVE => 'active',
500 502 STATUS_REGISTERED => 'registered',
501 503 STATUS_LOCKED => 'locked'
502 504 }
503 505
504 506 def css_classes
505 507 "user #{CSS_CLASS_BY_STATUS[status]}"
506 508 end
507 509
508 510 # Returns the current day according to user's time zone
509 511 def today
510 512 if time_zone.nil?
511 513 Date.today
512 514 else
513 515 Time.now.in_time_zone(time_zone).to_date
514 516 end
515 517 end
516 518
517 519 # Returns the day of +time+ according to user's time zone
518 520 def time_to_date(time)
519 521 if time_zone.nil?
520 522 time.to_date
521 523 else
522 524 time.in_time_zone(time_zone).to_date
523 525 end
524 526 end
525 527
526 528 def logged?
527 529 true
528 530 end
529 531
530 532 def anonymous?
531 533 !logged?
532 534 end
533 535
534 536 # Returns user's membership for the given project
535 537 # or nil if the user is not a member of project
536 538 def membership(project)
537 539 project_id = project.is_a?(Project) ? project.id : project
538 540
539 541 @membership_by_project_id ||= Hash.new {|h, project_id|
540 542 h[project_id] = memberships.where(:project_id => project_id).first
541 543 }
542 544 @membership_by_project_id[project_id]
543 545 end
544 546
545 547 # Returns the user's bult-in role
546 548 def builtin_role
547 549 @builtin_role ||= Role.non_member
548 550 end
549 551
550 552 # Return user's roles for project
551 553 def roles_for_project(project)
552 554 # No role on archived projects
553 555 return [] if project.nil? || project.archived?
554 556 if membership = membership(project)
555 557 membership.roles.to_a
556 558 elsif project.is_public?
557 559 project.override_roles(builtin_role)
558 560 else
559 561 []
560 562 end
561 563 end
562 564
563 565 # Returns a hash of user's projects grouped by roles
564 566 def projects_by_role
565 567 return @projects_by_role if @projects_by_role
566 568
567 569 hash = Hash.new([])
568 570
569 571 group_class = anonymous? ? GroupAnonymous : GroupNonMember
570 572 members = Member.joins(:project, :principal).
571 573 where("#{Project.table_name}.status <> 9").
572 574 where("#{Member.table_name}.user_id = ? OR (#{Project.table_name}.is_public = ? AND #{Principal.table_name}.type = ?)", self.id, true, group_class.name).
573 575 preload(:project, :roles).
574 576 to_a
575 577
576 578 members.reject! {|member| member.user_id != id && project_ids.include?(member.project_id)}
577 579 members.each do |member|
578 580 if member.project
579 581 member.roles.each do |role|
580 582 hash[role] = [] unless hash.key?(role)
581 583 hash[role] << member.project
582 584 end
583 585 end
584 586 end
585 587
586 588 hash.each do |role, projects|
587 589 projects.uniq!
588 590 end
589 591
590 592 @projects_by_role = hash
591 593 end
592 594
593 595 # Returns the ids of visible projects
594 596 def visible_project_ids
595 597 @visible_project_ids ||= Project.visible(self).pluck(:id)
596 598 end
597 599
598 600 # Returns the roles that the user is allowed to manage for the given project
599 601 def managed_roles(project)
600 602 if admin?
601 603 @managed_roles ||= Role.givable.to_a
602 604 else
603 605 membership(project).try(:managed_roles) || []
604 606 end
605 607 end
606 608
607 609 # Returns true if user is arg or belongs to arg
608 610 def is_or_belongs_to?(arg)
609 611 if arg.is_a?(User)
610 612 self == arg
611 613 elsif arg.is_a?(Group)
612 614 arg.users.include?(self)
613 615 else
614 616 false
615 617 end
616 618 end
617 619
618 620 # Return true if the user is allowed to do the specified action on a specific context
619 621 # Action can be:
620 622 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
621 623 # * a permission Symbol (eg. :edit_project)
622 624 # Context can be:
623 625 # * a project : returns true if user is allowed to do the specified action on this project
624 626 # * an array of projects : returns true if user is allowed on every project
625 627 # * nil with options[:global] set : check if user has at least one role allowed for this action,
626 628 # or falls back to Non Member / Anonymous permissions depending if the user is logged
627 629 def allowed_to?(action, context, options={}, &block)
628 630 if context && context.is_a?(Project)
629 631 return false unless context.allows_to?(action)
630 632 # Admin users are authorized for anything else
631 633 return true if admin?
632 634
633 635 roles = roles_for_project(context)
634 636 return false unless roles
635 637 roles.any? {|role|
636 638 (context.is_public? || role.member?) &&
637 639 role.allowed_to?(action) &&
638 640 (block_given? ? yield(role, self) : true)
639 641 }
640 642 elsif context && context.is_a?(Array)
641 643 if context.empty?
642 644 false
643 645 else
644 646 # Authorize if user is authorized on every element of the array
645 647 context.map {|project| allowed_to?(action, project, options, &block)}.reduce(:&)
646 648 end
647 649 elsif context
648 650 raise ArgumentError.new("#allowed_to? context argument must be a Project, an Array of projects or nil")
649 651 elsif options[:global]
650 652 # Admin users are always authorized
651 653 return true if admin?
652 654
653 655 # authorize if user has at least one role that has this permission
654 656 roles = memberships.collect {|m| m.roles}.flatten.uniq
655 657 roles << (self.logged? ? Role.non_member : Role.anonymous)
656 658 roles.any? {|role|
657 659 role.allowed_to?(action) &&
658 660 (block_given? ? yield(role, self) : true)
659 661 }
660 662 else
661 663 false
662 664 end
663 665 end
664 666
665 667 # Is the user allowed to do the specified action on any project?
666 668 # See allowed_to? for the actions and valid options.
667 669 #
668 670 # NB: this method is not used anywhere in the core codebase as of
669 671 # 2.5.2, but it's used by many plugins so if we ever want to remove
670 672 # it it has to be carefully deprecated for a version or two.
671 673 def allowed_to_globally?(action, options={}, &block)
672 674 allowed_to?(action, nil, options.reverse_merge(:global => true), &block)
673 675 end
674 676
675 677 def allowed_to_view_all_time_entries?(context)
676 678 allowed_to?(:view_time_entries, context) do |role, user|
677 679 role.time_entries_visibility == 'all'
678 680 end
679 681 end
680 682
681 683 # Returns true if the user is allowed to delete the user's own account
682 684 def own_account_deletable?
683 685 Setting.unsubscribe? &&
684 686 (!admin? || User.active.where("admin = ? AND id <> ?", true, id).exists?)
685 687 end
686 688
687 689 safe_attributes 'firstname',
688 690 'lastname',
689 691 'mail',
690 692 'mail_notification',
691 693 'notified_project_ids',
692 694 'language',
693 695 'custom_field_values',
694 696 'custom_fields',
695 697 'identity_url'
696 698
697 699 safe_attributes 'status',
698 700 'auth_source_id',
699 701 'generate_password',
700 702 'must_change_passwd',
701 703 :if => lambda {|user, current_user| current_user.admin?}
702 704
703 705 safe_attributes 'group_ids',
704 706 :if => lambda {|user, current_user| current_user.admin? && !user.new_record?}
705 707
706 708 # Utility method to help check if a user should be notified about an
707 709 # event.
708 710 #
709 711 # TODO: only supports Issue events currently
710 712 def notify_about?(object)
711 713 if mail_notification == 'all'
712 714 true
713 715 elsif mail_notification.blank? || mail_notification == 'none'
714 716 false
715 717 else
716 718 case object
717 719 when Issue
718 720 case mail_notification
719 721 when 'selected', 'only_my_events'
720 722 # user receives notifications for created/assigned issues on unselected projects
721 723 object.author == self || is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was)
722 724 when 'only_assigned'
723 725 is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was)
724 726 when 'only_owner'
725 727 object.author == self
726 728 end
727 729 when News
728 730 # always send to project members except when mail_notification is set to 'none'
729 731 true
730 732 end
731 733 end
732 734 end
733 735
734 736 def self.current=(user)
735 737 RequestStore.store[:current_user] = user
736 738 end
737 739
738 740 def self.current
739 741 RequestStore.store[:current_user] ||= User.anonymous
740 742 end
741 743
742 744 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
743 745 # one anonymous user per database.
744 746 def self.anonymous
745 747 anonymous_user = AnonymousUser.first
746 748 if anonymous_user.nil?
747 749 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :login => '', :status => 0)
748 750 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
749 751 end
750 752 anonymous_user
751 753 end
752 754
753 755 # Salts all existing unsalted passwords
754 756 # It changes password storage scheme from SHA1(password) to SHA1(salt + SHA1(password))
755 757 # This method is used in the SaltPasswords migration and is to be kept as is
756 758 def self.salt_unsalted_passwords!
757 759 transaction do
758 760 User.where("salt IS NULL OR salt = ''").find_each do |user|
759 761 next if user.hashed_password.blank?
760 762 salt = User.generate_salt
761 763 hashed_password = User.hash_password("#{salt}#{user.hashed_password}")
762 764 User.where(:id => user.id).update_all(:salt => salt, :hashed_password => hashed_password)
763 765 end
764 766 end
765 767 end
766 768
767 769 protected
768 770
769 771 def validate_password_length
770 772 return if password.blank? && generate_password?
771 773 # Password length validation based on setting
772 774 if !password.nil? && password.size < Setting.password_min_length.to_i
773 775 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
774 776 end
775 777 end
776 778
777 779 def instantiate_email_address
778 780 email_address || build_email_address
779 781 end
780 782
781 783 private
782 784
783 785 def generate_password_if_needed
784 786 if generate_password? && auth_source.nil?
785 787 length = [Setting.password_min_length.to_i + 2, 10].max
786 788 random_password(length)
787 789 end
788 790 end
789 791
790 792 # Delete all outstanding password reset tokens on password change.
791 793 # Delete the autologin tokens on password change to prohibit session leakage.
792 794 # This helps to keep the account secure in case the associated email account
793 795 # was compromised.
794 796 def destroy_tokens
795 797 if hashed_password_changed? || (status_changed? && !active?)
796 798 tokens = ['recovery', 'autologin', 'session']
797 799 Token.where(:user_id => id, :action => tokens).delete_all
798 800 end
799 801 end
800 802
801 803 # Removes references that are not handled by associations
802 804 # Things that are not deleted are reassociated with the anonymous user
803 805 def remove_references_before_destroy
804 806 return if self.id.nil?
805 807
806 808 substitute = User.anonymous
807 809 Attachment.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
808 810 Comment.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
809 811 Issue.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
810 812 Issue.where(['assigned_to_id = ?', id]).update_all('assigned_to_id = NULL')
811 813 Journal.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id])
812 814 JournalDetail.
813 815 where(["property = 'attr' AND prop_key = 'assigned_to_id' AND old_value = ?", id.to_s]).
814 816 update_all(['old_value = ?', substitute.id.to_s])
815 817 JournalDetail.
816 818 where(["property = 'attr' AND prop_key = 'assigned_to_id' AND value = ?", id.to_s]).
817 819 update_all(['value = ?', substitute.id.to_s])
818 820 Message.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
819 821 News.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
820 822 # Remove private queries and keep public ones
821 823 ::Query.delete_all ['user_id = ? AND visibility = ?', id, ::Query::VISIBILITY_PRIVATE]
822 824 ::Query.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id])
823 825 TimeEntry.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id])
824 826 Token.delete_all ['user_id = ?', id]
825 827 Watcher.delete_all ['user_id = ?', id]
826 828 WikiContent.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
827 829 WikiContent::Version.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
828 830 end
829 831
830 832 # Return password digest
831 833 def self.hash_password(clear_password)
832 834 Digest::SHA1.hexdigest(clear_password || "")
833 835 end
834 836
835 837 # Returns a 128bits random salt as a hex string (32 chars long)
836 838 def self.generate_salt
837 839 Redmine::Utils.random_hex(16)
838 840 end
839 841
840 842 # Send a security notification to all admins if the user has gained/lost admin privileges
841 843 def deliver_security_notification
842 844 options = {
843 845 field: :field_admin,
844 846 value: login,
845 847 title: :label_user_plural,
846 848 url: {controller: 'users', action: 'index'}
847 849 }
848 850
849 851 deliver = false
850 852 if (admin? && id_changed? && active?) || # newly created admin
851 853 (admin? && admin_changed? && active?) || # regular user became admin
852 854 (admin? && status_changed? && active?) # locked admin became active again
853 855
854 856 deliver = true
855 857 options[:message] = :mail_body_security_notification_add
856 858
857 859 elsif (admin? && destroyed? && active?) || # active admin user was deleted
858 860 (!admin? && admin_changed? && active?) || # admin is no longer admin
859 861 (admin? && status_changed? && !active?) # admin was locked
860 862
861 863 deliver = true
862 864 options[:message] = :mail_body_security_notification_remove
863 865 end
864 866
865 867 if deliver
866 868 users = User.active.where(admin: true).to_a
867 869 Mailer.security_notification(users, options).deliver
868 870 end
869 871 end
870 872 end
871 873
872 874 class AnonymousUser < User
873 875 validate :validate_anonymous_uniqueness, :on => :create
874 876
877 self.valid_statuses = [STATUS_ANONYMOUS]
878
875 879 def validate_anonymous_uniqueness
876 880 # There should be only one AnonymousUser in the database
877 881 errors.add :base, 'An anonymous user already exists.' if AnonymousUser.exists?
878 882 end
879 883
880 884 def available_custom_fields
881 885 []
882 886 end
883 887
884 888 # Overrides a few properties
885 889 def logged?; false end
886 890 def admin; false end
887 891 def name(*args); I18n.t(:label_user_anonymous) end
888 892 def mail=(*args); nil end
889 893 def mail; nil end
890 894 def time_zone; nil end
891 895 def rss_key; nil end
892 896
893 897 def pref
894 898 UserPreference.new(:user => self)
895 899 end
896 900
897 901 # Returns the user's bult-in role
898 902 def builtin_role
899 903 @builtin_role ||= Role.anonymous
900 904 end
901 905
902 906 def membership(*args)
903 907 nil
904 908 end
905 909
906 910 def member_of?(*args)
907 911 false
908 912 end
909 913
910 914 # Anonymous user can not be destroyed
911 915 def destroy
912 916 false
913 917 end
914 918
915 919 protected
916 920
917 921 def instantiate_email_address
918 922 end
919 923 end
@@ -1,1229 +1,1237
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class UserTest < ActiveSupport::TestCase
21 21 fixtures :users, :email_addresses, :members, :projects, :roles, :member_roles, :auth_sources,
22 22 :trackers, :issue_statuses,
23 23 :projects_trackers,
24 24 :watchers,
25 25 :issue_categories, :enumerations, :issues,
26 26 :journals, :journal_details,
27 27 :groups_users,
28 28 :enabled_modules,
29 29 :tokens
30 30
31 31 include Redmine::I18n
32 32
33 33 def setup
34 34 @admin = User.find(1)
35 35 @jsmith = User.find(2)
36 36 @dlopper = User.find(3)
37 37 end
38 38
39 39 def test_sorted_scope_should_sort_user_by_display_name
40 40 # Use .active to ignore anonymous with localized display name
41 41 assert_equal User.active.map(&:name).map(&:downcase).sort,
42 42 User.active.sorted.map(&:name).map(&:downcase)
43 43 end
44 44
45 45 def test_generate
46 46 User.generate!(:firstname => 'Testing connection')
47 47 User.generate!(:firstname => 'Testing connection')
48 48 assert_equal 2, User.where(:firstname => 'Testing connection').count
49 49 end
50 50
51 51 def test_truth
52 52 assert_kind_of User, @jsmith
53 53 end
54 54
55 def test_should_validate_status
56 user = User.new
57 user.status = 0
58
59 assert !user.save
60 assert_include I18n.translate('activerecord.errors.messages.invalid'), user.errors[:status]
61 end
62
55 63 def test_mail_should_be_stripped
56 64 u = User.new
57 65 u.mail = " foo@bar.com "
58 66 assert_equal "foo@bar.com", u.mail
59 67 end
60 68
61 69 def test_should_create_email_address
62 70 u = User.new(:firstname => "new", :lastname => "user")
63 71 u.login = "create_email_address"
64 72 u.mail = "defaultemail@somenet.foo"
65 73 assert u.save
66 74 u.reload
67 75 assert u.email_address
68 76 assert_equal "defaultemail@somenet.foo", u.email_address.address
69 77 assert_equal true, u.email_address.is_default
70 78 assert_equal true, u.email_address.notify
71 79 end
72 80
73 81 def test_should_not_create_user_without_mail
74 82 set_language_if_valid 'en'
75 83 u = User.new(:firstname => "new", :lastname => "user")
76 84 u.login = "user_without_mail"
77 85 assert !u.save
78 86 assert_equal ["Email #{I18n.translate('activerecord.errors.messages.blank')}"], u.errors.full_messages
79 87 end
80 88
81 89 def test_should_not_create_user_with_blank_mail
82 90 set_language_if_valid 'en'
83 91 u = User.new(:firstname => "new", :lastname => "user")
84 92 u.login = "user_with_blank_mail"
85 93 u.mail = ''
86 94 assert !u.save
87 95 assert_equal ["Email #{I18n.translate('activerecord.errors.messages.blank')}"], u.errors.full_messages
88 96 end
89 97
90 98 def test_should_not_update_user_with_blank_mail
91 99 set_language_if_valid 'en'
92 100 u = User.find(2)
93 101 u.mail = ''
94 102 assert !u.save
95 103 assert_equal ["Email #{I18n.translate('activerecord.errors.messages.blank')}"], u.errors.full_messages
96 104 end
97 105
98 106 def test_login_length_validation
99 107 user = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
100 108 user.login = "x" * (User::LOGIN_LENGTH_LIMIT+1)
101 109 assert !user.valid?
102 110
103 111 user.login = "x" * (User::LOGIN_LENGTH_LIMIT)
104 112 assert user.valid?
105 113 assert user.save
106 114 end
107 115
108 116 def test_generate_password_should_respect_minimum_password_length
109 117 with_settings :password_min_length => 15 do
110 118 user = User.generate!(:generate_password => true)
111 119 assert user.password.length >= 15
112 120 end
113 121 end
114 122
115 123 def test_generate_password_should_not_generate_password_with_less_than_10_characters
116 124 with_settings :password_min_length => 4 do
117 125 user = User.generate!(:generate_password => true)
118 126 assert user.password.length >= 10
119 127 end
120 128 end
121 129
122 130 def test_generate_password_on_create_should_set_password
123 131 user = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
124 132 user.login = "newuser"
125 133 user.generate_password = true
126 134 assert user.save
127 135
128 136 password = user.password
129 137 assert user.check_password?(password)
130 138 end
131 139
132 140 def test_generate_password_on_update_should_update_password
133 141 user = User.find(2)
134 142 hash = user.hashed_password
135 143 user.generate_password = true
136 144 assert user.save
137 145
138 146 password = user.password
139 147 assert user.check_password?(password)
140 148 assert_not_equal hash, user.reload.hashed_password
141 149 end
142 150
143 151 def test_create
144 152 user = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
145 153
146 154 user.login = "jsmith"
147 155 user.password, user.password_confirmation = "password", "password"
148 156 # login uniqueness
149 157 assert !user.save
150 158 assert_equal 1, user.errors.count
151 159
152 160 user.login = "newuser"
153 161 user.password, user.password_confirmation = "password", "pass"
154 162 # password confirmation
155 163 assert !user.save
156 164 assert_equal 1, user.errors.count
157 165
158 166 user.password, user.password_confirmation = "password", "password"
159 167 assert user.save
160 168 end
161 169
162 170 def test_user_before_create_should_set_the_mail_notification_to_the_default_setting
163 171 @user1 = User.generate!
164 172 assert_equal 'only_my_events', @user1.mail_notification
165 173 with_settings :default_notification_option => 'all' do
166 174 @user2 = User.generate!
167 175 assert_equal 'all', @user2.mail_notification
168 176 end
169 177 end
170 178
171 179 def test_user_login_should_be_case_insensitive
172 180 u = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
173 181 u.login = 'newuser'
174 182 u.password, u.password_confirmation = "password", "password"
175 183 assert u.save
176 184 u = User.new(:firstname => "Similar", :lastname => "User",
177 185 :mail => "similaruser@somenet.foo")
178 186 u.login = 'NewUser'
179 187 u.password, u.password_confirmation = "password", "password"
180 188 assert !u.save
181 189 assert_include I18n.translate('activerecord.errors.messages.taken'), u.errors[:login]
182 190 end
183 191
184 192 def test_mail_uniqueness_should_not_be_case_sensitive
185 193 set_language_if_valid 'en'
186 194 u = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
187 195 u.login = 'newuser1'
188 196 u.password, u.password_confirmation = "password", "password"
189 197 assert u.save
190 198
191 199 u = User.new(:firstname => "new", :lastname => "user", :mail => "newUser@Somenet.foo")
192 200 u.login = 'newuser2'
193 201 u.password, u.password_confirmation = "password", "password"
194 202 assert !u.save
195 203 assert_include "Email #{I18n.translate('activerecord.errors.messages.taken')}", u.errors.full_messages
196 204 end
197 205
198 206 def test_update
199 207 assert_equal "admin", @admin.login
200 208 @admin.login = "john"
201 209 assert @admin.save, @admin.errors.full_messages.join("; ")
202 210 @admin.reload
203 211 assert_equal "john", @admin.login
204 212 end
205 213
206 214 def test_update_should_not_fail_for_legacy_user_with_different_case_logins
207 215 u1 = User.new(:firstname => "new", :lastname => "user", :mail => "newuser1@somenet.foo")
208 216 u1.login = 'newuser1'
209 217 assert u1.save
210 218
211 219 u2 = User.new(:firstname => "new", :lastname => "user", :mail => "newuser2@somenet.foo")
212 220 u2.login = 'newuser1'
213 221 assert u2.save(:validate => false)
214 222
215 223 user = User.find(u2.id)
216 224 user.firstname = "firstname"
217 225 assert user.save, "Save failed"
218 226 end
219 227
220 228 def test_destroy_should_delete_members_and_roles
221 229 members = Member.where(:user_id => 2)
222 230 ms = members.count
223 231 rs = members.collect(&:roles).flatten.size
224 232 assert ms > 0
225 233 assert rs > 0
226 234 assert_difference 'Member.count', - ms do
227 235 assert_difference 'MemberRole.count', - rs do
228 236 User.find(2).destroy
229 237 end
230 238 end
231 239 assert_nil User.find_by_id(2)
232 240 assert_equal 0, Member.where(:user_id => 2).count
233 241 end
234 242
235 243 def test_destroy_should_update_attachments
236 244 attachment = Attachment.create!(:container => Project.find(1),
237 245 :file => uploaded_test_file("testfile.txt", "text/plain"),
238 246 :author_id => 2)
239 247
240 248 User.find(2).destroy
241 249 assert_nil User.find_by_id(2)
242 250 assert_equal User.anonymous, attachment.reload.author
243 251 end
244 252
245 253 def test_destroy_should_update_comments
246 254 comment = Comment.create!(
247 255 :commented => News.create!(:project_id => 1,
248 256 :author_id => 1, :title => 'foo', :description => 'foo'),
249 257 :author => User.find(2),
250 258 :comments => 'foo'
251 259 )
252 260
253 261 User.find(2).destroy
254 262 assert_nil User.find_by_id(2)
255 263 assert_equal User.anonymous, comment.reload.author
256 264 end
257 265
258 266 def test_destroy_should_update_issues
259 267 issue = Issue.create!(:project_id => 1, :author_id => 2,
260 268 :tracker_id => 1, :subject => 'foo')
261 269
262 270 User.find(2).destroy
263 271 assert_nil User.find_by_id(2)
264 272 assert_equal User.anonymous, issue.reload.author
265 273 end
266 274
267 275 def test_destroy_should_unassign_issues
268 276 issue = Issue.create!(:project_id => 1, :author_id => 1,
269 277 :tracker_id => 1, :subject => 'foo', :assigned_to_id => 2)
270 278
271 279 User.find(2).destroy
272 280 assert_nil User.find_by_id(2)
273 281 assert_nil issue.reload.assigned_to
274 282 end
275 283
276 284 def test_destroy_should_update_journals
277 285 issue = Issue.create!(:project_id => 1, :author_id => 2,
278 286 :tracker_id => 1, :subject => 'foo')
279 287 issue.init_journal(User.find(2), "update")
280 288 issue.save!
281 289
282 290 User.find(2).destroy
283 291 assert_nil User.find_by_id(2)
284 292 assert_equal User.anonymous, issue.journals.first.reload.user
285 293 end
286 294
287 295 def test_destroy_should_update_journal_details_old_value
288 296 issue = Issue.create!(:project_id => 1, :author_id => 1,
289 297 :tracker_id => 1, :subject => 'foo', :assigned_to_id => 2)
290 298 issue.init_journal(User.find(1), "update")
291 299 issue.assigned_to_id = nil
292 300 assert_difference 'JournalDetail.count' do
293 301 issue.save!
294 302 end
295 303 journal_detail = JournalDetail.order('id DESC').first
296 304 assert_equal '2', journal_detail.old_value
297 305
298 306 User.find(2).destroy
299 307 assert_nil User.find_by_id(2)
300 308 assert_equal User.anonymous.id.to_s, journal_detail.reload.old_value
301 309 end
302 310
303 311 def test_destroy_should_update_journal_details_value
304 312 issue = Issue.create!(:project_id => 1, :author_id => 1,
305 313 :tracker_id => 1, :subject => 'foo')
306 314 issue.init_journal(User.find(1), "update")
307 315 issue.assigned_to_id = 2
308 316 assert_difference 'JournalDetail.count' do
309 317 issue.save!
310 318 end
311 319 journal_detail = JournalDetail.order('id DESC').first
312 320 assert_equal '2', journal_detail.value
313 321
314 322 User.find(2).destroy
315 323 assert_nil User.find_by_id(2)
316 324 assert_equal User.anonymous.id.to_s, journal_detail.reload.value
317 325 end
318 326
319 327 def test_destroy_should_update_messages
320 328 board = Board.create!(:project_id => 1, :name => 'Board', :description => 'Board')
321 329 message = Message.create!(:board_id => board.id, :author_id => 2,
322 330 :subject => 'foo', :content => 'foo')
323 331 User.find(2).destroy
324 332 assert_nil User.find_by_id(2)
325 333 assert_equal User.anonymous, message.reload.author
326 334 end
327 335
328 336 def test_destroy_should_update_news
329 337 news = News.create!(:project_id => 1, :author_id => 2,
330 338 :title => 'foo', :description => 'foo')
331 339 User.find(2).destroy
332 340 assert_nil User.find_by_id(2)
333 341 assert_equal User.anonymous, news.reload.author
334 342 end
335 343
336 344 def test_destroy_should_delete_private_queries
337 345 query = Query.new(:name => 'foo', :visibility => Query::VISIBILITY_PRIVATE)
338 346 query.project_id = 1
339 347 query.user_id = 2
340 348 query.save!
341 349
342 350 User.find(2).destroy
343 351 assert_nil User.find_by_id(2)
344 352 assert_nil Query.find_by_id(query.id)
345 353 end
346 354
347 355 def test_destroy_should_update_public_queries
348 356 query = Query.new(:name => 'foo', :visibility => Query::VISIBILITY_PUBLIC)
349 357 query.project_id = 1
350 358 query.user_id = 2
351 359 query.save!
352 360
353 361 User.find(2).destroy
354 362 assert_nil User.find_by_id(2)
355 363 assert_equal User.anonymous, query.reload.user
356 364 end
357 365
358 366 def test_destroy_should_update_time_entries
359 367 entry = TimeEntry.new(:hours => '2', :spent_on => Date.today,
360 368 :activity => TimeEntryActivity.create!(:name => 'foo'))
361 369 entry.project_id = 1
362 370 entry.user_id = 2
363 371 entry.save!
364 372
365 373 User.find(2).destroy
366 374 assert_nil User.find_by_id(2)
367 375 assert_equal User.anonymous, entry.reload.user
368 376 end
369 377
370 378 def test_destroy_should_delete_tokens
371 379 token = Token.create!(:user_id => 2, :value => 'foo')
372 380
373 381 User.find(2).destroy
374 382 assert_nil User.find_by_id(2)
375 383 assert_nil Token.find_by_id(token.id)
376 384 end
377 385
378 386 def test_destroy_should_delete_watchers
379 387 issue = Issue.create!(:project_id => 1, :author_id => 1,
380 388 :tracker_id => 1, :subject => 'foo')
381 389 watcher = Watcher.create!(:user_id => 2, :watchable => issue)
382 390
383 391 User.find(2).destroy
384 392 assert_nil User.find_by_id(2)
385 393 assert_nil Watcher.find_by_id(watcher.id)
386 394 end
387 395
388 396 def test_destroy_should_update_wiki_contents
389 397 wiki_content = WikiContent.create!(
390 398 :text => 'foo',
391 399 :author_id => 2,
392 400 :page => WikiPage.create!(:title => 'Foo',
393 401 :wiki => Wiki.create!(:project_id => 3,
394 402 :start_page => 'Start'))
395 403 )
396 404 wiki_content.text = 'bar'
397 405 assert_difference 'WikiContent::Version.count' do
398 406 wiki_content.save!
399 407 end
400 408
401 409 User.find(2).destroy
402 410 assert_nil User.find_by_id(2)
403 411 assert_equal User.anonymous, wiki_content.reload.author
404 412 wiki_content.versions.each do |version|
405 413 assert_equal User.anonymous, version.reload.author
406 414 end
407 415 end
408 416
409 417 def test_destroy_should_nullify_issue_categories
410 418 category = IssueCategory.create!(:project_id => 1, :assigned_to_id => 2, :name => 'foo')
411 419
412 420 User.find(2).destroy
413 421 assert_nil User.find_by_id(2)
414 422 assert_nil category.reload.assigned_to_id
415 423 end
416 424
417 425 def test_destroy_should_nullify_changesets
418 426 changeset = Changeset.create!(
419 427 :repository => Repository::Subversion.create!(
420 428 :project_id => 1,
421 429 :url => 'file:///tmp',
422 430 :identifier => 'tmp'
423 431 ),
424 432 :revision => '12',
425 433 :committed_on => Time.now,
426 434 :committer => 'jsmith'
427 435 )
428 436 assert_equal 2, changeset.user_id
429 437
430 438 User.find(2).destroy
431 439 assert_nil User.find_by_id(2)
432 440 assert_nil changeset.reload.user_id
433 441 end
434 442
435 443 def test_anonymous_user_should_not_be_destroyable
436 444 assert_no_difference 'User.count' do
437 445 assert_equal false, User.anonymous.destroy
438 446 end
439 447 end
440 448
441 449 def test_password_change_should_destroy_tokens
442 450 recovery_token = Token.create!(:user_id => 2, :action => 'recovery')
443 451 autologin_token = Token.create!(:user_id => 2, :action => 'autologin')
444 452
445 453 user = User.find(2)
446 454 user.password, user.password_confirmation = "a new password", "a new password"
447 455 assert user.save
448 456
449 457 assert_nil Token.find_by_id(recovery_token.id)
450 458 assert_nil Token.find_by_id(autologin_token.id)
451 459 end
452 460
453 461 def test_mail_change_should_destroy_tokens
454 462 recovery_token = Token.create!(:user_id => 2, :action => 'recovery')
455 463 autologin_token = Token.create!(:user_id => 2, :action => 'autologin')
456 464
457 465 user = User.find(2)
458 466 user.mail = "user@somwehere.com"
459 467 assert user.save
460 468
461 469 assert_nil Token.find_by_id(recovery_token.id)
462 470 assert_equal autologin_token, Token.find_by_id(autologin_token.id)
463 471 end
464 472
465 473 def test_change_on_other_fields_should_not_destroy_tokens
466 474 recovery_token = Token.create!(:user_id => 2, :action => 'recovery')
467 475 autologin_token = Token.create!(:user_id => 2, :action => 'autologin')
468 476
469 477 user = User.find(2)
470 478 user.firstname = "Bobby"
471 479 assert user.save
472 480
473 481 assert_equal recovery_token, Token.find_by_id(recovery_token.id)
474 482 assert_equal autologin_token, Token.find_by_id(autologin_token.id)
475 483 end
476 484
477 485 def test_validate_login_presence
478 486 @admin.login = ""
479 487 assert !@admin.save
480 488 assert_equal 1, @admin.errors.count
481 489 end
482 490
483 491 def test_validate_mail_notification_inclusion
484 492 u = User.new
485 493 u.mail_notification = 'foo'
486 494 u.save
487 495 assert_not_equal [], u.errors[:mail_notification]
488 496 end
489 497
490 498 def test_password
491 499 user = User.try_to_login("admin", "admin")
492 500 assert_kind_of User, user
493 501 assert_equal "admin", user.login
494 502 user.password = "hello123"
495 503 assert user.save
496 504
497 505 user = User.try_to_login("admin", "hello123")
498 506 assert_kind_of User, user
499 507 assert_equal "admin", user.login
500 508 end
501 509
502 510 def test_validate_password_length
503 511 with_settings :password_min_length => '100' do
504 512 user = User.new(:firstname => "new100",
505 513 :lastname => "user100", :mail => "newuser100@somenet.foo")
506 514 user.login = "newuser100"
507 515 user.password, user.password_confirmation = "password100", "password100"
508 516 assert !user.save
509 517 assert_equal 1, user.errors.count
510 518 end
511 519 end
512 520
513 521 def test_name_format
514 522 assert_equal 'John S.', @jsmith.name(:firstname_lastinitial)
515 523 assert_equal 'Smith, John', @jsmith.name(:lastname_comma_firstname)
516 524 assert_equal 'J. Smith', @jsmith.name(:firstinitial_lastname)
517 525 assert_equal 'J.-P. Lang', User.new(:firstname => 'Jean-Philippe', :lastname => 'Lang').name(:firstinitial_lastname)
518 526 end
519 527
520 528 def test_name_should_use_setting_as_default_format
521 529 with_settings :user_format => :firstname_lastname do
522 530 assert_equal 'John Smith', @jsmith.reload.name
523 531 end
524 532 with_settings :user_format => :username do
525 533 assert_equal 'jsmith', @jsmith.reload.name
526 534 end
527 535 with_settings :user_format => :lastname do
528 536 assert_equal 'Smith', @jsmith.reload.name
529 537 end
530 538 end
531 539
532 540 def test_today_should_return_the_day_according_to_user_time_zone
533 541 preference = User.find(1).pref
534 542 date = Date.new(2012, 05, 15)
535 543 time = Time.gm(2012, 05, 15, 23, 30).utc # 2012-05-15 23:30 UTC
536 544 Date.stubs(:today).returns(date)
537 545 Time.stubs(:now).returns(time)
538 546
539 547 preference.update_attribute :time_zone, 'Baku' # UTC+4
540 548 assert_equal '2012-05-16', User.find(1).today.to_s
541 549
542 550 preference.update_attribute :time_zone, 'La Paz' # UTC-4
543 551 assert_equal '2012-05-15', User.find(1).today.to_s
544 552
545 553 preference.update_attribute :time_zone, ''
546 554 assert_equal '2012-05-15', User.find(1).today.to_s
547 555 end
548 556
549 557 def test_time_to_date_should_return_the_date_according_to_user_time_zone
550 558 preference = User.find(1).pref
551 559 time = Time.gm(2012, 05, 15, 23, 30).utc # 2012-05-15 23:30 UTC
552 560
553 561 preference.update_attribute :time_zone, 'Baku' # UTC+4
554 562 assert_equal '2012-05-16', User.find(1).time_to_date(time).to_s
555 563
556 564 preference.update_attribute :time_zone, 'La Paz' # UTC-4
557 565 assert_equal '2012-05-15', User.find(1).time_to_date(time).to_s
558 566
559 567 preference.update_attribute :time_zone, ''
560 568 assert_equal '2012-05-15', User.find(1).time_to_date(time).to_s
561 569 end
562 570
563 571 def test_fields_for_order_statement_should_return_fields_according_user_format_setting
564 572 with_settings :user_format => 'lastname_comma_firstname' do
565 573 assert_equal ['users.lastname', 'users.firstname', 'users.id'],
566 574 User.fields_for_order_statement
567 575 end
568 576 end
569 577
570 578 def test_fields_for_order_statement_width_table_name_should_prepend_table_name
571 579 with_settings :user_format => 'lastname_firstname' do
572 580 assert_equal ['authors.lastname', 'authors.firstname', 'authors.id'],
573 581 User.fields_for_order_statement('authors')
574 582 end
575 583 end
576 584
577 585 def test_fields_for_order_statement_with_blank_format_should_return_default
578 586 with_settings :user_format => '' do
579 587 assert_equal ['users.firstname', 'users.lastname', 'users.id'],
580 588 User.fields_for_order_statement
581 589 end
582 590 end
583 591
584 592 def test_fields_for_order_statement_with_invalid_format_should_return_default
585 593 with_settings :user_format => 'foo' do
586 594 assert_equal ['users.firstname', 'users.lastname', 'users.id'],
587 595 User.fields_for_order_statement
588 596 end
589 597 end
590 598
591 599 test ".try_to_login with good credentials should return the user" do
592 600 user = User.try_to_login("admin", "admin")
593 601 assert_kind_of User, user
594 602 assert_equal "admin", user.login
595 603 end
596 604
597 605 test ".try_to_login with wrong credentials should return nil" do
598 606 assert_nil User.try_to_login("admin", "foo")
599 607 end
600 608
601 609 def test_try_to_login_with_locked_user_should_return_nil
602 610 @jsmith.status = User::STATUS_LOCKED
603 611 @jsmith.save!
604 612
605 613 user = User.try_to_login("jsmith", "jsmith")
606 614 assert_equal nil, user
607 615 end
608 616
609 617 def test_try_to_login_with_locked_user_and_not_active_only_should_return_user
610 618 @jsmith.status = User::STATUS_LOCKED
611 619 @jsmith.save!
612 620
613 621 user = User.try_to_login("jsmith", "jsmith", false)
614 622 assert_equal @jsmith, user
615 623 end
616 624
617 625 test ".try_to_login should fall-back to case-insensitive if user login is not found as-typed" do
618 626 user = User.try_to_login("AdMin", "admin")
619 627 assert_kind_of User, user
620 628 assert_equal "admin", user.login
621 629 end
622 630
623 631 test ".try_to_login should select the exact matching user first" do
624 632 case_sensitive_user = User.generate! do |user|
625 633 user.password = "admin123"
626 634 end
627 635 # bypass validations to make it appear like existing data
628 636 case_sensitive_user.update_attribute(:login, 'ADMIN')
629 637
630 638 user = User.try_to_login("ADMIN", "admin123")
631 639 assert_kind_of User, user
632 640 assert_equal "ADMIN", user.login
633 641 end
634 642
635 643 if ldap_configured?
636 644 test "#try_to_login using LDAP with failed connection to the LDAP server" do
637 645 auth_source = AuthSourceLdap.find(1)
638 646 AuthSource.any_instance.stubs(:initialize_ldap_con).raises(Net::LDAP::LdapError, 'Cannot connect')
639 647
640 648 assert_equal nil, User.try_to_login('edavis', 'wrong')
641 649 end
642 650
643 651 test "#try_to_login using LDAP" do
644 652 assert_equal nil, User.try_to_login('edavis', 'wrong')
645 653 end
646 654
647 655 test "#try_to_login using LDAP binding with user's account" do
648 656 auth_source = AuthSourceLdap.find(1)
649 657 auth_source.account = "uid=$login,ou=Person,dc=redmine,dc=org"
650 658 auth_source.account_password = ''
651 659 auth_source.save!
652 660
653 661 ldap_user = User.new(:mail => 'example1@redmine.org', :firstname => 'LDAP', :lastname => 'user', :auth_source_id => 1)
654 662 ldap_user.login = 'example1'
655 663 ldap_user.save!
656 664
657 665 assert_equal ldap_user, User.try_to_login('example1', '123456')
658 666 assert_nil User.try_to_login('example1', '11111')
659 667 end
660 668
661 669 test "#try_to_login using LDAP on the fly registration" do
662 670 AuthSourceLdap.find(1).update_attribute :onthefly_register, true
663 671
664 672 assert_difference('User.count') do
665 673 assert User.try_to_login('edavis', '123456')
666 674 end
667 675
668 676 assert_no_difference('User.count') do
669 677 assert User.try_to_login('edavis', '123456')
670 678 end
671 679
672 680 assert_nil User.try_to_login('example1', '11111')
673 681 end
674 682
675 683 test "#try_to_login using LDAP on the fly registration and binding with user's account" do
676 684 auth_source = AuthSourceLdap.find(1)
677 685 auth_source.update_attribute :onthefly_register, true
678 686 auth_source = AuthSourceLdap.find(1)
679 687 auth_source.account = "uid=$login,ou=Person,dc=redmine,dc=org"
680 688 auth_source.account_password = ''
681 689 auth_source.save!
682 690
683 691 assert_difference('User.count') do
684 692 assert User.try_to_login('example1', '123456')
685 693 end
686 694
687 695 assert_no_difference('User.count') do
688 696 assert User.try_to_login('example1', '123456')
689 697 end
690 698
691 699 assert_nil User.try_to_login('example1', '11111')
692 700 end
693 701
694 702 else
695 703 puts "Skipping LDAP tests."
696 704 end
697 705
698 706 def test_create_anonymous
699 707 AnonymousUser.delete_all
700 708 anon = User.anonymous
701 709 assert !anon.new_record?
702 710 assert_kind_of AnonymousUser, anon
703 711 end
704 712
705 713 def test_ensure_single_anonymous_user
706 714 AnonymousUser.delete_all
707 715 anon1 = User.anonymous
708 716 assert !anon1.new_record?
709 717 assert_kind_of AnonymousUser, anon1
710 718 anon2 = AnonymousUser.create(
711 719 :lastname => 'Anonymous', :firstname => '',
712 720 :login => '', :status => 0)
713 721 assert_equal 1, anon2.errors.count
714 722 end
715 723
716 724 def test_rss_key
717 725 assert_nil @jsmith.rss_token
718 726 key = @jsmith.rss_key
719 727 assert_equal 40, key.length
720 728
721 729 @jsmith.reload
722 730 assert_equal key, @jsmith.rss_key
723 731 end
724 732
725 733 def test_rss_key_should_not_be_generated_twice
726 734 assert_difference 'Token.count', 1 do
727 735 key1 = @jsmith.rss_key
728 736 key2 = @jsmith.rss_key
729 737 assert_equal key1, key2
730 738 end
731 739 end
732 740
733 741 def test_api_key_should_not_be_generated_twice
734 742 assert_difference 'Token.count', 1 do
735 743 key1 = @jsmith.api_key
736 744 key2 = @jsmith.api_key
737 745 assert_equal key1, key2
738 746 end
739 747 end
740 748
741 749 test "#api_key should generate a new one if the user doesn't have one" do
742 750 user = User.generate!(:api_token => nil)
743 751 assert_nil user.api_token
744 752
745 753 key = user.api_key
746 754 assert_equal 40, key.length
747 755 user.reload
748 756 assert_equal key, user.api_key
749 757 end
750 758
751 759 test "#api_key should return the existing api token value" do
752 760 user = User.generate!
753 761 token = Token.create!(:action => 'api')
754 762 user.api_token = token
755 763 assert user.save
756 764
757 765 assert_equal token.value, user.api_key
758 766 end
759 767
760 768 test "#find_by_api_key should return nil if no matching key is found" do
761 769 assert_nil User.find_by_api_key('zzzzzzzzz')
762 770 end
763 771
764 772 test "#find_by_api_key should return nil if the key is found for an inactive user" do
765 773 user = User.generate!
766 774 user.status = User::STATUS_LOCKED
767 775 token = Token.create!(:action => 'api')
768 776 user.api_token = token
769 777 user.save
770 778
771 779 assert_nil User.find_by_api_key(token.value)
772 780 end
773 781
774 782 test "#find_by_api_key should return the user if the key is found for an active user" do
775 783 user = User.generate!
776 784 token = Token.create!(:action => 'api')
777 785 user.api_token = token
778 786 user.save
779 787
780 788 assert_equal user, User.find_by_api_key(token.value)
781 789 end
782 790
783 791 def test_default_admin_account_changed_should_return_false_if_account_was_not_changed
784 792 user = User.find_by_login("admin")
785 793 user.password = "admin"
786 794 assert user.save(:validate => false)
787 795
788 796 assert_equal false, User.default_admin_account_changed?
789 797 end
790 798
791 799 def test_default_admin_account_changed_should_return_true_if_password_was_changed
792 800 user = User.find_by_login("admin")
793 801 user.password = "newpassword"
794 802 user.save!
795 803
796 804 assert_equal true, User.default_admin_account_changed?
797 805 end
798 806
799 807 def test_default_admin_account_changed_should_return_true_if_account_is_disabled
800 808 user = User.find_by_login("admin")
801 809 user.password = "admin"
802 810 user.status = User::STATUS_LOCKED
803 811 assert user.save(:validate => false)
804 812
805 813 assert_equal true, User.default_admin_account_changed?
806 814 end
807 815
808 816 def test_default_admin_account_changed_should_return_true_if_account_does_not_exist
809 817 user = User.find_by_login("admin")
810 818 user.destroy
811 819
812 820 assert_equal true, User.default_admin_account_changed?
813 821 end
814 822
815 823 def test_membership_with_project_should_return_membership
816 824 project = Project.find(1)
817 825
818 826 membership = @jsmith.membership(project)
819 827 assert_kind_of Member, membership
820 828 assert_equal @jsmith, membership.user
821 829 assert_equal project, membership.project
822 830 end
823 831
824 832 def test_membership_with_project_id_should_return_membership
825 833 project = Project.find(1)
826 834
827 835 membership = @jsmith.membership(1)
828 836 assert_kind_of Member, membership
829 837 assert_equal @jsmith, membership.user
830 838 assert_equal project, membership.project
831 839 end
832 840
833 841 def test_membership_for_non_member_should_return_nil
834 842 project = Project.find(1)
835 843
836 844 user = User.generate!
837 845 membership = user.membership(1)
838 846 assert_nil membership
839 847 end
840 848
841 849 def test_roles_for_project_with_member_on_public_project_should_return_roles_and_non_member
842 850 roles = @jsmith.roles_for_project(Project.find(1))
843 851 assert_kind_of Role, roles.first
844 852 assert_equal ["Manager"], roles.map(&:name)
845 853 end
846 854
847 855 def test_roles_for_project_with_member_on_private_project_should_return_roles
848 856 Project.find(1).update_attribute :is_public, false
849 857
850 858 roles = @jsmith.roles_for_project(Project.find(1))
851 859 assert_kind_of Role, roles.first
852 860 assert_equal ["Manager"], roles.map(&:name)
853 861 end
854 862
855 863 def test_roles_for_project_with_non_member_with_public_project_should_return_non_member
856 864 set_language_if_valid 'en'
857 865 roles = User.find(8).roles_for_project(Project.find(1))
858 866 assert_equal ["Non member"], roles.map(&:name)
859 867 end
860 868
861 869 def test_roles_for_project_with_non_member_with_public_project_and_override_should_return_override_roles
862 870 project = Project.find(1)
863 871 Member.create!(:project => project, :principal => Group.non_member, :role_ids => [1, 2])
864 872 roles = User.find(8).roles_for_project(project)
865 873 assert_equal ["Developer", "Manager"], roles.map(&:name).sort
866 874 end
867 875
868 876 def test_roles_for_project_with_non_member_with_private_project_should_return_no_roles
869 877 Project.find(1).update_attribute :is_public, false
870 878
871 879 roles = User.find(8).roles_for_project(Project.find(1))
872 880 assert_equal [], roles.map(&:name)
873 881 end
874 882
875 883 def test_roles_for_project_with_non_member_with_private_project_and_override_should_return_no_roles
876 884 project = Project.find(1)
877 885 project.update_attribute :is_public, false
878 886 Member.create!(:project => project, :principal => Group.non_member, :role_ids => [1, 2])
879 887 roles = User.find(8).roles_for_project(project)
880 888 assert_equal [], roles.map(&:name).sort
881 889 end
882 890
883 891 def test_roles_for_project_with_anonymous_with_public_project_should_return_anonymous
884 892 set_language_if_valid 'en'
885 893 roles = User.anonymous.roles_for_project(Project.find(1))
886 894 assert_equal ["Anonymous"], roles.map(&:name)
887 895 end
888 896
889 897 def test_roles_for_project_with_anonymous_with_public_project_and_override_should_return_override_roles
890 898 project = Project.find(1)
891 899 Member.create!(:project => project, :principal => Group.anonymous, :role_ids => [1, 2])
892 900 roles = User.anonymous.roles_for_project(project)
893 901 assert_equal ["Developer", "Manager"], roles.map(&:name).sort
894 902 end
895 903
896 904 def test_roles_for_project_with_anonymous_with_private_project_should_return_no_roles
897 905 Project.find(1).update_attribute :is_public, false
898 906
899 907 roles = User.anonymous.roles_for_project(Project.find(1))
900 908 assert_equal [], roles.map(&:name)
901 909 end
902 910
903 911 def test_roles_for_project_with_anonymous_with_private_project_and_override_should_return_no_roles
904 912 project = Project.find(1)
905 913 project.update_attribute :is_public, false
906 914 Member.create!(:project => project, :principal => Group.anonymous, :role_ids => [1, 2])
907 915 roles = User.anonymous.roles_for_project(project)
908 916 assert_equal [], roles.map(&:name).sort
909 917 end
910 918
911 919 def test_roles_for_project_should_be_unique
912 920 m = Member.new(:user_id => 1, :project_id => 1)
913 921 m.member_roles.build(:role_id => 1)
914 922 m.member_roles.build(:role_id => 1)
915 923 m.save!
916 924
917 925 user = User.find(1)
918 926 project = Project.find(1)
919 927 assert_equal 1, user.roles_for_project(project).size
920 928 assert_equal [1], user.roles_for_project(project).map(&:id)
921 929 end
922 930
923 931 def test_projects_by_role_for_user_with_role
924 932 user = User.find(2)
925 933 assert_kind_of Hash, user.projects_by_role
926 934 assert_equal 2, user.projects_by_role.size
927 935 assert_equal [1,5], user.projects_by_role[Role.find(1)].collect(&:id).sort
928 936 assert_equal [2], user.projects_by_role[Role.find(2)].collect(&:id).sort
929 937 end
930 938
931 939 def test_accessing_projects_by_role_with_no_projects_should_return_an_empty_array
932 940 user = User.find(2)
933 941 assert_equal [], user.projects_by_role[Role.find(3)]
934 942 # should not update the hash
935 943 assert_nil user.projects_by_role.values.detect(&:blank?)
936 944 end
937 945
938 946 def test_projects_by_role_for_user_with_no_role
939 947 user = User.generate!
940 948 assert_equal({}, user.projects_by_role)
941 949 end
942 950
943 951 def test_projects_by_role_for_anonymous
944 952 assert_equal({}, User.anonymous.projects_by_role)
945 953 end
946 954
947 955 def test_valid_notification_options
948 956 # without memberships
949 957 assert_equal 5, User.find(7).valid_notification_options.size
950 958 # with memberships
951 959 assert_equal 6, User.find(2).valid_notification_options.size
952 960 end
953 961
954 962 def test_valid_notification_options_class_method
955 963 assert_equal 5, User.valid_notification_options.size
956 964 assert_equal 5, User.valid_notification_options(User.find(7)).size
957 965 assert_equal 6, User.valid_notification_options(User.find(2)).size
958 966 end
959 967
960 968 def test_notified_project_ids_setter_should_coerce_to_unique_integer_array
961 969 @jsmith.notified_project_ids = ["1", "123", "2u", "wrong", "12", 6, 12, -35, ""]
962 970 assert_equal [1, 123, 2, 12, 6], @jsmith.notified_projects_ids
963 971 end
964 972
965 973 def test_mail_notification_all
966 974 @jsmith.mail_notification = 'all'
967 975 @jsmith.notified_project_ids = []
968 976 @jsmith.save
969 977 @jsmith.reload
970 978 assert @jsmith.projects.first.recipients.include?(@jsmith.mail)
971 979 end
972 980
973 981 def test_mail_notification_selected
974 982 @jsmith.mail_notification = 'selected'
975 983 @jsmith.notified_project_ids = [1]
976 984 @jsmith.save
977 985 @jsmith.reload
978 986 assert Project.find(1).recipients.include?(@jsmith.mail)
979 987 end
980 988
981 989 def test_mail_notification_only_my_events
982 990 @jsmith.mail_notification = 'only_my_events'
983 991 @jsmith.notified_project_ids = []
984 992 @jsmith.save
985 993 @jsmith.reload
986 994 assert !@jsmith.projects.first.recipients.include?(@jsmith.mail)
987 995 end
988 996
989 997 def test_comments_sorting_preference
990 998 assert !@jsmith.wants_comments_in_reverse_order?
991 999 @jsmith.pref.comments_sorting = 'asc'
992 1000 assert !@jsmith.wants_comments_in_reverse_order?
993 1001 @jsmith.pref.comments_sorting = 'desc'
994 1002 assert @jsmith.wants_comments_in_reverse_order?
995 1003 end
996 1004
997 1005 def test_find_by_mail_should_be_case_insensitive
998 1006 u = User.find_by_mail('JSmith@somenet.foo')
999 1007 assert_not_nil u
1000 1008 assert_equal 'jsmith@somenet.foo', u.mail
1001 1009 end
1002 1010
1003 1011 def test_random_password
1004 1012 u = User.new
1005 1013 u.random_password
1006 1014 assert !u.password.blank?
1007 1015 assert !u.password_confirmation.blank?
1008 1016 end
1009 1017
1010 1018 test "#change_password_allowed? should be allowed if no auth source is set" do
1011 1019 user = User.generate!
1012 1020 assert user.change_password_allowed?
1013 1021 end
1014 1022
1015 1023 test "#change_password_allowed? should delegate to the auth source" do
1016 1024 user = User.generate!
1017 1025
1018 1026 allowed_auth_source = AuthSource.generate!
1019 1027 def allowed_auth_source.allow_password_changes?; true; end
1020 1028
1021 1029 denied_auth_source = AuthSource.generate!
1022 1030 def denied_auth_source.allow_password_changes?; false; end
1023 1031
1024 1032 assert user.change_password_allowed?
1025 1033
1026 1034 user.auth_source = allowed_auth_source
1027 1035 assert user.change_password_allowed?, "User not allowed to change password, though auth source does"
1028 1036
1029 1037 user.auth_source = denied_auth_source
1030 1038 assert !user.change_password_allowed?, "User allowed to change password, though auth source does not"
1031 1039 end
1032 1040
1033 1041 def test_own_account_deletable_should_be_true_with_unsubscrive_enabled
1034 1042 with_settings :unsubscribe => '1' do
1035 1043 assert_equal true, User.find(2).own_account_deletable?
1036 1044 end
1037 1045 end
1038 1046
1039 1047 def test_own_account_deletable_should_be_false_with_unsubscrive_disabled
1040 1048 with_settings :unsubscribe => '0' do
1041 1049 assert_equal false, User.find(2).own_account_deletable?
1042 1050 end
1043 1051 end
1044 1052
1045 1053 def test_own_account_deletable_should_be_false_for_a_single_admin
1046 1054 User.delete_all(["admin = ? AND id <> ?", true, 1])
1047 1055
1048 1056 with_settings :unsubscribe => '1' do
1049 1057 assert_equal false, User.find(1).own_account_deletable?
1050 1058 end
1051 1059 end
1052 1060
1053 1061 def test_own_account_deletable_should_be_true_for_an_admin_if_other_admin_exists
1054 1062 User.generate! do |user|
1055 1063 user.admin = true
1056 1064 end
1057 1065
1058 1066 with_settings :unsubscribe => '1' do
1059 1067 assert_equal true, User.find(1).own_account_deletable?
1060 1068 end
1061 1069 end
1062 1070
1063 1071 test "#allowed_to? for archived project should return false" do
1064 1072 project = Project.find(1)
1065 1073 project.archive
1066 1074 project.reload
1067 1075 assert_equal false, @admin.allowed_to?(:view_issues, project)
1068 1076 end
1069 1077
1070 1078 test "#allowed_to? for closed project should return true for read actions" do
1071 1079 project = Project.find(1)
1072 1080 project.close
1073 1081 project.reload
1074 1082 assert_equal false, @admin.allowed_to?(:edit_project, project)
1075 1083 assert_equal true, @admin.allowed_to?(:view_project, project)
1076 1084 end
1077 1085
1078 1086 test "#allowed_to? for project with module disabled should return false" do
1079 1087 project = Project.find(1)
1080 1088 project.enabled_module_names = ["issue_tracking"]
1081 1089 assert_equal true, @admin.allowed_to?(:add_issues, project)
1082 1090 assert_equal false, @admin.allowed_to?(:view_wiki_pages, project)
1083 1091 end
1084 1092
1085 1093 test "#allowed_to? for admin users should return true" do
1086 1094 project = Project.find(1)
1087 1095 assert ! @admin.member_of?(project)
1088 1096 %w(edit_issues delete_issues manage_news add_documents manage_wiki).each do |p|
1089 1097 assert_equal true, @admin.allowed_to?(p.to_sym, project)
1090 1098 end
1091 1099 end
1092 1100
1093 1101 test "#allowed_to? for normal users" do
1094 1102 project = Project.find(1)
1095 1103 assert_equal true, @jsmith.allowed_to?(:delete_messages, project) #Manager
1096 1104 assert_equal false, @dlopper.allowed_to?(:delete_messages, project) #Developper
1097 1105 end
1098 1106
1099 1107 test "#allowed_to? with empty array should return false" do
1100 1108 assert_equal false, @admin.allowed_to?(:view_project, [])
1101 1109 end
1102 1110
1103 1111 test "#allowed_to? with multiple projects" do
1104 1112 assert_equal true, @admin.allowed_to?(:view_project, Project.all.to_a)
1105 1113 assert_equal false, @dlopper.allowed_to?(:view_project, Project.all.to_a) #cannot see Project(2)
1106 1114 assert_equal true, @jsmith.allowed_to?(:edit_issues, @jsmith.projects.to_a) #Manager or Developer everywhere
1107 1115 assert_equal false, @jsmith.allowed_to?(:delete_issue_watchers, @jsmith.projects.to_a) #Dev cannot delete_issue_watchers
1108 1116 end
1109 1117
1110 1118 test "#allowed_to? with with options[:global] should return true if user has one role with the permission" do
1111 1119 @dlopper2 = User.find(5) #only Developper on a project, not Manager anywhere
1112 1120 @anonymous = User.find(6)
1113 1121 assert_equal true, @jsmith.allowed_to?(:delete_issue_watchers, nil, :global => true)
1114 1122 assert_equal false, @dlopper2.allowed_to?(:delete_issue_watchers, nil, :global => true)
1115 1123 assert_equal true, @dlopper2.allowed_to?(:add_issues, nil, :global => true)
1116 1124 assert_equal false, @anonymous.allowed_to?(:add_issues, nil, :global => true)
1117 1125 assert_equal true, @anonymous.allowed_to?(:view_issues, nil, :global => true)
1118 1126 end
1119 1127
1120 1128 # this is just a proxy method, the test only calls it to ensure it doesn't break trivially
1121 1129 test "#allowed_to_globally?" do
1122 1130 @dlopper2 = User.find(5) #only Developper on a project, not Manager anywhere
1123 1131 @anonymous = User.find(6)
1124 1132 assert_equal true, @jsmith.allowed_to_globally?(:delete_issue_watchers)
1125 1133 assert_equal false, @dlopper2.allowed_to_globally?(:delete_issue_watchers)
1126 1134 assert_equal true, @dlopper2.allowed_to_globally?(:add_issues)
1127 1135 assert_equal false, @anonymous.allowed_to_globally?(:add_issues)
1128 1136 assert_equal true, @anonymous.allowed_to_globally?(:view_issues)
1129 1137 end
1130 1138
1131 1139 def test_notify_about_issue
1132 1140 project = Project.find(1)
1133 1141 author = User.generate!
1134 1142 assignee = User.generate!
1135 1143 member = User.generate!
1136 1144 Member.create!(:user => member, :project => project, :role_ids => [1])
1137 1145 issue = Issue.generate!(:project => project, :assigned_to => assignee, :author => author)
1138 1146
1139 1147 tests = {
1140 1148 author => %w(all only_my_events only_owner selected),
1141 1149 assignee => %w(all only_my_events only_assigned selected),
1142 1150 member => %w(all)
1143 1151 }
1144 1152
1145 1153 tests.each do |user, expected|
1146 1154 User::MAIL_NOTIFICATION_OPTIONS.map(&:first).each do |option|
1147 1155 user.mail_notification = option
1148 1156 assert_equal expected.include?(option), user.notify_about?(issue)
1149 1157 end
1150 1158 end
1151 1159 end
1152 1160
1153 1161 def test_notify_about_issue_for_previous_assignee
1154 1162 assignee = User.generate!(:mail_notification => 'only_assigned')
1155 1163 new_assignee = User.generate!(:mail_notification => 'only_assigned')
1156 1164 issue = Issue.generate!(:assigned_to => assignee)
1157 1165
1158 1166 assert assignee.notify_about?(issue)
1159 1167 assert !new_assignee.notify_about?(issue)
1160 1168
1161 1169 issue.assigned_to = new_assignee
1162 1170 assert assignee.notify_about?(issue)
1163 1171 assert new_assignee.notify_about?(issue)
1164 1172
1165 1173 issue.save!
1166 1174 assert !assignee.notify_about?(issue)
1167 1175 assert new_assignee.notify_about?(issue)
1168 1176 end
1169 1177
1170 1178 def test_notify_about_news
1171 1179 user = User.generate!
1172 1180 news = News.new
1173 1181
1174 1182 User::MAIL_NOTIFICATION_OPTIONS.map(&:first).each do |option|
1175 1183 user.mail_notification = option
1176 1184 assert_equal (option != 'none'), user.notify_about?(news)
1177 1185 end
1178 1186 end
1179 1187
1180 1188 def test_salt_unsalted_passwords
1181 1189 # Restore a user with an unsalted password
1182 1190 user = User.find(1)
1183 1191 user.salt = nil
1184 1192 user.hashed_password = User.hash_password("unsalted")
1185 1193 user.save!
1186 1194
1187 1195 User.salt_unsalted_passwords!
1188 1196
1189 1197 user.reload
1190 1198 # Salt added
1191 1199 assert !user.salt.blank?
1192 1200 # Password still valid
1193 1201 assert user.check_password?("unsalted")
1194 1202 assert_equal user, User.try_to_login(user.login, "unsalted")
1195 1203 end
1196 1204
1197 1205 if Object.const_defined?(:OpenID)
1198 1206 def test_setting_identity_url
1199 1207 normalized_open_id_url = 'http://example.com/'
1200 1208 u = User.new( :identity_url => 'http://example.com/' )
1201 1209 assert_equal normalized_open_id_url, u.identity_url
1202 1210 end
1203 1211
1204 1212 def test_setting_identity_url_without_trailing_slash
1205 1213 normalized_open_id_url = 'http://example.com/'
1206 1214 u = User.new( :identity_url => 'http://example.com' )
1207 1215 assert_equal normalized_open_id_url, u.identity_url
1208 1216 end
1209 1217
1210 1218 def test_setting_identity_url_without_protocol
1211 1219 normalized_open_id_url = 'http://example.com/'
1212 1220 u = User.new( :identity_url => 'example.com' )
1213 1221 assert_equal normalized_open_id_url, u.identity_url
1214 1222 end
1215 1223
1216 1224 def test_setting_blank_identity_url
1217 1225 u = User.new( :identity_url => 'example.com' )
1218 1226 u.identity_url = ''
1219 1227 assert u.identity_url.blank?
1220 1228 end
1221 1229
1222 1230 def test_setting_invalid_identity_url
1223 1231 u = User.new( :identity_url => 'this is not an openid url' )
1224 1232 assert u.identity_url.blank?
1225 1233 end
1226 1234 else
1227 1235 puts "Skipping openid tests."
1228 1236 end
1229 1237 end
General Comments 0
You need to be logged in to leave comments. Login now