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,111 +1,113
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
@@ -1,99 +1,100
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
@@ -159,97 +160,220
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)
255 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 +
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
@@ -214,163 +214,167
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,159 +1,166
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
You need to be logged in to leave comments. Login now