Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Log out user from all devices #5106

Merged
merged 3 commits into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions kobo/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@
'allauth.socialaccount',
'allauth.socialaccount.providers.microsoft',
'allauth.socialaccount.providers.openid_connect',
'allauth.usersessions',
'hub.HubAppConfig',
'loginas',
'webpack_loader',
Expand Down Expand Up @@ -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',
Expand Down
47 changes: 47 additions & 0 deletions kpi/tests/api/v2/test_api_logout_all.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
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):
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)
2 changes: 2 additions & 0 deletions kpi/urls/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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')
]


Expand Down
32 changes: 32 additions & 0 deletions kpi/views/v2/logout.py
Original file line number Diff line number Diff line change
@@ -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
<pre class="prettyprint">
<b>POST</b> /logout-all/
</pre>
> 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')
Loading