diff --git a/oioioi/contests/controllers.py b/oioioi/contests/controllers.py index 7c0df0758..48c1bc6b3 100644 --- a/oioioi/contests/controllers.py +++ b/oioioi/contests/controllers.py @@ -6,7 +6,7 @@ from django.core.exceptions import PermissionDenied from django.core.mail import EmailMessage from django.db import transaction -from django.db.models import Q +from django.db.models import Subquery from django.template.loader import render_to_string from django.urls import reverse from django.utils.safestring import mark_safe @@ -295,8 +295,7 @@ def filter_participants(self, queryset): def filter_users_with_accessible_personal_data(self, queryset): submissions = Submission.objects.filter(problem_instance__contest=self.contest) - authors = [s.user for s in submissions] - return [q for q in queryset if q in authors] + return queryset.filter(id__in=Subquery(submissions.values('user_id'))) class ContestControllerContext(object): diff --git a/oioioi/deployment/settings.py.template b/oioioi/deployment/settings.py.template index 8e20cea92..8888f7e85 100755 --- a/oioioi/deployment/settings.py.template +++ b/oioioi/deployment/settings.py.template @@ -581,3 +581,9 @@ ZEUS_INSTANCES = { # Experimental # USE_ACE_EDITOR = False + +# If set to True, contest admins will be able to log in as participants of contests they admin. +CONTEST_ADMINS_CAN_SU = False + +# If set to False, contest admins switched to a participant will be able to make any type of request. +ALLOW_ONLY_GET_FOR_SU_CONTEST_ADMINS = True diff --git a/oioioi/su/README.rst b/oioioi/su/README.rst index 094f13792..f2be3722c 100644 --- a/oioioi/su/README.rst +++ b/oioioi/su/README.rst @@ -1,2 +1,6 @@ An option for admins to log in as other user for testing purposes. + +You can enable su for contest admins with ``CONTEST_ADMINS_CAN_SU`` option set to ``True`` in ``settings.py``. By default +this option is disabled. You can allow contest admins using su to make other request than `GET` by setting +``ALLOW_ONLY_GET_FOR_SU_CONTEST_ADMINS`` to ``True`` (default ``False``). diff --git a/oioioi/su/__init__.py b/oioioi/su/__init__.py index 1d6266649..f66d8d2fd 100644 --- a/oioioi/su/__init__.py +++ b/oioioi/su/__init__.py @@ -13,3 +13,13 @@ SU_UID_SESSION_KEY = 'su_effective_user_id' SU_BACKEND_SESSION_KEY = 'su_effective_backend' +SU_REAL_USER_IS_SUPERUSER = 'su_real_user_is_superuser' +SU_ORIGINAL_CONTEST = 'su_original_contest' + +BLOCKED_URLS = [ + 'api_token', 'api_regenerate_key', + 'submitservice_view_user_token', 'submitservice_clear_user_token', + 'edit_profile', 'delete_profile', + 'auth_password_change', 'auth_password_done', +] +BLOCKED_URL_NAMESPACES = ['two_factor', 'oioioiadmin'] diff --git a/oioioi/su/middleware.py b/oioioi/su/middleware.py index 15ef020cc..f2cca60bf 100644 --- a/oioioi/su/middleware.py +++ b/oioioi/su/middleware.py @@ -1,10 +1,20 @@ from django.core.exceptions import ImproperlyConfigured, PermissionDenied from django.http.response import HttpResponseForbidden from django.shortcuts import redirect +from django.conf import settings +from django.urls import resolve from oioioi.base.utils.middleware import was_response_generated_by_exception -from oioioi.su import SU_BACKEND_SESSION_KEY, SU_UID_SESSION_KEY -from oioioi.su.utils import get_user +from oioioi.contests.current_contest import contest_re +from oioioi.su import ( + SU_BACKEND_SESSION_KEY, + SU_UID_SESSION_KEY, + SU_REAL_USER_IS_SUPERUSER, + SU_ORIGINAL_CONTEST, + BLOCKED_URL_NAMESPACES, + BLOCKED_URLS +) +from oioioi.su.utils import get_user, reset_to_real_user REDIRECTION_AFTER_SU_KEY = "redirection_after_su" @@ -20,7 +30,9 @@ def __init__(self, get_response): self.get_response = get_response def __call__(self, request): - self._process_request(request) + response = self._process_request(request) + if response: + return response return self.get_response(request) @@ -39,6 +51,45 @@ def _process_request(self, request): request.session[SU_UID_SESSION_KEY], request.session[SU_BACKEND_SESSION_KEY], ) + # Check if the user is contest admin. + if not request.session.get(SU_REAL_USER_IS_SUPERUSER, True): + # Might happen when switching to a user which then becomes a superuser. + if request.user.is_superuser: + reset_to_real_user(request) + return redirect('index') + + original_contest_id = request.session.get(SU_ORIGINAL_CONTEST) + contest_id = None + m = contest_re.match(request.path) + if m is not None: + contest_id = m.group('c_name') + url = resolve(request.path_info) + is_su_reset_url = url.url_name == 'su_reset' + nonoioioi_namespace = url.namespaces == [] + for ns in url.namespaces: + if ns != 'contest' and ns != 'noncontest': + nonoioioi_namespace = True + break + # Redirect if the url is not in the same contest, is not a su reset url and is an url made by oioioi. + # For example, `nonoioioi_namespace` can be True when the url is /jsi18n/ + if ( + not is_su_reset_url and + not nonoioioi_namespace and + (contest_id is None or contest_id != original_contest_id) + ): + return redirect('su_url_not_allowed', contest_id=original_contest_id) + + for ns in url.namespaces: + if ns in BLOCKED_URL_NAMESPACES: + return redirect('su_url_not_allowed', contest_id=original_contest_id) + if url.url_name in BLOCKED_URLS: + return redirect('su_url_not_allowed', contest_id=original_contest_id) + + if ( + not is_su_reset_url and + getattr(settings, 'ALLOW_ONLY_GET_FOR_SU_CONTEST_ADMINS', True) and request.method != 'GET' + ): + return redirect('su_method_not_allowed', contest_id=original_contest_id) class SuFirstTimeRedirectionMiddleware(object): diff --git a/oioioi/su/templates/su/method-not-allowed.html b/oioioi/su/templates/su/method-not-allowed.html new file mode 100644 index 000000000..6d2781462 --- /dev/null +++ b/oioioi/su/templates/su/method-not-allowed.html @@ -0,0 +1,20 @@ +{% extends "simple-centered.html" %} + +{% load i18n %} + +{% block title %}{% trans "SU - Method not allowed" %}{% endblock %} + +{% block content %} + +