Protect pages (not tested)

This commit is contained in:
Yohann D'ANELLO 2021-01-17 12:40:23 +01:00
parent 1e413229a1
commit c151ff3611
Signed by: ynerant
GPG Key ID: 3A75C55819C8CF85
7 changed files with 207 additions and 37 deletions

View File

@ -21,7 +21,7 @@ from magic import Magic
from registration.models import AdminRegistration
from tfjm.lists import get_sympa_client
from tfjm.matrix import Matrix
from tfjm.views import AdminMixin
from tfjm.views import AdminMixin, VolunteerMixin
from .forms import JoinTeamForm, NoteForm, ParticipationForm, PassageForm, PoolForm, PoolTeamsForm, \
RequestValidationForm, TeamForm, TournamentForm, ValidateParticipationForm, SolutionForm, SynthesisForm
@ -156,9 +156,11 @@ class TeamDetailView(LoginRequiredMixin, FormMixin, ProcessFormView, DetailView)
def get(self, request, *args, **kwargs):
user = request.user
self.object = self.get_object()
# Ensure that the user is an admin or a member of the team
# Ensure that the user is an admin or a volunteer or a member of the team
if user.registration.is_admin or user.registration.participates and \
user.registration.team and user.registration.team.pk == kwargs["pk"]:
user.registration.team and user.registration.team.pk == kwargs["pk"] \
or user.registration.is_volunteer \
and self.object.participation.tournament in user.registration.interesting_tournaments:
return super().get(request, *args, **kwargs)
raise PermissionDenied
@ -270,8 +272,9 @@ class TeamUpdateView(LoginRequiredMixin, UpdateView):
if not user.is_authenticated:
return super().handle_no_permission()
if user.registration.is_admin or user.registration.participates and \
user.registration.team and \
user.registration.team.pk == kwargs["pk"]:
user.registration.team and user.registration.team.pk == kwargs["pk"] \
or user.registration.is_volunteer \
and self.object.participation.tournament in user.registration.interesting_tournaments:
return super().dispatch(request, *args, **kwargs)
raise PermissionDenied
@ -302,7 +305,9 @@ class TeamAuthorizationsView(LoginRequiredMixin, DetailView):
user = request.user
if not user.is_authenticated:
return super().handle_no_permission()
if user.registration.is_admin or user.registration.participates and user.registration.team.pk == kwargs["pk"]:
if user.registration.is_admin or user.registration.participates and user.registration.team.pk == kwargs["pk"] \
or user.registration.is_volunteer \
and self.object.participation.tournament in user.registration.interesting_tournaments:
return super().dispatch(request, *args, **kwargs)
raise PermissionDenied
@ -386,7 +391,9 @@ class ParticipationDetailView(LoginRequiredMixin, DetailView):
raise PermissionDenied(_("The team is not validated yet."))
if user.registration.is_admin or user.registration.participates \
and user.registration.team.participation \
and user.registration.team.participation.pk == kwargs["pk"]:
and user.registration.team.participation.pk == kwargs["pk"] \
or user.registration.is_volunteer \
and self.object.tournament in user.registration.interesting_tournaments:
return super().dispatch(request, *args, **kwargs)
raise PermissionDenied
@ -417,13 +424,20 @@ class TournamentCreateView(AdminMixin, CreateView):
return reverse_lazy("participation:tournament_detail", args=(self.object.pk,))
class TournamentUpdateView(AdminMixin, UpdateView):
class TournamentUpdateView(VolunteerMixin, UpdateView):
"""
Update tournament detail.
"""
model = Tournament
form_class = TournamentForm
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated or not self.request.user.registration.is_admin \
and not (self.request.user.registration.is_volunteer
and self.request.user.registration.organized_tournaments.all()):
return self.handle_no_permission()
return super().dispatch(request, *args, **kwargs)
class TournamentDetailView(DetailView):
"""
@ -454,6 +468,10 @@ class SolutionUploadView(LoginRequiredMixin, FormView):
if not qs.exists():
raise Http404
self.participation = qs.get()
if not self.request.user.is_authenticated or not self.request.user.registration.is_admin \
and not (self.request.user.registration.participates
and self.request.user.registration.team == self.participation.team):
return self.handle_no_permission()
return super().dispatch(request, *args, **kwargs)
def form_valid(self, form):
@ -486,6 +504,17 @@ class PoolCreateView(AdminMixin, CreateView):
class PoolDetailView(LoginRequiredMixin, DetailView):
model = Pool
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return self.handle_no_permission()
if request.user.registration.is_admin or request.user.registration.participates \
and request.user.registration.team \
and request.user.registration.team.participation in self.get_object().participations.all() \
or request.user.registration.is_volunteer \
and self.get_object().tournament in request.user.registration.interesting_tournaments:
return super().dispatch(request, *args, **kwargs)
return self.handle_no_permission()
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
@ -499,26 +528,53 @@ class PoolDetailView(LoginRequiredMixin, DetailView):
return context
class PoolUpdateView(AdminMixin, UpdateView):
class PoolUpdateView(VolunteerMixin, UpdateView):
model = Pool
form_class = PoolForm
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return self.handle_no_permission()
if request.user.registration.is_admin or request.user.registration.is_volunteer \
and (self.get_object().tournament in request.user.registration.organized_tournaments.all()
or request.user.registration in self.get_object().juries.all()):
return super().dispatch(request, *args, **kwargs)
return self.handle_no_permission()
class PoolUpdateTeamsView(AdminMixin, UpdateView):
class PoolUpdateTeamsView(VolunteerMixin, UpdateView):
model = Pool
form_class = PoolTeamsForm
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return self.handle_no_permission()
if request.user.registration.is_admin or request.user.registration.is_volunteer \
and (self.get_object().tournament in request.user.registration.organized_tournaments.all()
or request.user.registration in self.get_object().juries.all()):
return super().dispatch(request, *args, **kwargs)
return self.handle_no_permission()
class PassageCreateView(AdminMixin, CreateView):
class PassageCreateView(VolunteerMixin, CreateView):
model = Passage
form_class = PassageForm
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return self.handle_no_permission()
qs = Pool.objects.filter(pk=self.kwargs["pk"])
if not qs.exists():
raise Http404
self.pool = qs.get()
return super().dispatch(request, *args, **kwargs)
if request.user.registration.is_admin or request.user.registration.is_volunteer \
and (self.pool.tournament in request.user.registration.organized_tournaments.all()
or request.user.registration in self.pool.juries.all()):
return super().dispatch(request, *args, **kwargs)
return self.handle_no_permission()
def get_form(self, form_class=None):
form = super().get_form(form_class)
@ -532,6 +588,19 @@ class PassageCreateView(AdminMixin, CreateView):
class PassageDetailView(LoginRequiredMixin, DetailView):
model = Passage
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return self.handle_no_permission()
if request.user.registration.is_admin or request.user.registration.is_volunteer \
and (self.get_object().pool.tournament in request.user.registration.organized_tournaments.all()
or request.user.registration in self.get_object().pool.juries.all()) \
or request.user.registration.participates and request.user.registration.team \
and request.user.registration.team.participation in [self.get_object().defender,
self.get_object().opponent,
self.get_object().reporter]:
return super().dispatch(request, *args, **kwargs)
return self.handle_no_permission()
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
if self.request.user.registration in self.object.pool.juries.all():
@ -540,21 +609,39 @@ class PassageDetailView(LoginRequiredMixin, DetailView):
return context
class PassageUpdateView(AdminMixin, UpdateView):
class PassageUpdateView(VolunteerMixin, UpdateView):
model = Passage
form_class = PassageForm
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return self.handle_no_permission()
if request.user.registration.is_admin or request.user.registration.is_volunteer \
and (self.get_object().pool.tournament in request.user.registration.organized_tournaments.all()
or request.user.registration in self.get_object().pool.juries.all()):
return super().dispatch(request, *args, **kwargs)
return self.handle_no_permission()
class SynthesisUploadView(LoginRequiredMixin, FormView):
template_name = "participation/upload_synthesis.html"
form_class = SynthesisForm
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated or not request.user.registration.participates:
return self.handle_no_permission()
qs = Passage.objects.filter(pk=self.kwargs["pk"])
if not qs.exists():
raise Http404
self.participation = self.request.user.registration.team.participation
self.passage = qs.get()
if self.participation not in [self.passage.defender, self.passage.opponent, self.passage.reporter]:
return self.handle_no_permission()
return super().dispatch(request, *args, **kwargs)
def form_valid(self, form):
@ -579,6 +666,16 @@ class SynthesisUploadView(LoginRequiredMixin, FormView):
return reverse_lazy("participation:passage_detail", args=(self.passage.pk,))
class NoteUpdateView(LoginRequiredMixin, UpdateView):
class NoteUpdateView(VolunteerMixin, UpdateView):
model = Note
form_class = NoteForm
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return self.handle_no_permission()
if request.user.registration.is_admin or request.user.registration.is_volunteer \
and self.get_object().jury == request.user.registration:
return super().dispatch(request, *args, **kwargs)
return self.handle_no_permission()

View File

@ -55,6 +55,7 @@ class AddOrganizerForm(forms.ModelForm):
("volunteer", _("volunteer").capitalize()),
("admin", _("admin").capitalize()),
],
initial="volunteer",
)
def clean_email(self):

View File

@ -81,6 +81,10 @@ class Registration(PolymorphicModel):
def is_admin(self):
return isinstance(self, AdminRegistration) or self.user.is_superuser
@property
def is_volunteer(self):
return isinstance(self, VolunteerRegistration)
@property
def matrix_username(self):
return f"tfjm_{self.user.pk}"
@ -249,6 +253,10 @@ class VolunteerRegistration(Registration):
verbose_name=_("professional activity"),
)
@property
def interesting_tournaments(self) -> set:
return set(self.organized_tournaments.all()).union(map(lambda pool: pool.tournament, self.jury_in.all()))
@property
def type(self):
return _('volunteer')

View File

@ -30,8 +30,8 @@
{% block extrajavascript %}
<script>
$("#id_role").change(function() {
let selected_role = $("#id_role :selected");
$("#id_type").change(function() {
let selected_role = $("#id_type :selected");
if (selected_role.val() === "volunteer") {
$("#registration_form").html($("#volunteer_registration_form").html());
}

View File

@ -4,9 +4,9 @@
{% block content %}
{% if user.registration.is_admin %}
<button href="{% url "registration:add_organizer" %}" class="btn btn-block btn-secondary">
<a href="{% url "registration:add_organizer" %}" class="btn btn-block btn-secondary">
<i class="fas fa-user-plus"></i> {% trans "Add organizer" %}
</button>
</a>
<hr>
{% endif %}

View File

@ -8,6 +8,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.models import User
from django.core.exceptions import PermissionDenied, ValidationError
from django.db import transaction
from django.db.models import Q
from django.http import FileResponse, Http404
from django.shortcuts import redirect, resolve_url
from django.urls import reverse_lazy
@ -17,9 +18,9 @@ from django.views.generic import CreateView, DetailView, RedirectView, TemplateV
from django_tables2 import SingleTableView
from magic import Magic
from participation.models import Solution, Synthesis
from participation.models import Solution, Synthesis, Passage
from tfjm.tokens import email_validation_token
from tfjm.views import AdminMixin, UserMixin
from tfjm.views import AdminMixin, UserMixin, VolunteerMixin
from .forms import AddOrganizerForm, AdminRegistrationForm, CoachRegistrationForm, HealthSheetForm, \
ParentalAuthorizationForm, PhotoAuthorizationForm, SignupForm, StudentRegistrationForm, UserForm, \
@ -67,6 +68,7 @@ class SignupView(CreateView):
registration = registration_form.instance
registration.user = form.instance
registration.save()
registration.send_email_validation_link()
return ret
@ -74,7 +76,7 @@ class SignupView(CreateView):
return reverse_lazy("registration:email_validation_sent")
class AddOrganizerView(AdminMixin, CreateView):
class AddOrganizerView(VolunteerMixin, CreateView):
model = User
form_class = AddOrganizerForm
template_name = "registration/add_organizer.html"
@ -89,6 +91,10 @@ class AddOrganizerView(AdminMixin, CreateView):
del context["volunteer_registration_form"].fields["email_confirmed"]
del context["admin_registration_form"].fields["email_confirmed"]
if not self.request.user.registration.is_admin:
del context["form"].fields["type"]
del context["admin_registration_form"]
return context
@transaction.atomic
@ -107,6 +113,7 @@ class AddOrganizerView(AdminMixin, CreateView):
registration = registration_form.instance
registration.user = form.instance
registration.save()
registration.send_email_validation_link()
return ret
@ -114,7 +121,6 @@ class AddOrganizerView(AdminMixin, CreateView):
return reverse_lazy("registration:email_validation_sent")
class UserValidateView(TemplateView):
"""
A view to validate the email address.
@ -204,6 +210,19 @@ class UserDetailView(UserMixin, DetailView):
context_object_name = "user_object"
template_name = "registration/user_detail.html"
def dispatch(self, request, *args, **kwargs):
me = request.user
if not me.is_authenticated:
return self.handle_no_permission()
user = self.get_object()
if user == me or me.registration.is_admin or me.registration.is_volunteer \
and user.registration.participates and user.registration.team \
and user.registration.team.participation.tournament in user.registration.organized_tournaments.all() \
or user.registration.is_volunteer and me.registration.is_volunteer \
and me.registration.interesting_tournaments.intersection(user.registration.intersting_tournaments):
return super().dispatch(request, *args, **kwargs)
raise PermissionDenied
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["title"] = _("Detail of user {user}").format(user=str(self.object.registration))
@ -227,6 +246,12 @@ class UserUpdateView(UserMixin, UpdateView):
form_class = UserForm
template_name = "registration/update_user.html"
def dispatch(self, request, *args, **kwargs):
if not self.request.user.is_authenticated or \
not self.request.user.registration.is_admin and self.request.user != self.get_object():
return self.handle_no_permission()
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
user = self.get_object()
@ -268,6 +293,12 @@ class UserUploadPhotoAuthorizationView(UserMixin, UpdateView):
template_name = "registration/upload_photo_authorization.html"
extra_context = dict(title=_("Upload photo authorization"))
def dispatch(self, request, *args, **kwargs):
if not self.request.user.is_authenticated or \
not self.request.user.registration.is_admin and self.request.user != self.get_object().user:
return self.handle_no_permission()
return super().dispatch(request, *args, **kwargs)
@transaction.atomic
def form_valid(self, form):
old_instance = StudentRegistration.objects.get(pk=self.object.pk)
@ -288,6 +319,12 @@ class UserUploadHealthSheetView(UserMixin, UpdateView):
template_name = "registration/upload_health_sheet.html"
extra_context = dict(title=_("Upload health sheet"))
def dispatch(self, request, *args, **kwargs):
if not self.request.user.is_authenticated or \
not self.request.user.registration.is_admin and self.request.user != self.get_object().user:
return self.handle_no_permission()
return super().dispatch(request, *args, **kwargs)
@transaction.atomic
def form_valid(self, form):
old_instance = StudentRegistration.objects.get(pk=self.object.pk)
@ -308,6 +345,12 @@ class UserUploadParentalAuthorizationView(UserMixin, UpdateView):
template_name = "registration/upload_parental_authorization.html"
extra_context = dict(title=_("Upload parental authorization"))
def dispatch(self, request, *args, **kwargs):
if not self.request.user.is_authenticated or \
not self.request.user.registration.is_admin and self.request.user != self.get_object().user:
return self.handle_no_permission()
return super().dispatch(request, *args, **kwargs)
@transaction.atomic
def form_valid(self, form):
old_instance = StudentRegistration.objects.get(pk=self.object.pk)
@ -330,7 +373,8 @@ class PhotoAuthorizationView(LoginRequiredMixin, View):
raise Http404
student = ParticipantRegistration.objects.get(photo_authorization__endswith=filename)
user = request.user
if not user.registration.is_admin and user.pk != student.user.pk:
if not (student.user == user or user.registration.is_admin or user.registration.is_volunteer and student.team
and student.team.participation.tournament in user.registration.organized_tournaments.all()):
raise PermissionDenied
# Guess mime type of the file
mime = Magic(mime=True)
@ -352,7 +396,8 @@ class HealthSheetView(LoginRequiredMixin, View):
raise Http404
student = ParticipantRegistration.objects.get(health_sheet__endswith=filename)
user = request.user
if not user.registration.is_admin and user.pk != student.user.pk:
if not (student.user == user or user.registration.is_admin or user.registration.is_volunteer and student.team
and student.team.participation.tournament in user.registration.organized_tournaments.all()):
raise PermissionDenied
# Guess mime type of the file
mime = Magic(mime=True)
@ -374,7 +419,8 @@ class ParentalAuthorizationView(LoginRequiredMixin, View):
raise Http404
student = StudentRegistration.objects.get(parental_authorization__endswith=filename)
user = request.user
if not user.registration.is_admin and user.pk != student.user.pk:
if not (student.user == user or user.registration.is_admin or user.registration.is_volunteer and student.team
and student.team.participation.tournament in user.registration.organized_tournaments.all()):
raise PermissionDenied
# Guess mime type of the file
mime = Magic(mime=True)
@ -395,10 +441,19 @@ class SolutionView(LoginRequiredMixin, View):
if not os.path.exists(path):
raise Http404
solution = Solution.objects.get(file__endswith=filename)
# user = request.user
# if False:
# FIXME Check ACL
# raise PermissionDenied
user = request.user
if not (user.registration.is_admin or user.registration.is_volunteer
and Passage.objects.filter(Q(pool__juries=user.registration)
| Q(pool__tournament__in=user.registration.organized_tournaments.all()),
defender=solution.participation,
solution_number=solution.problem).exists()
or user.registration.participates and user.registration.team
and Passage.objects.filter(Q(defender=user.registration.team.participation)
| Q(opponent=user.registration.team.participation)
| Q(reporter=user.registration.team.participation),
defender=solution.participation,
solution_number=solution.problem).exists()):
raise PermissionDenied
# Guess mime type of the file
mime = Magic(mime=True)
mime_type = mime.from_file(path)
@ -417,17 +472,19 @@ class SynthesisView(LoginRequiredMixin, View):
path = f"media/syntheses/{filename}"
if not os.path.exists(path):
raise Http404
solution = Synthesis.objects.get(file__endswith=filename)
# user = request.user
# if False:
# FIXME Check ACL
# raise PermissionDenied
synthesis = Synthesis.objects.get(file__endswith=filename)
user = request.user
if not (user.registration.is_admin or user.registration.is_volunteer
and (user.registration in synthesis.passage.pool.juries.all()
or user.registration in synthesis.passage.pool.tournament.organizers.all())
or user.registration.participates and user.registration.team == synthesis.participation.team):
raise PermissionDenied
# Guess mime type of the file
mime = Magic(mime=True)
mime_type = mime.from_file(path)
ext = mime_type.split("/")[1].replace("jpeg", "jpg")
# Replace file name
true_file_name = str(solution) + f".{ext}"
true_file_name = str(synthesis) + f".{ext}"
return FileResponse(open(path, "rb"), content_type=mime_type, filename=true_file_name)

View File

@ -9,7 +9,14 @@ from haystack.generic_views import SearchView
class AdminMixin(LoginRequiredMixin):
def dispatch(self, request, *args, **kwargs):
if request.user.is_authenticated and not request.user.registration.is_admin:
raise PermissionDenied
self.handle_no_permission()
return super().dispatch(request, *args, **kwargs)
class VolunteerMixin(LoginRequiredMixin):
def dispatch(self, request, *args, **kwargs):
if request.user.is_authenticated and not request.user.registration.is_volunteer:
self.handle_no_permission()
return super().dispatch(request, *args, **kwargs)
@ -17,7 +24,7 @@ class UserMixin(LoginRequiredMixin):
def dispatch(self, request, *args, **kwargs):
user = request.user
if user.is_authenticated and not user.registration.is_admin and user.registration.pk != kwargs["pk"]:
raise PermissionDenied
self.handle_no_permission()
return super().dispatch(request, *args, **kwargs)