Skip to content
Merged
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
26 changes: 1 addition & 25 deletions cms/server/contest/handlers/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,15 @@

"""

from collections.abc import Callable
import functools
import ipaddress
import logging
import typing

from cms.db.submission import Submission
from cms.server import multi_contest
from cms.server.contest.authentication import validate_login
from cms.server.contest.submission import \
UnacceptableSubmission, accept_submission
from .contest import ContestHandler
from .contest import ContestHandler, api_login_required
from ..phase_management import actual_phase_required

logger = logging.getLogger(__name__)
Expand All @@ -47,27 +44,6 @@ def __init__(self, *args, **kwargs):
self.api_request = True


_P = typing.ParamSpec("_P")
_R = typing.TypeVar("_R")
_Self = typing.TypeVar("_Self", bound="ApiContestHandler")

def api_login_required(
func: Callable[typing.Concatenate[_Self, _P], _R],
) -> Callable[typing.Concatenate[_Self, _P], _R | None]:
"""A decorator filtering out unauthenticated requests.

"""

@functools.wraps(func)
def wrapped(self: _Self, *args: _P.args, **kwargs: _P.kwargs):
if not self.current_user:
self.json({"error": "An authenticated user is required"}, 403)
else:
return func(self, *args, **kwargs)

return wrapped


class ApiLoginHandler(ApiContestHandler):
"""Login handler.

Expand Down
26 changes: 26 additions & 0 deletions cms/server/contest/handlers/contest.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,12 @@

"""

from collections.abc import Callable
import functools
import ipaddress
import json
import logging
import typing

import collections

Expand Down Expand Up @@ -334,3 +337,26 @@ def check_xsrf_cookie(self):

class FileHandler(ContestHandler, FileHandlerMixin):
pass

_P = typing.ParamSpec("_P")
_R = typing.TypeVar("_R")
_Self = typing.TypeVar("_Self", bound="ContestHandler")

def api_login_required(
func: Callable[typing.Concatenate[_Self, _P], _R],
) -> Callable[typing.Concatenate[_Self, _P], _R | None]:
"""A decorator filtering out unauthenticated requests.

Unlike @tornado.web.authenticated, this returns a JSON error instead of
redirecting.

"""

@functools.wraps(func)
def wrapped(self: _Self, *args: _P.args, **kwargs: _P.kwargs):
if not self.current_user:
self.json({"error": "An authenticated user is required"}, 403)
else:
return func(self, *args, **kwargs)

return wrapped
4 changes: 2 additions & 2 deletions cms/server/contest/handlers/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
UnacceptablePrintJob
from cmscommon.crypto import hash_password, validate_password
from cmscommon.datetime import make_datetime, make_timestamp
from .contest import ContestHandler
from .contest import ContestHandler, api_login_required
from ..phase_management import actual_phase_required


Expand Down Expand Up @@ -294,7 +294,7 @@ class NotificationsHandler(ContestHandler):

refresh_cookie = False

@tornado.web.authenticated
@api_login_required
@multi_contest
def get(self):
participation: Participation = self.current_user
Expand Down
6 changes: 3 additions & 3 deletions cms/server/contest/handlers/tasksubmission.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
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
from .contest import ContestHandler, FileHandler, api_login_required
from ..phase_management import actual_phase_required


Expand Down Expand Up @@ -236,7 +236,7 @@ def add_task_score(self, participation: Participation, task: Task, data: dict):
data["task_tokened_score"], score_type.max_score, None,
task.score_precision, translation=self.translation)

@tornado.web.authenticated
@api_login_required
@actual_phase_required(0, 1, 2, 3, 4)
@multi_contest
def get(self, task_name, opaque_id):
Expand Down Expand Up @@ -296,7 +296,7 @@ class SubmissionDetailsHandler(ContestHandler):

refresh_cookie = False

@tornado.web.authenticated
@api_login_required
@actual_phase_required(0, 1, 2, 3, 4)
@multi_contest
def get(self, task_name, opaque_id):
Expand Down
6 changes: 3 additions & 3 deletions cms/server/contest/handlers/taskusertest.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
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
from .contest import ContestHandler, FileHandler, api_login_required
from ..phase_management import actual_phase_required


Expand Down Expand Up @@ -166,7 +166,7 @@ class UserTestStatusHandler(ContestHandler):

refresh_cookie = False

@tornado.web.authenticated
@api_login_required
@actual_phase_required(0)
@multi_contest
def get(self, task_name, user_test_num):
Expand Down Expand Up @@ -221,7 +221,7 @@ class UserTestDetailsHandler(ContestHandler):

refresh_cookie = False

@tornado.web.authenticated
@api_login_required
@actual_phase_required(0)
@multi_contest
def get(self, task_name, user_test_num):
Expand Down
6 changes: 5 additions & 1 deletion cms/server/contest/templates/task_submissions.html
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,11 @@
var modal = $("#submission_detail");
var modal_body = modal.children(".modal-body");
modal_body.html('<div class="loading"><img src="{{ url("static", "loading.gif") }}"/>{% trans %}loading...{% endtrans %}</div>');
modal_body.load(utils.contest_url("tasks", "{{ task.name }}", "submissions", submission_id, "details"), function() {
modal_body.load(utils.contest_url("tasks", "{{ task.name }}", "submissions", submission_id, "details"), function(response, status, xhr) {
if(status != "success") {
$(this).html("{% trans %}Error loading details, please refresh the page.{% endtrans %}");
return;
}
$(".score_details .subtask .subtask-head").each(function () {
$(this).prepend("<i class=\"icon-chevron-right\"></i>");
});
Expand Down
6 changes: 5 additions & 1 deletion cms/server/contest/templates/test_interface.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@
var modal = $("#user_test_detail");
var modal_body = modal.children(".modal-body");
modal_body.html('<div class="loading"><img src="{{ url("static", "loading.gif") }}"/>{% trans %}loading...{% endtrans %}</div>');
modal_body.load(utils.contest_url("tasks", task_id, "tests", user_test_id, "details"));
modal_body.load(utils.contest_url("tasks", task_id, "tests", user_test_id, "details"), function(response, status, xhr) {
if(status != "success") {
$(this).html("{% trans %}Error loading details, please refresh the page.{% endtrans %}");
}
});
modal.modal("show");
});

Expand Down