Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cms/db/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@

# Instantiate or import these objects.

version = 46
version = 47

engine = create_engine(config.database.url, echo=config.database.debug,
pool_timeout=60, pool_recycle=120)
Expand Down
20 changes: 0 additions & 20 deletions cms/db/submission.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
"""

from datetime import datetime
import random
from sqlalchemy import Boolean
from sqlalchemy.dialects.postgresql import ARRAY, JSONB
from sqlalchemy.orm import relationship
Expand Down Expand Up @@ -187,25 +186,6 @@ def tokened(self) -> bool:
"""
return self.token is not None

@classmethod
def generate_opaque_id(cls, session, participation_id):
randint_upper_bound = 2**63-1

opaque_id = random.randint(0, randint_upper_bound)

# Note that in theory this may cause the transaction to fail by
# generating a non-actually-unique ID. This is however extremely
# unlikely (prob. ~num_parallel_submissions_per_contestant^2/2**63).
while (session
.query(Submission)
.filter(Submission.participation_id == participation_id)
.filter(Submission.opaque_id == opaque_id)
.first()
is not None):
opaque_id = random.randint(0, randint_upper_bound)

return opaque_id


class File(Base):
"""Class to store information about one file submitted within a
Expand Down
8 changes: 8 additions & 0 deletions cms/db/usertest.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,20 @@ class UserTest(Base):

"""
__tablename__ = 'user_tests'
__table_args__ = (
UniqueConstraint("participation_id", "opaque_id"),
)

# Auto increment primary key.
id: int = Column(
Integer,
primary_key=True)

# Opaque ID to be used to refer to this user test.
opaque_id: int = Column(
BigInteger,
nullable=False)

# User and Contest, thus Participation (id and object) that did the
# submission.
participation_id: int = Column(
Expand Down
22 changes: 22 additions & 0 deletions cms/db/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

"""

import random
import sys
import logging

Expand Down Expand Up @@ -372,3 +373,24 @@ def enumerate_files(
digests = set(r[0] for r in session.execute(union(*queries)))
digests.discard(Digest.TOMBSTONE)
return digests

def generate_opaque_id(
cls: type[Submission | UserTest], session: Session, participation_id: int
):
randint_upper_bound = 2**63-1

opaque_id = random.randint(0, randint_upper_bound)

# Note that in theory this may cause the transaction to fail by
# generating a non-actually-unique ID. This is however extremely
# unlikely (prob. ~num_parallel_submissions_per_contestant^2/2**63).
while (
session.query(cls)
.filter(cls.participation_id == participation_id)
.filter(cls.opaque_id == opaque_id)
.first()
is not None
):
opaque_id = random.randint(0, randint_upper_bound)

return opaque_id
3 changes: 1 addition & 2 deletions cms/server/admin/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
from werkzeug.wrappers import Request, Response

from cms import config
from cmscommon.binary import hex_to_bin
from cmscommon.datetime import make_timestamp


Expand Down Expand Up @@ -130,7 +129,7 @@ def wsgi_app(self, environ: dict, start_response: Callable):
self._local.request = Request(environ)
self._local.cookie = JSONSecureCookie.load_cookie(
self._request, AWSAuthMiddleware.COOKIE,
hex_to_bin(config.web_server.secret_key))
bytes.fromhex(config.web_server.secret_key))
self._verify_cookie()

def my_start_response(status, headers, exc_info=None):
Expand Down
4 changes: 2 additions & 2 deletions cms/server/admin/handlers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,15 +212,15 @@

# Submissions

(r"/submission/([0-9]+)(?:/([0-9]+))?", SubmissionHandler),
(r"/submission/((?:opaque_)?[0-9]+)(?:/([0-9]+))?", SubmissionHandler),
(r"/submission/([0-9]+)(?:/([0-9]+))?/comment", SubmissionCommentHandler),
(r"/submission/([0-9]+)(?:/([0-9]+))?/official", SubmissionOfficialStatusHandler),
(r"/submission_file/([0-9]+)", SubmissionFileHandler),
(r"/submission_diff/([0-9]+)/([0-9]+)", SubmissionDiffHandler),

# User tests

(r"/user_test/([0-9]+)(?:/([0-9]+))?", UserTestHandler),
(r"/user_test/((?:opaque_)?[0-9]+)(?:/([0-9]+))?", UserTestHandler),
(r"/user_test_file/([0-9]+)", UserTestFileHandler),

# The following prefixes are handled by WSGI middlewares:
Expand Down
23 changes: 21 additions & 2 deletions cms/server/admin/handlers/submission.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@
import logging
import difflib

import collections
try:
collections.MutableMapping
except:
# Monkey-patch: Tornado 4.5.3 does not work on Python 3.11 by default
collections.MutableMapping = collections.abc.MutableMapping

import tornado.web

from cms.db import Dataset, File, Submission
from cms.grading.languagemanager import safe_get_lang_filename
from cmscommon.datetime import make_datetime
Expand All @@ -47,8 +56,18 @@ class SubmissionHandler(BaseHandler):

"""
@require_permission(BaseHandler.AUTHENTICATED)
def get(self, submission_id, dataset_id=None):
submission = self.safe_get_item(Submission, submission_id)
def get(self, submission_id: str, dataset_id=None):
if submission_id.startswith("opaque_"):
oid = int(submission_id.removeprefix("opaque_"))
submission = (
self.sql_session.query(Submission)
.filter(Submission.opaque_id == oid)
.first()
)
if submission is None:
raise tornado.web.HTTPError(404)
else:
submission = self.safe_get_item(Submission, submission_id)
task = submission.task
self.contest = task.contest

Expand Down
23 changes: 21 additions & 2 deletions cms/server/admin/handlers/usertest.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@

"""

import collections
try:
collections.MutableMapping
except:
# Monkey-patch: Tornado 4.5.3 does not work on Python 3.11 by default
collections.MutableMapping = collections.abc.MutableMapping

import tornado.web

from cms.db import Dataset, UserTestFile, UserTest
from cms.grading.languagemanager import safe_get_lang_filename

Expand All @@ -29,8 +38,18 @@
class UserTestHandler(BaseHandler):
"""Shows the details of a user test."""
@require_permission(BaseHandler.AUTHENTICATED)
def get(self, user_test_id, dataset_id=None):
user_test = self.safe_get_item(UserTest, user_test_id)
def get(self, user_test_id: str, dataset_id=None):
if user_test_id.startswith("opaque_"):
oid = int(user_test_id.removeprefix("opaque_"))
user_test = (
self.sql_session.query(UserTest)
.filter(UserTest.opaque_id == oid)
.first()
)
if user_test is None:
raise tornado.web.HTTPError(404)
else:
user_test = self.safe_get_item(UserTest, user_test_id)
task = user_test.task
self.contest = task.contest

Expand Down
3 changes: 1 addition & 2 deletions cms/server/admin/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
from cms.db import SessionGen, Dataset, Submission, SubmissionResult, Task
from cms.io import WebService, rpc_method
from cms.service import EvaluationService
from cmscommon.binary import hex_to_bin
from .authentication import AWSAuthMiddleware
from .handlers import HANDLERS
from .jinja2_toolbox import AWS_ENVIRONMENT
Expand All @@ -52,7 +51,7 @@ def __init__(self, shard: int):
parameters = {
"static_files": [("cms.server", "static"),
("cms.server.admin", "static")],
"cookie_secret": hex_to_bin(config.web_server.secret_key),
"cookie_secret": bytes.fromhex(config.web_server.secret_key),
"debug": config.web_server.tornado_debug,
"num_proxies_used": config.admin_web_server.num_proxies_used,
"auth_middleware": AWSAuthMiddleware,
Expand Down
22 changes: 8 additions & 14 deletions cms/server/contest/handlers/contest.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,15 +263,12 @@ def get_task(self, task_name: str) -> Task | None:
.one_or_none()

def get_submission(self, task: Task, opaque_id: str | int) -> Submission | None:
"""Return the num-th contestant's submission on the given task.
"""Return a contestant's specific submission on the given task.

task: a task for the contest that is being served.
submission_num: a positive number, in decimal encoding.
opaque_id: submission's opaque_id

return: the submission_num-th submission
(1-based), in chronological order, that was sent by the
currently logged in contestant on the given task (None if
not found).
return: Submission with this opaque_id, or None if not found.

"""
return self.sql_session.query(Submission) \
Expand All @@ -280,22 +277,19 @@ def get_submission(self, task: Task, opaque_id: str | int) -> Submission | None:
.filter(Submission.opaque_id == int(opaque_id)) \
.first()

def get_user_test(self, task: Task, user_test_num: int) -> UserTest | None:
"""Return the num-th contestant's test on the given task.
def get_user_test(self, task: Task, opaque_id: str | int) -> UserTest | None:
"""Return a contestant's specific user test on the given task.

task: a task for the contest that is being served.
user_test_num: a positive number, in decimal encoding.
opaque_id: user test's opaque_id

return: the user_test_num-th user test, in
chronological order, that was sent by the currently logged
in contestant on the given task (None if not found).
return: User test with this opaque_id, or None if not found.

"""
return self.sql_session.query(UserTest) \
.filter(UserTest.participation == self.current_user) \
.filter(UserTest.task == task) \
.order_by(UserTest.timestamp) \
.offset(int(user_test_num) - 1) \
.filter(UserTest.opaque_id == int(opaque_id)) \
.first()

def add_notification(
Expand Down
9 changes: 3 additions & 6 deletions cms/server/contest/handlers/tasksubmission.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@
UnacceptableSubmission, accept_submission
from cms.server.contest.tokening import \
UnacceptableToken, TokenAlreadyPlayed, accept_token, tokens_available
from cmscommon.crypto import encrypt_number
from cmscommon.mimetypes import get_type_for_file_name
from .contest import ContestHandler, FileHandler, api_login_required
from ..phase_management import actual_phase_required
Expand Down Expand Up @@ -109,11 +108,9 @@ def post(self, task_name):
self.notify_success(N_("Submission received"),
N_("Your submission has been received "
"and is currently being evaluated."))
# The argument (encrypted submission id) is not used by CWS
# (nor it discloses information to the user), but it is
# useful for automatic testing to obtain the submission id).
query_args["submission_id"] = \
encrypt_number(submission.id, config.web_server.secret_key)
# The argument is not used by CWS, but it is useful for automatic
# testing to obtain the submission id.
query_args["submission_id"] = submission.opaque_id

self.redirect(self.contest_url("tasks", task.name, "submissions",
**query_args))
Expand Down
21 changes: 9 additions & 12 deletions cms/server/contest/handlers/taskusertest.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@
from cms.server import multi_contest
from cms.server.contest.submission import get_submission_count, \
TestingNotAllowed, UnacceptableUserTest, accept_user_test
from cmscommon.crypto import encrypt_number
from cmscommon.mimetypes import get_type_for_file_name
from .contest import ContestHandler, FileHandler, api_login_required
from ..phase_management import actual_phase_required
Expand Down Expand Up @@ -152,11 +151,9 @@ def post(self, task_name):
self.notify_success(N_("Test received"),
N_("Your test has been received "
"and is currently being executed."))
# The argument (encrypted user test id) is not used by CWS
# (nor it discloses information to the user), but it is
# useful for automatic testing to obtain the user test id).
query_args["user_test_id"] = \
encrypt_number(user_test.id, config.web_server.secret_key)
# The argument is not used by CWS, but it is useful for automatic
# testing to obtain the user test id.
query_args["user_test_id"] = user_test.opaque_id

self.redirect(self.contest_url("testing", task_name=task.name,
**query_args))
Expand Down Expand Up @@ -224,15 +221,15 @@ class UserTestDetailsHandler(ContestHandler):
@api_login_required
@actual_phase_required(0)
@multi_contest
def get(self, task_name, user_test_num):
def get(self, task_name, opaque_id):
if not self.r_params["testing_enabled"]:
raise tornado.web.HTTPError(404)

task = self.get_task(task_name)
if task is None:
raise tornado.web.HTTPError(404)

user_test = self.get_user_test(task, user_test_num)
user_test = self.get_user_test(task, opaque_id)
if user_test is None:
raise tornado.web.HTTPError(404)

Expand All @@ -249,15 +246,15 @@ class UserTestIOHandler(FileHandler):
@tornado.web.authenticated
@actual_phase_required(0)
@multi_contest
def get(self, task_name, user_test_num, io):
def get(self, task_name, opaque_id, io):
if not self.r_params["testing_enabled"]:
raise tornado.web.HTTPError(404)

task = self.get_task(task_name)
if task is None:
raise tornado.web.HTTPError(404)

user_test = self.get_user_test(task, user_test_num)
user_test = self.get_user_test(task, opaque_id)
if user_test is None:
raise tornado.web.HTTPError(404)

Expand All @@ -283,15 +280,15 @@ class UserTestFileHandler(FileHandler):
@tornado.web.authenticated
@actual_phase_required(0)
@multi_contest
def get(self, task_name, user_test_num, filename):
def get(self, task_name, opaque_id, filename):
if not self.r_params["testing_enabled"]:
raise tornado.web.HTTPError(404)

task = self.get_task(task_name)
if task is None:
raise tornado.web.HTTPError(404)

user_test = self.get_user_test(task, user_test_num)
user_test = self.get_user_test(task, opaque_id)
if user_test is None:
raise tornado.web.HTTPError(404)

Expand Down
3 changes: 1 addition & 2 deletions cms/server/contest/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@
from cms.io import WebService
from cms.locale import get_translations
from cms.server.contest.jinja2_toolbox import CWS_ENVIRONMENT
from cmscommon.binary import hex_to_bin
from .handlers import HANDLERS
from .handlers.base import ContestListHandler
from .handlers.main import MainHandler
Expand All @@ -66,7 +65,7 @@ def __init__(self, shard: int, contest_id: int | None = None):
parameters = {
"static_files": [("cms.server", "static"),
("cms.server.contest", "static")],
"cookie_secret": hex_to_bin(config.web_server.secret_key),
"cookie_secret": bytes.fromhex(config.web_server.secret_key),
"debug": config.web_server.tornado_debug,
"is_proxy_used": None,
"num_proxies_used": config.contest_web_server.num_proxies_used,
Expand Down
Loading