Description:
a cleaner, testable way to log out user after contest changed
Commit status:
[Not Reviewed]
References:
Comments:
0 Commit comments 0 Inline Comments
Unresolved TODOs:
There are no unresolved TODOs
Add another comment

r295:4f81b9ab5d77 - - 11 files changed: 170 inserted, 18 deleted

@@ -0,0 +1,9
1 + class AddForcedLogoutToUserContestStat < ActiveRecord::Migration
2 + def self.up
3 + add_column :user_contest_stats, :forced_logout, :boolean
4 + end
5 +
6 + def self.down
7 + remove_column :user_contest_stats, :forced_logout, :boolean
8 + end
9 + end
@@ -0,0 +1,73
1 + require 'delorean'
2 +
3 + require File.dirname(__FILE__) + '/../spec_helper'
4 + require File.dirname(__FILE__) + '/../config_spec_helper'
5 +
6 + describe UserAdminController, "when manage contest" do
7 +
8 + include ConfigSpecHelperMethods
9 +
10 + fixtures :users
11 + fixtures :problems
12 + fixtures :contests
13 + fixtures :roles
14 +
15 + def change_users_contest_to(user_login_list, contest, reset_timer=false)
16 + post_data = {
17 + :contest => {:id => contest.id},
18 + :operation => 'assign',
19 + :login_list => user_login_list
20 + }
21 + post_data[:reset_timer] = true if reset_timer
22 + post 'manage_contest', post_data, {:user_id => @admin_user.id}
23 + end
24 +
25 + before(:each) do
26 + @admin_user = users(:mary)
27 + @contest_b = contests(:contest_b)
28 + @james = users(:james)
29 + @jack = users(:jack)
30 +
31 + set_contest_time_limit('3:00')
32 + set_indv_contest_mode
33 + end
34 +
35 + it "should allow admin to see contest management page" do
36 + get 'contest_management', {}, {:user_id => @admin_user.id}
37 +
38 + response.should render_template 'user_admin/contest_management'
39 + end
40 +
41 + it "should change users' contest" do
42 + change_users_contest_to("james\njack", @contest_b)
43 + response.should redirect_to :action => 'contest_management'
44 +
45 + @james.contests(true).should include @contest_b
46 + @jack.contests(true).should_not include @contest_a
47 + end
48 +
49 + it "should reset users' timer when their contests change" do
50 + @james.update_start_time
51 +
52 + Delorean.time_travel_to(190.minutes.since) do
53 + @james.contest_finished?.should be_true
54 +
55 + change_users_contest_to("james", @contest_b, true)
56 +
57 + @james.contest_finished?.should be_false
58 + end
59 + end
60 +
61 + it "should set forced_logout flag for users when their contests change" do
62 + @james.update_start_time
63 +
64 + Delorean.time_travel_to(190.minutes.since) do
65 + @james.contest_finished?.should be_true
66 +
67 + change_users_contest_to("james", @contest_b, true)
68 +
69 + @james.contest_stat(true).forced_logout.should be_true
70 + end
71 + end
72 +
73 + end
@@ -1,74 +1,84
1 1 # Filters added to this controller apply to all controllers in the application.
2 2 # Likewise, all the methods added will be available for all controllers.
3 3
4 4 class ApplicationController < ActionController::Base
5 5
6 6 SINGLE_USER_MODE_CONF_KEY = 'system.single_user_mode'
7 7
8 8 def admin_authorization
9 9 return false unless authenticate
10 10 user = User.find(session[:user_id], :include => ['roles'])
11 11 redirect_to :controller => 'main', :action => 'login' unless user.admin?
12 12 end
13 13
14 14 def authorization_by_roles(allowed_roles)
15 15 return false unless authenticate
16 16 user = User.find(session[:user_id])
17 17 unless user.roles.detect { |role| allowed_roles.member?(role.name) }
18 18 flash[:notice] = 'You are not authorized to view the page you requested'
19 19 redirect_to :controller => 'main', :action => 'login'
20 20 return false
21 21 end
22 22 end
23 23
24 24 protected
25 25
26 26 def authenticate
27 27 unless session[:user_id]
28 28 redirect_to :controller => 'main', :action => 'login'
29 29 return false
30 30 end
31 31
32 - #Configuration.reload
33 32 # check if run in single user mode
34 - if (Configuration[SINGLE_USER_MODE_CONF_KEY])
33 + if Configuration[SINGLE_USER_MODE_CONF_KEY]
35 34 user = User.find(session[:user_id])
36 35 if user==nil or (not user.admin?)
37 36 redirect_to :controller => 'main', :action => 'login'
38 37 return false
39 38 end
39 + return true
40 40 end
41 41
42 + if Configuration.multicontests?
43 + user = User.find(session[:user_id])
44 + begin
45 + if user.contest_stat(true).forced_logout
46 + flash[:notice] = 'You have been automatically logged out.'
47 + redirect_to :controller => 'main', :action => 'index'
48 + end
49 + rescue
50 + end
51 + end
42 52 return true
43 53 end
44 54
45 55 def authorization
46 56 return false unless authenticate
47 57 user = User.find(session[:user_id])
48 58 unless user.roles.detect { |role|
49 59 role.rights.detect{ |right|
50 60 right.controller == self.class.controller_name and
51 61 (right.action == 'all' or right.action == action_name)
52 62 }
53 63 }
54 64 flash[:notice] = 'You are not authorized to view the page you requested'
55 65 #request.env['HTTP_REFERER'] ? (redirect_to :back) : (redirect_to :controller => 'login')
56 66 redirect_to :controller => 'main', :action => 'login'
57 67 return false
58 68 end
59 69 end
60 70
61 71 def verify_time_limit
62 72 return true if session[:user_id]==nil
63 73 user = User.find(session[:user_id], :include => :site)
64 74 return true if user==nil or user.site == nil
65 75 if user.contest_finished?
66 76 flash[:notice] = 'Error: the contest you are participating is over.'
67 77 redirect_to :back
68 78 return false
69 79 end
70 80 return true
71 81 end
72 82
73 83 end
74 84
@@ -1,39 +1,51
1 1 class LoginController < ApplicationController
2 2
3 3 def index
4 4 # show login screen
5 5 reset_session
6 6 redirect_to :controller => 'main', :action => 'login'
7 7 end
8 8
9 9 def login
10 10 if user = User.authenticate(params[:login], params[:password])
11 11 session[:user_id] = user.id
12 12 session[:admin] = user.admin?
13 +
14 + # clear forced logout flag for multicontests contest change
15 + if Configuration.multicontests?
16 + contest_stat = user.contest_stat
17 + if contest_stat.respond_to? :forced_logout
18 + if contest_stat.forced_logout
19 + contest_stat.forced_logout = false
20 + contest_stat.save
21 + end
22 + end
23 + end
24 +
13 25 redirect_to :controller => 'main', :action => 'list'
14 26 else
15 27 flash[:notice] = 'Wrong password'
16 28 redirect_to :controller => 'main', :action => 'login'
17 29 end
18 30 end
19 31
20 32 def site_login
21 33 begin
22 34 site = Site.find(params[:login][:site_id])
23 35 rescue ActiveRecord::RecordNotFound
24 36 site = nil
25 37 end
26 38 if site==nil
27 39 flash[:notice] = 'Wrong site'
28 40 redirect_to :controller => 'main', :action => 'login' and return
29 41 end
30 42 if (site.password) and (site.password == params[:login][:password])
31 43 session[:site_id] = site.id
32 44 redirect_to :controller => 'site', :action => 'index'
33 45 else
34 46 flash[:notice] = 'Wrong site password'
35 47 redirect_to :controller => 'site', :action => 'login'
36 48 end
37 49 end
38 50
39 51 end
@@ -154,196 +154,203
154 154 flash[:notice] = 'Output not found.'
155 155 redirect_to :action => 'list' and return
156 156 end
157 157
158 158 if defined?(USE_APACHE_XSENDFILE) and USE_APACHE_XSENDFILE
159 159 response.headers['Content-Type'] = "application/force-download"
160 160 response.headers['Content-Disposition'] = "attachment; filename=\"output-#{case_num}.txt\""
161 161 response.headers["X-Sendfile"] = out_filename
162 162 response.headers['Content-length'] = File.size(out_filename)
163 163 render :nothing => true
164 164 else
165 165 send_file out_filename, :stream => false, :filename => "output-#{case_num}.txt", :type => "text/plain"
166 166 end
167 167 end
168 168
169 169 def error
170 170 @user = User.find(session[:user_id])
171 171 end
172 172
173 173 # announcement refreshing and hiding methods
174 174
175 175 def announcements
176 176 if params.has_key? 'recent'
177 177 prepare_announcements(params[:recent])
178 178 else
179 179 prepare_announcements
180 180 end
181 181 render(:partial => 'announcement',
182 182 :collection => @announcements,
183 183 :locals => {:announcement_effect => true})
184 184 end
185 185
186 186 protected
187 187
188 188 def prepare_announcements(recent=nil)
189 189 if Configuration.show_tasks_to?(@user)
190 190 @announcements = Announcement.find_published(true)
191 191 else
192 192 @announcements = Announcement.find_published
193 193 end
194 194 if recent!=nil
195 195 recent_id = recent.to_i
196 196 @announcements = @announcements.find_all { |a| a.id > recent_id }
197 197 end
198 198 end
199 199
200 200 def prepare_list_information
201 201 @user = User.find(session[:user_id])
202 202 if not Configuration.multicontests?
203 203 @problems = @user.available_problems
204 204 else
205 205 @contest_problems = @user.available_problems_group_by_contests
206 206 @problems = @user.available_problems
207 207 end
208 208 @prob_submissions = {}
209 209 @problems.each do |p|
210 210 sub = Submission.find_last_by_user_and_problem(@user.id,p.id)
211 211 if sub!=nil
212 212 @prob_submissions[p.id] = { :count => sub.number, :submission => sub }
213 213 else
214 214 @prob_submissions[p.id] = { :count => 0, :submission => nil }
215 215 end
216 216 end
217 217 prepare_announcements
218 218 end
219 219
220 220 def check_viewability
221 221 @user = User.find(session[:user_id])
222 222 if (!Configuration.show_tasks_to?(@user)) and
223 223 ((action_name=='submission') or (action_name=='submit'))
224 224 redirect_to :action => 'list' and return
225 225 end
226 226 end
227 227
228 228 def prepare_grading_result(submission)
229 229 if Configuration.task_grading_info.has_key? submission.problem.name
230 230 grading_info = Configuration.task_grading_info[submission.problem.name]
231 231 else
232 232 # guess task info from problem.full_score
233 233 cases = submission.problem.full_score / 10
234 234 grading_info = {
235 235 'testruns' => cases,
236 236 'testcases' => cases
237 237 }
238 238 end
239 239 @test_runs = []
240 240 if grading_info['testruns'].is_a? Integer
241 241 trun_count = grading_info['testruns']
242 242 trun_count.times do |i|
243 243 @test_runs << [ read_grading_result(@user.login,
244 244 submission.problem.name,
245 245 submission.id,
246 246 i+1) ]
247 247 end
248 248 else
249 249 grading_info['testruns'].keys.sort.each do |num|
250 250 run = []
251 251 testrun = grading_info['testruns'][num]
252 252 testrun.each do |c|
253 253 run << read_grading_result(@user.login,
254 254 submission.problem.name,
255 255 submission.id,
256 256 c)
257 257 end
258 258 @test_runs << run
259 259 end
260 260 end
261 261 end
262 262
263 263 def grading_result_dir(user_name, problem_name, submission_id, case_num)
264 264 return "#{GRADING_RESULT_DIR}/#{user_name}/#{problem_name}/#{submission_id}/test-result/#{case_num}"
265 265 end
266 266
267 267 def output_filename(user_name, problem_name, submission_id, case_num)
268 268 dir = grading_result_dir(user_name,problem_name, submission_id, case_num)
269 269 return "#{dir}/output.txt"
270 270 end
271 271
272 272 def read_grading_result(user_name, problem_name, submission_id, case_num)
273 273 dir = grading_result_dir(user_name,problem_name, submission_id, case_num)
274 274 result_file_name = "#{dir}/result"
275 275 if !FileTest.exists?(result_file_name)
276 276 return {:num => case_num, :msg => 'program did not run'}
277 277 else
278 278 results = File.open(result_file_name).readlines
279 279 run_stat = extract_running_stat(results)
280 280 output_filename = "#{dir}/output.txt"
281 281 if FileTest.exists?(output_filename)
282 282 output_file = true
283 283 output_size = File.size(output_filename)
284 284 else
285 285 output_file = false
286 286 output_size = 0
287 287 end
288 288
289 289 return {
290 290 :num => case_num,
291 291 :msg => results[0],
292 292 :run_stat => run_stat,
293 293 :output => output_file,
294 294 :output_size => output_size
295 295 }
296 296 end
297 297 end
298 298
299 299 # copied from grader/script/lib/test_request_helper.rb
300 300 def extract_running_stat(results)
301 301 running_stat_line = results[-1]
302 302
303 303 # extract exit status line
304 304 run_stat = ""
305 305 if !(/[Cc]orrect/.match(results[0]))
306 306 run_stat = results[0].chomp
307 307 else
308 308 run_stat = 'Program exited normally'
309 309 end
310 310
311 311 logger.info "Stat line: #{running_stat_line}"
312 312
313 313 # extract running time
314 314 if res = /r(.*)u(.*)s/.match(running_stat_line)
315 315 seconds = (res[1].to_f + res[2].to_f)
316 316 time_stat = "Time used: #{seconds} sec."
317 317 else
318 318 seconds = nil
319 319 time_stat = "Time used: n/a sec."
320 320 end
321 321
322 322 # extract memory usage
323 323 if res = /s(.*)m/.match(running_stat_line)
324 324 memory_used = res[1].to_i
325 325 else
326 326 memory_used = -1
327 327 end
328 328
329 329 return {
330 330 :msg => "#{run_stat}\n#{time_stat}",
331 331 :running_time => seconds,
332 332 :exit_status => run_stat,
333 333 :memory_usage => memory_used
334 334 }
335 335 end
336 336
337 337 def update_user_start_time
338 338 user = User.find(session[:user_id])
339 339 user.update_start_time
340 340 end
341 341
342 342 def reject_announcement_refresh_when_logged_out
343 343 if not session[:user_id]
344 344 render :text => 'Access forbidden', :status => 403
345 345 end
346 +
347 + if Configuration.multicontests?
348 + user = User.find(session[:user_id])
349 + if user.contest_stat.forced_logout
350 + render :text => 'Access forbidden', :status => 403
351 + end
352 + end
346 353 end
347 354
348 355 end
349 356
@@ -7,337 +7,341
7 7 render :action => 'list'
8 8 end
9 9
10 10 # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
11 11 verify :method => :post, :only => [ :destroy,
12 12 :create, :create_from_list,
13 13 :update ],
14 14 :redirect_to => { :action => :list }
15 15
16 16 def list
17 17 @users = User.find(:all)
18 18 @hidden_columns = ['hashed_password', 'salt', 'created_at', 'updated_at']
19 19 @contests = Contest.all(:conditions => {:enabled => true})
20 20 end
21 21
22 22 def active
23 23 sessions = ActiveRecord::SessionStore::Session.find(:all, :conditions => ["updated_at >= ?", 60.minutes.ago])
24 24 @users = []
25 25 sessions.each do |session|
26 26 if session.data[:user_id]
27 27 @users << User.find(session.data[:user_id])
28 28 end
29 29 end
30 30 end
31 31
32 32 def show
33 33 @user = User.find(params[:id])
34 34 end
35 35
36 36 def new
37 37 @user = User.new
38 38 end
39 39
40 40 def create
41 41 @user = User.new(params[:user])
42 42 @user.activated = true
43 43 if @user.save
44 44 flash[:notice] = 'User was successfully created.'
45 45 redirect_to :action => 'list'
46 46 else
47 47 render :action => 'new'
48 48 end
49 49 end
50 50
51 51 def create_from_list
52 52 lines = params[:user_list]
53 53
54 54 note = []
55 55
56 56 lines.split("\n").each do |line|
57 57 items = line.chomp.split(',')
58 58 if items.length>=2
59 59 login = items[0]
60 60 full_name = items[1]
61 61
62 62 added_random_password = false
63 63 if items.length>=3
64 64 password = items[2]
65 65 user_alias = (items.length>=4) ? items[3] : login
66 66 else
67 67 password = random_password
68 68 user_alias = (items.length>=4) ? items[3] : login
69 69 added_random_password = true
70 70 end
71 71
72 72 user = User.new({:login => login,
73 73 :full_name => full_name,
74 74 :password => password,
75 75 :password_confirmation => password,
76 76 :alias => user_alias})
77 77 user.activated = true
78 78 user.save
79 79
80 80 if added_random_password
81 81 note << "'#{login}' (+)"
82 82 else
83 83 note << login
84 84 end
85 85 end
86 86 end
87 87 flash[:notice] = 'User(s) ' + note.join(', ') +
88 88 ' were successfully created. ' +
89 89 '( (+) - created with random passwords.)'
90 90 redirect_to :action => 'list'
91 91 end
92 92
93 93 def edit
94 94 @user = User.find(params[:id])
95 95 end
96 96
97 97 def update
98 98 @user = User.find(params[:id])
99 99 if @user.update_attributes(params[:user])
100 100 flash[:notice] = 'User was successfully updated.'
101 101 redirect_to :action => 'show', :id => @user
102 102 else
103 103 render :action => 'edit'
104 104 end
105 105 end
106 106
107 107 def destroy
108 108 User.find(params[:id]).destroy
109 109 redirect_to :action => 'list'
110 110 end
111 111
112 112 def user_stat
113 113 @problems = Problem.find_available_problems
114 114 @users = User.find(:all)
115 115 @scorearray = Array.new
116 116 @users.each do |u|
117 117 ustat = Array.new
118 118 ustat[0] = u
119 119 @problems.each do |p|
120 120 sub = Submission.find_last_by_user_and_problem(u.id,p.id)
121 121 if (sub!=nil) and (sub.points!=nil)
122 122 ustat << [(sub.points.to_f*100/p.full_score).round, (sub.points>=p.full_score)]
123 123 else
124 124 ustat << [0,false]
125 125 end
126 126 end
127 127 @scorearray << ustat
128 128 end
129 129 end
130 130
131 131 def import
132 132 if params[:file]==''
133 133 flash[:notice] = 'Error importing no file'
134 134 redirect_to :action => 'list' and return
135 135 end
136 136 import_from_file(params[:file])
137 137 end
138 138
139 139 def random_all_passwords
140 140 users = User.find(:all)
141 141 @prefix = params[:prefix] || ''
142 142 @non_admin_users = User.find_non_admin_with_prefix(@prefix)
143 143 @changed = false
144 144 if request.request_method == :post
145 145 @non_admin_users.each do |user|
146 146 password = random_password
147 147 user.password = password
148 148 user.password_confirmation = password
149 149 user.save
150 150 end
151 151 @changed = true
152 152 end
153 153 end
154 154
155 155 # contest management
156 156
157 157 def add_to_contest
158 158 user = User.find(params[:id])
159 159 contest = Contest.find(params[:contest_id])
160 160 if user and contest
161 161 user.contests << contest
162 162 end
163 163 redirect_to :action => 'list'
164 164 end
165 165
166 166 def remove_from_contest
167 167 user = User.find(params[:id])
168 168 contest = Contest.find(params[:contest_id])
169 169 if user and contest
170 170 user.contests.delete(contest)
171 171 end
172 172 redirect_to :action => 'list'
173 173 end
174 174
175 175 def contest_management
176 176 end
177 177
178 178 def manage_contest
179 179 contest = Contest.find(params[:contest][:id])
180 180 if !contest
181 181 flash[:notice] = 'You did not choose the contest.'
182 182 redirect_to :action => 'contest_management' and return
183 183 end
184 184
185 185 operation = params[:operation]
186 186
187 187 if not ['add','remove','assign'].include? operation
188 188 flash[:notice] = 'You did not choose the operation to perform.'
189 189 redirect_to :action => 'contest_management' and return
190 190 end
191 191
192 192 lines = params[:login_list]
193 193 if !lines or lines.blank?
194 194 flash[:notice] = 'You entered an empty list.'
195 195 redirect_to :action => 'contest_management' and return
196 196 end
197 197
198 198 note = []
199 - user_ids = {}
199 + users = []
200 200 lines.split("\n").each do |line|
201 201 user = User.find_by_login(line.chomp)
202 202 if user
203 203 if operation=='add'
204 204 if ! user.contests.include? contest
205 205 user.contests << contest
206 206 end
207 207 elsif operation=='remove'
208 208 user.contests.delete(contest)
209 209 else
210 210 user.contests = [contest]
211 211 end
212 212
213 - user.contest_stat.destroy if params[:reset_timer]
213 + if params[:reset_timer]
214 + user.contest_stat.forced_logout = true
215 + user.contest_stat.reset_timer_and_save
216 + end
214 217
215 218 note << user.login
216 - user_ids[user.id] = true
219 + users << user
217 220 end
218 221 end
219 222
220 223 if params[:reset_timer]
221 - logout_users(user_ids)
224 + logout_users(users)
222 225 end
223 226
224 227 flash[:notice] = 'User(s) ' + note.join(', ') +
225 228 ' were successfully modified. '
226 229 redirect_to :action => 'contest_management'
227 230 end
228 231
229 232 # admin management
230 233
231 234 def admin
232 235 @admins = User.find(:all).find_all {|user| user.admin? }
233 236 end
234 237
235 238 def grant_admin
236 239 login = params[:login]
237 240 user = User.find_by_login(login)
238 241 if user!=nil
239 242 admin_role = Role.find_by_name('admin')
240 243 user.roles << admin_role
241 244 else
242 245 flash[:notice] = 'Unknown user'
243 246 end
244 247 flash[:notice] = 'User added as admins'
245 248 redirect_to :action => 'admin'
246 249 end
247 250
248 251 def revoke_admin
249 252 user = User.find(params[:id])
250 253 if user==nil
251 254 flash[:notice] = 'Unknown user'
252 255 redirect_to :action => 'admin' and return
253 256 elsif user.login == 'root'
254 257 flash[:notice] = 'You cannot revoke admisnistrator permission from root.'
255 258 redirect_to :action => 'admin' and return
256 259 end
257 260
258 261 admin_role = Role.find_by_name('admin')
259 262 user.roles.delete(admin_role)
260 263 flash[:notice] = 'User permission revoked'
261 264 redirect_to :action => 'admin'
262 265 end
263 266
264 267 protected
265 268
266 269 def random_password(length=5)
267 270 chars = 'abcdefghijkmnopqrstuvwxyz23456789'
268 271 newpass = ""
269 272 length.times { newpass << chars[rand(chars.size-1)] }
270 273 return newpass
271 274 end
272 275
273 276 def import_from_file(f)
274 277 data_hash = YAML.load(f)
275 278 @import_log = ""
276 279
277 280 country_data = data_hash[:countries]
278 281 site_data = data_hash[:sites]
279 282 user_data = data_hash[:users]
280 283
281 284 # import country
282 285 countries = {}
283 286 country_data.each_pair do |id,country|
284 287 c = Country.find_by_name(country[:name])
285 288 if c!=nil
286 289 countries[id] = c
287 290 @import_log << "Found #{country[:name]}\n"
288 291 else
289 292 countries[id] = Country.new(:name => country[:name])
290 293 countries[id].save
291 294 @import_log << "Created #{country[:name]}\n"
292 295 end
293 296 end
294 297
295 298 # import sites
296 299 sites = {}
297 300 site_data.each_pair do |id,site|
298 301 s = Site.find_by_name(site[:name])
299 302 if s!=nil
300 303 @import_log << "Found #{site[:name]}\n"
301 304 else
302 305 s = Site.new(:name => site[:name])
303 306 @import_log << "Created #{site[:name]}\n"
304 307 end
305 308 s.password = site[:password]
306 309 s.country = countries[site[:country_id]]
307 310 s.save
308 311 sites[id] = s
309 312 end
310 313
311 314 # import users
312 315 user_data.each_pair do |id,user|
313 316 u = User.find_by_login(user[:login])
314 317 if u!=nil
315 318 @import_log << "Found #{user[:login]}\n"
316 319 else
317 320 u = User.new(:login => user[:login])
318 321 @import_log << "Created #{user[:login]}\n"
319 322 end
320 323 u.full_name = user[:name]
321 324 u.password = user[:password]
322 325 u.country = countries[user[:country_id]]
323 326 u.site = sites[user[:site_id]]
324 327 u.activated = true
325 328 u.email = "empty-#{u.login}@none.com"
326 329 if not u.save
327 330 @import_log << "Errors\n"
328 331 u.errors.each { |attr,msg| @import_log << "#{attr} - #{msg}\n" }
329 332 end
330 333 end
331 334
332 335 end
333 336
334 - def logout_users(user_ids)
335 - sessions = ActiveRecord::SessionStore::Session.find(:all, :conditions => ["updated_at >= ?", 60.minutes.ago])
336 - sessions.each do |session|
337 - if user_ids.has_key? session.data[:user_id]
338 - session.destroy
337 + def logout_users(users)
338 + users.each do |user|
339 + contest_stat = user.contest_stat(true)
340 + if contest_stat and !contest_stat.forced_logout
341 + contest_stat.forced_logout = true
342 + contest_stat.save
339 343 end
340 344 end
341 345 end
342 346
343 347 end
@@ -1,296 +1,296
1 1 require 'digest/sha1'
2 2
3 3 class User < ActiveRecord::Base
4 4
5 5 has_and_belongs_to_many :roles
6 6
7 7 has_many :test_requests, :order => "submitted_at DESC"
8 8
9 9 has_many :messages,
10 10 :class_name => "Message",
11 11 :foreign_key => "sender_id",
12 12 :order => 'created_at DESC'
13 13
14 14 has_many :replied_messages,
15 15 :class_name => "Message",
16 16 :foreign_key => "receiver_id",
17 17 :order => 'created_at DESC'
18 18
19 19 has_one :contest_stat, :class_name => "UserContestStat", :dependent => :destroy
20 20
21 21 belongs_to :site
22 22 belongs_to :country
23 23
24 24 has_and_belongs_to_many :contests, :uniq => true, :order => 'name'
25 25
26 26 named_scope :activated_users, :conditions => {:activated => true}
27 27
28 28 validates_presence_of :login
29 29 validates_uniqueness_of :login
30 30 validates_format_of :login, :with => /^[\_A-Za-z0-9]+$/
31 31 validates_length_of :login, :within => 3..30
32 32
33 33 validates_presence_of :full_name
34 34 validates_length_of :full_name, :minimum => 1
35 35
36 36 validates_presence_of :password, :if => :password_required?
37 37 validates_length_of :password, :within => 4..20, :if => :password_required?
38 38 validates_confirmation_of :password, :if => :password_required?
39 39
40 40 validates_format_of :email,
41 41 :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i,
42 42 :if => :email_validation?
43 43 validate :uniqueness_of_email_from_activated_users,
44 44 :if => :email_validation?
45 45 validate :enough_time_interval_between_same_email_registrations,
46 46 :if => :email_validation?
47 47
48 48 # these are for ytopc
49 49 # disable for now
50 50 #validates_presence_of :province
51 51
52 52 attr_accessor :password
53 53
54 54 before_save :encrypt_new_password
55 55 before_save :assign_default_site
56 56
57 57 def self.authenticate(login, password)
58 58 user = find_by_login(login)
59 59 return user if user && user.authenticated?(password)
60 60 end
61 61
62 62 def authenticated?(password)
63 63 if self.activated
64 64 hashed_password == User.encrypt(password,self.salt)
65 65 else
66 66 false
67 67 end
68 68 end
69 69
70 70 def admin?
71 71 self.roles.detect {|r| r.name == 'admin' }
72 72 end
73 73
74 74 def email_for_editing
75 75 if self.email==nil
76 76 "(unknown)"
77 77 elsif self.email==''
78 78 "(blank)"
79 79 else
80 80 self.email
81 81 end
82 82 end
83 83
84 84 def email_for_editing=(e)
85 85 self.email=e
86 86 end
87 87
88 88 def alias_for_editing
89 89 if self.alias==nil
90 90 "(unknown)"
91 91 elsif self.alias==''
92 92 "(blank)"
93 93 else
94 94 self.alias
95 95 end
96 96 end
97 97
98 98 def alias_for_editing=(e)
99 99 self.alias=e
100 100 end
101 101
102 102 def activation_key
103 103 if self.hashed_password==nil
104 104 encrypt_new_password
105 105 end
106 106 Digest::SHA1.hexdigest(self.hashed_password)[0..7]
107 107 end
108 108
109 109 def verify_activation_key(key)
110 110 key == activation_key
111 111 end
112 112
113 113 def self.random_password(length=5)
114 114 chars = 'abcdefghjkmnopqrstuvwxyz'
115 115 password = ''
116 116 length.times { password << chars[rand(chars.length - 1)] }
117 117 password
118 118 end
119 119
120 120 def self.find_non_admin_with_prefix(prefix='')
121 121 users = User.find(:all)
122 122 return users.find_all { |u| !(u.admin?) and u.login.index(prefix)==0 }
123 123 end
124 124
125 125 # Contest information
126 126
127 127 def contest_time_left
128 128 if Configuration.contest_mode?
129 129 return nil if site==nil
130 130 return site.time_left
131 131 elsif Configuration.indv_contest_mode?
132 132 time_limit = Configuration.contest_time_limit
133 133 if time_limit == nil
134 134 return nil
135 135 end
136 - if contest_stat==nil
136 + if contest_stat==nil or contest_stat.started_at==nil
137 137 return (Time.now.gmtime + time_limit) - Time.now.gmtime
138 138 else
139 139 finish_time = contest_stat.started_at + time_limit
140 140 current_time = Time.now.gmtime
141 141 if current_time > finish_time
142 142 return 0
143 143 else
144 144 return finish_time - current_time
145 145 end
146 146 end
147 147 else
148 148 return nil
149 149 end
150 150 end
151 151
152 152 def contest_finished?
153 153 if Configuration.contest_mode?
154 154 return false if site==nil
155 155 return site.finished?
156 156 elsif Configuration.indv_contest_mode?
157 157 return false if self.contest_stat(true)==nil
158 158 return contest_time_left == 0
159 159 else
160 160 return false
161 161 end
162 162 end
163 163
164 164 def contest_started?
165 165 if Configuration.contest_mode?
166 166 return true if site==nil
167 167 return site.started
168 168 else
169 169 return true
170 170 end
171 171 end
172 172
173 173 def update_start_time
174 174 stat = self.contest_stat
175 - if stat == nil
176 - stat = UserContestStat.new(:user => self,
177 - :started_at => Time.now.gmtime)
175 + if stat == nil or stat.started_at == nil
176 + stat ||= UserContestStat.new(:user => self)
177 + stat.started_at = Time.now.gmtime
178 178 stat.save
179 179 end
180 180 end
181 181
182 182 def problem_in_user_contests?(problem)
183 183 problem_contests = problem.contests.all
184 184
185 185 if problem_contests.length == 0 # this is public contest
186 186 return true
187 187 end
188 188
189 189 contests.each do |contest|
190 190 if problem_contests.find {|c| c.id == contest.id }
191 191 return true
192 192 end
193 193 end
194 194 return false
195 195 end
196 196
197 197 def available_problems_group_by_contests
198 198 contest_problems = []
199 199 pin = {}
200 200 contests.enabled.each do |contest|
201 201 available_problems = contest.problems.available
202 202 contest_problems << {
203 203 :contest => contest,
204 204 :problems => available_problems
205 205 }
206 206 available_problems.each {|p| pin[p.id] = true}
207 207 end
208 208 other_avaiable_problems = Problem.available.find_all {|p| pin[p.id]==nil and p.contests.length==0}
209 209 contest_problems << {
210 210 :contest => nil,
211 211 :problems => other_avaiable_problems
212 212 }
213 213 return contest_problems
214 214 end
215 215
216 216 def available_problems
217 217 if not Configuration.multicontests?
218 218 return Problem.find_available_problems
219 219 else
220 220 contest_problems = []
221 221 pin = {}
222 222 contests.enabled.each do |contest|
223 223 contest.problems.available.each do |problem|
224 224 if not pin.has_key? problem.id
225 225 contest_problems << problem
226 226 end
227 227 pin[problem.id] = true
228 228 end
229 229 end
230 230 other_avaiable_problems = Problem.available.find_all {|p| pin[p.id]==nil and p.contests.length==0}
231 231 return contest_problems + other_avaiable_problems
232 232 end
233 233 end
234 234
235 235 def can_view_problem?(problem)
236 236 if not Configuration.multicontests?
237 237 return problem.available
238 238 else
239 239 return problem_in_user_contests? problem
240 240 end
241 241 end
242 242
243 243 protected
244 244 def encrypt_new_password
245 245 return if password.blank?
246 246 self.salt = (10+rand(90)).to_s
247 247 self.hashed_password = User.encrypt(self.password,self.salt)
248 248 end
249 249
250 250 def assign_default_site
251 251 # have to catch error when migrating (because self.site is not available).
252 252 begin
253 253 if self.site==nil
254 254 self.site = Site.find_by_name('default')
255 255 if self.site==nil
256 256 self.site = Site.find(1) # when 'default has be renamed'
257 257 end
258 258 end
259 259 rescue
260 260 end
261 261 end
262 262
263 263 def password_required?
264 264 self.hashed_password.blank? || !self.password.blank?
265 265 end
266 266
267 267 def self.encrypt(string,salt)
268 268 Digest::SHA1.hexdigest(salt + string)
269 269 end
270 270
271 271 def uniqueness_of_email_from_activated_users
272 272 user = User.activated_users.find_by_email(self.email)
273 273 if user and (user.login != self.login)
274 274 self.errors.add_to_base("Email has already been taken")
275 275 end
276 276 end
277 277
278 278 def enough_time_interval_between_same_email_registrations
279 279 return if !self.new_record?
280 280 return if self.activated
281 281 open_user = User.find_by_email(self.email,
282 282 :order => 'created_at DESC')
283 283 if open_user and open_user.created_at and
284 284 (open_user.created_at > Time.now.gmtime - 5.minutes)
285 285 self.errors.add_to_base("There are already unactivated registrations with this e-mail address (please wait for 5 minutes)")
286 286 end
287 287 end
288 288
289 289 def email_validation?
290 290 begin
291 291 return VALIDATE_USER_EMAILS
292 292 rescue
293 293 return false
294 294 end
295 295 end
296 296 end
@@ -1,5 +1,10
1 1 class UserContestStat < ActiveRecord::Base
2 2
3 3 belongs_to :user
4 4
5 + def reset_timer_and_save
6 + self.started_at = nil
7 + save
5 8 end
9 +
10 + end
@@ -1,231 +1,232
1 1 # This file is auto-generated from the current state of the database. Instead of editing this file,
2 2 # please use the migrations feature of Active Record to incrementally modify your database, and
3 3 # then regenerate this schema definition.
4 4 #
5 5 # Note that this schema.rb definition is the authoritative source for your database schema. If you need
6 6 # to create the application database on another system, you should be using db:schema:load, not running
7 7 # all the migrations from scratch. The latter is a flawed and unsustainable approach (the more migrations
8 8 # you'll amass, the slower it'll run and the greater likelihood for issues).
9 9 #
10 10 # It's strongly recommended to check this file into your version control system.
11 11
12 - ActiveRecord::Schema.define(:version => 20100303095700) do
12 + ActiveRecord::Schema.define(:version => 20100328123325) do
13 13
14 14 create_table "announcements", :force => true do |t|
15 15 t.string "author"
16 16 t.text "body"
17 17 t.boolean "published"
18 18 t.datetime "created_at"
19 19 t.datetime "updated_at"
20 20 t.boolean "frontpage", :default => false
21 21 t.boolean "contest_only", :default => false
22 22 t.string "title"
23 23 end
24 24
25 25 create_table "configurations", :force => true do |t|
26 26 t.string "key"
27 27 t.string "value_type"
28 28 t.string "value"
29 29 t.datetime "created_at"
30 30 t.datetime "updated_at"
31 31 t.text "description"
32 32 end
33 33
34 34 create_table "contests", :force => true do |t|
35 35 t.string "title"
36 36 t.boolean "enabled"
37 37 t.datetime "created_at"
38 38 t.datetime "updated_at"
39 39 t.string "name"
40 40 end
41 41
42 42 create_table "contests_problems", :id => false, :force => true do |t|
43 43 t.integer "contest_id"
44 44 t.integer "problem_id"
45 45 end
46 46
47 47 create_table "contests_users", :id => false, :force => true do |t|
48 48 t.integer "contest_id"
49 49 t.integer "user_id"
50 50 end
51 51
52 52 create_table "countries", :force => true do |t|
53 53 t.string "name"
54 54 t.datetime "created_at"
55 55 t.datetime "updated_at"
56 56 end
57 57
58 58 create_table "descriptions", :force => true do |t|
59 59 t.text "body"
60 60 t.boolean "markdowned"
61 61 t.datetime "created_at"
62 62 t.datetime "updated_at"
63 63 end
64 64
65 65 create_table "grader_processes", :force => true do |t|
66 66 t.string "host", :limit => 20
67 67 t.integer "pid"
68 68 t.string "mode"
69 69 t.boolean "active"
70 70 t.datetime "created_at"
71 71 t.datetime "updated_at"
72 72 t.integer "task_id"
73 73 t.string "task_type"
74 74 t.boolean "terminated"
75 75 end
76 76
77 77 add_index "grader_processes", ["host", "pid"], :name => "index_grader_processes_on_ip_and_pid"
78 78
79 79 create_table "languages", :force => true do |t|
80 80 t.string "name", :limit => 10
81 81 t.string "pretty_name"
82 82 t.string "ext", :limit => 10
83 83 t.string "common_ext"
84 84 end
85 85
86 86 create_table "messages", :force => true do |t|
87 87 t.integer "sender_id"
88 88 t.integer "receiver_id"
89 89 t.integer "replying_message_id"
90 90 t.text "body"
91 91 t.boolean "replied"
92 92 t.datetime "created_at"
93 93 t.datetime "updated_at"
94 94 end
95 95
96 96 create_table "problems", :force => true do |t|
97 97 t.string "name", :limit => 30
98 98 t.string "full_name"
99 99 t.integer "full_score"
100 100 t.date "date_added"
101 101 t.boolean "available"
102 102 t.string "url"
103 103 t.integer "description_id"
104 104 t.boolean "test_allowed"
105 105 t.boolean "output_only"
106 106 t.string "description_filename"
107 107 end
108 108
109 109 create_table "rights", :force => true do |t|
110 110 t.string "name"
111 111 t.string "controller"
112 112 t.string "action"
113 113 end
114 114
115 115 create_table "rights_roles", :id => false, :force => true do |t|
116 116 t.integer "right_id"
117 117 t.integer "role_id"
118 118 end
119 119
120 120 add_index "rights_roles", ["role_id"], :name => "index_rights_roles_on_role_id"
121 121
122 122 create_table "roles", :force => true do |t|
123 123 t.string "name"
124 124 end
125 125
126 126 create_table "roles_users", :id => false, :force => true do |t|
127 127 t.integer "role_id"
128 128 t.integer "user_id"
129 129 end
130 130
131 131 add_index "roles_users", ["user_id"], :name => "index_roles_users_on_user_id"
132 132
133 133 create_table "sessions", :force => true do |t|
134 134 t.string "session_id"
135 135 t.text "data"
136 136 t.datetime "updated_at"
137 137 end
138 138
139 139 add_index "sessions", ["session_id"], :name => "index_sessions_on_session_id"
140 140 add_index "sessions", ["updated_at"], :name => "index_sessions_on_updated_at"
141 141
142 142 create_table "sites", :force => true do |t|
143 143 t.string "name"
144 144 t.boolean "started"
145 145 t.datetime "start_time"
146 146 t.datetime "created_at"
147 147 t.datetime "updated_at"
148 148 t.integer "country_id"
149 149 t.string "password"
150 150 end
151 151
152 152 create_table "submissions", :force => true do |t|
153 153 t.integer "user_id"
154 154 t.integer "problem_id"
155 155 t.integer "language_id"
156 156 t.text "source"
157 157 t.binary "binary"
158 158 t.datetime "submitted_at"
159 159 t.datetime "compiled_at"
160 160 t.text "compiler_message"
161 161 t.datetime "graded_at"
162 162 t.integer "points"
163 163 t.text "grader_comment"
164 164 t.integer "number"
165 165 t.string "source_filename"
166 166 end
167 167
168 168 add_index "submissions", ["user_id", "problem_id", "number"], :name => "index_submissions_on_user_id_and_problem_id_and_number", :unique => true
169 169 add_index "submissions", ["user_id", "problem_id"], :name => "index_submissions_on_user_id_and_problem_id"
170 170
171 171 create_table "tasks", :force => true do |t|
172 172 t.integer "submission_id"
173 173 t.datetime "created_at"
174 174 t.integer "status"
175 175 t.datetime "updated_at"
176 176 end
177 177
178 178 create_table "test_pairs", :force => true do |t|
179 179 t.integer "problem_id"
180 180 t.text "input", :limit => 16777215
181 181 t.text "solution", :limit => 16777215
182 182 t.datetime "created_at"
183 183 t.datetime "updated_at"
184 184 end
185 185
186 186 create_table "test_requests", :force => true do |t|
187 187 t.integer "user_id"
188 188 t.integer "problem_id"
189 189 t.integer "submission_id"
190 190 t.string "input_file_name"
191 191 t.string "output_file_name"
192 192 t.string "running_stat"
193 193 t.integer "status"
194 194 t.datetime "updated_at"
195 195 t.datetime "submitted_at"
196 196 t.datetime "compiled_at"
197 197 t.text "compiler_message"
198 198 t.datetime "graded_at"
199 199 t.string "grader_comment"
200 200 t.datetime "created_at"
201 201 t.float "running_time"
202 202 t.string "exit_status"
203 203 t.integer "memory_usage"
204 204 end
205 205
206 206 add_index "test_requests", ["user_id", "problem_id"], :name => "index_test_requests_on_user_id_and_problem_id"
207 207
208 208 create_table "user_contest_stats", :force => true do |t|
209 209 t.integer "user_id"
210 210 t.datetime "started_at"
211 211 t.datetime "created_at"
212 212 t.datetime "updated_at"
213 + t.boolean "forced_logout"
213 214 end
214 215
215 216 create_table "users", :force => true do |t|
216 217 t.string "login", :limit => 50
217 218 t.string "full_name"
218 219 t.string "hashed_password"
219 220 t.string "salt", :limit => 5
220 221 t.string "alias"
221 222 t.string "email"
222 223 t.integer "site_id"
223 224 t.integer "country_id"
224 225 t.boolean "activated", :default => false
225 226 t.datetime "created_at"
226 227 t.datetime "updated_at"
227 228 end
228 229
229 230 add_index "users", ["login"], :name => "index_users_on_login", :unique => true
230 231
231 232 end
@@ -1,40 +1,41
1 1
2 2 var Announcement = {
3 3
4 4 mostRecentId: 0,
5 5
6 6 refreshUrl: '/main/announcements',
7 7
8 8 setMostRecentId: function(id) {
9 9 Announcement.mostRecentId = id;
10 10 },
11 11
12 12 updateRecentId: function(id) {
13 13 if(Announcement.mostRecentId < id)
14 14 Announcement.mostRecentId = id;
15 15 },
16 16
17 17 refreshAnnouncement: function() {
18 18 var url = Announcement.refreshUrl;
19 19 new Ajax.Request(url, {
20 20 method: 'get',
21 21 parameters: { recent: Announcement.mostRecentId },
22 22 onSuccess: function(transport) {
23 - if(transport.responseText.match(/\S/)!=null) {
23 + if((transport.status == 200) &&
24 + (transport.responseText.match(/\S/)!=null)) {
24 25 var announcementBody = $("announcementbox-body");
25 26 announcementBody.insert({ top: transport.responseText });
26 27 var announcementBoxes = $$(".announcementbox");
27 28 if(announcementBoxes.length!=0)
28 29 announcementBoxes[0].show();
30 + Announcement.registerRefreshEventTimer();
29 31 }
30 32 }
31 33 });
32 - Announcement.registerRefreshEventTimer();
33 34 },
34 35
35 36 registerRefreshEventTimer: function() {
36 37 setTimeout(function () {
37 38 Announcement.refreshAnnouncement();
38 39 }, 30000);
39 40 }
40 41 };
@@ -1,87 +1,117
1 1 require 'spec_helper'
2 2 require 'config_spec_helper'
3 3 require 'delorean'
4 4
5 5 describe "ContestManagements" do
6 6 include ConfigSpecHelperMethods
7 7
8 8 fixtures :users
9 9 fixtures :problems
10 10 fixtures :contests
11 11 fixtures :roles
12 12
13 13 before(:each) do
14 14 @admin_user = users(:mary)
15 15 @contest_b = contests(:contest_b)
16 16 @james = users(:james)
17 17 @jack = users(:jack)
18 18
19 19 set_contest_time_limit('3:00')
20 20 set_indv_contest_mode
21 + enable_multicontest
21 22 end
22 23
23 24 it "should reset users' timer when their contests change" do
24 25 james_session = open_session
25 26 james_session.extend(MainSessionMethods)
26 27
27 28 james_login_and_get_main_list(james_session)
28 29 james_session.response.should_not have_text(/OVER/)
29 30
30 31 Delorean.time_travel_to(190.minutes.since) do
31 32 james_session.get_main_list
32 33 james_session.response.should have_text(/OVER/)
33 34
34 35 james_session.get '/' # logout
35 36 james_session.get '/main/list' # clearly log out
36 37 james_session.response.should_not render_template 'main/list'
37 38
38 39 admin_change_users_contest_to("james", @contest_b, true)
39 40
40 41 james_login_and_get_main_list(james_session)
41 42 james_session.response.should_not have_text(/OVER/)
42 43 end
43 44 end
44 45
46 + it "should force users to log out when their contests change" do
47 + james_session = open_session
48 + james_session.extend(MainSessionMethods)
49 +
50 + james_login_and_get_main_list(james_session)
51 + james_session.response.should_not have_text(/OVER/)
52 +
53 + Delorean.time_travel_to(190.minutes.since) do
54 + james_session.get_main_list
55 + james_session.response.should have_text(/OVER/)
56 +
57 + admin_change_users_contest_to("james", @contest_b, true)
58 +
59 + james_session.get '/main/list'
60 + james_session.response.should_not render_template 'main/list'
61 + james_session.should be_redirect
62 +
63 + Delorean.time_travel_to(200.minutes.since) do
64 + james_login_and_get_main_list(james_session)
65 + james_session.response.should_not have_text(/OVER/)
66 + end
67 + end
68 + end
69 +
45 70 private
46 71
47 72 module MainSessionMethods
48 73 def login(login_name, password)
49 74 post '/login/login', :login => login_name, :password => password
50 75 assert_redirected_to '/main/list'
51 76 end
52 77
53 78 def get_main_list
54 79 get '/main/list'
55 80 assert_template 'main/list'
56 81 end
82 +
83 + def get_main_list_and_assert_logout
84 + get '/main/list'
85 + assert_redirected_to '/main'
86 + end
57 87 end
58 88
59 89 module ContestManagementSessionMethods
60 90 def change_users_contest_to(user_login_list, contest, reset_timer=false)
61 91 post_data = {
62 92 :contest => {:id => contest.id},
63 93 :operation => 'assign',
64 94 :login_list => user_login_list
65 95 }
66 96 post_data[:reset_timer] = true if reset_timer
67 97 post '/user_admin/manage_contest', post_data
68 98 end
69 99 end
70 100
71 101 def admin_change_users_contest_to(user_list, contest, reset_timer)
72 102 admin_session = open_session
73 103 admin_session.extend(MainSessionMethods)
74 104 admin_session.extend(ContestManagementSessionMethods)
75 105
76 106 admin_session.login('mary','goodbye')
77 107 admin_session.get '/main/list'
78 108 admin_session.change_users_contest_to(user_list, contest, reset_timer)
79 109 end
80 110
81 111 def james_login_and_get_main_list(session)
82 112 session.login('james', 'morning')
83 113 session.get_main_list
84 114 end
85 115
86 116 end
87 117
You need to be logged in to leave comments. Login now