diff --git a/lacommunaute/forum_member/tests/__snapshots__/tests_view.ambr b/lacommunaute/forum_member/tests/__snapshots__/tests_view.ambr index d52f93c1f..0d6f074f5 100644 --- a/lacommunaute/forum_member/tests/__snapshots__/tests_view.ambr +++ b/lacommunaute/forum_member/tests/__snapshots__/tests_view.ambr @@ -1,5 +1,5 @@ # serializer version: 1 -# name: TestCreateUserView.test_post_existing_email[create_user_view_content] +# name: TestCreateUserView.test_post_existing_email[/-/][create_user_view_content] '''
@@ -21,7 +21,7 @@
- Un lien de connexion vous a été envoyé à l'adresse john@travolta.it. Veuillez vérifier votre boîte de réception et cliquer sur le lien pour vous connecter. + Un lien de connexion vous a été envoyé à l'adresse samuel@jackson.com. Veuillez vérifier votre boîte de réception et cliquer sur le lien pour vous connecter.
@@ -31,7 +31,135 @@
''' # --- -# name: TestCreateUserView.test_post_new_email[create_user_view_content] +# name: TestCreateUserView.test_post_existing_email[/topics/-/topics/][create_user_view_content] + ''' +
+ + + + + + +
+
+
+
+

Se connecter | S'inscrire

+
+
+
+
+
+
+
+
+ Un lien de connexion vous a été envoyé à l'adresse samuel@jackson.com. Veuillez vérifier votre boîte de réception et cliquer sur le lien pour vous connecter. +
+
+
+
+ + +
+ ''' +# --- +# name: TestCreateUserView.test_post_existing_email[http://www.unallowed_host.com-/][create_user_view_content] + ''' +
+ + + + + + +
+
+
+
+

Se connecter | S'inscrire

+
+
+
+
+
+
+
+
+ Un lien de connexion vous a été envoyé à l'adresse samuel@jackson.com. Veuillez vérifier votre boîte de réception et cliquer sur le lien pour vous connecter. +
+
+
+
+ + +
+ ''' +# --- +# name: TestCreateUserView.test_post_new_email[/-/][create_user_view_content] + ''' +
+ + + + + + +
+
+
+
+

Se connecter | S'inscrire

+
+
+
+
+
+
+
+
+ Un lien de connexion vous a été envoyé à l'adresse john@travolta.lt. Veuillez vérifier votre boîte de réception et cliquer sur le lien pour vous connecter. +
+
+
+
+ + +
+ ''' +# --- +# name: TestCreateUserView.test_post_new_email[/topics/-/topics/][create_user_view_content] + ''' +
+ + + + + + +
+
+
+
+

Se connecter | S'inscrire

+
+
+
+
+
+
+
+
+ Un lien de connexion vous a été envoyé à l'adresse john@travolta.lt. Veuillez vérifier votre boîte de réception et cliquer sur le lien pour vous connecter. +
+
+
+
+ + +
+ ''' +# --- +# name: TestCreateUserView.test_post_new_email[http://www.unallowed_host.com-/][create_user_view_content] '''
@@ -87,7 +215,7 @@
-
+
@@ -146,7 +274,7 @@
-
+
@@ -205,7 +333,7 @@
-
+
@@ -240,7 +368,7 @@
''' # --- -# name: TestLoginView.test_post[/][login_view_content] +# name: TestLoginView.test_post[/-/][login_view_content] '''
@@ -262,7 +390,7 @@
- Un lien de connexion vous a été envoyé à l'adresse test@server.com. Veuillez vérifier votre boîte de réception et cliquer sur le lien pour vous connecter. + Un lien de connexion vous a été envoyé à l'adresse samuel@jackson.com. Veuillez vérifier votre boîte de réception et cliquer sur le lien pour vous connecter.
@@ -272,7 +400,7 @@
''' # --- -# name: TestLoginView.test_post[/topics/][login_view_content] +# name: TestLoginView.test_post[/topics/-/topics/][login_view_content] '''
@@ -294,7 +422,7 @@
- Un lien de connexion vous a été envoyé à l'adresse test@server.com. Veuillez vérifier votre boîte de réception et cliquer sur le lien pour vous connecter. + Un lien de connexion vous a été envoyé à l'adresse samuel@jackson.com. Veuillez vérifier votre boîte de réception et cliquer sur le lien pour vous connecter.
@@ -304,7 +432,7 @@
''' # --- -# name: TestLoginView.test_post[None][login_view_content] +# name: TestLoginView.test_post[http://www.unallowed_host.com-/][login_view_content] '''
@@ -326,7 +454,7 @@
- Un lien de connexion vous a été envoyé à l'adresse test@server.com. Veuillez vérifier votre boîte de réception et cliquer sur le lien pour vous connecter. + Un lien de connexion vous a été envoyé à l'adresse samuel@jackson.com. Veuillez vérifier votre boîte de réception et cliquer sur le lien pour vous connecter.
@@ -336,18 +464,6 @@
''' # --- -# name: TestSendMagicLink.test_payload_when_sending_magic_link[/-/][send_magic_link_payload] - '{"sender": {"name": "La Communaut\\u00e9", "email": "noreply@inclusion.beta.gouv.fr"}, "to": [{"email": "samuel@jackson.com"}], "params": {"display_name": "Samuel J.", "login_link": "LOGIN_LINK"}, "templateId": 31}' -# --- -# name: TestSendMagicLink.test_payload_when_sending_magic_link[/topics/-/][send_magic_link_payload] - '{"sender": {"name": "La Communaut\\u00e9", "email": "noreply@inclusion.beta.gouv.fr"}, "to": [{"email": "samuel@jackson.com"}], "params": {"display_name": "Samuel J.", "login_link": "LOGIN_LINK"}, "templateId": 31}' -# --- -# name: TestSendMagicLink.test_payload_when_sending_magic_link[None-/][send_magic_link_payload] - '{"sender": {"name": "La Communaut\\u00e9", "email": "noreply@inclusion.beta.gouv.fr"}, "to": [{"email": "samuel@jackson.com"}], "params": {"display_name": "Samuel J.", "login_link": "LOGIN_LINK"}, "templateId": 31}' -# --- -# name: TestSendMagicLink.test_payload_when_sending_magic_link[http://www.unallowed_host.com-/][send_magic_link_payload] - '{"sender": {"name": "La Communaut\\u00e9", "email": "noreply@inclusion.beta.gouv.fr"}, "to": [{"email": "samuel@jackson.com"}], "params": {"display_name": "Samuel J.", "login_link": "LOGIN_LINK"}, "templateId": 31}' -# --- -# name: TestSendMagicLink.test_payload_when_sending_magic_link[send_magic_link_payload] - '{"sender": {"name": "La Communaut\\u00e9", "email": "noreply@inclusion.beta.gouv.fr"}, "to": [{"email": "samuel@jackson.com"}], "params": {"display_name": "Samuel J.", "login_link": "LOGIN_LINK"}, "templateId": 31}' +# name: TestSendMagicLink.test_send_magic_link[send_magic_link_payload] + '{"sender": {"name": "La Communaut\\u00e9", "email": "noreply@inclusion.beta.gouv.fr"}, "to": [{"email": "edith@martin.co"}], "params": {"display_name": "Edith M.", "login_link": "LOGIN_LINK"}, "templateId": 31}' # --- diff --git a/lacommunaute/forum_member/tests/tests_view.py b/lacommunaute/forum_member/tests/tests_view.py index 0ed945306..12674b4ce 100644 --- a/lacommunaute/forum_member/tests/tests_view.py +++ b/lacommunaute/forum_member/tests/tests_view.py @@ -8,7 +8,7 @@ from lacommunaute.forum_member.factories import ForumProfileFactory from lacommunaute.forum_member.models import ForumProfile from lacommunaute.forum_member.shortcuts import get_forum_member_display_name -from lacommunaute.forum_member.views import ForumProfileUpdateView +from lacommunaute.forum_member.views import ForumProfileUpdateView, send_magic_link from lacommunaute.users.enums import IdentityProvider from lacommunaute.users.factories import UserFactory from lacommunaute.users.models import User @@ -17,14 +17,16 @@ from django.utils.encoding import force_bytes from django.utils.http import urlsafe_base64_encode from django.contrib.auth.tokens import default_token_generator -from lacommunaute.forum_member.views import send_magic_link from django.conf import settings import json from lacommunaute.utils.urls import clean_next_url +from unittest import mock PermissionHandler = get_class("forum_permission.handler", "PermissionHandler") assign_perm = get_class("forum_permission.shortcuts", "assign_perm") +next_url_tuples = [("/", "/"), ("/topics/", "/topics/"), ("http://www.unallowed_host.com", "/")] + @pytest.fixture(name="mock_respx_post_to_sib_smtp_url") def mock_respx_post_to_sib_smtp_url_fixture(): @@ -33,14 +35,53 @@ def mock_respx_post_to_sib_smtp_url_fixture(): yield -@pytest.fixture(name="user_token") -def user_token_fixture(): - user = UserFactory(first_name="Samuel", last_name="Jackson", email="samuel@jackson.com") +@pytest.fixture(name="mock_token_generator") +def mock_token_generator_fixture(): + with mock.patch.object(default_token_generator, "make_token", return_value="fixed-token"): + yield default_token_generator + + +def custom_user_token(user, fixed_token=False): uidb64 = urlsafe_base64_encode(force_bytes(user.pk)) - token = default_token_generator.make_token(user) + if fixed_token: + token = "fixed-token" + else: + token = default_token_generator.make_token(user) return user, uidb64, token +@pytest.fixture(name="user") +def user_fixture(): + return UserFactory( + first_name="Samuel", + last_name="Jackson", + email="samuel@jackson.com", + identity_provider=IdentityProvider.PRO_CONNECT.value, + ) + + +@pytest.fixture(name="user_token") +def user_token_fixture(user): + return custom_user_token(user) + + +@pytest.fixture(name="fixed_user_token") +def fixed_user_token_fixture(user): + return custom_user_token(user, fixed_token=True) + + +def validate_magiclink_payload(payload_as_str, uidb64, token, expected): + url = f"{settings.COMMU_PROTOCOL}://{settings.COMMU_FQDN}" + reverse( + "members:login_with_link", kwargs={"uidb64": uidb64, "token": token} + ) + payload = json.loads(payload_as_str) + + if payload["params"]["login_link"] != url + "?" + urlencode({"next": expected}): + return False + + return True + + class ForumProfileUpdateViewTest(TestCase): def test_success_url(self): forum_profiles = ForumProfileFactory() @@ -92,27 +133,21 @@ def test_show_linkedin_link(self, client, db): class TestSendMagicLink: - @pytest.mark.parametrize( - "next_url,expected", - [(None, "/"), ("/", "/"), ("/topics/", "/topics/"), ("http://www.unallowed_host.com", "/")], - ) - def test_payload_when_sending_magic_link( - self, client, db, user_token, next_url, expected, snapshot, mock_respx_post_to_sib_smtp_url - ): - user, uidb64, token = user_token + def test_send_magic_link(self, db, snapshot, mock_respx_post_to_sib_smtp_url): + user = UserFactory(first_name="Edith", last_name="Martin", email="edith@martin.co") + next_url = "/topics/" + send_magic_link(user, next_url) + + token = default_token_generator.make_token(user) + uidb64 = urlsafe_base64_encode(force_bytes(user.pk)) url = reverse("members:login_with_link", kwargs={"uidb64": uidb64, "token": token}) query_params = urlencode({"next": clean_next_url(next_url)}) login_link = f"{settings.COMMU_PROTOCOL}://{settings.COMMU_FQDN}{url}?{query_params}" - send_magic_link(user, next_url) - - payload = json.loads(respx.calls[0].request.content.decode()) - assert payload["params"][ - "login_link" - ] == f"{settings.COMMU_PROTOCOL}://{settings.COMMU_FQDN}{url}?" + urlencode({"next": expected}) - assert respx.calls[0].request.content.decode().replace(login_link, "LOGIN_LINK") == snapshot( - name="send_magic_link_payload" - ) + payload_as_str = respx.calls[0].request.content.decode() + payload = json.loads(payload_as_str) + assert payload["params"]["login_link"] == login_link + assert payload_as_str.replace(login_link, "LOGIN_LINK") == snapshot(name="send_magic_link_payload") class TestLoginView: @@ -124,39 +159,64 @@ def test_content(self, client, db, next_url, snapshot): content = parse_response_to_soup(response, selector="main") assert str(content) == snapshot(name="login_view_content") - @pytest.mark.parametrize("next_url", [None, "/", "/topics/"]) - def test_post(self, client, db, next_url, snapshot, mock_respx_post_to_sib_smtp_url): - user = UserFactory(email="test@server.com") + @pytest.mark.parametrize("next_url,expected", next_url_tuples) + def test_post( + self, + client, + db, + fixed_user_token, + next_url, + expected, + snapshot, + mock_token_generator, + mock_respx_post_to_sib_smtp_url, + ): + user, uidb64, token = fixed_user_token response = client.post( - reverse("members:login") + f"?next={next_url}", - data={"email": user.email}, + reverse("members:login"), + data={"email": user.email, "next": next_url}, ) + assert response.status_code == 200 content = parse_response_to_soup(response, selector="main") assert str(content) == snapshot(name="login_view_content") - @pytest.mark.parametrize( - "next_url,expected", - [(None, "/"), ("/", "/"), ("/topics/", "/topics/"), ("http://www.unallowed_host.com", "/")], - ) + payload_as_str = respx.calls[0].request.content.decode() + assert validate_magiclink_payload(payload_as_str, uidb64, token, expected) + + @pytest.mark.parametrize("next_url,expected", next_url_tuples) def test_redirection_when_email_is_unknown(self, client, db, next_url, expected, mock_respx_post_to_sib_smtp_url): response = client.post( - reverse("members:login") + f"?next={next_url}" if next_url else reverse("members:login"), - data={"email": "john@travolta.es"}, + reverse("members:login"), + data={"email": "john@travolta.es", "next": next_url}, ) assert response.status_code == 302 assert response.url == reverse("members:create") + "?" + urlencode( {"email": "john@travolta.es", "next": expected} ) + @pytest.mark.parametrize("next_url,expected", next_url_tuples) + def test_user_is_already_authenticated(self, client, db, next_url, expected): + user = UserFactory() + client.force_login(user) + url = reverse("members:login") + f"?next={next_url}" + response = client.get(url) + assert response.status_code == 302 + assert response.url == expected + class TestCreateUserView: - def test_post_new_email(self, client, db, snapshot, mock_respx_post_to_sib_smtp_url): + @pytest.mark.parametrize("next_url,expected", next_url_tuples) + def test_post_new_email( + self, client, db, next_url, expected, snapshot, mock_token_generator, mock_respx_post_to_sib_smtp_url + ): email = "john@travolta.lt" response = client.post( - reverse("members:create"), data={"email": email, "first_name": "John", "last_name": "Travolta"} + reverse("members:create"), + data={"email": email, "first_name": "John", "last_name": "Travolta", "next": next_url}, ) assert response.status_code == 200 + content = parse_response_to_soup(response, selector="main") assert str(content) == snapshot(name="create_user_view_content") @@ -168,15 +228,19 @@ def test_post_new_email(self, client, db, snapshot, mock_respx_post_to_sib_smtp_ created_forum_profile = ForumProfile.objects.get(user=created_user) assert created_forum_profile.user.email == email - def test_post_existing_email(self, client, db, snapshot, mock_respx_post_to_sib_smtp_url): - first_name = "John" - last_name = "Travolta" - identity_provider = IdentityProvider.PRO_CONNECT.value - user = UserFactory( - email="john@travolta.it", first_name=first_name, last_name=last_name, identity_provider=identity_provider - ) + _, uidb64, token = custom_user_token(created_user, fixed_token=True) + payload_as_str = respx.calls[0].request.content.decode() + assert validate_magiclink_payload(payload_as_str, uidb64, token, expected) + + @pytest.mark.parametrize("next_url,expected", next_url_tuples) + def test_post_existing_email( + self, client, db, user, next_url, expected, snapshot, mock_token_generator, mock_respx_post_to_sib_smtp_url + ): + first_name = user.first_name + last_name = user.last_name response = client.post( - reverse("members:create"), data={"email": user.email, "first_name": "Adam", "last_name": "Smith"} + reverse("members:create"), + data={"email": user.email, "first_name": "Adam", "last_name": "Smith", "next": next_url}, ) assert response.status_code == 200 content = parse_response_to_soup(response, selector="main") @@ -187,6 +251,10 @@ def test_post_existing_email(self, client, db, snapshot, mock_respx_post_to_sib_ assert user.last_name == last_name assert user.identity_provider == IdentityProvider.PRO_CONNECT + _, uidb64, token = custom_user_token(user, fixed_token=True) + payload_as_str = respx.calls[0].request.content.decode() + assert validate_magiclink_payload(payload_as_str, uidb64, token, expected) + class TestLoginWithLinkView: def test_user_not_found(self, client, db): @@ -197,7 +265,7 @@ def test_user_not_found(self, client, db): assert response.url == reverse("members:login") def test_invalid_token(self, client, db, user_token): - user, uidb64, token = user_token + _, uidb64, __doc__ = user_token response = client.get(reverse("members:login_with_link", kwargs={"uidb64": uidb64, "token": "invalid_token"})) assert response.status_code == 302 assert response.url == reverse("members:login") @@ -214,7 +282,7 @@ def test_expired_token(self, client, db, user_token): assert response.url == reverse("members:login") def test_success(self, client, db, user_token): - user, uidb64, token = user_token + _, uidb64, token = user_token next_url = "/topics/" response = client.get( @@ -225,7 +293,7 @@ def test_success(self, client, db, user_token): assert client.session.get(IdentityProvider.MAGIC_LINK.name) == 1 def test_unallowed_next_url(self, client, db, user_token): - user, uidb64, token = user_token + _, uidb64, token = user_token next_url = "http://www.unallowed_host.com" response = client.get( diff --git a/lacommunaute/forum_member/views.py b/lacommunaute/forum_member/views.py index 3b4fda4c1..a0e402af0 100644 --- a/lacommunaute/forum_member/views.py +++ b/lacommunaute/forum_member/views.py @@ -83,11 +83,11 @@ def dispatch(self, request, *args, **kwargs): # if user uses a blocked email or a blocked domain if request.user.is_authenticated: next_url = request.GET.get("next", "/") - return redirect(next_url) + return redirect(clean_next_url(next_url)) return super().dispatch(request, *args, **kwargs) def form_valid(self, form): - next_url = self.request.GET.get("next", "/") + next_url = self.request.POST.get("next", "/") email = form.cleaned_data["email"] try: diff --git a/lacommunaute/templates/registration/create_user.html b/lacommunaute/templates/registration/create_user.html index c5618c914..d65408b44 100644 --- a/lacommunaute/templates/registration/create_user.html +++ b/lacommunaute/templates/registration/create_user.html @@ -38,7 +38,7 @@

{% include "partials/form_field.html" with field=form.first_name %} {% include "partials/form_field.html" with field=form.last_name %}
- +
diff --git a/lacommunaute/templates/registration/login_with_magic_link.html b/lacommunaute/templates/registration/login_with_magic_link.html index 997a4920d..81655203a 100644 --- a/lacommunaute/templates/registration/login_with_magic_link.html +++ b/lacommunaute/templates/registration/login_with_magic_link.html @@ -36,6 +36,7 @@

{% trans "Login | Sign in" %}

{% endif %} {% include "partials/form_field.html" with field=form.email %}
+