Description:
merge with algo-bm, take cheat report
Commit status:
[Not Reviewed]
References:
merge java
Comments:
0 Commit comments 0 Inline Comments
Unresolved TODOs:
There are no unresolved TODOs
Add another comment

r526:f7b4a30e2f5d - - 12 files changed: 252 inserted, 6 deleted

@@ -0,0 +1,77
1 + - content_for :header do
2 + = stylesheet_link_tag 'tablesorter-theme.cafe'
3 + = javascript_include_tag 'local_jquery'
4 +
5 + %script{:type=>"text/javascript"}
6 + $(function () {
7 + $('#since_datetime').datetimepicker({ showButtonPanel: true, dateFormat: "yy-mm-dd", controlType: "slider"} );
8 + $('#until_datetime').datetimepicker({ showButtonPanel: true, dateFormat: "yy-mm-dd", controlType: "slider"} );
9 + $('#my_table').tablesorter({widthFixed: true, widgets: ['zebra']});
10 + $('#my_table2').tablesorter({widthFixed: true, widgets: ['zebra']});
11 + $('#sub_table').tablesorter({widthFixed: true, widgets: ['zebra']});
12 + });
13 +
14 + %h1 Login status
15 +
16 + =render partial: 'report_menu'
17 + =render partial: 'date_range', locals: {param_text: 'Login date range:', title: 'Query login stat in the range' }
18 +
19 + %h2 Suspect
20 +
21 + %table.tablesorter-cafe#my_table
22 + %thead
23 + %tr
24 + %th login
25 + %th full name
26 + %th login count
27 + %tbody
28 + - @ml.each do |l|
29 + %tr{class: cycle('info-even','info-odd')}
30 + %td= link_to l[:login], controller: 'users', action: 'profile', id: l[:id]
31 + %td= l[:full_name]
32 + %td= l[:count]
33 +
34 +
35 + %h2 Multiple Logins Report
36 + This section reports all logins record that have either multiple ip per login or multiple login per ip.
37 +
38 + %table.tablesorter-cafe#my_table2
39 + %thead
40 + %tr
41 + %th login
42 + %th full name
43 + %th IP
44 + %th time
45 + %tbody
46 + - @mld.each do |l|
47 + %tr{class: cycle('info-even','info-odd')}
48 + %td= link_to l.user[:login], controller: 'users', action: 'profile', id: l[:user_id]
49 + %td= l.user[:full_name]
50 + %td= l[:ip_address]
51 + %td= l[:created_at]
52 +
53 + %h2 Multiple IP Submissions Report
54 + This section reports all submission records that have USER_ID matchs ID that logins on multiple IP
55 + and that have IP_ADDRESS that has multiple ID logins
56 +
57 + Be noted that when submission IP address is not available, this might exclude
58 + submissions that come from ID that login on multiple-login IP
59 +
60 + %table.tablesorter-cafe#sub_table
61 + %thead
62 + %tr
63 + %th login
64 + %th full name
65 + %th IP
66 + %th problem
67 + %th Submission
68 + %th time
69 + %tbody
70 + - @subs.each do |s|
71 + %tr{class: cycle('info-even','info-odd')}
72 + %td= link_to s.user[:login], controller: 'users', action: 'profile', id: s[:user_id]
73 + %td= s.user[:full_name]
74 + %td= s[:ip_address]
75 + %td= s.problem.name
76 + %td= link_to(s.id, controller: 'graders' , action: 'submission', id: s.id)
77 + %td= s[:submitted_at]
@@ -0,0 +1,9
1 + class ChangeUseridOnLogin < ActiveRecord::Migration
2 + def up
3 + change_column :logins, :user_id, :integer
4 + end
5 +
6 + def down
7 + change_column :logins, :user_id, :string
8 + end
9 + end
@@ -1,92 +1,110
1 1 class ApplicationController < ActionController::Base
2 2 protect_from_forgery
3 3
4 4 SINGLE_USER_MODE_CONF_KEY = 'system.single_user_mode'
5 + MULTIPLE_IP_LOGIN_CONF_KEY = 'right.multiple_ip_login'
5 6
6 7 def admin_authorization
7 8 return false unless authenticate
8 9 user = User.find(session[:user_id], :include => ['roles'])
9 10 unless user.admin?
10 11 flash[:notice] = 'You are not authorized to view the page you requested'
11 12 redirect_to :controller => 'main', :action => 'login' unless user.admin?
12 13 return false
13 14 end
14 15 return true
15 16 end
16 17
17 18 def authorization_by_roles(allowed_roles)
18 19 return false unless authenticate
19 20 user = User.find(session[:user_id])
20 21 unless user.roles.detect { |role| allowed_roles.member?(role.name) }
21 22 flash[:notice] = 'You are not authorized to view the page you requested'
22 23 redirect_to :controller => 'main', :action => 'login'
23 24 return false
24 25 end
25 26 end
26 27
27 28 protected
28 29
29 30 def authenticate
30 31 unless session[:user_id]
31 32 flash[:notice] = 'You need to login'
32 33 if GraderConfiguration[SINGLE_USER_MODE_CONF_KEY]
33 34 flash[:notice] = 'You need to login but you cannot log in at this time'
34 35 end
35 36 redirect_to :controller => 'main', :action => 'login'
36 37 return false
37 38 end
38 39
39 40 # check if run in single user mode
40 41 if GraderConfiguration[SINGLE_USER_MODE_CONF_KEY]
41 42 user = User.find(session[:user_id])
42 43 if user==nil or (not user.admin?)
43 44 flash[:notice] = 'You cannot log in at this time'
44 45 redirect_to :controller => 'main', :action => 'login'
45 46 return false
46 47 end
47 48 return true
48 49 end
49 50
50 51 if GraderConfiguration.multicontests?
51 52 user = User.find(session[:user_id])
52 53 return true if user.admin?
53 54 begin
54 55 if user.contest_stat(true).forced_logout
55 56 flash[:notice] = 'You have been automatically logged out.'
56 57 redirect_to :controller => 'main', :action => 'index'
57 58 end
58 59 rescue
59 60 end
60 61 end
61 62 return true
62 63 end
63 64
65 + def authenticate_by_ip_address
66 + #this assume that we have already authenticate normally
67 + unless GraderConfiguration[MULTIPLE_IP_LOGIN_CONF_KEY]
68 + user = User.find(session[:user_id])
69 + if (not user.admin? and user.last_ip and user.last_ip != request.remote_ip)
70 + flash[:notice] = "You cannot use the system from #{request.remote_ip}. Your last ip is #{user.last_ip}"
71 + redirect_to :controller => 'main', :action => 'login'
72 + return false
73 + end
74 + unless user.last_ip
75 + user.last_ip = request.remote_ip
76 + user.save
77 + end
78 + end
79 + return true
80 + end
81 +
64 82 def authorization
65 83 return false unless authenticate
66 84 user = User.find(session[:user_id])
67 85 unless user.roles.detect { |role|
68 86 role.rights.detect{ |right|
69 87 right.controller == self.class.controller_name and
70 88 (right.action == 'all' or right.action == action_name)
71 89 }
72 90 }
73 91 flash[:notice] = 'You are not authorized to view the page you requested'
74 92 #request.env['HTTP_REFERER'] ? (redirect_to :back) : (redirect_to :controller => 'login')
75 93 redirect_to :controller => 'main', :action => 'login'
76 94 return false
77 95 end
78 96 end
79 97
80 98 def verify_time_limit
81 99 return true if session[:user_id]==nil
82 100 user = User.find(session[:user_id], :include => :site)
83 101 return true if user==nil or user.site == nil
84 102 if user.contest_finished?
85 103 flash[:notice] = 'Error: the contest you are participating is over.'
86 104 redirect_to :back
87 105 return false
88 106 end
89 107 return true
90 108 end
91 109
92 110 end
@@ -1,28 +1,29
1 1 class ConfigurationsController < ApplicationController
2 2
3 3 before_filter :authenticate
4 4 before_filter { |controller| controller.authorization_by_roles(['admin'])}
5 5
6 6
7 7 def index
8 8 @configurations = GraderConfiguration.find(:all,
9 9 :order => '`key`')
10 10 end
11 11
12 12 def reload
13 13 GraderConfiguration.reload
14 14 redirect_to :action => 'index'
15 15 end
16 16
17 17 def update
18 18 @config = GraderConfiguration.find(params[:id])
19 + User.clear_last_login if @config.key = 'multiple_ip_login' and @config.value == 'true' and params[:grader_configuration][:value] == 'false'
19 20 respond_to do |format|
20 21 if @config.update_attributes(params[:grader_configuration])
21 22 format.json { head :ok }
22 23 else
23 24 format.json { respond_with_bip(@config) }
24 25 end
25 26 end
26 27 end
27 28
28 29 end
@@ -1,383 +1,385
1 1 class MainController < ApplicationController
2 2
3 3 before_filter :authenticate, :except => [:index, :login]
4 4 before_filter :check_viewability, :except => [:index, :login]
5 5
6 6 append_before_filter :confirm_and_update_start_time,
7 7 :except => [:index,
8 8 :login,
9 9 :confirm_contest_start]
10 10
11 11 # to prevent log in box to be shown when user logged out of the
12 12 # system only in some tab
13 13 prepend_before_filter :reject_announcement_refresh_when_logged_out,
14 14 :only => [:announcements]
15 15
16 + before_filter :authenticate_by_ip_address, :only => [:list]
17 +
16 18 # COMMENTED OUT: filter in each action instead
17 19 # before_filter :verify_time_limit, :only => [:submit]
18 20
19 21 verify :method => :post, :only => [:submit],
20 22 :redirect_to => { :action => :index }
21 23
22 24 # COMMENT OUT: only need when having high load
23 25 # caches_action :index, :login
24 26
25 27 # NOTE: This method is not actually needed, 'config/routes.rb' has
26 28 # assigned action login as a default action.
27 29 def index
28 30 redirect_to :action => 'login'
29 31 end
30 32
31 33 def login
32 34 saved_notice = flash[:notice]
33 35 reset_session
34 36 flash.now[:notice] = saved_notice
35 37
36 38 # EXPERIMENT:
37 39 # Hide login if in single user mode and the url does not
38 40 # explicitly specify /login
39 41 #
40 42 # logger.info "PATH: #{request.path}"
41 43 # if GraderConfiguration['system.single_user_mode'] and
42 44 # request.path!='/main/login'
43 45 # @hidelogin = true
44 46 # end
45 47
46 48 @announcements = Announcement.find_for_frontpage
47 49 render :action => 'login', :layout => 'empty'
48 50 end
49 51
50 52 def list
51 53 prepare_list_information
52 54 end
53 55
54 56 def help
55 57 @user = User.find(session[:user_id])
56 58 end
57 59
58 60 def submit
59 61 user = User.find(session[:user_id])
60 62
61 63 @submission = Submission.new
62 64 @submission.problem_id = params[:submission][:problem_id]
63 65 @submission.user = user
64 66 @submission.language_id = 0
65 67 if (params['file']) and (params['file']!='')
66 68 @submission.source = File.open(params['file'].path,'r:UTF-8',&:read)
67 69 @submission.source.encode!('UTF-8','UTF-8',invalid: :replace, replace: '')
68 70 @submission.source_filename = params['file'].original_filename
69 71 end
70 72 @submission.submitted_at = Time.new.gmtime
71 73 @submission.ip_address = request.remote_ip
72 74
73 75 if GraderConfiguration.time_limit_mode? and user.contest_finished?
74 76 @submission.errors.add(:base,"The contest is over.")
75 77 prepare_list_information
76 78 render :action => 'list' and return
77 79 end
78 80
79 81 if @submission.valid?
80 82 if @submission.save == false
81 83 flash[:notice] = 'Error saving your submission'
82 84 elsif Task.create(:submission_id => @submission.id,
83 85 :status => Task::STATUS_INQUEUE) == false
84 86 flash[:notice] = 'Error adding your submission to task queue'
85 87 end
86 88 else
87 89 prepare_list_information
88 90 render :action => 'list' and return
89 91 end
90 92 redirect_to :action => 'list'
91 93 end
92 94
93 95 def source
94 96 submission = Submission.find(params[:id])
95 97 if ((submission.user_id == session[:user_id]) and
96 98 (submission.problem != nil) and
97 99 (submission.problem.available))
98 100 send_data(submission.source,
99 101 {:filename => submission.download_filename,
100 102 :type => 'text/plain'})
101 103 else
102 104 flash[:notice] = 'Error viewing source'
103 105 redirect_to :action => 'list'
104 106 end
105 107 end
106 108
107 109 def compiler_msg
108 110 @submission = Submission.find(params[:id])
109 111 if @submission.user_id == session[:user_id]
110 112 render :action => 'compiler_msg', :layout => 'empty'
111 113 else
112 114 flash[:notice] = 'Error viewing source'
113 115 redirect_to :action => 'list'
114 116 end
115 117 end
116 118
117 119 def submission
118 120 @user = User.find(session[:user_id])
119 121 @problems = @user.available_problems
120 122 if params[:id]==nil
121 123 @problem = nil
122 124 @submissions = nil
123 125 else
124 126 @problem = Problem.find_by_name(params[:id])
125 127 if not @problem.available
126 128 redirect_to :action => 'list'
127 129 flash[:notice] = 'Error: submissions for that problem are not viewable.'
128 130 return
129 131 end
130 132 @submissions = Submission.find_all_by_user_problem(@user.id, @problem.id)
131 133 end
132 134 end
133 135
134 136 def result
135 137 if !GraderConfiguration.show_grading_result
136 138 redirect_to :action => 'list' and return
137 139 end
138 140 @user = User.find(session[:user_id])
139 141 @submission = Submission.find(params[:id])
140 142 if @submission.user!=@user
141 143 flash[:notice] = 'You are not allowed to view result of other users.'
142 144 redirect_to :action => 'list' and return
143 145 end
144 146 prepare_grading_result(@submission)
145 147 end
146 148
147 149 def load_output
148 150 if !GraderConfiguration.show_grading_result or params[:num]==nil
149 151 redirect_to :action => 'list' and return
150 152 end
151 153 @user = User.find(session[:user_id])
152 154 @submission = Submission.find(params[:id])
153 155 if @submission.user!=@user
154 156 flash[:notice] = 'You are not allowed to view result of other users.'
155 157 redirect_to :action => 'list' and return
156 158 end
157 159 case_num = params[:num].to_i
158 160 out_filename = output_filename(@user.login,
159 161 @submission.problem.name,
160 162 @submission.id,
161 163 case_num)
162 164 if !FileTest.exists?(out_filename)
163 165 flash[:notice] = 'Output not found.'
164 166 redirect_to :action => 'list' and return
165 167 end
166 168
167 169 if defined?(USE_APACHE_XSENDFILE) and USE_APACHE_XSENDFILE
168 170 response.headers['Content-Type'] = "application/force-download"
169 171 response.headers['Content-Disposition'] = "attachment; filename=\"output-#{case_num}.txt\""
170 172 response.headers["X-Sendfile"] = out_filename
171 173 response.headers['Content-length'] = File.size(out_filename)
172 174 render :nothing => true
173 175 else
174 176 send_file out_filename, :stream => false, :filename => "output-#{case_num}.txt", :type => "text/plain"
175 177 end
176 178 end
177 179
178 180 def error
179 181 @user = User.find(session[:user_id])
180 182 end
181 183
182 184 # announcement refreshing and hiding methods
183 185
184 186 def announcements
185 187 if params.has_key? 'recent'
186 188 prepare_announcements(params[:recent])
187 189 else
188 190 prepare_announcements
189 191 end
190 192 render(:partial => 'announcement',
191 193 :collection => @announcements,
192 194 :locals => {:announcement_effect => true})
193 195 end
194 196
195 197 def confirm_contest_start
196 198 user = User.find(session[:user_id])
197 199 if request.method == 'POST'
198 200 user.update_start_time
199 201 redirect_to :action => 'list'
200 202 else
201 203 @contests = user.contests
202 204 @user = user
203 205 end
204 206 end
205 207
206 208 protected
207 209
208 210 def prepare_announcements(recent=nil)
209 211 if GraderConfiguration.show_tasks_to?(@user)
210 212 @announcements = Announcement.find_published(true)
211 213 else
212 214 @announcements = Announcement.find_published
213 215 end
214 216 if recent!=nil
215 217 recent_id = recent.to_i
216 218 @announcements = @announcements.find_all { |a| a.id > recent_id }
217 219 end
218 220 end
219 221
220 222 def prepare_list_information
221 223 @user = User.find(session[:user_id])
222 224 if not GraderConfiguration.multicontests?
223 225 @problems = @user.available_problems
224 226 else
225 227 @contest_problems = @user.available_problems_group_by_contests
226 228 @problems = @user.available_problems
227 229 end
228 230 @prob_submissions = {}
229 231 @problems.each do |p|
230 232 sub = Submission.find_last_by_user_and_problem(@user.id,p.id)
231 233 if sub!=nil
232 234 @prob_submissions[p.id] = { :count => sub.number, :submission => sub }
233 235 else
234 236 @prob_submissions[p.id] = { :count => 0, :submission => nil }
235 237 end
236 238 end
237 239 prepare_announcements
238 240 end
239 241
240 242 def check_viewability
241 243 @user = User.find(session[:user_id])
242 244 if (!GraderConfiguration.show_tasks_to?(@user)) and
243 245 ((action_name=='submission') or (action_name=='submit'))
244 246 redirect_to :action => 'list' and return
245 247 end
246 248 end
247 249
248 250 def prepare_grading_result(submission)
249 251 if GraderConfiguration.task_grading_info.has_key? submission.problem.name
250 252 grading_info = GraderConfiguration.task_grading_info[submission.problem.name]
251 253 else
252 254 # guess task info from problem.full_score
253 255 cases = submission.problem.full_score / 10
254 256 grading_info = {
255 257 'testruns' => cases,
256 258 'testcases' => cases
257 259 }
258 260 end
259 261 @test_runs = []
260 262 if grading_info['testruns'].is_a? Integer
261 263 trun_count = grading_info['testruns']
262 264 trun_count.times do |i|
263 265 @test_runs << [ read_grading_result(@user.login,
264 266 submission.problem.name,
265 267 submission.id,
266 268 i+1) ]
267 269 end
268 270 else
269 271 grading_info['testruns'].keys.sort.each do |num|
270 272 run = []
271 273 testrun = grading_info['testruns'][num]
272 274 testrun.each do |c|
273 275 run << read_grading_result(@user.login,
274 276 submission.problem.name,
275 277 submission.id,
276 278 c)
277 279 end
278 280 @test_runs << run
279 281 end
280 282 end
281 283 end
282 284
283 285 def grading_result_dir(user_name, problem_name, submission_id, case_num)
284 286 return "#{GRADING_RESULT_DIR}/#{user_name}/#{problem_name}/#{submission_id}/test-result/#{case_num}"
285 287 end
286 288
287 289 def output_filename(user_name, problem_name, submission_id, case_num)
288 290 dir = grading_result_dir(user_name,problem_name, submission_id, case_num)
289 291 return "#{dir}/output.txt"
290 292 end
291 293
292 294 def read_grading_result(user_name, problem_name, submission_id, case_num)
293 295 dir = grading_result_dir(user_name,problem_name, submission_id, case_num)
294 296 result_file_name = "#{dir}/result"
295 297 if !FileTest.exists?(result_file_name)
296 298 return {:num => case_num, :msg => 'program did not run'}
297 299 else
298 300 results = File.open(result_file_name).readlines
299 301 run_stat = extract_running_stat(results)
300 302 output_filename = "#{dir}/output.txt"
301 303 if FileTest.exists?(output_filename)
302 304 output_file = true
303 305 output_size = File.size(output_filename)
304 306 else
305 307 output_file = false
306 308 output_size = 0
307 309 end
308 310
309 311 return {
310 312 :num => case_num,
311 313 :msg => results[0],
312 314 :run_stat => run_stat,
313 315 :output => output_file,
314 316 :output_size => output_size
315 317 }
316 318 end
317 319 end
318 320
319 321 # copied from grader/script/lib/test_request_helper.rb
320 322 def extract_running_stat(results)
321 323 running_stat_line = results[-1]
322 324
323 325 # extract exit status line
324 326 run_stat = ""
325 327 if !(/[Cc]orrect/.match(results[0]))
326 328 run_stat = results[0].chomp
327 329 else
328 330 run_stat = 'Program exited normally'
329 331 end
330 332
331 333 logger.info "Stat line: #{running_stat_line}"
332 334
333 335 # extract running time
334 336 if res = /r(.*)u(.*)s/.match(running_stat_line)
335 337 seconds = (res[1].to_f + res[2].to_f)
336 338 time_stat = "Time used: #{seconds} sec."
337 339 else
338 340 seconds = nil
339 341 time_stat = "Time used: n/a sec."
340 342 end
341 343
342 344 # extract memory usage
343 345 if res = /s(.*)m/.match(running_stat_line)
344 346 memory_used = res[1].to_i
345 347 else
346 348 memory_used = -1
347 349 end
348 350
349 351 return {
350 352 :msg => "#{run_stat}\n#{time_stat}",
351 353 :running_time => seconds,
352 354 :exit_status => run_stat,
353 355 :memory_usage => memory_used
354 356 }
355 357 end
356 358
357 359 def confirm_and_update_start_time
358 360 user = User.find(session[:user_id])
359 361 if (GraderConfiguration.indv_contest_mode? and
360 362 GraderConfiguration['contest.confirm_indv_contest_start'] and
361 363 !user.contest_started?)
362 364 redirect_to :action => 'confirm_contest_start' and return
363 365 end
364 366 if not GraderConfiguration.analysis_mode?
365 367 user.update_start_time
366 368 end
367 369 end
368 370
369 371 def reject_announcement_refresh_when_logged_out
370 372 if not session[:user_id]
371 373 render :text => 'Access forbidden', :status => 403
372 374 end
373 375
374 376 if GraderConfiguration.multicontests?
375 377 user = User.find(session[:user_id])
376 378 if user.contest_stat.forced_logout
377 379 render :text => 'Access forbidden', :status => 403
378 380 end
379 381 end
380 382 end
381 383
382 384 end
383 385
@@ -1,255 +1,379
1 1 class ReportController < ApplicationController
2 2
3 - before_filter :admin_authorization, only: [:login_stat,:submission_stat, :stuck]
3 + before_filter :admin_authorization, only: [:login_stat,:submission_stat, :stuck, :cheat_report, :cheat_scruntinize]
4 +
4 5 before_filter(only: [:problem_hof]) { |c|
5 6 return false unless authenticate
6 7
7 8 if GraderConfiguration["right.user_view_submission"]
8 9 return true;
9 10 end
10 11
11 12 admin_authorization
12 13 }
13 14
14 15 def login_stat
15 16 @logins = Array.new
16 17
17 18 date_and_time = '%Y-%m-%d %H:%M'
18 19 begin
19 20 md = params[:since_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
20 21 @since_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
21 22 rescue
22 23 @since_time = DateTime.new(1000,1,1)
23 24 end
24 25 begin
25 26 md = params[:until_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
26 27 @until_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
27 28 rescue
28 29 @until_time = DateTime.new(3000,1,1)
29 30 end
30 31
31 32 User.all.each do |user|
32 33 @logins << { id: user.id,
33 34 login: user.login,
34 35 full_name: user.full_name,
35 36 count: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
36 37 user.id,@since_time,@until_time)
37 38 .count(:id),
38 39 min: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
39 40 user.id,@since_time,@until_time)
40 41 .minimum(:created_at),
41 42 max: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
42 43 user.id,@since_time,@until_time)
43 44 .maximum(:created_at),
44 45 ip: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
45 46 user.id,@since_time,@until_time)
46 47 .select(:ip_address).uniq
47 48
48 49 }
49 50 end
50 51 end
51 52
52 53 def submission_stat
53 54
54 55 date_and_time = '%Y-%m-%d %H:%M'
55 56 begin
56 57 @since_time = DateTime.strptime(params[:since_datetime],date_and_time)
57 58 rescue
58 59 @since_time = DateTime.new(1000,1,1)
59 60 end
60 61 begin
61 62 @until_time = DateTime.strptime(params[:until_datetime],date_and_time)
62 63 rescue
63 64 @until_time = DateTime.new(3000,1,1)
64 65 end
65 66
66 67 @submissions = {}
67 68
68 69 User.find_each do |user|
69 70 @submissions[user.id] = { login: user.login, full_name: user.full_name, count: 0, sub: { } }
70 71 end
71 72
72 73 Submission.where("submitted_at >= ? AND submitted_at <= ?",@since_time,@until_time).find_each do |s|
73 74 if @submissions[s.user_id]
74 75 if not @submissions[s.user_id][:sub].has_key?(s.problem_id)
75 76 a = nil
76 77 begin
77 78 a = Problem.find(s.problem_id)
78 79 rescue
79 80 a = nil
80 81 end
81 82 @submissions[s.user_id][:sub][s.problem_id] =
82 83 { prob_name: (a ? a.full_name : '(NULL)'),
83 84 sub_ids: [s.id] }
84 85 else
85 86 @submissions[s.user_id][:sub][s.problem_id][:sub_ids] << s.id
86 87 end
87 88 @submissions[s.user_id][:count] += 1
88 89 end
89 90 end
90 91 end
91 92
92 93 def problem_hof
93 94 # gen problem list
94 95 @user = User.find(session[:user_id])
95 96 @problems = @user.available_problems
96 97
97 98 # get selected problems or the default
98 99 if params[:id]
99 100 begin
100 101 @problem = Problem.available.find(params[:id])
101 102 rescue
102 103 redirect_to action: :problem_hof
103 104 flash[:notice] = 'Error: submissions for that problem are not viewable.'
104 105 return
105 106 end
106 107 end
107 108
108 109 return unless @problem
109 110
110 111 @by_lang = {} #aggregrate by language
111 112
112 113 range =65
113 114 @histogram = { data: Array.new(range,0), summary: {} }
114 115 @summary = {count: 0, solve: 0, attempt: 0}
115 116 user = Hash.new(0)
116 117 Submission.where(problem_id: @problem.id).find_each do |sub|
117 118 #histogram
118 119 d = (DateTime.now.in_time_zone - sub.submitted_at) / 24 / 60 / 60
119 120 @histogram[:data][d.to_i] += 1 if d < range
120 121
121 122 next unless sub.points
122 123 @summary[:count] += 1
123 124 user[sub.user_id] = [user[sub.user_id], (sub.points >= @problem.full_score) ? 1 : 0].max
124 125
125 126 lang = Language.find_by_id(sub.language_id)
126 127 next unless lang
127 128 next unless sub.points >= @problem.full_score
128 129
129 130 #initialize
130 131 unless @by_lang.has_key?(lang.pretty_name)
131 132 @by_lang[lang.pretty_name] = {
132 133 runtime: { avail: false, value: 2**30-1 },
133 134 memory: { avail: false, value: 2**30-1 },
134 135 length: { avail: false, value: 2**30-1 },
135 136 first: { avail: false, value: DateTime.new(3000,1,1) }
136 137 }
137 138 end
138 139
139 140 if sub.max_runtime and sub.max_runtime < @by_lang[lang.pretty_name][:runtime][:value]
140 141 @by_lang[lang.pretty_name][:runtime] = { avail: true, user_id: sub.user_id, value: sub.max_runtime, sub_id: sub.id }
141 142 end
142 143
143 144 if sub.peak_memory and sub.peak_memory < @by_lang[lang.pretty_name][:memory][:value]
144 145 @by_lang[lang.pretty_name][:memory] = { avail: true, user_id: sub.user_id, value: sub.peak_memory, sub_id: sub.id }
145 146 end
146 147
147 148 if sub.submitted_at and sub.submitted_at < @by_lang[lang.pretty_name][:first][:value] and
148 149 !sub.user.admin?
149 150 @by_lang[lang.pretty_name][:first] = { avail: true, user_id: sub.user_id, value: sub.submitted_at, sub_id: sub.id }
150 151 end
151 152
152 153 if @by_lang[lang.pretty_name][:length][:value] > sub.effective_code_length
153 154 @by_lang[lang.pretty_name][:length] = { avail: true, user_id: sub.user_id, value: sub.effective_code_length, sub_id: sub.id }
154 155 end
155 156 end
156 157
157 158 #process user_id
158 159 @by_lang.each do |lang,prop|
159 160 prop.each do |k,v|
160 161 v[:user] = User.exists?(v[:user_id]) ? User.find(v[:user_id]).full_name : "(NULL)"
161 162 end
162 163 end
163 164
164 165 #sum into best
165 166 if @by_lang and @by_lang.first
166 167 @best = @by_lang.first[1].clone
167 168 @by_lang.each do |lang,prop|
168 169 if @best[:runtime][:value] >= prop[:runtime][:value]
169 170 @best[:runtime] = prop[:runtime]
170 171 @best[:runtime][:lang] = lang
171 172 end
172 173 if @best[:memory][:value] >= prop[:memory][:value]
173 174 @best[:memory] = prop[:memory]
174 175 @best[:memory][:lang] = lang
175 176 end
176 177 if @best[:length][:value] >= prop[:length][:value]
177 178 @best[:length] = prop[:length]
178 179 @best[:length][:lang] = lang
179 180 end
180 181 if @best[:first][:value] >= prop[:first][:value]
181 182 @best[:first] = prop[:first]
182 183 @best[:first][:lang] = lang
183 184 end
184 185 end
185 186 end
186 187
187 188 @histogram[:summary][:max] = [@histogram[:data].max,1].max
188 189 @summary[:attempt] = user.count
189 190 user.each_value { |v| @summary[:solve] += 1 if v == 1 }
190 191 end
191 192
192 193 def stuck #report struggling user,problem
193 194 # init
194 195 user,problem = nil
195 196 solve = true
196 197 tries = 0
197 198 @struggle = Array.new
198 199 record = {}
199 200 Submission.includes(:problem,:user).order(:problem_id,:user_id).find_each do |sub|
200 201 next unless sub.problem and sub.user
201 202 if user != sub.user_id or problem != sub.problem_id
202 203 @struggle << { user: record[:user], problem: record[:problem], tries: tries } unless solve
203 204 record = {user: sub.user, problem: sub.problem}
204 205 user,problem = sub.user_id, sub.problem_id
205 206 solve = false
206 207 tries = 0
207 208 end
208 209 if sub.points >= sub.problem.full_score
209 210 solve = true
210 211 else
211 212 tries += 1
212 213 end
213 214 end
214 215 @struggle.sort!{|a,b| b[:tries] <=> a[:tries] }
215 216 @struggle = @struggle[0..50]
216 217 end
217 218
218 219
219 220 def multiple_login
220 221 #user with multiple IP
221 222 raw = Submission.joins(:user).joins(:problem).where("problems.available != 0").group("login,ip_address").order(:login)
222 223 last,count = 0,0
223 224 first = 0
224 225 @users = []
225 226 raw.each do |r|
226 227 if last != r.user.login
227 228 count = 1
228 229 last = r.user.login
229 230 first = r
230 231 else
231 232 @users << first if count == 1
232 233 @users << r
233 234 count += 1
234 235 end
235 236 end
236 237
237 238 #IP with multiple user
238 239 raw = Submission.joins(:user).joins(:problem).where("problems.available != 0").group("login,ip_address").order(:ip_address)
239 240 last,count = 0,0
240 241 first = 0
241 242 @ip = []
242 243 raw.each do |r|
243 244 if last != r.ip_address
244 245 count = 1
245 246 last = r.ip_address
246 247 first = r
247 248 else
248 249 @ip << first if count == 1
249 250 @ip << r
250 251 count += 1
251 252 end
252 253 end
253 254 end
254 255
256 + def cheat_report
257 + date_and_time = '%Y-%m-%d %H:%M'
258 + begin
259 + md = params[:since_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
260 + @since_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
261 + rescue
262 + @since_time = Time.zone.now.ago( 90.minutes)
263 + end
264 + begin
265 + md = params[:until_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
266 + @until_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
267 + rescue
268 + @until_time = Time.zone.now
269 + end
270 +
271 + #multi login
272 + @ml = Login.joins(:user).where("logins.created_at >= ? and logins.created_at <= ?",@since_time,@until_time).select('users.login,count(distinct ip_address) as count,users.full_name').group("users.id").having("count > 1")
273 +
274 + st = <<-SQL
275 + SELECT l2.*
276 + FROM logins l2 INNER JOIN
277 + (SELECT u.id,COUNT(DISTINCT ip_address) as count,u.login,u.full_name
278 + FROM logins l
279 + INNER JOIN users u ON l.user_id = u.id
280 + WHERE l.created_at >= '#{@since_time.in_time_zone("UTC")}' and l.created_at <= '#{@until_time.in_time_zone("UTC")}'
281 + GROUP BY u.id
282 + HAVING count > 1
283 + ) ml ON l2.user_id = ml.id
284 + WHERE l2.created_at >= '#{@since_time.in_time_zone("UTC")}' and l2.created_at <= '#{@until_time.in_time_zone("UTC")}'
285 + UNION
286 + SELECT l2.*
287 + FROM logins l2 INNER JOIN
288 + (SELECT l.ip_address,COUNT(DISTINCT u.id) as count
289 + FROM logins l
290 + INNER JOIN users u ON l.user_id = u.id
291 + WHERE l.created_at >= '#{@since_time.in_time_zone("UTC")}' and l.created_at <= '#{@until_time.in_time_zone("UTC")}'
292 + GROUP BY l.ip_address
293 + HAVING count > 1
294 + ) ml on ml.ip_address = l2.ip_address
295 + INNER JOIN users u ON l2.user_id = u.id
296 + WHERE l2.created_at >= '#{@since_time.in_time_zone("UTC")}' and l2.created_at <= '#{@until_time.in_time_zone("UTC")}'
297 + ORDER BY ip_address,created_at
298 + SQL
299 + @mld = Login.find_by_sql(st)
300 +
301 + st = <<-SQL
302 + SELECT s.id,s.user_id,s.ip_address,s.submitted_at,s.problem_id
303 + FROM submissions s INNER JOIN
304 + (SELECT u.id,COUNT(DISTINCT ip_address) as count,u.login,u.full_name
305 + FROM logins l
306 + INNER JOIN users u ON l.user_id = u.id
307 + WHERE l.created_at >= ? and l.created_at <= ?
308 + GROUP BY u.id
309 + HAVING count > 1
310 + ) ml ON s.user_id = ml.id
311 + WHERE s.submitted_at >= ? and s.submitted_at <= ?
312 + UNION
313 + SELECT s.id,s.user_id,s.ip_address,s.submitted_at,s.problem_id
314 + FROM submissions s INNER JOIN
315 + (SELECT l.ip_address,COUNT(DISTINCT u.id) as count
316 + FROM logins l
317 + INNER JOIN users u ON l.user_id = u.id
318 + WHERE l.created_at >= ? and l.created_at <= ?
319 + GROUP BY l.ip_address
320 + HAVING count > 1
321 + ) ml on ml.ip_address = s.ip_address
322 + WHERE s.submitted_at >= ? and s.submitted_at <= ?
323 + ORDER BY ip_address,submitted_at
324 + SQL
325 + @subs = Submission.joins(:problem).find_by_sql([st,@since_time,@until_time,
326 + @since_time,@until_time,
327 + @since_time,@until_time,
328 + @since_time,@until_time])
329 +
330 + end
331 +
332 + def cheat_scruntinize
333 + #convert date & time
334 + date_and_time = '%Y-%m-%d %H:%M'
335 + begin
336 + md = params[:since_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
337 + @since_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
338 + rescue
339 + @since_time = Time.zone.now.ago( 90.minutes)
340 + end
341 + begin
342 + md = params[:until_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
343 + @until_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
344 + rescue
345 + @until_time = Time.zone.now
346 + end
347 +
348 + #convert sid
349 + @sid = params[:SID].split(/[,\s]/) if params[:SID]
350 + unless @sid and @sid.size > 0
351 + return
352 + redirect_to actoin: :cheat_scruntinize
353 + flash[:notice] = 'Please enter at least 1 student id'
354 + end
355 + mark = Array.new(@sid.size,'?')
356 + condition = "(u.login = " + mark.join(' OR u.login = ') + ')'
357 +
358 + @st = <<-SQL
359 + SELECT l.created_at as submitted_at ,-1 as id,u.login,u.full_name,l.ip_address,"" as problem_id,"" as points,l.user_id
360 + FROM logins l INNER JOIN users u on l.user_id = u.id
361 + WHERE l.created_at >= ? AND l.created_at <= ? AND #{condition}
362 + UNION
363 + SELECT s.submitted_at,s.id,u.login,u.full_name,s.ip_address,s.problem_id,s.points,s.user_id
364 + FROM submissions s INNER JOIN users u ON s.user_id = u.id
365 + WHERE s.submitted_at >= ? AND s.submitted_at <= ? AND #{condition}
366 + ORDER BY submitted_at
367 + SQL
368 +
369 + p = [@st,@since_time,@until_time] + @sid + [@since_time,@until_time] + @sid
370 + @logs = Submission.joins(:problem).find_by_sql(p)
371 +
372 +
373 +
374 +
375 +
376 + end
377 +
378 +
255 379 end
@@ -1,3 +1,5
1 1 class Login < ActiveRecord::Base
2 + belongs_to :user
3 +
2 4 attr_accessible :ip_address, :logged_in_at, :user_id
3 5 end
@@ -1,376 +1,380
1 1 require 'digest/sha1'
2 2 require 'net/pop'
3 3 require 'net/https'
4 4 require 'net/http'
5 5 require 'json'
6 6
7 7 class User < ActiveRecord::Base
8 8
9 9 has_and_belongs_to_many :roles
10 10
11 11 has_many :test_requests, :order => "submitted_at DESC"
12 12
13 13 has_many :messages,
14 14 :class_name => "Message",
15 15 :foreign_key => "sender_id",
16 16 :order => 'created_at DESC'
17 17
18 18 has_many :replied_messages,
19 19 :class_name => "Message",
20 20 :foreign_key => "receiver_id",
21 21 :order => 'created_at DESC'
22 22
23 23 has_one :contest_stat, :class_name => "UserContestStat", :dependent => :destroy
24 24
25 25 belongs_to :site
26 26 belongs_to :country
27 27
28 28 has_and_belongs_to_many :contests, :uniq => true, :order => 'name'
29 29
30 30 scope :activated_users, :conditions => {:activated => true}
31 31
32 32 validates_presence_of :login
33 33 validates_uniqueness_of :login
34 34 validates_format_of :login, :with => /^[\_A-Za-z0-9]+$/
35 35 validates_length_of :login, :within => 3..30
36 36
37 37 validates_presence_of :full_name
38 38 validates_length_of :full_name, :minimum => 1
39 39
40 40 validates_presence_of :password, :if => :password_required?
41 41 validates_length_of :password, :within => 4..20, :if => :password_required?
42 42 validates_confirmation_of :password, :if => :password_required?
43 43
44 44 validates_format_of :email,
45 45 :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i,
46 46 :if => :email_validation?
47 47 validate :uniqueness_of_email_from_activated_users,
48 48 :if => :email_validation?
49 49 validate :enough_time_interval_between_same_email_registrations,
50 50 :if => :email_validation?
51 51
52 52 # these are for ytopc
53 53 # disable for now
54 54 #validates_presence_of :province
55 55
56 56 attr_accessor :password
57 57
58 58 before_save :encrypt_new_password
59 59 before_save :assign_default_site
60 60 before_save :assign_default_contest
61 61
62 62 # this is for will_paginate
63 63 cattr_reader :per_page
64 64 @@per_page = 50
65 65
66 66 def self.authenticate(login, password)
67 67 user = find_by_login(login)
68 68 if user
69 69 return user if user.authenticated?(password)
70 70 if user.authenticated_by_cucas?(password) or user.authenticated_by_pop3?(password)
71 71 user.password = password
72 72 user.save
73 73 return user
74 74 end
75 75 end
76 76 end
77 77
78 78 def authenticated?(password)
79 79 if self.activated
80 80 hashed_password == User.encrypt(password,self.salt)
81 81 else
82 82 false
83 83 end
84 84 end
85 85
86 86 def authenticated_by_pop3?(password)
87 87 Net::POP3.enable_ssl
88 88 pop = Net::POP3.new('pops.it.chula.ac.th')
89 89 authen = true
90 90 begin
91 91 pop.start(login, password)
92 92 pop.finish
93 93 return true
94 94 rescue
95 95 return false
96 96 end
97 97 end
98 98
99 99 def authenticated_by_cucas?(password)
100 100 url = URI.parse('https://www.cas.chula.ac.th/cas/api/?q=studentAuthenticate')
101 101 appid = '41508763e340d5858c00f8c1a0f5a2bb'
102 102 appsecret ='d9cbb5863091dbe186fded85722a1e31'
103 103 post_args = {
104 104 'appid' => appid,
105 105 'appsecret' => appsecret,
106 106 'username' => login,
107 107 'password' => password
108 108 }
109 109
110 110 #simple call
111 111 begin
112 112 http = Net::HTTP.new('www.cas.chula.ac.th', 443)
113 113 http.use_ssl = true
114 114 result = [ ]
115 115 http.start do |http|
116 116 req = Net::HTTP::Post.new('/cas/api/?q=studentAuthenticate')
117 117 param = "appid=#{appid}&appsecret=#{appsecret}&username=#{login}&password=#{password}"
118 118 resp = http.request(req,param)
119 119 result = JSON.parse resp.body
120 120 end
121 121 return true if result["type"] == "beanStudent"
122 122 rescue
123 123 return false
124 124 end
125 125 return false
126 126 end
127 127
128 128 def admin?
129 129 self.roles.detect {|r| r.name == 'admin' }
130 130 end
131 131
132 132 def email_for_editing
133 133 if self.email==nil
134 134 "(unknown)"
135 135 elsif self.email==''
136 136 "(blank)"
137 137 else
138 138 self.email
139 139 end
140 140 end
141 141
142 142 def email_for_editing=(e)
143 143 self.email=e
144 144 end
145 145
146 146 def alias_for_editing
147 147 if self.alias==nil
148 148 "(unknown)"
149 149 elsif self.alias==''
150 150 "(blank)"
151 151 else
152 152 self.alias
153 153 end
154 154 end
155 155
156 156 def alias_for_editing=(e)
157 157 self.alias=e
158 158 end
159 159
160 160 def activation_key
161 161 if self.hashed_password==nil
162 162 encrypt_new_password
163 163 end
164 164 Digest::SHA1.hexdigest(self.hashed_password)[0..7]
165 165 end
166 166
167 167 def verify_activation_key(key)
168 168 key == activation_key
169 169 end
170 170
171 171 def self.random_password(length=5)
172 172 chars = 'abcdefghjkmnopqrstuvwxyz'
173 173 password = ''
174 174 length.times { password << chars[rand(chars.length - 1)] }
175 175 password
176 176 end
177 177
178 178 def self.find_non_admin_with_prefix(prefix='')
179 179 users = User.find(:all)
180 180 return users.find_all { |u| !(u.admin?) and u.login.index(prefix)==0 }
181 181 end
182 182
183 183 # Contest information
184 184
185 185 def self.find_users_with_no_contest()
186 186 users = User.find(:all)
187 187 return users.find_all { |u| u.contests.length == 0 }
188 188 end
189 189
190 190
191 191 def contest_time_left
192 192 if GraderConfiguration.contest_mode?
193 193 return nil if site==nil
194 194 return site.time_left
195 195 elsif GraderConfiguration.indv_contest_mode?
196 196 time_limit = GraderConfiguration.contest_time_limit
197 197 if time_limit == nil
198 198 return nil
199 199 end
200 200 if contest_stat==nil or contest_stat.started_at==nil
201 201 return (Time.now.gmtime + time_limit) - Time.now.gmtime
202 202 else
203 203 finish_time = contest_stat.started_at + time_limit
204 204 current_time = Time.now.gmtime
205 205 if current_time > finish_time
206 206 return 0
207 207 else
208 208 return finish_time - current_time
209 209 end
210 210 end
211 211 else
212 212 return nil
213 213 end
214 214 end
215 215
216 216 def contest_finished?
217 217 if GraderConfiguration.contest_mode?
218 218 return false if site==nil
219 219 return site.finished?
220 220 elsif GraderConfiguration.indv_contest_mode?
221 221 return false if self.contest_stat(true)==nil
222 222 return contest_time_left == 0
223 223 else
224 224 return false
225 225 end
226 226 end
227 227
228 228 def contest_started?
229 229 if GraderConfiguration.indv_contest_mode?
230 230 stat = self.contest_stat
231 231 return ((stat != nil) and (stat.started_at != nil))
232 232 elsif GraderConfiguration.contest_mode?
233 233 return true if site==nil
234 234 return site.started
235 235 else
236 236 return true
237 237 end
238 238 end
239 239
240 240 def update_start_time
241 241 stat = self.contest_stat
242 242 if stat == nil or stat.started_at == nil
243 243 stat ||= UserContestStat.new(:user => self)
244 244 stat.started_at = Time.now.gmtime
245 245 stat.save
246 246 end
247 247 end
248 248
249 249 def problem_in_user_contests?(problem)
250 250 problem_contests = problem.contests.all
251 251
252 252 if problem_contests.length == 0 # this is public contest
253 253 return true
254 254 end
255 255
256 256 contests.each do |contest|
257 257 if problem_contests.find {|c| c.id == contest.id }
258 258 return true
259 259 end
260 260 end
261 261 return false
262 262 end
263 263
264 264 def available_problems_group_by_contests
265 265 contest_problems = []
266 266 pin = {}
267 267 contests.enabled.each do |contest|
268 268 available_problems = contest.problems.available
269 269 contest_problems << {
270 270 :contest => contest,
271 271 :problems => available_problems
272 272 }
273 273 available_problems.each {|p| pin[p.id] = true}
274 274 end
275 275 other_avaiable_problems = Problem.available.find_all {|p| pin[p.id]==nil and p.contests.length==0}
276 276 contest_problems << {
277 277 :contest => nil,
278 278 :problems => other_avaiable_problems
279 279 }
280 280 return contest_problems
281 281 end
282 282
283 283 def available_problems
284 284 if not GraderConfiguration.multicontests?
285 285 return Problem.find_available_problems
286 286 else
287 287 contest_problems = []
288 288 pin = {}
289 289 contests.enabled.each do |contest|
290 290 contest.problems.available.each do |problem|
291 291 if not pin.has_key? problem.id
292 292 contest_problems << problem
293 293 end
294 294 pin[problem.id] = true
295 295 end
296 296 end
297 297 other_avaiable_problems = Problem.available.find_all {|p| pin[p.id]==nil and p.contests.length==0}
298 298 return contest_problems + other_avaiable_problems
299 299 end
300 300 end
301 301
302 302 def can_view_problem?(problem)
303 303 if not GraderConfiguration.multicontests?
304 304 return problem.available
305 305 else
306 306 return problem_in_user_contests? problem
307 307 end
308 308 end
309 309
310 + def self.clear_last_login
311 + User.update_all(:last_ip => nil)
312 + end
313 +
310 314 protected
311 315 def encrypt_new_password
312 316 return if password.blank?
313 317 self.salt = (10+rand(90)).to_s
314 318 self.hashed_password = User.encrypt(self.password,self.salt)
315 319 end
316 320
317 321 def assign_default_site
318 322 # have to catch error when migrating (because self.site is not available).
319 323 begin
320 324 if self.site==nil
321 325 self.site = Site.find_by_name('default')
322 326 if self.site==nil
323 327 self.site = Site.find(1) # when 'default has be renamed'
324 328 end
325 329 end
326 330 rescue
327 331 end
328 332 end
329 333
330 334 def assign_default_contest
331 335 # have to catch error when migrating (because self.site is not available).
332 336 begin
333 337 if self.contests.length == 0
334 338 default_contest = Contest.find_by_name(GraderConfiguration['contest.default_contest_name'])
335 339 if default_contest
336 340 self.contests = [default_contest]
337 341 end
338 342 end
339 343 rescue
340 344 end
341 345 end
342 346
343 347 def password_required?
344 348 self.hashed_password.blank? || !self.password.blank?
345 349 end
346 350
347 351 def self.encrypt(string,salt)
348 352 Digest::SHA1.hexdigest(salt + string)
349 353 end
350 354
351 355 def uniqueness_of_email_from_activated_users
352 356 user = User.activated_users.find_by_email(self.email)
353 357 if user and (user.login != self.login)
354 358 self.errors.add(:base,"Email has already been taken")
355 359 end
356 360 end
357 361
358 362 def enough_time_interval_between_same_email_registrations
359 363 return if !self.new_record?
360 364 return if self.activated
361 365 open_user = User.find_by_email(self.email,
362 366 :order => 'created_at DESC')
363 367 if open_user and open_user.created_at and
364 368 (open_user.created_at > Time.now.gmtime - 5.minutes)
365 369 self.errors.add(:base,"There are already unactivated registrations with this e-mail address (please wait for 5 minutes)")
366 370 end
367 371 end
368 372
369 373 def email_validation?
370 374 begin
371 375 return VALIDATE_USER_EMAILS
372 376 rescue
373 377 return false
374 378 end
375 379 end
376 380 end
@@ -1,23 +1,23
1 1
2 2 = form_tag({session: :url }) do
3 3 .submitbox
4 4 %table
5 5 %tr
6 6 %td{colspan: 6, style: 'font-weight: bold'}= title
7 7 %tr
8 8 %td{style: 'width: 120px; font-weight: bold'}= param_text
9 9 %td{align: 'right'} since:
10 - %td= text_field_tag 'since_datetime'
10 + %td= text_field_tag 'since_datetime', @since_time
11 11 %tr
12 12 %td
13 13 %td{align: 'right'} until:
14 - %td= text_field_tag 'until_datetime'
14 + %td= text_field_tag 'until_datetime', @until_time
15 15 %tr
16 16 %td
17 17 %td
18 18 %td Blank mean no condition
19 19 %tr
20 20 %td
21 21 %td
22 22 %td= submit_tag 'query'
23 23
@@ -1,8 +1,9
1 1
2 2 .task-menu
3 3 Reports
4 4 %br/
5 5 = link_to '[Hall of Fame]', :action => 'problem_hof'
6 6 = link_to '[Struggle]', :action => 'stuck'
7 - = link_to '[Login]', :action => 'login_stat'
7 + = link_to '[Cheat Detection]', :action => 'cheat_report'
8 + = link_to '[Cheat Detail]', :action => 'cheat_scruntinize'
8 9 = link_to '[Multiple Login]', :action => 'multiple_login'
@@ -1,249 +1,250
1 1 # encoding: UTF-8
2 2 # This file is auto-generated from the current state of the database. Instead
3 3 # of editing this file, please use the migrations feature of Active Record to
4 4 # incrementally modify your database, and then regenerate this schema definition.
5 5 #
6 6 # Note that this schema.rb definition is the authoritative source for your
7 7 # database schema. If you need to create the application database on another
8 8 # system, you should be using db:schema:load, not running all the migrations
9 9 # from scratch. The latter is a flawed and unsustainable approach (the more migrations
10 10 # you'll amass, the slower it'll run and the greater likelihood for issues).
11 11 #
12 12 # It's strongly recommended to check this file into your version control system.
13 13
14 - ActiveRecord::Schema.define(:version => 20150203153534) do
14 + ActiveRecord::Schema.define(:version => 20150618085823) do
15 15
16 16 create_table "announcements", :force => true do |t|
17 17 t.string "author"
18 18 t.text "body", :limit => 16777215
19 19 t.boolean "published"
20 20 t.datetime "created_at", :null => false
21 21 t.datetime "updated_at", :null => false
22 22 t.boolean "frontpage", :default => false
23 23 t.boolean "contest_only", :default => false
24 24 t.string "title"
25 25 t.string "notes"
26 26 end
27 27
28 28 create_table "contests", :force => true do |t|
29 29 t.string "title"
30 30 t.boolean "enabled"
31 31 t.datetime "created_at", :null => false
32 32 t.datetime "updated_at", :null => false
33 33 t.string "name"
34 34 end
35 35
36 36 create_table "contests_problems", :id => false, :force => true do |t|
37 37 t.integer "contest_id"
38 38 t.integer "problem_id"
39 39 end
40 40
41 41 create_table "contests_users", :id => false, :force => true do |t|
42 42 t.integer "contest_id"
43 43 t.integer "user_id"
44 44 end
45 45
46 46 create_table "countries", :force => true do |t|
47 47 t.string "name"
48 48 t.datetime "created_at", :null => false
49 49 t.datetime "updated_at", :null => false
50 50 end
51 51
52 52 create_table "descriptions", :force => true do |t|
53 53 t.text "body", :limit => 16777215
54 54 t.boolean "markdowned"
55 55 t.datetime "created_at", :null => false
56 56 t.datetime "updated_at", :null => false
57 57 end
58 58
59 59 create_table "grader_configurations", :force => true do |t|
60 60 t.string "key"
61 61 t.string "value_type"
62 62 t.string "value"
63 63 t.datetime "created_at", :null => false
64 64 t.datetime "updated_at", :null => false
65 65 t.text "description", :limit => 16777215
66 66 end
67 67
68 68 create_table "grader_processes", :force => true do |t|
69 69 t.string "host", :limit => 20
70 70 t.integer "pid"
71 71 t.string "mode"
72 72 t.boolean "active"
73 73 t.datetime "created_at", :null => false
74 74 t.datetime "updated_at", :null => false
75 75 t.integer "task_id"
76 76 t.string "task_type"
77 77 t.boolean "terminated"
78 78 end
79 79
80 80 add_index "grader_processes", ["host", "pid"], :name => "index_grader_processes_on_ip_and_pid"
81 81
82 82 create_table "languages", :force => true do |t|
83 83 t.string "name", :limit => 10
84 84 t.string "pretty_name"
85 85 t.string "ext", :limit => 10
86 86 t.string "common_ext"
87 87 end
88 88
89 89 create_table "logins", :force => true do |t|
90 - t.string "user_id"
90 + t.integer "user_id"
91 91 t.string "ip_address"
92 92 t.datetime "created_at", :null => false
93 93 t.datetime "updated_at", :null => false
94 94 end
95 95
96 96 create_table "messages", :force => true do |t|
97 97 t.integer "sender_id"
98 98 t.integer "receiver_id"
99 99 t.integer "replying_message_id"
100 100 t.text "body", :limit => 16777215
101 101 t.boolean "replied"
102 102 t.datetime "created_at", :null => false
103 103 t.datetime "updated_at", :null => false
104 104 end
105 105
106 106 create_table "problems", :force => true do |t|
107 107 t.string "name", :limit => 30
108 108 t.string "full_name"
109 109 t.integer "full_score"
110 110 t.date "date_added"
111 111 t.boolean "available"
112 112 t.string "url"
113 113 t.integer "description_id"
114 114 t.boolean "test_allowed"
115 115 t.boolean "output_only"
116 116 t.string "description_filename"
117 117 end
118 118
119 119 create_table "rights", :force => true do |t|
120 120 t.string "name"
121 121 t.string "controller"
122 122 t.string "action"
123 123 end
124 124
125 125 create_table "rights_roles", :id => false, :force => true do |t|
126 126 t.integer "right_id"
127 127 t.integer "role_id"
128 128 end
129 129
130 130 add_index "rights_roles", ["role_id"], :name => "index_rights_roles_on_role_id"
131 131
132 132 create_table "roles", :force => true do |t|
133 133 t.string "name"
134 134 end
135 135
136 136 create_table "roles_users", :id => false, :force => true do |t|
137 137 t.integer "role_id"
138 138 t.integer "user_id"
139 139 end
140 140
141 141 add_index "roles_users", ["user_id"], :name => "index_roles_users_on_user_id"
142 142
143 143 create_table "sessions", :force => true do |t|
144 144 t.string "session_id"
145 145 t.text "data", :limit => 16777215
146 146 t.datetime "updated_at"
147 147 end
148 148
149 149 add_index "sessions", ["session_id"], :name => "index_sessions_on_session_id"
150 150 add_index "sessions", ["updated_at"], :name => "index_sessions_on_updated_at"
151 151
152 152 create_table "sites", :force => true do |t|
153 153 t.string "name"
154 154 t.boolean "started"
155 155 t.datetime "start_time"
156 156 t.datetime "created_at", :null => false
157 157 t.datetime "updated_at", :null => false
158 158 t.integer "country_id"
159 159 t.string "password"
160 160 end
161 161
162 162 create_table "submissions", :force => true do |t|
163 163 t.integer "user_id"
164 164 t.integer "problem_id"
165 165 t.integer "language_id"
166 166 t.text "source", :limit => 16777215
167 167 t.binary "binary"
168 168 t.datetime "submitted_at"
169 169 t.datetime "compiled_at"
170 170 t.text "compiler_message", :limit => 16777215
171 171 t.datetime "graded_at"
172 172 t.integer "points"
173 173 t.text "grader_comment", :limit => 16777215
174 174 t.integer "number"
175 175 t.string "source_filename"
176 176 t.float "max_runtime"
177 177 t.integer "peak_memory"
178 178 t.integer "effective_code_length"
179 179 t.string "ip_address"
180 180 end
181 181
182 182 add_index "submissions", ["user_id", "problem_id", "number"], :name => "index_submissions_on_user_id_and_problem_id_and_number", :unique => true
183 183 add_index "submissions", ["user_id", "problem_id"], :name => "index_submissions_on_user_id_and_problem_id"
184 184
185 185 create_table "tasks", :force => true do |t|
186 186 t.integer "submission_id"
187 187 t.datetime "created_at"
188 188 t.integer "status"
189 189 t.datetime "updated_at"
190 190 end
191 191
192 192 create_table "test_pairs", :force => true do |t|
193 193 t.integer "problem_id"
194 194 t.text "input", :limit => 2147483647
195 195 t.text "solution", :limit => 2147483647
196 196 t.datetime "created_at", :null => false
197 197 t.datetime "updated_at", :null => false
198 198 end
199 199
200 200 create_table "test_requests", :force => true do |t|
201 201 t.integer "user_id"
202 202 t.integer "problem_id"
203 203 t.integer "submission_id"
204 204 t.string "input_file_name"
205 205 t.string "output_file_name"
206 206 t.string "running_stat"
207 207 t.integer "status"
208 208 t.datetime "updated_at", :null => false
209 209 t.datetime "submitted_at"
210 210 t.datetime "compiled_at"
211 211 t.text "compiler_message", :limit => 16777215
212 212 t.datetime "graded_at"
213 213 t.string "grader_comment"
214 214 t.datetime "created_at", :null => false
215 215 t.float "running_time"
216 216 t.string "exit_status"
217 217 t.integer "memory_usage"
218 218 end
219 219
220 220 add_index "test_requests", ["user_id", "problem_id"], :name => "index_test_requests_on_user_id_and_problem_id"
221 221
222 222 create_table "user_contest_stats", :force => true do |t|
223 223 t.integer "user_id"
224 224 t.datetime "started_at"
225 225 t.datetime "created_at", :null => false
226 226 t.datetime "updated_at", :null => false
227 227 t.boolean "forced_logout"
228 228 end
229 229
230 230 create_table "users", :force => true do |t|
231 231 t.string "login", :limit => 50
232 232 t.string "full_name"
233 233 t.string "hashed_password"
234 234 t.string "salt", :limit => 5
235 235 t.string "alias"
236 236 t.string "email"
237 237 t.integer "site_id"
238 238 t.integer "country_id"
239 239 t.boolean "activated", :default => false
240 240 t.datetime "created_at"
241 241 t.datetime "updated_at"
242 242 t.string "section"
243 243 t.boolean "enabled", :default => true
244 244 t.string "remark"
245 + t.string "last_ip"
245 246 end
246 247
247 248 add_index "users", ["login"], :name => "index_users_on_login", :unique => true
248 249
249 250 end
@@ -1,223 +1,230
1 1 CONFIGURATIONS =
2 2 [
3 3 {
4 4 :key => 'system.single_user_mode',
5 5 :value_type => 'boolean',
6 6 :default_value => 'false',
7 7 :description => 'Only admins can log in to the system when running under single user mode.'
8 8 },
9 9
10 10 {
11 11 :key => 'ui.front.title',
12 12 :value_type => 'string',
13 13 :default_value => 'Grader'
14 14 },
15 15
16 16 {
17 17 :key => 'ui.front.welcome_message',
18 18 :value_type => 'string',
19 19 :default_value => 'Welcome!'
20 20 },
21 21
22 22 {
23 23 :key => 'ui.show_score',
24 24 :value_type => 'boolean',
25 25 :default_value => 'true'
26 26 },
27 27
28 28 {
29 29 :key => 'contest.time_limit',
30 30 :value_type => 'string',
31 31 :default_value => 'unlimited',
32 32 :description => 'Time limit in format hh:mm, or "unlimited" for contests with no time limits. This config is CACHED. Restart the server before the change can take effect.'
33 33 },
34 34
35 35 {
36 36 :key => 'system.mode',
37 37 :value_type => 'string',
38 38 :default_value => 'standard',
39 39 :description => 'Current modes are "standard", "contest", "indv-contest", and "analysis".'
40 40 },
41 41
42 42 {
43 43 :key => 'contest.name',
44 44 :value_type => 'string',
45 45 :default_value => 'Grader',
46 46 :description => 'This name will be shown on the user header bar.'
47 47 },
48 48
49 49 {
50 50 :key => 'contest.multisites',
51 51 :value_type => 'boolean',
52 52 :default_value => 'false',
53 53 :description => 'If the server is in contest mode and this option is true, on the log in of the admin a menu for site selections is shown.'
54 54 },
55 55
56 56 {
57 57 :key => 'right.user_hall_of_fame',
58 58 :value_type => 'boolean',
59 59 :default_value => 'false',
60 60 :description => 'If true, any user can access hall of fame page.'
61 61 },
62 62
63 63 {
64 + :key => 'right.multiple_ip_login',
65 + :value_type => 'boolean',
66 + :default_value => 'true',
67 + :description => 'When change from true to false, a user can login from the first IP they logged into afterward.'
68 + },
69 +
70 + {
64 71 :key => 'right.user_view_submission',
65 72 :value_type => 'boolean',
66 73 :default_value => 'false',
67 74 :description => 'If true, any user can view submissions of every one.'
68 75 },
69 76
70 77 # If Configuration['system.online_registration'] is true, the
71 78 # system allows online registration, and will use these
72 79 # information for sending confirmation emails.
73 80 {
74 81 :key => 'system.online_registration.smtp',
75 82 :value_type => 'string',
76 83 :default_value => 'smtp.somehost.com'
77 84 },
78 85
79 86 {
80 87 :key => 'system.online_registration.from',
81 88 :value_type => 'string',
82 89 :default_value => 'your.email@address'
83 90 },
84 91
85 92 {
86 93 :key => 'system.admin_email',
87 94 :value_type => 'string',
88 95 :default_value => 'admin@admin.email'
89 96 },
90 97
91 98 {
92 99 :key => 'system.user_setting_enabled',
93 100 :value_type => 'boolean',
94 101 :default_value => 'true',
95 102 :description => 'If this option is true, users can change their settings'
96 103 },
97 104
98 105 {
99 106 :key => 'system.user_setting_enabled',
100 107 :value_type => 'boolean',
101 108 :default_value => 'true',
102 109 :description => 'If this option is true, users can change their settings'
103 110 },
104 111
105 112 # If Configuration['contest.test_request.early_timeout'] is true
106 113 # the user will not be able to use test request at 30 minutes
107 114 # before the contest ends.
108 115 {
109 116 :key => 'contest.test_request.early_timeout',
110 117 :value_type => 'boolean',
111 118 :default_value => 'false'
112 119 },
113 120
114 121 {
115 122 :key => 'system.multicontests',
116 123 :value_type => 'boolean',
117 124 :default_value => 'false'
118 125 },
119 126
120 127 {
121 128 :key => 'contest.confirm_indv_contest_start',
122 129 :value_type => 'boolean',
123 130 :default_value => 'false'
124 131 },
125 132
126 133 {
127 134 :key => 'contest.default_contest_name',
128 135 :value_type => 'string',
129 136 :default_value => 'none',
130 137 :description => "New user will be assigned to this contest automatically, if it exists. Set to 'none' if there is no default contest."
131 138 }
132 139
133 140 ]
134 141
135 142
136 143 def create_configuration_key(key,
137 144 value_type,
138 145 default_value,
139 146 description='')
140 147 conf = (GraderConfiguration.find_by_key(key) ||
141 148 GraderConfiguration.new(:key => key,
142 149 :value_type => value_type,
143 150 :value => default_value))
144 151 conf.description = description
145 152 conf.save
146 153 end
147 154
148 155 def seed_config
149 156 CONFIGURATIONS.each do |conf|
150 157 if conf.has_key? :description
151 158 desc = conf[:description]
152 159 else
153 160 desc = ''
154 161 end
155 162 create_configuration_key(conf[:key],
156 163 conf[:value_type],
157 164 conf[:default_value],
158 165 desc)
159 166 end
160 167 end
161 168
162 169 def seed_roles
163 170 return if Role.find_by_name('admin')
164 171
165 172 role = Role.create(:name => 'admin')
166 173 user_admin_right = Right.create(:name => 'user_admin',
167 174 :controller => 'user_admin',
168 175 :action => 'all')
169 176 problem_admin_right = Right.create(:name=> 'problem_admin',
170 177 :controller => 'problems',
171 178 :action => 'all')
172 179
173 180 graders_right = Right.create(:name => 'graders_admin',
174 181 :controller => 'graders',
175 182 :action => 'all')
176 183
177 184 role.rights << user_admin_right;
178 185 role.rights << problem_admin_right;
179 186 role.rights << graders_right;
180 187 role.save
181 188 end
182 189
183 190 def seed_root
184 191 return if User.find_by_login('root')
185 192
186 193 root = User.new(:login => 'root',
187 194 :full_name => 'Administrator',
188 195 :alias => 'root')
189 196 root.password = 'ioionrails';
190 197
191 198 class << root
192 199 public :encrypt_new_password
193 200 def valid?(context=nil)
194 201 true
195 202 end
196 203 end
197 204
198 205 root.encrypt_new_password
199 206
200 207 root.roles << Role.find_by_name('admin')
201 208
202 209 root.activated = true
203 210 root.save
204 211 end
205 212
206 213 def seed_users_and_roles
207 214 seed_roles
208 215 seed_root
209 216 end
210 217
211 218 def seed_more_languages
212 219 Language.delete_all
213 220 Language.create( name: 'c', pretty_name: 'C', ext: 'c', common_ext: 'c' )
214 221 Language.create( name: 'cpp', pretty_name: 'C++', ext: 'cpp', common_ext: 'cpp,cc' )
215 222 Language.create( name: 'pas', pretty_name: 'Pascal', ext: 'pas', common_ext: 'pas' )
216 223 Language.create( name: 'ruby', pretty_name: 'Ruby', ext: 'rb', common_ext: 'rb' )
217 224 Language.create( name: 'python', pretty_name: 'Python', ext: 'py', common_ext: 'py' )
218 225 Language.create( name: 'java', pretty_name: 'Java', ext: 'java', common_ext: 'java' )
219 226 end
220 227
221 228 seed_config
222 229 seed_users_and_roles
223 230 seed_more_languages
You need to be logged in to leave comments. Login now