apply review comments

This commit is contained in:
imperosol
2026-04-26 22:35:13 +02:00
parent 876b6d3f4e
commit 4aa2675572
3 changed files with 149 additions and 91 deletions
+24 -4
View File
@@ -1,6 +1,7 @@
from unittest import mock from unittest import mock
from unittest.mock import Mock from unittest.mock import Mock
from django.contrib.messages import Message, get_messages
from django.db.models import Max from django.db.models import Max
from django.test import TestCase from django.test import TestCase
from django.urls import reverse from django.urls import reverse
@@ -87,7 +88,15 @@ class TestThirdPartyAuth(TestCase):
del self.query["signature"] del self.query["signature"]
self.query["signature"] = hmac_hexdigest(new_key, self.query) self.query["signature"] = hmac_hexdigest(new_key, self.query)
res = self.client.get(reverse("api-link:third-party-auth", query=self.query)) res = self.client.get(reverse("api-link:third-party-auth", query=self.query))
assert res.status_code == 403 assert list(get_messages(res.wsgi_request)) == [
Message(
level=40,
message=(
"La signature est incorrecte. "
"Nous ne pouvons pas garantir l'authenticité de la requête."
),
)
]
def test_cgu_not_accepted(self): def test_cgu_not_accepted(self):
self.client.force_login(self.user) self.client.force_login(self.user)
@@ -102,13 +111,24 @@ class TestThirdPartyAuth(TestCase):
assert res.status_code == 200 assert res.status_code == 200
def test_invalid_client(self): def test_invalid_client(self):
self.client.force_login(self.user)
self.query["client_id"] = ApiClient.objects.aggregate(res=Max("id"))["res"] + 1 self.query["client_id"] = ApiClient.objects.aggregate(res=Max("id"))["res"] + 1
res = self.client.get(reverse("api-link:third-party-auth", query=self.query)) res = self.client.get(reverse("api-link:third-party-auth", query=self.query))
assert res.status_code == 403 assert list(get_messages(res.wsgi_request)) == [
Message(
level=40,
message="Les données fournies pour l'authentification sont incorrectes.",
)
]
def test_missing_parameter(self): def test_missing_parameter(self):
"""Test that a 403 is raised if there is a missing parameter.""" self.client.force_login(self.user)
del self.query["username"] del self.query["username"]
self.query["signature"] = hmac_hexdigest(self.api_client.hmac_key, self.query) self.query["signature"] = hmac_hexdigest(self.api_client.hmac_key, self.query)
res = self.client.get(reverse("api-link:third-party-auth", query=self.query)) res = self.client.get(reverse("api-link:third-party-auth", query=self.query))
assert res.status_code == 403 assert list(get_messages(res.wsgi_request)) == [
Message(
level=40,
message="Les données fournies pour l'authentification sont incorrectes.",
)
]
+39 -12
View File
@@ -3,10 +3,11 @@ from urllib.parse import unquote
import pydantic import pydantic
import requests import requests
import sentry_sdk
from django.conf import settings from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import AccessMixin, LoginRequiredMixin
from django.core.exceptions import PermissionDenied from django.shortcuts import render
from django.urls import reverse, reverse_lazy from django.urls import reverse, reverse_lazy
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.views.generic import FormView, TemplateView from django.views.generic import FormView, TemplateView
@@ -20,16 +21,19 @@ from core.schemas import UserProfileSchema
from core.utils import hmac_hexdigest from core.utils import hmac_hexdigest
class ThirdPartyAuthView(LoginRequiredMixin, FormView): class ThirdPartyAuthView(AccessMixin, FormView):
form_class = ThirdPartyAuthForm form_class = ThirdPartyAuthForm
template_name = "api/third_party/auth.jinja" template_name = "api/third_party/auth.jinja"
success_url = reverse_lazy("core:index") success_url = reverse_lazy("core:index")
def parse_params(self) -> ThirdPartyAuthParamsSchema: def parse_params(self) -> ThirdPartyAuthParamsSchema | None:
"""Parse and check the authentication parameters. """Parse and check the authentication parameters.
Raises: If parsing fails, messages will be created using the django message
PermissionDenied: if the verification failed. infrastructure.
Returns:
The parses parameters, or None if the parsing failed.
""" """
# This is here rather than in ThirdPartyAuthForm because # This is here rather than in ThirdPartyAuthForm because
# the given parameters and their signature are checked during both # the given parameters and their signature are checked during both
@@ -39,20 +43,39 @@ class ThirdPartyAuthView(LoginRequiredMixin, FormView):
params = {key: unquote(val) for key, val in params.items()} params = {key: unquote(val) for key, val in params.items()}
try: try:
params = ThirdPartyAuthParamsSchema(**params) params = ThirdPartyAuthParamsSchema(**params)
except pydantic.ValidationError as e: except pydantic.ValidationError:
raise PermissionDenied("Wrong data format") from e messages.error(
self.request, _("The data provided for authentication is incorrect")
)
return None
client: ApiClient = get_object_or_none(ApiClient, id=params.client_id) client: ApiClient = get_object_or_none(ApiClient, id=params.client_id)
if not client: if not client:
raise PermissionDenied messages.error(
self.request, _("The data provided for authentication is incorrect")
)
return None
if not hmac.compare_digest( if not hmac.compare_digest(
hmac_hexdigest(client.hmac_key, params.model_dump(exclude={"signature"})), hmac_hexdigest(client.hmac_key, params.model_dump(exclude={"signature"})),
params.signature, params.signature,
): ):
raise PermissionDenied("Bad signature") messages.error(
self.request,
_(
"The signature is incorrect. "
"We cannot ensure the provenance of the request."
),
)
return None
return params return params
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return self.handle_no_permission()
self.params = self.parse_params() self.params = self.parse_params()
if not self.params:
# if parameters parsing failed, shortcut the operation and display
# an empty page with just the error messages.
return render(request, "core/base.jinja")
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
def get(self, *args, **kwargs): def get(self, *args, **kwargs):
@@ -73,10 +96,14 @@ class ThirdPartyAuthView(LoginRequiredMixin, FormView):
client = ApiClient.objects.get(id=form.cleaned_data["client_id"]) client = ApiClient.objects.get(id=form.cleaned_data["client_id"])
user = UserProfileSchema.from_orm(self.request.user).model_dump() user = UserProfileSchema.from_orm(self.request.user).model_dump()
data = {"user": user, "signature": hmac_hexdigest(client.hmac_key, user)} data = {"user": user, "signature": hmac_hexdigest(client.hmac_key, user)}
response = requests.post(form.cleaned_data["callback_url"], json=data) try:
ok = requests.post(form.cleaned_data["callback_url"], json=data).ok
except requests.RequestException as e:
sentry_sdk.capture_exception(e)
ok = False
self.success_url = reverse( self.success_url = reverse(
"api-link:third-party-auth-result", "api-link:third-party-auth-result",
kwargs={"result": "success" if response.ok else "failure"}, kwargs={"result": "success" if ok else "failure"},
) )
return super().form_valid(form) return super().form_valid(form)
+86 -75
View File
@@ -148,14 +148,24 @@ msgid ""
"href=\"%(sith_cgu_link)s\">the Students' Association</a> applies as soon as " "href=\"%(sith_cgu_link)s\">the Students' Association</a> applies as soon as "
"the form is submitted." "the form is submitted."
msgstr "" msgstr ""
"Les politiques de confidentialité de <a href=\"%(privacy_link)s\">%(app)s</a> et de <a " "Les politiques de confidentialité de <a href=\"%(privacy_link)s\">%(app)s</"
"href=\"%(sith_cgu_link)s\">l'Association des Etudiants</a> s'appliquent dès la soumission " "a> et de <a href=\"%(sith_cgu_link)s\">l'Association des Etudiants</a> "
"du formulaire." "s'appliquent dès la soumission du formulaire."
#: api/templates/api/third_party/auth.jinja #: api/templates/api/third_party/auth.jinja
msgid "Confirmation of identity" msgid "Confirmation of identity"
msgstr "Confirmation d'identité" msgstr "Confirmation d'identité"
#: api/views.py
msgid "The data provided for authentication is incorrect"
msgstr "Les données fournies pour l'authentification sont incorrectes."
#: api/views.py
msgid ""
"The signature is incorrect. We cannot ensure the provenance of the request."
msgstr ""
"La signature est incorrecte. Nous ne pouvons pas garantir l'authenticité de la requête."
#: api/views.py #: api/views.py
#, python-format #, python-format
msgid "" msgid ""
@@ -167,7 +177,9 @@ msgstr ""
#: api/views.py #: api/views.py
msgid "You have been successfully authenticated. You can now close this page." msgid "You have been successfully authenticated. You can now close this page."
msgstr "Vous avez été authentifié avec succès. Vous pouvez maintenant fermer cette page." msgstr ""
"Vous avez été authentifié avec succès. Vous pouvez maintenant fermer cette "
"page."
#: api/views.py #: api/views.py
msgid "" msgid ""
@@ -175,9 +187,9 @@ msgid ""
"during the interaction with the third-party application. Please contact the " "during the interaction with the third-party application. Please contact the "
"managers of the latter." "managers of the latter."
msgstr "" msgstr ""
"Votre authentification sur le site AE a fonctionné, mais une erreur est arrivée " "Votre authentification sur le site AE a fonctionné, mais une erreur est "
"durant l'interaction avec l'application tierce. Veuillez contacter les responsables " "arrivée durant l'interaction avec l'application tierce. Veuillez contacter "
"de cette dernière." "les responsables de cette dernière."
#: club/forms.py #: club/forms.py
msgid "Users to add" msgid "Users to add"
@@ -263,6 +275,24 @@ msgstr "Vous devez être cotisant pour faire partie d'un club"
msgid "You are already a member of this club" msgid "You are already a member of this club"
msgstr "Vous êtes déjà membre de ce club." msgstr "Vous êtes déjà membre de ce club."
#: club/forms.py
#, fuzzy
#| msgid "Club state"
msgid "Club status"
msgstr "Etat du club"
#: club/forms.py
msgid "Active"
msgstr "Actif"
#: club/forms.py
msgid "Inactive"
msgstr "Inactif"
#: club/forms.py
msgid "All clubs"
msgstr "Tous les clubs"
#: club/models.py #: club/models.py
msgid "slug name" msgid "slug name"
msgstr "nom slug" msgstr "nom slug"
@@ -383,37 +413,22 @@ msgstr "Cet email est déjà abonné à cette mailing"
msgid "Unregistered user" msgid "Unregistered user"
msgstr "Utilisateur non enregistré" msgstr "Utilisateur non enregistré"
#: club/templates/club/club_list.jinja
msgid "Club list"
msgstr "Liste des clubs"
#: club/templates/club/club_list.jinja #: club/templates/club/club_list.jinja
msgid "The list of all clubs existing at UTBM." msgid "The list of all clubs existing at UTBM."
msgstr "La liste de tous les clubs existants à l'UTBM" msgstr "La liste de tous les clubs existants à l'UTBM"
#: club/templates/club/club_list.jinja
msgid "Club list"
msgstr "Liste des clubs"
#: club/templates/club/club_list.jinja #: club/templates/club/club_list.jinja
msgid "Filters" msgid "Filters"
msgstr "Filtres" msgstr "Filtres"
#: club/templates/club/club_list.jinja #: club/templates/club/club_list.jinja core/templates/core/base/header.jinja
msgid "Name" #: forum/templates/forum/macros.jinja matmat/templates/matmat/search_form.jinja
msgstr "Nom" msgid "Search"
msgstr "Recherche"
#: club/templates/club/club_list.jinja
msgid "Club state"
msgstr "Etat du club"
#: club/templates/club/club_list.jinja
msgid "Active"
msgstr "Actif"
#: club/templates/club/club_list.jinja
msgid "Inactive"
msgstr "Inactif"
#: club/templates/club/club_list.jinja
msgid "All clubs"
msgstr "Tous les clubs"
#: club/templates/club/club_list.jinja core/templates/core/user_tools.jinja #: club/templates/club/club_list.jinja core/templates/core/user_tools.jinja
msgid "New club" msgid "New club"
@@ -1945,11 +1960,6 @@ msgstr "Connexion"
msgid "Register" msgid "Register"
msgstr "Inscription" msgstr "Inscription"
#: core/templates/core/base/header.jinja forum/templates/forum/macros.jinja
#: matmat/templates/matmat/search_form.jinja
msgid "Search"
msgstr "Recherche"
#: core/templates/core/base/header.jinja #: core/templates/core/base/header.jinja
msgid "Logout" msgid "Logout"
msgstr "Déconnexion" msgstr "Déconnexion"
@@ -4294,6 +4304,47 @@ msgstr ""
msgid "this page" msgid "this page"
msgstr "cette page" msgstr "cette page"
#: eboutic/templates/eboutic/eboutic_main.jinja
msgid "Eurockéennes 2025 partnership"
msgstr "Partenariat Eurockéennes 2025"
#: eboutic/templates/eboutic/eboutic_main.jinja
msgid ""
"Our partner uses Weezevent to sell tickets. Weezevent may collect user info "
"according to its own privacy policy. By clicking the accept button you "
"consent to their terms of services."
msgstr ""
"Notre partenaire utilises Wezevent pour vendre ses billets. Weezevent peut "
"collecter des informations utilisateur conformément à sa propre politique de "
"confidentialité. En cliquant sur le bouton d'acceptation vous consentez à "
"leurs termes de service."
#: eboutic/templates/eboutic/eboutic_main.jinja
msgid "Privacy policy"
msgstr "Politique de confidentialité"
#: eboutic/templates/eboutic/eboutic_main.jinja
#: trombi/templates/trombi/comment_moderation.jinja
msgid "Accept"
msgstr "Accepter"
#: eboutic/templates/eboutic/eboutic_main.jinja
msgid ""
"You must be subscribed to benefit from the partnership with the Eurockéennes."
msgstr ""
"Vous devez être cotisant pour bénéficier du partenariat avec les "
"Eurockéennes."
#: eboutic/templates/eboutic/eboutic_main.jinja
#, python-format
msgid ""
"This partnership offers a discount of up to 33%% on tickets for Friday, "
"Saturday and Sunday, as well as the 3-day package from Friday to Sunday."
msgstr ""
"Ce partenariat permet de profiter d'une réduction jusqu'à 33%% sur les "
"billets du vendredi, du samedi et du dimanche, ainsi qu'au forfait 3 jours, "
"du vendredi au dimanche."
#: eboutic/templates/eboutic/eboutic_main.jinja #: eboutic/templates/eboutic/eboutic_main.jinja
msgid "There are no items available for sale" msgid "There are no items available for sale"
msgstr "Aucun article n'est disponible à la vente" msgstr "Aucun article n'est disponible à la vente"
@@ -5720,10 +5771,6 @@ msgstr "fin"
msgid "Moderate Trombi comments" msgid "Moderate Trombi comments"
msgstr "Modérer les commentaires du Trombi" msgstr "Modérer les commentaires du Trombi"
#: trombi/templates/trombi/comment_moderation.jinja
msgid "Accept"
msgstr "Accepter"
#: trombi/templates/trombi/comment_moderation.jinja #: trombi/templates/trombi/comment_moderation.jinja
msgid "Reject" msgid "Reject"
msgstr "Refuser" msgstr "Refuser"
@@ -5965,39 +6012,3 @@ msgstr "Vous ne pouvez plus écrire de commentaires, la date est passée."
#, python-format #, python-format
msgid "Maximum characters: %(max_length)s" msgid "Maximum characters: %(max_length)s"
msgstr "Nombre de caractères max: %(max_length)s" msgstr "Nombre de caractères max: %(max_length)s"
#: eboutic/templates/eboutic/eboutic_main.jinja
msgid "Eurockéennes 2025 partnership"
msgstr "Partenariat Eurockéennes 2025"
#: eboutic/templates/eboutic/eboutic_main.jinja
msgid ""
"Our partner uses Weezevent to sell tickets. Weezevent may collect user info "
"according to its own privacy policy. By clicking the accept button you "
"consent to their terms of services."
msgstr ""
"Notre partenaire utilises Wezevent pour vendre ses billets. Weezevent peut "
"collecter des informations utilisateur conformément à sa propre politique de "
"confidentialité. En cliquant sur le bouton d'acceptation vous consentez à "
"leurs termes de service."
#: eboutic/templates/eboutic/eboutic_main.jinja
msgid "Privacy policy"
msgstr "Politique de confidentialité"
#: eboutic/templates/eboutic/eboutic_main.jinja
msgid ""
"You must be subscribed to benefit from the partnership with the Eurockéennes."
msgstr ""
"Vous devez être cotisant pour bénéficier du partenariat avec les "
"Eurockéennes."
#: eboutic/templates/eboutic/eboutic_main.jinja
#, python-format
msgid ""
"This partnership offers a discount of up to 33%% on tickets for Friday, "
"Saturday and Sunday, as well as the 3-day package from Friday to Sunday."
msgstr ""
"Ce partenariat permet de profiter d'une réduction jusqu'à 33%% sur les "
"billets du vendredi, du samedi et du dimanche, ainsi qu'au forfait 3 jours, "
"du vendredi au dimanche."