From dbdc624d760aa2034e33e0d5bb1ddb14ba2ef573 Mon Sep 17 00:00:00 2001 From: rgraber Date: Wed, 11 Sep 2024 16:10:24 -0400 Subject: [PATCH 1/3] feat: endpoint to log user out of all sessions --- kobo/settings/base.py | 2 ++ kpi/tests/api/v2/test_api_logout_all.py | 48 +++++++++++++++++++++++++ kpi/urls/__init__.py | 2 ++ kpi/urls/router_api_v2.py | 1 + kpi/views/v2/logout.py | 32 +++++++++++++++++ 5 files changed, 85 insertions(+) create mode 100644 kpi/tests/api/v2/test_api_logout_all.py create mode 100644 kpi/views/v2/logout.py diff --git a/kobo/settings/base.py b/kobo/settings/base.py index e207737429..2b942448af 100644 --- a/kobo/settings/base.py +++ b/kobo/settings/base.py @@ -105,6 +105,7 @@ 'allauth.socialaccount', 'allauth.socialaccount.providers.microsoft', 'allauth.socialaccount.providers.openid_connect', + 'allauth.usersessions', 'hub.HubAppConfig', 'loginas', 'webpack_loader', @@ -154,6 +155,7 @@ 'django.contrib.sessions.middleware.SessionMiddleware', 'hub.middleware.LocaleMiddleware', 'allauth.account.middleware.AccountMiddleware', + 'allauth.usersessions.middleware.UserSessionsMiddleware', 'django.middleware.common.CommonMiddleware', # Still needed really? 'kobo.apps.openrosa.libs.utils.middleware.LocaleMiddlewareWithTweaks', diff --git a/kpi/tests/api/v2/test_api_logout_all.py b/kpi/tests/api/v2/test_api_logout_all.py new file mode 100644 index 0000000000..e1fcc5c389 --- /dev/null +++ b/kpi/tests/api/v2/test_api_logout_all.py @@ -0,0 +1,48 @@ +from allauth.usersessions.models import UserSession +from django.urls import reverse + +from kobo.apps.kobo_auth.shortcuts import User +from kpi.tests.base_test_case import BaseTestCase + + +class TestLogoutAll(BaseTestCase): + + fixtures = ['test_data'] + + def test_logout_all_sessions(self): + # create 2 user sessions + user = User.objects.get(username='someuser') + UserSession.objects.create(user=user, session_key='12345', ip='1.2.3.4') + UserSession.objects.create(user=user, session_key='56789', ip='5.6.7.8') + count = UserSession.objects.filter(user=user).count() + self.assertEqual(count, 2) + self.client.force_login(user) + url = self._get_endpoint('logout_all') + self.client.post(reverse(url)) + + # ensure both sessions have been deleted + count = UserSession.objects.filter(user=user).count() + self.assertEqual(count, 0) + + def test_logout_all_sessions_does_not_affect_other_users(self): + # create 2 user sessions + user1 = User.objects.get(username='someuser') + user2 = User.objects.get(username='anotheruser') + # create sessions for user1 + UserSession.objects.create( + user=user1, session_key='12345', ip='1.2.3.4' + ) + UserSession.objects.create( + user=user1, session_key='56789', ip='5.6.7.8' + ) + count = UserSession.objects.count() + self.assertEqual(count, 2) + + # login user2 + self.client.force_login(user2) + url = self._get_endpoint('logout_all') + self.client.post(reverse(url)) + + # ensure no sessions have been deleted + count = UserSession.objects.filter().count() + self.assertEqual(count, 2) diff --git a/kpi/urls/__init__.py b/kpi/urls/__init__.py index 786df65e38..9cf2ce732b 100644 --- a/kpi/urls/__init__.py +++ b/kpi/urls/__init__.py @@ -13,6 +13,7 @@ from .router_api_v1 import router_api_v1 from .router_api_v2 import router_api_v2, URL_NAMESPACE +from ..views.v2.logout import logout_from_all_devices # TODO: Give other apps their own `urls.py` files instead of importing their # views directly! See @@ -50,6 +51,7 @@ re_path(r'^private-media/', include(private_storage.urls)), # Statistics for superusers re_path(r'^superuser_stats/', include(('kobo.apps.superuser_stats.urls', 'superuser_stats'))), + path('logout-all/', logout_from_all_devices, name='logout_all') ] diff --git a/kpi/urls/router_api_v2.py b/kpi/urls/router_api_v2.py index 7c7dbd2575..033fc1566c 100644 --- a/kpi/urls/router_api_v2.py +++ b/kpi/urls/router_api_v2.py @@ -21,6 +21,7 @@ from kpi.views.v2.data import DataViewSet from kpi.views.v2.export_task import ExportTaskViewSet from kpi.views.v2.import_task import ImportTaskViewSet +from kpi.views.v2.logout import logout_from_all_devices from kpi.views.v2.paired_data import PairedDataViewset from kpi.views.v2.permission import PermissionViewSet from kpi.views.v2.service_usage import ServiceUsageViewSet diff --git a/kpi/views/v2/logout.py b/kpi/views/v2/logout.py new file mode 100644 index 0000000000..959f6a8330 --- /dev/null +++ b/kpi/views/v2/logout.py @@ -0,0 +1,32 @@ +from allauth.usersessions.adapter import get_adapter +from allauth.usersessions.models import UserSession +from rest_framework.decorators import api_view, permission_classes +from rest_framework.response import Response + +from kpi.permissions import IsAuthenticated + + +@api_view(['POST']) +@permission_classes((IsAuthenticated,)) +def logout_from_all_devices(request): + """ + Log calling user out from all devices + +
+    POST /logout-all/
+    
+ + > Example + > + > curl -H 'Authorization Token 12345' -X POST https://[kpi-url]/logout-all + + > Response 200 + + > { "Logged out of all sessions" } + + """ + user = request.user + all_user_sessions = UserSession.objects.purge_and_list(user) + adapter = get_adapter() + adapter.end_sessions(all_user_sessions) + return Response('Logged out of all sessions') From f5e0ed3fb2d71b5613a06b8d6e3f0709407a16b0 Mon Sep 17 00:00:00 2001 From: Rebecca Graber Date: Thu, 12 Sep 2024 12:26:07 -0400 Subject: [PATCH 2/3] fixup!: rm bad comment --- kpi/tests/api/v2/test_api_logout_all.py | 1 - 1 file changed, 1 deletion(-) diff --git a/kpi/tests/api/v2/test_api_logout_all.py b/kpi/tests/api/v2/test_api_logout_all.py index e1fcc5c389..c4c04ee2ea 100644 --- a/kpi/tests/api/v2/test_api_logout_all.py +++ b/kpi/tests/api/v2/test_api_logout_all.py @@ -25,7 +25,6 @@ def test_logout_all_sessions(self): self.assertEqual(count, 0) def test_logout_all_sessions_does_not_affect_other_users(self): - # create 2 user sessions user1 = User.objects.get(username='someuser') user2 = User.objects.get(username='anotheruser') # create sessions for user1 From 435ee420e02c6c700641a7646736e371daa78c5b Mon Sep 17 00:00:00 2001 From: Rebecca Graber Date: Thu, 19 Sep 2024 16:20:53 -0400 Subject: [PATCH 3/3] fixup!: rm unneeded import --- kpi/urls/router_api_v2.py | 1 - 1 file changed, 1 deletion(-) diff --git a/kpi/urls/router_api_v2.py b/kpi/urls/router_api_v2.py index 033fc1566c..7c7dbd2575 100644 --- a/kpi/urls/router_api_v2.py +++ b/kpi/urls/router_api_v2.py @@ -21,7 +21,6 @@ from kpi.views.v2.data import DataViewSet from kpi.views.v2.export_task import ExportTaskViewSet from kpi.views.v2.import_task import ImportTaskViewSet -from kpi.views.v2.logout import logout_from_all_devices from kpi.views.v2.paired_data import PairedDataViewset from kpi.views.v2.permission import PermissionViewSet from kpi.views.v2.service_usage import ServiceUsageViewSet