Skip to content

Commit

Permalink
www: Allow logout on GET
Browse files Browse the repository at this point in the history
This allows the perms middleware to logout an employer without company
membership
  • Loading branch information
tonial committed Sep 3, 2024
1 parent e965cfa commit a5d2e07
Show file tree
Hide file tree
Showing 11 changed files with 73 additions and 14 deletions.
6 changes: 6 additions & 0 deletions config/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from itou.www.dashboard import views as dashboard_views
from itou.www.error import server_error
from itou.www.login import views as login_views
from itou.www.logout import views as logout_views
from itou.www.signup import views as signup_views


Expand All @@ -30,6 +31,11 @@
# Customized login pages per user type are handled by login.urls.
re_path(r"^accounts/login/$", login_views.ItouLoginView.as_view()),
# --------------------------------------------------------------------------------------
# Override allauth `account_logout` URL.
# /accounts/login/ <=> account_login
# This allows to logout on GET with a query parameter
re_path(r"^accounts/logout/$", logout_views.ItouLogoutView.as_view()),
# --------------------------------------------------------------------------------------
# Override allauth `account_change_password` URL.
# /accounts/password/change/ <=> account_change_password
# https://github.com/pennersr/django-allauth/issues/468
Expand Down
4 changes: 2 additions & 2 deletions itou/utils/perms/middleware.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
from django.conf import settings
from django.contrib import messages
from django.http import HttpResponseRedirect
from django.shortcuts import redirect
from django.urls import reverse
from django.utils.safestring import mark_safe

from itou.users.enums import IdentityProvider, UserKind
from itou.utils import constants as global_constants
from itou.utils.urls import add_url_params
from itou.www.login import urls as login_urls


Expand Down Expand Up @@ -164,6 +164,6 @@ def __call__(self, request):

if redirect_message is not None:
messages.warning(request, redirect_message)
return redirect("account_logout")
return HttpResponseRedirect(add_url_params(reverse("account_logout"), {"forced": True}))

return self.get_response(request)
Empty file added itou/www/logout/__init__.py
Empty file.
8 changes: 8 additions & 0 deletions itou/www/logout/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from allauth.account.views import LogoutView


class ItouLogoutView(LogoutView):
def get(self, *args, **kwargs):
if self.request.GET.get("forced"):
return self.post(*args, **kwargs)
return super().get(*args, **kwargs)
6 changes: 5 additions & 1 deletion tests/api/employee_record_api/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,11 @@ def test_permission_ko_with_session(self, api_client):
api_client.force_login(self.unauthorized_user)

response = api_client.get(ENDPOINT_URL, format="json")
assertRedirects(response, reverse("account_logout"), status_code=302, target_status_code=200)
assertRedirects(response, reverse("account_logout") + "?forced=True", fetch_redirect_response=False)

# FIXME(alaurent) : l'api renvoi une page html ?
response = api_client.get(response.url, follow=True)
assert response.status_code == 200


class TestEmployeeRecordAPIFetchList:
Expand Down
3 changes: 0 additions & 3 deletions tests/openid_connect/inclusion_connect/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,6 @@ def mock_oauth_dance(
else:
assert response.url.startswith(constants.INCLUSION_CONNECT_ENDPOINT_AUTHORIZE)

# User is logged out from IC when an error happens during the oauth dance.
respx.get(constants.INCLUSION_CONNECT_ENDPOINT_LOGOUT).respond(200)

token_json = {"access_token": "access_token", "token_type": "Bearer", "expires_in": 60, "id_token": "123456"}
respx.post(constants.INCLUSION_CONNECT_ENDPOINT_TOKEN).mock(return_value=httpx.Response(200, json=token_json))

Expand Down
6 changes: 3 additions & 3 deletions tests/utils/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ def test_siae_no_member(self, mocked_get_response_for_middlewaremixin):
with assertNumQueries(1): # Retrieve user memberships
response = ItouCurrentOrganizationMiddleware(get_response_for_middlewaremixin)(request)
assert mocked_get_response_for_middlewaremixin.call_count == 0
assertRedirects(response, reverse("account_logout"), fetch_redirect_response=False)
assertRedirects(response, reverse("account_logout") + "?forced=True", fetch_redirect_response=False)
assert list(messages.get_messages(request)) == [
messages.Message(
messages.WARNING,
Expand Down Expand Up @@ -238,7 +238,7 @@ def test_employer_of_inactive_siae(self, mocked_get_response_for_middlewaremixin
):
response = ItouCurrentOrganizationMiddleware(mocked_get_response_for_middlewaremixin)(request)
assert mocked_get_response_for_middlewaremixin.call_count == 0
assertRedirects(response, reverse("account_logout"), fetch_redirect_response=False)
assertRedirects(response, reverse("account_logout") + "?forced=True", fetch_redirect_response=False)
assert list(messages.get_messages(request)) == [
messages.Message(
messages.WARNING,
Expand Down Expand Up @@ -434,7 +434,7 @@ def test_labor_inspector_no_member(self, mocked_get_response_for_middlewaremixin
with assertNumQueries(1): # retrieve user memberships
response = ItouCurrentOrganizationMiddleware(mocked_get_response_for_middlewaremixin)(request)
assert mocked_get_response_for_middlewaremixin.call_count == 0
assertRedirects(response, reverse("account_logout"), fetch_redirect_response=False)
assertRedirects(response, reverse("account_logout") + "?forced=True", fetch_redirect_response=False)
assert list(messages.get_messages(request)) == [
messages.Message(
messages.WARNING,
Expand Down
2 changes: 1 addition & 1 deletion tests/www/companies_views/test_members_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ def test_user_with_no_company_left(self):

# should be redirected to logout
assert response.status_code == 302
assert response.url == reverse("account_logout")
assert response.url == reverse("account_logout") + "?forced=True"

def test_structure_selector(self):
"""
Expand Down
10 changes: 6 additions & 4 deletions tests/www/dashboard/test_dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import pytest
from dateutil.relativedelta import relativedelta
from django.conf import settings
from django.contrib.auth import get_user
from django.test import override_settings
from django.urls import reverse
from django.utils import timezone
Expand Down Expand Up @@ -103,11 +104,12 @@ def test_user_with_inactive_company_cannot_login_after_grace_period(self):
self.client.force_login(user)

url = reverse("dashboard:index")
response = self.client.get(url, follow=True)
assert response.status_code == 200
last_url = response.redirect_chain[-1][0]
assert last_url == reverse("account_logout")
response = self.client.get(url)
self.assertRedirects(response, reverse("account_logout") + "?forced=True", fetch_redirect_response=False)

response = self.client.get(response.url, follow=True)
assert response.status_code == 200
assert not get_user(self.client).is_authenticated
expected_message = "votre compte n&#x27;est malheureusement plus actif"
self.assertContains(response, expected_message)

Expand Down
Empty file added tests/www/logout/__init__.py
Empty file.
42 changes: 42 additions & 0 deletions tests/www/logout/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from django.contrib import messages
from django.contrib.auth import get_user
from django.urls import reverse
from pytest_django.asserts import assertMessages

from tests.users.factories import EmployerFactory, JobSeekerFactory


CONNECT_WITH_IC = "Se connecter avec Inclusion Connect"


class TestItouLogout:
def test_logout_on_get(self, client):
user = JobSeekerFactory()
client.force_login(user)
assert get_user(client).is_authenticated

client.get(reverse("account_logout"))
assert get_user(client).is_authenticated

client.get(reverse("account_logout") + "?forced=True")
assert not get_user(client).is_authenticated

def test_employeur_no_membership(self, client):
employer = EmployerFactory(with_company=False)
client.force_login(employer)

response = client.get(reverse("dashboard:index"), follow=True)
assert not get_user(client).is_authenticated
assertMessages(
response,
[
messages.Message(
messages.WARNING,
(
"Nous sommes désolés, votre compte n'est actuellement rattaché à aucune structure."
"<br>Nous espérons cependant avoir l'occasion de vous accueillir de nouveau."
),
),
messages.Message(messages.SUCCESS, "Vous êtes déconnecté(e)."),
],
)

0 comments on commit a5d2e07

Please sign in to comment.