##// END OF EJS Templates
Fixed: invalid SQL query on User#destroy (#1781)....
Jean-Philippe Lang -
r1754:6fc62d393cc4
parent child
Show More
@@ -1,293 +1,294
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 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 < ActiveRecord::Base
21 21
22 22 # Account statuses
23 23 STATUS_ANONYMOUS = 0
24 24 STATUS_ACTIVE = 1
25 25 STATUS_REGISTERED = 2
26 26 STATUS_LOCKED = 3
27 27
28 28 USER_FORMATS = {
29 29 :firstname_lastname => '#{firstname} #{lastname}',
30 30 :firstname => '#{firstname}',
31 31 :lastname_firstname => '#{lastname} #{firstname}',
32 32 :lastname_coma_firstname => '#{lastname}, #{firstname}',
33 33 :username => '#{login}'
34 34 }
35 35
36 has_many :memberships, :class_name => 'Member', :include => [ :project, :role ], :conditions => "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}", :order => "#{Project.table_name}.name", :dependent => :delete_all
36 has_many :memberships, :class_name => 'Member', :include => [ :project, :role ], :conditions => "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}", :order => "#{Project.table_name}.name"
37 has_many :members, :dependent => :delete_all
37 38 has_many :projects, :through => :memberships
38 39 has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
39 40 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
40 41 has_one :rss_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='feeds'"
41 42 belongs_to :auth_source
42 43
43 44 acts_as_customizable
44 45
45 46 attr_accessor :password, :password_confirmation
46 47 attr_accessor :last_before_login_on
47 48 # Prevents unauthorized assignments
48 49 attr_protected :login, :admin, :password, :password_confirmation, :hashed_password
49 50
50 51 validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
51 52 validates_uniqueness_of :login, :if => Proc.new { |user| !user.login.blank? }
52 53 validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }
53 54 # Login must contain lettres, numbers, underscores only
54 55 validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
55 56 validates_length_of :login, :maximum => 30
56 57 validates_format_of :firstname, :lastname, :with => /^[\w\s\'\-\.]*$/i
57 58 validates_length_of :firstname, :lastname, :maximum => 30
58 59 validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_nil => true
59 60 validates_length_of :mail, :maximum => 60, :allow_nil => true
60 61 validates_length_of :password, :minimum => 4, :allow_nil => true
61 62 validates_confirmation_of :password, :allow_nil => true
62 63
63 64 def before_create
64 65 self.mail_notification = false
65 66 true
66 67 end
67 68
68 69 def before_save
69 70 # update hashed_password if password was set
70 71 self.hashed_password = User.hash_password(self.password) if self.password
71 72 end
72 73
73 74 def self.active
74 75 with_scope :find => { :conditions => [ "status = ?", STATUS_ACTIVE ] } do
75 76 yield
76 77 end
77 78 end
78 79
79 80 def self.find_active(*args)
80 81 active do
81 82 find(*args)
82 83 end
83 84 end
84 85
85 86 # Returns the user that matches provided login and password, or nil
86 87 def self.try_to_login(login, password)
87 88 # Make sure no one can sign in with an empty password
88 89 return nil if password.to_s.empty?
89 90 user = find(:first, :conditions => ["login=?", login])
90 91 if user
91 92 # user is already in local database
92 93 return nil if !user.active?
93 94 if user.auth_source
94 95 # user has an external authentication method
95 96 return nil unless user.auth_source.authenticate(login, password)
96 97 else
97 98 # authentication with local password
98 99 return nil unless User.hash_password(password) == user.hashed_password
99 100 end
100 101 else
101 102 # user is not yet registered, try to authenticate with available sources
102 103 attrs = AuthSource.authenticate(login, password)
103 104 if attrs
104 105 user = new(*attrs)
105 106 user.login = login
106 107 user.language = Setting.default_language
107 108 if user.save
108 109 user.reload
109 110 logger.info("User '#{user.login}' created from the LDAP") if logger
110 111 end
111 112 end
112 113 end
113 114 user.update_attribute(:last_login_on, Time.now) if user && !user.new_record?
114 115 user
115 116 rescue => text
116 117 raise text
117 118 end
118 119
119 120 # Return user's full name for display
120 121 def name(formatter = nil)
121 122 f = USER_FORMATS[formatter || Setting.user_format] || USER_FORMATS[:firstname_lastname]
122 123 eval '"' + f + '"'
123 124 end
124 125
125 126 def active?
126 127 self.status == STATUS_ACTIVE
127 128 end
128 129
129 130 def registered?
130 131 self.status == STATUS_REGISTERED
131 132 end
132 133
133 134 def locked?
134 135 self.status == STATUS_LOCKED
135 136 end
136 137
137 138 def check_password?(clear_password)
138 139 User.hash_password(clear_password) == self.hashed_password
139 140 end
140 141
141 142 def pref
142 143 self.preference ||= UserPreference.new(:user => self)
143 144 end
144 145
145 146 def time_zone
146 147 self.pref.time_zone.nil? ? nil : TimeZone[self.pref.time_zone]
147 148 end
148 149
149 150 def wants_comments_in_reverse_order?
150 151 self.pref[:comments_sorting] == 'desc'
151 152 end
152 153
153 154 # Return user's RSS key (a 40 chars long string), used to access feeds
154 155 def rss_key
155 156 token = self.rss_token || Token.create(:user => self, :action => 'feeds')
156 157 token.value
157 158 end
158 159
159 160 # Return an array of project ids for which the user has explicitly turned mail notifications on
160 161 def notified_projects_ids
161 162 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
162 163 end
163 164
164 165 def notified_project_ids=(ids)
165 166 Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
166 167 Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
167 168 @notified_projects_ids = nil
168 169 notified_projects_ids
169 170 end
170 171
171 172 def self.find_by_rss_key(key)
172 173 token = Token.find_by_value(key)
173 174 token && token.user.active? ? token.user : nil
174 175 end
175 176
176 177 def self.find_by_autologin_key(key)
177 178 token = Token.find_by_action_and_value('autologin', key)
178 179 token && (token.created_on > Setting.autologin.to_i.day.ago) && token.user.active? ? token.user : nil
179 180 end
180 181
181 182 def <=>(user)
182 183 if user.nil?
183 184 -1
184 185 elsif lastname.to_s.downcase == user.lastname.to_s.downcase
185 186 firstname.to_s.downcase <=> user.firstname.to_s.downcase
186 187 else
187 188 lastname.to_s.downcase <=> user.lastname.to_s.downcase
188 189 end
189 190 end
190 191
191 192 def to_s
192 193 name
193 194 end
194 195
195 196 def logged?
196 197 true
197 198 end
198 199
199 200 def anonymous?
200 201 !logged?
201 202 end
202 203
203 204 # Return user's role for project
204 205 def role_for_project(project)
205 206 # No role on archived projects
206 207 return nil unless project && project.active?
207 208 if logged?
208 209 # Find project membership
209 210 membership = memberships.detect {|m| m.project_id == project.id}
210 211 if membership
211 212 membership.role
212 213 else
213 214 @role_non_member ||= Role.non_member
214 215 end
215 216 else
216 217 @role_anonymous ||= Role.anonymous
217 218 end
218 219 end
219 220
220 221 # Return true if the user is a member of project
221 222 def member_of?(project)
222 223 role_for_project(project).member?
223 224 end
224 225
225 226 # Return true if the user is allowed to do the specified action on project
226 227 # action can be:
227 228 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
228 229 # * a permission Symbol (eg. :edit_project)
229 230 def allowed_to?(action, project, options={})
230 231 if project
231 232 # No action allowed on archived projects
232 233 return false unless project.active?
233 234 # No action allowed on disabled modules
234 235 return false unless project.allows_to?(action)
235 236 # Admin users are authorized for anything else
236 237 return true if admin?
237 238
238 239 role = role_for_project(project)
239 240 return false unless role
240 241 role.allowed_to?(action) && (project.is_public? || role.member?)
241 242
242 243 elsif options[:global]
243 244 # authorize if user has at least one role that has this permission
244 245 roles = memberships.collect {|m| m.role}.uniq
245 246 roles.detect {|r| r.allowed_to?(action)}
246 247 else
247 248 false
248 249 end
249 250 end
250 251
251 252 def self.current=(user)
252 253 @current_user = user
253 254 end
254 255
255 256 def self.current
256 257 @current_user ||= User.anonymous
257 258 end
258 259
259 260 def self.anonymous
260 261 anonymous_user = AnonymousUser.find(:first)
261 262 if anonymous_user.nil?
262 263 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
263 264 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
264 265 end
265 266 anonymous_user
266 267 end
267 268
268 269 private
269 270 # Return password digest
270 271 def self.hash_password(clear_password)
271 272 Digest::SHA1.hexdigest(clear_password || "")
272 273 end
273 274 end
274 275
275 276 class AnonymousUser < User
276 277
277 278 def validate_on_create
278 279 # There should be only one AnonymousUser in the database
279 280 errors.add_to_base 'An anonymous user already exists.' if AnonymousUser.find(:first)
280 281 end
281 282
282 283 def available_custom_fields
283 284 []
284 285 end
285 286
286 287 # Overrides a few properties
287 288 def logged?; false end
288 289 def admin; false end
289 290 def name; 'Anonymous' end
290 291 def mail; nil end
291 292 def time_zone; nil end
292 293 def rss_key; nil end
293 294 end
@@ -1,155 +1,161
1 1 # redMine - project management software
2 2 # Copyright (C) 2006 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.dirname(__FILE__) + '/../test_helper'
19 19
20 20 class UserTest < Test::Unit::TestCase
21 21 fixtures :users, :members, :projects
22 22
23 23 def setup
24 24 @admin = User.find(1)
25 25 @jsmith = User.find(2)
26 26 @dlopper = User.find(3)
27 27 end
28 28
29 29 def test_truth
30 30 assert_kind_of User, @jsmith
31 31 end
32 32
33 33 def test_create
34 34 user = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
35 35
36 36 user.login = "jsmith"
37 37 user.password, user.password_confirmation = "password", "password"
38 38 # login uniqueness
39 39 assert !user.save
40 40 assert_equal 1, user.errors.count
41 41
42 42 user.login = "newuser"
43 43 user.password, user.password_confirmation = "passwd", "password"
44 44 # password confirmation
45 45 assert !user.save
46 46 assert_equal 1, user.errors.count
47 47
48 48 user.password, user.password_confirmation = "password", "password"
49 49 assert user.save
50 50 end
51 51
52 52 def test_update
53 53 assert_equal "admin", @admin.login
54 54 @admin.login = "john"
55 55 assert @admin.save, @admin.errors.full_messages.join("; ")
56 56 @admin.reload
57 57 assert_equal "john", @admin.login
58 58 end
59 59
60 def test_destroy
61 User.find(2).destroy
62 assert_nil User.find_by_id(2)
63 assert Member.find_all_by_user_id(2).empty?
64 end
65
60 66 def test_validate
61 67 @admin.login = ""
62 68 assert !@admin.save
63 69 assert_equal 1, @admin.errors.count
64 70 end
65 71
66 72 def test_password
67 73 user = User.try_to_login("admin", "admin")
68 74 assert_kind_of User, user
69 75 assert_equal "admin", user.login
70 76 user.password = "hello"
71 77 assert user.save
72 78
73 79 user = User.try_to_login("admin", "hello")
74 80 assert_kind_of User, user
75 81 assert_equal "admin", user.login
76 82 assert_equal User.hash_password("hello"), user.hashed_password
77 83 end
78 84
79 85 def test_name_format
80 86 assert_equal 'Smith, John', @jsmith.name(:lastname_coma_firstname)
81 87 Setting.user_format = :firstname_lastname
82 88 assert_equal 'John Smith', @jsmith.name
83 89 Setting.user_format = :username
84 90 assert_equal 'jsmith', @jsmith.name
85 91 end
86 92
87 93 def test_lock
88 94 user = User.try_to_login("jsmith", "jsmith")
89 95 assert_equal @jsmith, user
90 96
91 97 @jsmith.status = User::STATUS_LOCKED
92 98 assert @jsmith.save
93 99
94 100 user = User.try_to_login("jsmith", "jsmith")
95 101 assert_equal nil, user
96 102 end
97 103
98 104 def test_create_anonymous
99 105 AnonymousUser.delete_all
100 106 anon = User.anonymous
101 107 assert !anon.new_record?
102 108 assert_kind_of AnonymousUser, anon
103 109 end
104 110
105 111 def test_rss_key
106 112 assert_nil @jsmith.rss_token
107 113 key = @jsmith.rss_key
108 114 assert_equal 40, key.length
109 115
110 116 @jsmith.reload
111 117 assert_equal key, @jsmith.rss_key
112 118 end
113 119
114 120 def test_role_for_project
115 121 # user with a role
116 122 role = @jsmith.role_for_project(Project.find(1))
117 123 assert_kind_of Role, role
118 124 assert_equal "Manager", role.name
119 125
120 126 # user with no role
121 127 assert !@dlopper.role_for_project(Project.find(2)).member?
122 128 end
123 129
124 130 def test_mail_notification_all
125 131 @jsmith.mail_notification = true
126 132 @jsmith.notified_project_ids = []
127 133 @jsmith.save
128 134 @jsmith.reload
129 135 assert @jsmith.projects.first.recipients.include?(@jsmith.mail)
130 136 end
131 137
132 138 def test_mail_notification_selected
133 139 @jsmith.mail_notification = false
134 140 @jsmith.notified_project_ids = [1]
135 141 @jsmith.save
136 142 @jsmith.reload
137 143 assert Project.find(1).recipients.include?(@jsmith.mail)
138 144 end
139 145
140 146 def test_mail_notification_none
141 147 @jsmith.mail_notification = false
142 148 @jsmith.notified_project_ids = []
143 149 @jsmith.save
144 150 @jsmith.reload
145 151 assert !@jsmith.projects.first.recipients.include?(@jsmith.mail)
146 152 end
147 153
148 154 def test_comments_sorting_preference
149 155 assert !@jsmith.wants_comments_in_reverse_order?
150 156 @jsmith.pref.comments_sorting = 'asc'
151 157 assert !@jsmith.wants_comments_in_reverse_order?
152 158 @jsmith.pref.comments_sorting = 'desc'
153 159 assert @jsmith.wants_comments_in_reverse_order?
154 160 end
155 161 end
General Comments 0
You need to be logged in to leave comments. Login now