Protect stateful admin endpoints against CSRF

This commit is contained in:
Nguyen Viet Dung 2024-07-26 13:06:01 +07:00 committed by Quantum
parent 2b36e045c7
commit 8cebcedfa2
8 changed files with 36 additions and 8 deletions

View File

@ -8,8 +8,10 @@ from django.http import Http404, HttpResponseRedirect
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('<a class="button rejudge-link" href="{0}">{1}</a>',
return format_html('<a class="button rejudge-link action-link" href="{0}">{1}</a>',
reverse('admin:judge_contest_rejudge', args=(obj.contest.id, obj.id)), _('Rejudge'))
@ -272,6 +274,7 @@ class ContestAdmin(NoBatchDeleteMixin, VersionAdmin):
path('<int:contest_id>/judge/<int:problem_id>/', 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 @@ class ContestAdmin(NoBatchDeleteMixin, VersionAdmin):
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 @@ class ContestAdmin(NoBatchDeleteMixin, VersionAdmin):
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()

View File

@ -4,9 +4,11 @@ from django.forms import ModelForm, TextInput
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 @@ class JudgeAdmin(VersionAdmin):
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):

View File

@ -9,8 +9,10 @@ from django.db.models import Q
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 @@ class SubmissionAdmin(VersionAdmin):
path('<int:id>/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()

View File

@ -0,0 +1,15 @@
{% extends "admin/base_site.html" %}
{% block pretitle %}{{ block.super }}
<form style="display: none" id="empty-action-form" method="post">
{% csrf_token %}
</form>
<script>
django.jQuery(function ($) {
$('.action-link').on('click', function () {
$('#empty-action-form').attr('action', this.href).trigger('submit');
return false;
});
});
</script>
{% endblock %}

View File

@ -15,7 +15,7 @@
{% block after_field_sets %}{{ block.super }}
{% if original and original.is_rated and original.ended and perms.judge.contest_rating %}
<a style="display: none" title="{% trans "Rate" %}" href="{% url 'admin:judge_contest_rate' original.pk %}"
class="button rerate-link">
class="button rerate-link action-link">
<i class="fa fa-lg fa-signal"></i>
<span class="text">{% trans "Rate" %}</span>
</a>

View File

@ -5,7 +5,7 @@
{{ block.super }}
{% if not is_popup and perms.judge.contest_rating %}
<li>
<a href="{% url 'admin:judge_contest_rate_all' %}" class="ratealllink">
<a href="{% url 'admin:judge_contest_rate_all' %}" class="ratealllink action-link">
<i class="fa fa-signal"></i> {% trans "Rate all ratable contests" %}
</a>
</li>

View File

@ -14,24 +14,24 @@
{% block after_field_sets %}{{ block.super }}
{% if original %}
<a style="display: none" title="{% trans "Disconnect" %}" href="{% url 'admin:judge_judge_disconnect' original.pk %}"
class="button disconnect-link">
class="button disconnect-link action-link">
<i class="fa fa-lg fa-power-off"></i>
<span class="text">{% trans "Disconnect" %}</span>
</a>
<a style="display: none" title="{% trans "Terminate" %}" href="{% url 'admin:judge_judge_terminate' original.pk %}"
class="button terminate-link">
class="button terminate-link action-link">
<i class="fa fa-lg fa-plug"></i>
<span class="text">{% trans "Terminate" %}</span>
</a>
{% if not original.is_disabled %}
<a style="display: none" title="{% trans "Disable" %}" href="{% url 'admin:judge_judge_disable' original.pk %}"
class="button disable-link">
class="button disable-link action-link">
<i class="fa fa-lg fa-times"></i>
<span class="text">{% trans "Disable" %}</span>
</a>
{% else %}
<a style="display: none" title="{% trans "Enable" %}" href="{% url 'admin:judge_judge_disable' original.pk %}"
class="button disable-link">
class="button disable-link action-link">
<i class="fa fa-lg fa-check"></i>
<span class="text">{% trans "Enable" %}</span>
</a>

View File

@ -12,7 +12,7 @@
{% block after_field_sets %}{{ block.super }}
{% if original and not original.is_locked %}
<a style="display: none" title="{% trans "Rejudge" %}" href="{% url 'admin:judge_submission_rejudge' original.pk %}"
class="button rejudgelink">
class="button rejudgelink action-link">
<i class="fa fa-lg fa-refresh"></i>
<span class="text">{% trans "Rejudge" %}</span>
</a>