From 8cebcedfa2aa2f8e7508d5ebec37595bfe7bd156 Mon Sep 17 00:00:00 2001
From: Nguyen Viet Dung <29406816+magnified103@users.noreply.github.com>
Date: Fri, 26 Jul 2024 13:06:01 +0700
Subject: [PATCH] Protect stateful admin endpoints against CSRF
---
judge/admin/contest.py | 7 ++++++-
judge/admin/runtime.py | 5 +++++
judge/admin/submission.py | 3 +++
templates/admin/base_site.html | 15 +++++++++++++++
templates/admin/judge/contest/change_form.html | 2 +-
templates/admin/judge/contest/change_list.html | 2 +-
templates/admin/judge/judge/change_form.html | 8 ++++----
templates/admin/judge/submission/change_form.html | 2 +-
8 files changed, 36 insertions(+), 8 deletions(-)
create mode 100644 templates/admin/base_site.html
diff --git a/judge/admin/contest.py b/judge/admin/contest.py
index f6e45b2f05..0d8c02551c 100644
--- a/judge/admin/contest.py
+++ b/judge/admin/contest.py
@@ -8,8 +8,10 @@
from django.shortcuts import get_object_or_404
from django.urls import path, reverse, reverse_lazy
from django.utils import timezone
+from django.utils.decorators import method_decorator
from django.utils.html import format_html
from django.utils.translation import gettext_lazy as _, ngettext
+from django.views.decorators.http import require_POST
from reversion.admin import VersionAdmin
from django_ace import AceWidget
@@ -73,7 +75,7 @@ class ContestProblemInline(SortableInlineAdminMixin, admin.TabularInline):
def rejudge_column(self, obj):
if obj.id is None:
return ''
- return format_html('{1}',
+ return format_html('{1}',
reverse('admin:judge_contest_rejudge', args=(obj.contest.id, obj.id)), _('Rejudge'))
@@ -272,6 +274,7 @@ def get_urls(self):
path('/judge//', self.rejudge_view, name='judge_contest_rejudge'),
] + super(ContestAdmin, self).get_urls()
+ @method_decorator(require_POST)
def rejudge_view(self, request, contest_id, problem_id):
contest = get_object_or_404(Contest, id=contest_id)
if not self.has_change_permission(request, contest):
@@ -285,6 +288,7 @@ def rejudge_view(self, request, contest_id, problem_id):
len(queryset)) % len(queryset))
return HttpResponseRedirect(reverse('admin:judge_contest_change', args=(contest_id,)))
+ @method_decorator(require_POST)
def rate_all_view(self, request):
if not request.user.has_perm('judge.contest_rating'):
raise PermissionDenied()
@@ -296,6 +300,7 @@ def rate_all_view(self, request):
rate_contest(contest)
return HttpResponseRedirect(reverse('admin:judge_contest_changelist'))
+ @method_decorator(require_POST)
def rate_view(self, request, id):
if not request.user.has_perm('judge.contest_rating'):
raise PermissionDenied()
diff --git a/judge/admin/runtime.py b/judge/admin/runtime.py
index 12e0798269..db5b271b38 100644
--- a/judge/admin/runtime.py
+++ b/judge/admin/runtime.py
@@ -4,9 +4,11 @@
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.urls import path, reverse
+from django.utils.decorators import method_decorator
from django.utils.html import format_html
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _
+from django.views.decorators.http import require_POST
from reversion.admin import VersionAdmin
from django_ace import AceWidget
@@ -85,18 +87,21 @@ def disconnect_judge(self, id, force=False):
judge.disconnect(force=force)
return HttpResponseRedirect(reverse('admin:judge_judge_changelist'))
+ @method_decorator(require_POST)
def disconnect_view(self, request, id):
judge = get_object_or_404(Judge, id=id)
if not self.has_change_permission(request, judge):
raise PermissionDenied()
return self.disconnect_judge(id)
+ @method_decorator(require_POST)
def terminate_view(self, request, id):
judge = get_object_or_404(Judge, id=id)
if not self.has_change_permission(request, judge):
raise PermissionDenied()
return self.disconnect_judge(id, force=True)
+ @method_decorator(require_POST)
def disable_view(self, request, id):
judge = get_object_or_404(Judge, id=id)
if not self.has_change_permission(request, judge):
diff --git a/judge/admin/submission.py b/judge/admin/submission.py
index aa3889d304..ba2fcf229e 100644
--- a/judge/admin/submission.py
+++ b/judge/admin/submission.py
@@ -9,8 +9,10 @@
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.urls import path, reverse
+from django.utils.decorators import method_decorator
from django.utils.html import format_html
from django.utils.translation import gettext, gettext_lazy as _, ngettext, pgettext
+from django.views.decorators.http import require_POST
from reversion.admin import VersionAdmin
from django_ace import AceWidget
@@ -250,6 +252,7 @@ def get_urls(self):
path('/judge/', self.judge_view, name='judge_submission_rejudge'),
] + super(SubmissionAdmin, self).get_urls()
+ @method_decorator(require_POST)
def judge_view(self, request, id):
if not request.user.has_perm('judge.rejudge_submission') or not request.user.has_perm('judge.edit_own_problem'):
raise PermissionDenied()
diff --git a/templates/admin/base_site.html b/templates/admin/base_site.html
new file mode 100644
index 0000000000..c5c2e2ca1d
--- /dev/null
+++ b/templates/admin/base_site.html
@@ -0,0 +1,15 @@
+{% extends "admin/base_site.html" %}
+
+{% block pretitle %}{{ block.super }}
+
+
+{% endblock %}
diff --git a/templates/admin/judge/contest/change_form.html b/templates/admin/judge/contest/change_form.html
index 7f540f654b..1a692032ef 100644
--- a/templates/admin/judge/contest/change_form.html
+++ b/templates/admin/judge/contest/change_form.html
@@ -15,7 +15,7 @@
{% block after_field_sets %}{{ block.super }}
{% if original and original.is_rated and original.ended and perms.judge.contest_rating %}
+ class="button rerate-link action-link">
{% trans "Rate" %}
diff --git a/templates/admin/judge/contest/change_list.html b/templates/admin/judge/contest/change_list.html
index ae409fc83c..6095ef3030 100644
--- a/templates/admin/judge/contest/change_list.html
+++ b/templates/admin/judge/contest/change_list.html
@@ -5,7 +5,7 @@
{{ block.super }}
{% if not is_popup and perms.judge.contest_rating %}
-
+
{% trans "Rate all ratable contests" %}
diff --git a/templates/admin/judge/judge/change_form.html b/templates/admin/judge/judge/change_form.html
index 738d8a5c33..a97af4a16d 100644
--- a/templates/admin/judge/judge/change_form.html
+++ b/templates/admin/judge/judge/change_form.html
@@ -14,24 +14,24 @@
{% block after_field_sets %}{{ block.super }}
{% if original %}
+ class="button disconnect-link action-link">
{% trans "Disconnect" %}
+ class="button terminate-link action-link">
{% trans "Terminate" %}
{% if not original.is_disabled %}
+ class="button disable-link action-link">
{% trans "Disable" %}
{% else %}
+ class="button disable-link action-link">
{% trans "Enable" %}
diff --git a/templates/admin/judge/submission/change_form.html b/templates/admin/judge/submission/change_form.html
index 0b5e5bf6c0..96ebba97bf 100644
--- a/templates/admin/judge/submission/change_form.html
+++ b/templates/admin/judge/submission/change_form.html
@@ -12,7 +12,7 @@
{% block after_field_sets %}{{ block.super }}
{% if original and not original.is_locked %}
+ class="button rejudgelink action-link">
{% trans "Rejudge" %}