Skip to content

Commit c908d9e

Browse files
authored
providers/oauth2, rac: make sure tokens are revoked after session deletion (#14011)
1 parent a07fd8d commit c908d9e

File tree

3 files changed

+117
-10
lines changed

3 files changed

+117
-10
lines changed

authentik/providers/oauth2/signals.py

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,29 @@
11
from django.contrib.auth.signals import user_logged_out
2-
from django.db.models.signals import post_save
2+
from django.db.models.signals import post_save, pre_delete
33
from django.dispatch import receiver
44
from django.http import HttpRequest
55

6-
from authentik.core.models import User
6+
from authentik.core.models import AuthenticatedSession, User
77
from authentik.providers.oauth2.models import AccessToken, DeviceToken, RefreshToken
88

99

1010
@receiver(user_logged_out)
11-
def user_logged_out_oauth_access_token(sender, request: HttpRequest, user: User, **_):
12-
"""Revoke access tokens upon user logout"""
11+
def user_logged_out_oauth_tokens_removal(sender, request: HttpRequest, user: User, **_):
12+
"""Revoke tokens upon user logout"""
1313
if not request.session or not request.session.session_key:
1414
return
1515
AccessToken.objects.filter(
16-
user=user, session__session__session_key=request.session.session_key
16+
user=user,
17+
session__session__session_key=request.session.session_key,
18+
).delete()
19+
20+
21+
@receiver(pre_delete, sender=AuthenticatedSession)
22+
def user_session_deleted_oauth_tokens_removal(sender, instance: AuthenticatedSession, **_):
23+
"""Revoke tokens upon user logout"""
24+
AccessToken.objects.filter(
25+
user=instance.user,
26+
session__session__session_key=instance.session.session_key,
1727
).delete()
1828

1929

@@ -22,6 +32,6 @@ def user_deactivated(sender, instance: User, **_):
2232
"""Remove user tokens when deactivated"""
2333
if instance.is_active:
2434
return
25-
AccessToken.objects.filter(session__user=instance).delete()
26-
RefreshToken.objects.filter(session__user=instance).delete()
27-
DeviceToken.objects.filter(session__user=instance).delete()
35+
AccessToken.objects.filter(user=instance).delete()
36+
RefreshToken.objects.filter(user=instance).delete()
37+
DeviceToken.objects.filter(user=instance).delete()

authentik/providers/oauth2/tests/test_revoke.py

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,21 @@
77
from django.urls import reverse
88
from django.utils import timezone
99

10-
from authentik.core.models import Application
10+
from authentik.core.models import Application, AuthenticatedSession, Session
1111
from authentik.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow
1212
from authentik.lib.generators import generate_id
1313
from authentik.providers.oauth2.models import (
1414
AccessToken,
1515
ClientTypes,
16+
DeviceToken,
1617
IDToken,
1718
OAuth2Provider,
1819
RedirectURI,
1920
RedirectURIMatchingMode,
2021
RefreshToken,
2122
)
2223
from authentik.providers.oauth2.tests.utils import OAuthTestCase
24+
from authentik.root.middleware import ClientIPMiddleware
2325

2426

2527
class TesOAuth2Revoke(OAuthTestCase):
@@ -135,3 +137,86 @@ def test_revoke_public(self):
135137
},
136138
)
137139
self.assertEqual(res.status_code, 200)
140+
141+
def test_revoke_logout(self):
142+
"""Test revoke on logout"""
143+
self.client.force_login(self.user)
144+
AccessToken.objects.create(
145+
provider=self.provider,
146+
user=self.user,
147+
session=self.client.session["authenticatedsession"],
148+
token=generate_id(),
149+
auth_time=timezone.now(),
150+
_scope="openid user profile",
151+
_id_token=json.dumps(
152+
asdict(
153+
IDToken("foo", "bar"),
154+
)
155+
),
156+
)
157+
self.client.logout()
158+
self.assertEqual(AccessToken.objects.all().count(), 0)
159+
160+
def test_revoke_session_delete(self):
161+
"""Test revoke on logout"""
162+
session = AuthenticatedSession.objects.create(
163+
session=Session.objects.create(
164+
session_key=generate_id(),
165+
last_ip=ClientIPMiddleware.default_ip,
166+
),
167+
user=self.user,
168+
)
169+
AccessToken.objects.create(
170+
provider=self.provider,
171+
user=self.user,
172+
session=session,
173+
token=generate_id(),
174+
auth_time=timezone.now(),
175+
_scope="openid user profile",
176+
_id_token=json.dumps(
177+
asdict(
178+
IDToken("foo", "bar"),
179+
)
180+
),
181+
)
182+
session.delete()
183+
self.assertEqual(AccessToken.objects.all().count(), 0)
184+
185+
def test_revoke_user_deactivated(self):
186+
"""Test revoke on logout"""
187+
AccessToken.objects.create(
188+
provider=self.provider,
189+
user=self.user,
190+
token=generate_id(),
191+
auth_time=timezone.now(),
192+
_scope="openid user profile",
193+
_id_token=json.dumps(
194+
asdict(
195+
IDToken("foo", "bar"),
196+
)
197+
),
198+
)
199+
RefreshToken.objects.create(
200+
provider=self.provider,
201+
user=self.user,
202+
token=generate_id(),
203+
auth_time=timezone.now(),
204+
_scope="openid user profile",
205+
_id_token=json.dumps(
206+
asdict(
207+
IDToken("foo", "bar"),
208+
)
209+
),
210+
)
211+
DeviceToken.objects.create(
212+
provider=self.provider,
213+
user=self.user,
214+
_scope="openid user profile",
215+
)
216+
217+
self.user.is_active = False
218+
self.user.save()
219+
220+
self.assertEqual(AccessToken.objects.all().count(), 0)
221+
self.assertEqual(RefreshToken.objects.all().count(), 0)
222+
self.assertEqual(DeviceToken.objects.all().count(), 0)

authentik/providers/rac/signals.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from django.dispatch import receiver
99
from django.http import HttpRequest
1010

11-
from authentik.core.models import User
11+
from authentik.core.models import AuthenticatedSession, User
1212
from authentik.providers.rac.api.endpoints import user_endpoint_cache_key
1313
from authentik.providers.rac.consumer_client import (
1414
RAC_CLIENT_GROUP_SESSION,
@@ -32,6 +32,18 @@ def user_logged_out_session(sender, request: HttpRequest, user: User, **_):
3232
)
3333

3434

35+
@receiver(pre_delete, sender=AuthenticatedSession)
36+
def user_session_deleted(sender, instance: AuthenticatedSession, **_):
37+
layer = get_channel_layer()
38+
async_to_sync(layer.group_send)(
39+
RAC_CLIENT_GROUP_SESSION
40+
% {
41+
"session": instance.session.session_key,
42+
},
43+
{"type": "event.disconnect", "reason": "session_logout"},
44+
)
45+
46+
3547
@receiver(pre_delete, sender=ConnectionToken)
3648
def pre_delete_connection_token_disconnect(sender, instance: ConnectionToken, **_):
3749
"""Disconnect session when connection token is deleted"""

0 commit comments

Comments
 (0)