Skip to content

Commit

Permalink
Fixes #168
Browse files Browse the repository at this point in the history
  • Loading branch information
jdabtieu committed Dec 15, 2023
1 parent 7953c1b commit ef26afd
Show file tree
Hide file tree
Showing 2 changed files with 192 additions and 23 deletions.
67 changes: 45 additions & 22 deletions src/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,14 +246,15 @@ def create_jwt(data, secret_key, time=1800):
return jwt.encode(data, secret_key, algorithm='HS256')


def update_dyn_score(contest_id, problem_id, update_curr_user=True):
def update_dyn_score(contest_id, problem_id, update_curr_user=True, transact=True):
from application import db
"""
Updates the dynamic scoring of contest_id/problem_id, using the db object
For details see: https://www.desmos.com/calculator/eifeir81wk
https://github.com/jdabtieu/CTFOJ/issues/2
"""
db.execute("BEGIN")
if transact:
db.execute("BEGIN")
if update_curr_user:
db.execute(("INSERT INTO contest_solved(contest_id, user_id, problem_id) "
"VALUES(:cid, :uid, :pid)"),
Expand Down Expand Up @@ -287,7 +288,8 @@ def update_dyn_score(contest_id, problem_id, update_curr_user=True):
"(SELECT user_id FROM contest_solved WHERE "
"contest_id=:cid AND problem_id=:pid)"),
point_change=point_diff, cid=contest_id, pid=problem_id)
db.execute("COMMIT")
if transact:
db.execute("COMMIT")


def contest_exists(contest_id):
Expand Down Expand Up @@ -369,35 +371,56 @@ def rejudge_contest_problem(contest_id, problem_id, new_flag):
"""
Rejudges a contest problem
"""
db.execute("BEGIN")
data = db.execute(
"SELECT * FROM contest_problems WHERE contest_id=:cid AND problem_id=:pid",
cid=contest_id, pid=problem_id)[0]
"SELECT * FROM contest_problems WHERE contest_id=? AND problem_id=?",
contest_id, problem_id)[0]

# Reset all previously correct submissions
db.execute(("UPDATE contest_users SET points=points-:points WHERE user_id IN (SELECT "
"user_id FROM contest_solved WHERE contest_id=:cid AND problem_id=:pid)"),
points=data["point_value"], cid=contest_id, pid=problem_id)
affected_users = [x["user_id"] for x in db.execute(
"SELECT user_id FROM contest_solved WHERE contest_id=? AND problem_id=?",
contest_id, problem_id
)]
db.execute("UPDATE contest_users SET points=points-? WHERE user_id IN (?)",
data["point_value"], affected_users)
db.execute(
"UPDATE submissions SET correct=0 WHERE contest_id=:cid AND problem_id=:pid",
cid=contest_id, pid=problem_id)
db.execute("DELETE FROM contest_solved WHERE contest_id=:cid AND problem_id=:pid",
cid=contest_id, pid=problem_id)
"UPDATE submissions SET correct=0 WHERE contest_id=? AND problem_id=?",
contest_id, problem_id)
db.execute("DELETE FROM contest_solved WHERE contest_id=? AND problem_id=?",
contest_id, problem_id)
if data["score_users"] >= 0: # Reset dynamic scoring
update_dyn_score(contest_id, problem_id, update_curr_user=False)
update_dyn_score(contest_id, problem_id, False, False)

# Set all new correct submissions
db.execute(("UPDATE submissions SET correct=1 WHERE contest_id=:cid AND "
"problem_id=:pid AND submitted=:flag"),
cid=contest_id, pid=problem_id, flag=new_flag)
db.execute(("UPDATE submissions SET correct=1 WHERE contest_id=? AND "
"problem_id=? AND submitted=?"),
contest_id, problem_id, new_flag)
affected_users += [x["user_id"] for x in db.execute(
("SELECT DISTINCT user_id FROM submissions WHERE contest_id=? AND "
"problem_id=? AND correct=1"),
contest_id, problem_id)]
affected_users = list(set(affected_users)) # Remove duplicates
db.execute(("INSERT INTO contest_solved (user_id, contest_id, problem_id) "
"SELECT DISTINCT user_id, contest_id, problem_id FROM submissions WHERE "
"contest_id=:cid AND problem_id=:pid AND correct=1"),
cid=contest_id, pid=problem_id)
"contest_id=? AND problem_id=? AND correct=1"),
contest_id, problem_id)
if data["score_users"] == -1: # Instructions for static scoring
old_points = data["point_value"]
else: # Instructions for dynamic scoring
old_points = data["score_max"]
update_dyn_score(contest_id, problem_id, update_curr_user=False)
db.execute(("UPDATE contest_users SET points=points+:points WHERE user_id IN (SELECT "
"user_id FROM contest_solved WHERE contest_id=:cid AND problem_id=:pid)"),
points=old_points, cid=contest_id, pid=problem_id)
update_dyn_score(contest_id, problem_id, False, False)
db.execute(("UPDATE contest_users SET points=points+? WHERE user_id IN (SELECT "
"user_id FROM contest_solved WHERE contest_id=? AND problem_id=?)"),
old_points, contest_id, problem_id)
db.execute("UPDATE contest_users SET lastAC=NULL WHERE user_id IN (?)",
affected_users)
new_lastAC = db.execute(
("SELECT user_id, max(firstAC) AS lastAC FROM ("
"SELECT user_id, min(date) AS firstAC, problem_id FROM submissions WHERE "
"contest_id=? AND correct=1 GROUP BY user_id, problem_id ORDER BY user_id ASC) "
"GROUP BY user_id"), contest_id)
for entry in new_lastAC:
# sqlite3 < 3.33 doesn't support UPDATE FROM
db.execute("UPDATE contest_users SET lastAC=? WHERE user_id=?",
entry["lastAC"], entry["user_id"])
db.execute("COMMIT")
148 changes: 147 additions & 1 deletion src/tests/test_contest.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,6 @@ def test_contest(client, database):
'users_point_value': 0,
'category': 'general',
'flag': 'ctf{hello}',
'draft': False,
'file': ('test_upload.txt', 'test_upload.txt')
})
os.remove("test_upload.txt")
Expand Down Expand Up @@ -349,3 +348,150 @@ def test_contest(client, database):
shutil.rmtree('dl')
os.mkdir('dl')
shutil.rmtree('metadata/problems/testingcontest-helloworldtesting')


def test_contest_rejudge(client, database):
"""
Test rejudge behavior because of how complex it is
"""

# Set up users
database.execute(
("INSERT INTO 'users' VALUES(1, 'admin', 'pbkdf2:sha256:150000$XoLKRd3I$"
"2dbdacb6a37de2168298e419c6c54e768d242aee475aadf1fa9e6c30aa02997f', 'e1', "
"datetime('now'), 0, 1, 0, NULL, 0, 0, 0)"))
database.execute("INSERT INTO user_perms VALUES(1, ?)", USER_PERM["ADMIN"])
client.post('/login', data={'username': 'admin', 'password': 'CTFOJadmin'})

# Set up contest
result = client.post('/contests/create', data={
'contest_id': 'testingcontest',
'contest_name': 'Testing Contest',
'start': datetime.strftime(datetime.now(), "%Y-%m-%dT%H:%M:%S.%fZ"),
'end': datetime.strftime(datetime.now() + timedelta(600), "%Y-%m-%dT%H:%M:%S.%fZ"), # noqa E501
'description': 'testing contest description',
'scoreboard_visible': True
}, follow_redirects=True)
assert result.status_code == 200
assert b'Testing Contest' in result.data

file = open("test_upload.txt", "w")
file.write('ree')
file.close()
result = client.post('/contest/testingcontest/addproblem', data={
'id': 'static',
'name': 'static',
'description': 'static',
'hints': 'static',
'point_value': 1,
'category': 'general',
'flag': 'ctf{hello}',
'flag_hint': 'ctf{...}',
'file': ('test_upload.txt', 'test_upload.txt')
})
assert result.status_code == 302

result = client.post('/contest/testingcontest/addproblem', data={
'id': 'dynamic',
'name': 'dynamic',
'description': 'dynamic',
'hints': 'dynamic',
'score_type': 'dynamic',
'min_point_value': 1,
'max_point_value': 500,
'users_point_value': 1,
'category': 'general',
'flag': 'ctf{hello}',
'flag_hint': 'ctf{...}',
'file': ('test_upload.txt', 'test_upload.txt')
})
assert result.status_code == 302
os.remove('test_upload.txt')

# WA --> First AC
result = client.post('/contest/testingcontest/problem/dynamic', data={
'flag': 'ctf{wrong}'
})
assert result.status_code == 200

result = client.get('/contest/testingcontest/scoreboard')
assert result.status_code == 200
assert b'None' in result.data

result = client.post('/contest/testingcontest/problem/dynamic/edit', data={
'name': 'dynamic',
'description': 'dynamic is fun',
'category': 'web',
'flag': 'ctf{wrong}',
'rejudge': True,
'file': ('fake_empty_file', ''),
}, follow_redirects=True)
assert result.status_code == 200
assert b'successfully' in result.data

result1 = client.get('/contest/testingcontest/scoreboard')
assert result1.status_code == 200
assert b'None' not in result1.data

# First AC --> WA
result = client.post('/contest/testingcontest/problem/dynamic/edit', data={
'name': 'dynamic',
'description': 'dynamic is fun',
'category': 'web',
'flag': 'ctf{hello}',
'rejudge': True,
'file': ('fake_empty_file', ''),
}, follow_redirects=True)
assert result.status_code == 200
assert b'successfully' in result.data

result = client.get('/contest/testingcontest/scoreboard')
assert result.status_code == 200
assert b'None' in result.data

# WA --> AC and AC --> WA
result = client.post('/contest/testingcontest/problem/static', data={
'flag': 'ctf{hello}'
})
assert result.status_code == 200

result2 = client.get('/contest/testingcontest/scoreboard')
assert result2.status_code == 200
assert b'None' not in result2.data

result = client.post('/contest/testingcontest/problem/dynamic/edit', data={
'name': 'dynamic',
'description': 'dynamic is fun',
'category': 'web',
'flag': 'ctf{wrong}',
'rejudge': True,
'file': ('fake_empty_file', ''),
}, follow_redirects=True)
assert result.status_code == 200
assert b'successfully' in result.data

result = client.get('/contest/testingcontest/scoreboard')
assert result.status_code == 200
assert b'None' not in result.data
assert result.data.replace(b'501', b'1') == result2.data # Check points and last AC


result = client.post('/contest/testingcontest/problem/static/edit', data={
'name': 'static',
'description': 'static is fun',
'category': 'web',
'point_value': 1,
'flag': 'ctf{wrong}',
'rejudge': True,
'file': ('fake_empty_file', ''),
}, follow_redirects=True)
assert result.status_code == 200
assert b'successfully' in result.data

result = client.get('/contest/testingcontest/scoreboard')
assert result.status_code == 200
assert b'None' not in result.data
assert result.data == result1.data # Check points and last AC

client.post('/contest/testingcontest/delete', follow_redirects=True)
assert result.status_code == 200

Check warning on line 497 in src/tests/test_contest.py

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

W292 no newline at end of file

Check warning on line 497 in src/tests/test_contest.py

View workflow job for this annotation

GitHub Actions / build (windows-latest)

W292 no newline at end of file

0 comments on commit ef26afd

Please sign in to comment.