Skip to content

Commit

Permalink
Log out user from all devices (#5106)
Browse files Browse the repository at this point in the history
  • Loading branch information
rgraber authored Sep 20, 2024
1 parent a658e4b commit bf20690
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 0 deletions.
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')

0 comments on commit bf20690

Please sign in to comment.