diff --git a/api/tests/test_third_party_auth.py b/api/tests/test_third_party_auth.py
index 39faebce..1fca8ccb 100644
--- a/api/tests/test_third_party_auth.py
+++ b/api/tests/test_third_party_auth.py
@@ -1,6 +1,7 @@
from unittest import mock
from unittest.mock import Mock
+from django.contrib.messages import Message, get_messages
from django.db.models import Max
from django.test import TestCase
from django.urls import reverse
@@ -87,7 +88,15 @@ class TestThirdPartyAuth(TestCase):
del self.query["signature"]
self.query["signature"] = hmac_hexdigest(new_key, 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):
self.client.force_login(self.user)
@@ -102,13 +111,24 @@ class TestThirdPartyAuth(TestCase):
assert res.status_code == 200
def test_invalid_client(self):
+ self.client.force_login(self.user)
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))
- 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):
- """Test that a 403 is raised if there is a missing parameter."""
+ self.client.force_login(self.user)
del self.query["username"]
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))
- 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.",
+ )
+ ]
diff --git a/api/views.py b/api/views.py
index 6b66db03..9f519eaa 100644
--- a/api/views.py
+++ b/api/views.py
@@ -3,10 +3,11 @@ from urllib.parse import unquote
import pydantic
import requests
+import sentry_sdk
from django.conf import settings
from django.contrib import messages
-from django.contrib.auth.mixins import LoginRequiredMixin
-from django.core.exceptions import PermissionDenied
+from django.contrib.auth.mixins import AccessMixin, LoginRequiredMixin
+from django.shortcuts import render
from django.urls import reverse, reverse_lazy
from django.utils.translation import gettext as _
from django.views.generic import FormView, TemplateView
@@ -20,16 +21,19 @@ from core.schemas import UserProfileSchema
from core.utils import hmac_hexdigest
-class ThirdPartyAuthView(LoginRequiredMixin, FormView):
+class ThirdPartyAuthView(AccessMixin, FormView):
form_class = ThirdPartyAuthForm
template_name = "api/third_party/auth.jinja"
success_url = reverse_lazy("core:index")
- def parse_params(self) -> ThirdPartyAuthParamsSchema:
+ def parse_params(self) -> ThirdPartyAuthParamsSchema | None:
"""Parse and check the authentication parameters.
- Raises:
- PermissionDenied: if the verification failed.
+ If parsing fails, messages will be created using the django message
+ infrastructure.
+
+ Returns:
+ The parses parameters, or None if the parsing failed.
"""
# This is here rather than in ThirdPartyAuthForm because
# 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()}
try:
params = ThirdPartyAuthParamsSchema(**params)
- except pydantic.ValidationError as e:
- raise PermissionDenied("Wrong data format") from e
+ except pydantic.ValidationError:
+ messages.error(
+ self.request, _("The data provided for authentication is incorrect")
+ )
+ return None
client: ApiClient = get_object_or_none(ApiClient, id=params.client_id)
if not client:
- raise PermissionDenied
+ messages.error(
+ self.request, _("The data provided for authentication is incorrect")
+ )
+ return None
if not hmac.compare_digest(
hmac_hexdigest(client.hmac_key, params.model_dump(exclude={"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
def dispatch(self, request, *args, **kwargs):
+ if not request.user.is_authenticated:
+ return self.handle_no_permission()
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)
def get(self, *args, **kwargs):
@@ -73,10 +96,14 @@ class ThirdPartyAuthView(LoginRequiredMixin, FormView):
client = ApiClient.objects.get(id=form.cleaned_data["client_id"])
user = UserProfileSchema.from_orm(self.request.user).model_dump()
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(
"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)
diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po
index e3fb81f7..92e160fd 100644
--- a/locale/fr/LC_MESSAGES/django.po
+++ b/locale/fr/LC_MESSAGES/django.po
@@ -148,14 +148,24 @@ msgid ""
"href=\"%(sith_cgu_link)s\">the Students' Association applies as soon as "
"the form is submitted."
msgstr ""
-"Les politiques de confidentialité de %(app)s et de l'Association des Etudiants s'appliquent dès la soumission "
-"du formulaire."
+"Les politiques de confidentialité de %(app)s"
+"a> et de l'Association des Etudiants "
+"s'appliquent dès la soumission du formulaire."
#: api/templates/api/third_party/auth.jinja
msgid "Confirmation of identity"
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
#, python-format
msgid ""
@@ -167,7 +177,9 @@ msgstr ""
#: api/views.py
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
msgid ""
@@ -175,9 +187,9 @@ msgid ""
"during the interaction with the third-party application. Please contact the "
"managers of the latter."
msgstr ""
-"Votre authentification sur le site AE a fonctionné, mais une erreur est arrivée "
-"durant l'interaction avec l'application tierce. Veuillez contacter les responsables "
-"de cette dernière."
+"Votre authentification sur le site AE a fonctionné, mais une erreur est "
+"arrivée durant l'interaction avec l'application tierce. Veuillez contacter "
+"les responsables de cette dernière."
#: club/forms.py
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"
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
msgid "slug name"
msgstr "nom slug"
@@ -383,37 +413,22 @@ msgstr "Cet email est déjà abonné à cette mailing"
msgid "Unregistered user"
msgstr "Utilisateur non enregistré"
-#: club/templates/club/club_list.jinja
-msgid "Club list"
-msgstr "Liste des clubs"
-
#: club/templates/club/club_list.jinja
msgid "The list of all clubs existing at 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
msgid "Filters"
msgstr "Filtres"
-#: club/templates/club/club_list.jinja
-msgid "Name"
-msgstr "Nom"
-
-#: 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/base/header.jinja
+#: forum/templates/forum/macros.jinja matmat/templates/matmat/search_form.jinja
+msgid "Search"
+msgstr "Recherche"
#: club/templates/club/club_list.jinja core/templates/core/user_tools.jinja
msgid "New club"
@@ -1945,11 +1960,6 @@ msgstr "Connexion"
msgid "Register"
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
msgid "Logout"
msgstr "Déconnexion"
@@ -4294,6 +4304,47 @@ msgstr ""
msgid "this 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
msgid "There are no items available for sale"
msgstr "Aucun article n'est disponible à la vente"
@@ -5720,10 +5771,6 @@ msgstr "fin"
msgid "Moderate Trombi comments"
msgstr "Modérer les commentaires du Trombi"
-#: trombi/templates/trombi/comment_moderation.jinja
-msgid "Accept"
-msgstr "Accepter"
-
#: trombi/templates/trombi/comment_moderation.jinja
msgid "Reject"
msgstr "Refuser"
@@ -5965,39 +6012,3 @@ msgstr "Vous ne pouvez plus écrire de commentaires, la date est passée."
#, python-format
msgid "Maximum characters: %(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."