Compare commits

...
Author SHA1 Message Date
imperosol 74d17e6a96 test is_logged_in_counter 2026-10-31 12:54:59 +01:00
imperosol 7bc980113a allow is_logged_in_counter to target a specific counter 2026-10-30 14:38:00 +01:00
6 changed files with 97 additions and 60 deletions

No files matched your search

+50 -5
View File
@@ -21,11 +21,12 @@ import pytest
from bs4 import BeautifulSoup
from dateutil.relativedelta import relativedelta
from django.conf import settings
from django.contrib.auth.models import Permission, make_password
from django.contrib.auth.hashers import make_password
from django.contrib.auth.models import Permission
from django.contrib.messages import DEFAULT_LEVELS, get_messages
from django.shortcuts import resolve_url
from django.test import Client, TestCase
from django.urls import reverse
from django.test import Client, RequestFactory, TestCase
from django.urls import resolve, reverse
from django.utils import timezone
from django.utils.timezone import localdate, now
from freezegun import freeze_time
@@ -47,6 +48,7 @@ from counter.models import (
ReturnableProduct,
Selling,
)
from counter.utils import is_logged_in_counter
if TYPE_CHECKING:
from django.http import HttpResponse
@@ -65,8 +67,9 @@ class TestFullClickBase(TestCase):
@classmethod
def setUpTestData(cls):
cls.customer = subscriber_user.make()
cls.barmen = subscriber_user.make(password=make_password("plop"))
cls.board_admin = board_user.make(password=make_password("plop"))
password = make_password("plop")
cls.barmen = subscriber_user.make(password=password)
cls.board_admin = board_user.make(password=password)
cls.club_admin = subscriber_user.make()
cls.root = baker.make(User, is_superuser=True)
cls.subscriber = subscriber_user.make()
@@ -919,6 +922,48 @@ class TestBarmanConnection(TestCase):
assert self.barman in response.wsgi_request.barmen
class TestIsLoggedInCounter(TestCase):
@classmethod
def setUpTestData(cls):
cls.counter = baker.make(Counter, type="BAR")
cls.other_counter = baker.make(Counter, type="BAR")
cls.barman = subscriber_user.make()
cls.other_barman = subscriber_user.make()
cls.url = reverse("counter:details", kwargs={"counter_id": cls.counter.id})
def make_request(self, barmen):
"""Requête émise depuis une vue du namespace `counter`."""
request = RequestFactory().get(self.url)
request.resolver_match = resolve(self.url)
request.barmen = set(barmen)
return request
def test_denied(self):
# nobody is logged
request = self.make_request([])
assert not is_logged_in_counter(request)
# somebody is logged, but on another counter
self.other_counter.permanencies.create(user=self.barman, start=now())
assert not is_logged_in_counter(request, counter=self.counter)
def test_ok(self):
request = self.make_request([self.barman])
assert is_logged_in_counter(request)
self.counter.permanencies.create(user=self.barman, start=now())
assert is_logged_in_counter(request, counter=self.counter)
def test_request_outside_counter_app(self):
"""A request coming from outside the counter app is denied."""
request = self.make_request([self.barman])
request.resolver_match = resolve(reverse("core:index"))
assert not is_logged_in_counter(request)
self.counter.permanencies.create(user=self.barman, start=now())
assert not is_logged_in_counter(request, counter=self.counter)
@pytest.mark.django_db
def test_barman_timeout(client: Client):
"""Test that barmen timeout is well managed."""
+5 -1
View File
@@ -6,8 +6,10 @@ from django.urls import resolve
if TYPE_CHECKING:
from django.http import HttpRequest
from counter.models import Counter
def is_logged_in_counter(request: HttpRequest) -> bool:
def is_logged_in_counter(request: HttpRequest, counter: Counter | None = None) -> 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.
@@ -30,4 +32,6 @@ def is_logged_in_counter(request: HttpRequest) -> bool:
if not referer_ok and request.resolver_match.app_name != "counter":
return False
if counter is None:
return bool(request.barmen)
return bool(request.barmen) and request.barmen.issubset(set(counter.barmen_list))
+25 -36
View File
@@ -306,52 +306,41 @@ class ReturnableProductDeleteView(
}
class RefillingDeleteView(DeleteView):
class CounterTransactionDeletionView(DeleteView):
template_name = "core/delete_confirm.jinja"
def get_queryset(self):
return super().get_queryset().select_related("counter", "customer")
def dispatch(self, request, *args, **kwargs):
self.object: Selling | Refilling = self.get_object()
if timezone.now() - self.object.date <= timedelta(
minutes=settings.SITH_LAST_OPERATIONS_LIMIT
) and is_logged_in_counter(request, counter=self.object.counter):
self.success_url = reverse(
"counter:details", kwargs={"counter_id": self.object.counter_id}
)
return super().dispatch(request, *args, **kwargs)
elif self.object.is_owned_by(request.user):
self.success_url = reverse(
"core:user_account", kwargs={"user_id": self.object.customer.user_id}
)
return super().dispatch(request, *args, **kwargs)
raise PermissionDenied
class RefillingDeleteView(CounterTransactionDeletionView):
"""Delete a refilling (for the admins)."""
model = Refilling
pk_url_kwarg = "refilling_id"
template_name = "core/delete_confirm.jinja"
def dispatch(self, request, *args, **kwargs):
self.object = self.get_object()
if timezone.now() - self.object.date <= timedelta(
minutes=settings.SITH_LAST_OPERATIONS_LIMIT
) and is_logged_in_counter(request):
self.success_url = reverse(
"counter:details", kwargs={"counter_id": self.object.counter.id}
)
return super().dispatch(request, *args, **kwargs)
elif self.object.is_owned_by(request.user):
self.success_url = reverse(
"core:user_account", kwargs={"user_id": self.object.customer.user.id}
)
return super().dispatch(request, *args, **kwargs)
raise PermissionDenied
class SellingDeleteView(DeleteView):
class SellingDeleteView(CounterTransactionDeletionView):
"""Delete a selling (for the admins)."""
model = Selling
pk_url_kwarg = "selling_id"
template_name = "core/delete_confirm.jinja"
def dispatch(self, request, *args, **kwargs):
self.object = self.get_object()
if timezone.now() - self.object.date <= timedelta(
minutes=settings.SITH_LAST_OPERATIONS_LIMIT
) and is_logged_in_counter(request):
self.success_url = reverse(
"counter:details", kwargs={"counter_id": self.object.counter.id}
)
return super().dispatch(request, *args, **kwargs)
elif self.object.is_owned_by(request.user):
self.success_url = reverse(
"core:user_account", kwargs={"user_id": self.object.customer.user.id}
)
return super().dispatch(request, *args, **kwargs)
raise PermissionDenied
class CounterStatView(PermissionRequiredMixin, DetailView):
+3 -7
View File
@@ -242,15 +242,11 @@ class RefillingCreateView(FragmentMixin, CreateView):
if not self.customer.can_buy:
raise Http404
if not is_logged_in_counter(request):
raise PermissionDenied
self.counter: Counter = get_object_or_404(Counter, id=self.kwargs["counter_id"])
if not (
request.barmen
and request.barmen.issubset(self.counter.barmen_list)
and self.counter.can_refill
if (
not is_logged_in_counter(request, counter=self.counter)
or not self.counter.can_refill
):
raise PermissionDenied
+8 -9
View File
@@ -16,9 +16,9 @@ from datetime import timedelta
from typing import TYPE_CHECKING
from django.conf import settings
from django.contrib.auth.mixins import UserPassesTestMixin
from django.core.exceptions import PermissionDenied
from django.db.models import F
from django.http import HttpRequest, HttpResponseRedirect
from django.shortcuts import redirect
from django.urls import reverse
from django.utils import timezone
@@ -36,6 +36,7 @@ from counter.utils import is_logged_in_counter
from counter.views.mixins import CounterTabsMixin
if TYPE_CHECKING:
from django.http import HttpRequest, HttpResponseRedirect
from django.utils.safestring import SafeString
@@ -155,7 +156,7 @@ class CounterMain(
return super().form_valid(form)
class CounterLastOperationsView(CounterTabsMixin, CanViewMixin, DetailView):
class CounterLastOperationsView(CounterTabsMixin, UserPassesTestMixin, DetailView):
"""Provide the last operations to allow barmen to delete them."""
model = Counter
@@ -163,14 +164,12 @@ class CounterLastOperationsView(CounterTabsMixin, CanViewMixin, DetailView):
template_name = "counter/last_ops.jinja"
current_tab = "last_ops"
def dispatch(self, request, *args, **kwargs):
"""We have here again a very particular right handling."""
def test_func(self):
self.object = self.get_object()
if is_logged_in_counter(request) and self.object.barmen_list:
return super().dispatch(request, *args, **kwargs)
return HttpResponseRedirect(
reverse("counter:details", kwargs={"counter_id": self.object.id})
+ "?bad_location"
return is_logged_in_counter(
self.request, counter=self.object
) or self.request.user.has_perms(
["counter.view_selling", "counter.view_refilling"]
)
def get_context_data(self, **kwargs):
+6 -2
View File
@@ -22,7 +22,7 @@ from counter.utils import is_logged_in_counter
class CounterTabsMixin(TabedViewMixin):
def get_tabs_title(self):
return self.object
return self.object.name
def get_list_of_tabs(self):
if self.object.type != "BAR":
@@ -46,7 +46,11 @@ class CounterTabsMixin(TabedViewMixin):
"name": _("Cash summary"),
}
)
if is_logged_in_counter(self.request):
if is_logged_in_counter(
self.request, counter=self.object
) or self.request.user.has_perms(
["counter.view_selling", "counter.view_refilling"]
):
tab_list.append(
{
"url": reverse(