diff --git a/core/lookups.py b/core/lookups.py index ecd508a2..0e8af504 100644 --- a/core/lookups.py +++ b/core/lookups.py @@ -21,19 +21,12 @@ from club.models import Club from core.models import Group, SithFile, User from core.views.site import search_user from counter.models import Counter, Customer, Product - - -def check_token(request): - return ( - "counter_token" in request.session.keys() - and request.session["counter_token"] - and Counter.objects.filter(token=request.session["counter_token"]).exists() - ) +from counter.utils import sent_from_logged_counter class RightManagedLookupChannel(LookupChannel): def check_auth(self, request): - if not request.user.was_subscribed and not check_token(request): + if not request.user.was_subscribed and not sent_from_logged_counter(request): raise PermissionDenied diff --git a/core/models.py b/core/models.py index c9aceab6..ef85aded 100644 --- a/core/models.py +++ b/core/models.py @@ -1137,11 +1137,9 @@ class SithFile(models.Model): else: self._check_path_consistence() - def __getattribute__(self, attr): - if attr == "is_file": - return not self.is_folder - else: - return super().__getattribute__(attr) + @property + def is_file(self): + return not self.is_folder @cached_property def as_picture(self): diff --git a/core/views/files.py b/core/views/files.py index 161578d0..6bc76a5e 100644 --- a/core/views/files.py +++ b/core/views/files.py @@ -21,7 +21,7 @@ from django import forms from django.conf import settings from django.core.exceptions import PermissionDenied from django.forms.models import modelform_factory -from django.http import Http404, HttpResponse +from django.http import Http404, HttpRequest, HttpResponse from django.shortcuts import get_object_or_404, redirect from django.urls import reverse from django.utils.http import http_date @@ -37,27 +37,23 @@ from core.views import ( CanViewMixin, can_view, ) -from counter.models import Counter +from counter.utils import sent_from_logged_counter -def send_file(request, file_id, file_class=SithFile, file_attr="file"): +def send_file( + request: HttpRequest, + file_id: int, + file_class: type[SithFile] = SithFile, + file_attr: str = "file", +): """Send a file through Django without loading the whole file into memory at once. The FileWrapper will turn the file object into an iterator for chunks of 8KB. """ f = get_object_or_404(file_class, id=file_id) - if not ( - can_view(f, request.user) - or ( - "counter_token" in request.session.keys() - and request.session["counter_token"] - and Counter.objects.filter( # check if not null for counters that have no token set - token=request.session["counter_token"] - ).exists() - ) - ): + if not can_view(f, request.user) and not sent_from_logged_counter(request): raise PermissionDenied - name = f.__getattribute__(file_attr).name + name = getattr(f, file_attr).name filepath = settings.MEDIA_ROOT / name # check if file exists on disk diff --git a/counter/utils.py b/counter/utils.py new file mode 100644 index 00000000..6196081f --- /dev/null +++ b/counter/utils.py @@ -0,0 +1,36 @@ +from urllib.parse import urlparse + +from django.http import HttpRequest +from django.urls import resolve + +from counter.models import Counter + + +def sent_from_logged_counter(request: HttpRequest) -> bool: + """Check if the request is sent from a device logged to a counter. + + The request must also be sent within the frame of a counter's activity. + Trying to use this function to manage access to non-sas + related resources probably won't work. + + A request is considered as coming from a logged counter if : + + - Its referer comes from the counter app + (eg. fetching user pictures from the click UI) + or the request path belongs to the counter app + (eg. the barman went back to the main by missclick and go back + to the counter) + - The current session has a counter token associated with it. + - A counter with this token exists. + """ + referer = urlparse(request.META["HTTP_REFERER"]).path + path_ok = ( + request.resolver_match.app_name == "counter" + or resolve(referer).app_name == "counter" + ) + return ( + path_ok + and "counter_token" in request.session + and request.session["counter_token"] + and Counter.objects.filter(token=request.session["counter_token"]).exists() + ) diff --git a/counter/views.py b/counter/views.py index a426c6fd..aae2f183 100644 --- a/counter/views.py +++ b/counter/views.py @@ -80,6 +80,7 @@ from counter.models import ( Selling, StudentCard, ) +from counter.utils import sent_from_logged_counter class CounterAdminMixin(View): @@ -901,15 +902,9 @@ class RefillingDeleteView(DeleteView): def dispatch(self, request, *args, **kwargs): """We have here a very particular right handling, we can't inherit from CanEditPropMixin.""" self.object = self.get_object() - if ( - timezone.now() - self.object.date - <= timedelta(minutes=settings.SITH_LAST_OPERATIONS_LIMIT) - and "counter_token" in request.session.keys() - and request.session["counter_token"] - and Counter.objects.filter( # check if not null for counters that have no token set - token=request.session["counter_token"] - ).exists() - ): + if timezone.now() - self.object.date <= timedelta( + minutes=settings.SITH_LAST_OPERATIONS_LIMIT + ) and sent_from_logged_counter(request): self.success_url = reverse( "counter:details", kwargs={"counter_id": self.object.counter.id} ) @@ -932,15 +927,9 @@ class SellingDeleteView(DeleteView): def dispatch(self, request, *args, **kwargs): """We have here a very particular right handling, we can't inherit from CanEditPropMixin.""" self.object = self.get_object() - if ( - timezone.now() - self.object.date - <= timedelta(minutes=settings.SITH_LAST_OPERATIONS_LIMIT) - and "counter_token" in request.session.keys() - and request.session["counter_token"] - and Counter.objects.filter( # check if not null for counters that have no token set - token=request.session["counter_token"] - ).exists() - ): + if timezone.now() - self.object.date <= timedelta( + minutes=settings.SITH_LAST_OPERATIONS_LIMIT + ) and sent_from_logged_counter(request): self.success_url = reverse( "counter:details", kwargs={"counter_id": self.object.counter.id} ) @@ -1175,14 +1164,7 @@ class CounterLastOperationsView(CounterTabsMixin, CanViewMixin, DetailView): def dispatch(self, request, *args, **kwargs): """We have here again a very particular right handling.""" self.object = self.get_object() - if ( - self.object.barmen_list - and "counter_token" in request.session.keys() - and request.session["counter_token"] - and Counter.objects.filter( # check if not null for counters that have no token set - token=request.session["counter_token"] - ).exists() - ): + if sent_from_logged_counter(request) and self.object.barmen_list: return super().dispatch(request, *args, **kwargs) return HttpResponseRedirect( reverse("counter:details", kwargs={"counter_id": self.object.id}) @@ -1215,14 +1197,7 @@ class CounterCashSummaryView(CounterTabsMixin, CanViewMixin, DetailView): def dispatch(self, request, *args, **kwargs): """We have here again a very particular right handling.""" self.object = self.get_object() - if ( - self.object.barmen_list - and "counter_token" in request.session.keys() - and request.session["counter_token"] - and Counter.objects.filter( # check if not null for counters that have no token set - token=request.session["counter_token"] - ).exists() - ): + if sent_from_logged_counter(request) and self.object.barmen_list: return super().dispatch(request, *args, **kwargs) return HttpResponseRedirect( reverse("counter:details", kwargs={"counter_id": self.object.id})