From 14ecc3b3195313d62bef34256d84c0263995b139 Mon Sep 17 00:00:00 2001 From: vincent porte Date: Wed, 13 Nov 2024 14:26:17 +0100 Subject: [PATCH] add login_with_link and logout views --- lacommunaute/forum_member/tests/tests_view.py | 70 +++++++++++++++++++ lacommunaute/forum_member/urls.py | 4 ++ lacommunaute/forum_member/views.py | 21 ++++++ .../registration/includes/logout_link.html | 6 ++ .../registration/login_link_sent.html | 28 ++++++++ 5 files changed, 129 insertions(+) create mode 100644 lacommunaute/templates/registration/includes/logout_link.html create mode 100644 lacommunaute/templates/registration/login_link_sent.html diff --git a/lacommunaute/forum_member/tests/tests_view.py b/lacommunaute/forum_member/tests/tests_view.py index 549d883c4..471b07499 100644 --- a/lacommunaute/forum_member/tests/tests_view.py +++ b/lacommunaute/forum_member/tests/tests_view.py @@ -11,6 +11,7 @@ from lacommunaute.forum_member.views import ForumProfileUpdateView from lacommunaute.users.enums import IdentityProvider from lacommunaute.users.factories import UserFactory +from lacommunaute.users.models import User from lacommunaute.utils.testing import parse_response_to_soup from urllib.parse import urlencode from django.utils.encoding import force_bytes @@ -166,3 +167,72 @@ def test_post_existing_email(self, client, db, snapshot, mock_respx_post_to_sib_ assert user.first_name == first_name assert user.last_name == last_name assert user.identity_provider == IdentityProvider.PRO_CONNECT + + +class TestLoginWithLinkView: + def test_user_not_found(self, client, db): + response = client.get( + reverse("members:login_with_link", kwargs={"uidb64": "dGVzdEB0ZXN0LmNvbQ==", "token": "token"}) + ) + assert response.status_code == 302 + assert response.url == reverse("members:login") + + def test_invalid_token(self, client, db): + user = UserFactory() + uidb64 = urlsafe_base64_encode(force_bytes(user.pk)) + response = client.get(reverse("members:login_with_link", kwargs={"uidb64": uidb64, "token": "token"})) + assert response.status_code == 302 + assert response.url == reverse("members:login") + + def test_expired_token(self, client, db): + user = UserFactory() + uidb64 = urlsafe_base64_encode(force_bytes(user.pk)) + expired_token = default_token_generator.make_token(user) + + client.force_login(user) + # force logout + client.session.flush() + + response = client.get(reverse("members:login_with_link", kwargs={"uidb64": uidb64, "token": expired_token})) + assert response.status_code == 302 + assert response.url == reverse("members:login") + + def test_success(self, client, db): + user = UserFactory() + uidb64 = urlsafe_base64_encode(force_bytes(user.pk)) + token = default_token_generator.make_token(user) + next_url = "/topics/" + + response = client.get( + reverse("members:login_with_link", kwargs={"uidb64": uidb64, "token": token}), data={"next": next_url} + ) + assert response.status_code == 302 + assert response.url == next_url + assert client.session.get(IdentityProvider.MAGIC_LINK.name) == 1 + + +class TestLogoutView: + @pytest.mark.parametrize( + "login,expected_logout_url", + [("proconnect", reverse("openid_connect:logout")), ("magiclink", reverse("members:logout"))], + ) + def test_logout_url_in_template(self, client, db, login, expected_logout_url): + user = UserFactory() + client.force_login(user) + + if login == "magiclink": + session = client.session + session[IdentityProvider.MAGIC_LINK.name] = 1 + session.save() + + response = client.get(reverse("pages:home")) + assertContains(response, expected_logout_url) + + def test_logout_from_magiclink_session(self, client, db): + user = UserFactory() + client.force_login(user) + response = client.get(reverse("members:logout")) + assert response.status_code == 302 + assert response.url == reverse("pages:home") + assert client.session.get("_auth_user_id") is None + assert client.session.get("_auth_user_backend") is None diff --git a/lacommunaute/forum_member/urls.py b/lacommunaute/forum_member/urls.py index e018b6e06..68a9845a6 100644 --- a/lacommunaute/forum_member/urls.py +++ b/lacommunaute/forum_member/urls.py @@ -6,6 +6,8 @@ ForumProfileUpdateView, LoginView, SeekersListView, + login_with_link, + logout_view, ) @@ -17,4 +19,6 @@ path("profile//", ForumProfileDetailView.as_view(), name="profile"), path("seekers/", SeekersListView.as_view(), name="seekers"), path("login/", LoginView.as_view(), name="login"), + path("login///", login_with_link, name="login_with_link"), + path("logout/", logout_view, name="logout"), ] diff --git a/lacommunaute/forum_member/views.py b/lacommunaute/forum_member/views.py index dabeee999..031c695ca 100644 --- a/lacommunaute/forum_member/views.py +++ b/lacommunaute/forum_member/views.py @@ -2,6 +2,7 @@ from urllib.parse import urlencode from django.conf import settings +from django.contrib.auth import login, logout from django.contrib.auth.tokens import default_token_generator from django.http import HttpResponseRedirect from django.shortcuts import redirect, render @@ -132,3 +133,23 @@ def form_valid(self, form): send_magic_link(user, next_url) return render(self.request, "registration/login_link_sent.html", {"email": email}) + + +def login_with_link(request, uidb64, token): + try: + uid = force_str(urlsafe_base64_decode(uidb64)) + user = User.objects.get(pk=uid) + except (TypeError, ValueError, OverflowError, User.DoesNotExist): + user = None + + if user and default_token_generator.check_token(user, token): + login(request, user) + request.session[IdentityProvider.MAGIC_LINK.name] = 1 + return HttpResponseRedirect(request.GET.get("next", "/")) + + return HttpResponseRedirect(reverse("members:login")) + + +def logout_view(request): + logout(request) + return HttpResponseRedirect(reverse("pages:home")) diff --git a/lacommunaute/templates/registration/includes/logout_link.html b/lacommunaute/templates/registration/includes/logout_link.html new file mode 100644 index 000000000..c6a68c90e --- /dev/null +++ b/lacommunaute/templates/registration/includes/logout_link.html @@ -0,0 +1,6 @@ +{% if request.session.MAGIC_LINK %} + {% url 'members:logout' as logout_url %} +{% else %} + {% url 'openid_connect:logout' as logout_url %} +{% endif %} +Déconnexion diff --git a/lacommunaute/templates/registration/login_link_sent.html b/lacommunaute/templates/registration/login_link_sent.html new file mode 100644 index 000000000..a7624f148 --- /dev/null +++ b/lacommunaute/templates/registration/login_link_sent.html @@ -0,0 +1,28 @@ +{% extends "layouts/base.html" %} +{% load static %} +{% load i18n %} +{% load theme_inclusion %} +{% block title %}Connexion {{ block.super }}{% endblock %} +{% block meta_description %} + {% trans "Login | Sign in" %} +{% endblock meta_description %} +{% block content %} +
+
+
+
+

{% trans "Login | Sign in" %}

+
+
+
+
+
+
+
+
+ Un lien de connexion vous a été envoyé à l'adresse {{ email }}. Veuillez vérifier votre boîte de réception et cliquer sur le lien pour vous connecter. +
+
+
+
+{% endblock content %}