Files
authentik/authentik/providers/oauth2/tests/test_revoke.py
Dominic R 420c3e36bc alpha4
2026-08-30 21:17:59 -04:00

368 lines
12 KiB
Python

"""Test revoke view"""
import json
from base64 import b64encode
from dataclasses import asdict
from django.urls import reverse
from django.utils import timezone
from authentik.core.models import Application, AuthenticatedSession, Session
from authentik.core.signals import deactivation_inhibit_cleanup
from authentik.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow
from authentik.lib.generators import generate_id
from authentik.providers.oauth2.id_token import IDToken
from authentik.providers.oauth2.models import (
AccessToken,
ClientType,
DeviceToken,
OAuth2Provider,
RedirectURI,
RedirectURIMatchingMode,
RefreshToken,
)
from authentik.providers.oauth2.tests.utils import OAuthTestCase
from authentik.root.middleware import ClientIPMiddleware
class TesOAuth2Revoke(OAuthTestCase):
"""Test revoke view"""
def setUp(self) -> None:
super().setUp()
self.provider: OAuth2Provider = OAuth2Provider.objects.create(
name=generate_id(),
authorization_flow=create_test_flow(),
redirect_uris=[RedirectURI(RedirectURIMatchingMode.STRICT, "")],
signing_key=create_test_cert(),
)
self.app = Application.objects.create(
name=generate_id(), slug=generate_id(), provider=self.provider
)
self.app.save()
self.user = create_test_admin_user()
self.auth = b64encode(
f"{self.provider.client_id}:{self.provider.secret.get_value()}".encode()
).decode()
def test_revoke_refresh(self):
"""Test revoke"""
token = RefreshToken.objects.create(
provider=self.provider,
user=self.user,
token=generate_id(),
auth_time=timezone.now(),
_scope="openid user profile",
_id_token=json.dumps(
asdict(
IDToken("foo", "bar"),
)
),
)
res = self.client.post(
reverse("authentik_providers_oauth2:token-revoke"),
HTTP_AUTHORIZATION=f"Basic {self.auth}",
data={
"token": token.token,
},
)
self.assertEqual(res.status_code, 200)
def test_revoke_access(self):
"""Test revoke"""
token = AccessToken.objects.create(
provider=self.provider,
user=self.user,
token=generate_id(),
auth_time=timezone.now(),
_scope="openid user profile",
_id_token=json.dumps(
asdict(
IDToken("foo", "bar"),
)
),
)
res = self.client.post(
reverse("authentik_providers_oauth2:token-revoke"),
HTTP_AUTHORIZATION=f"Basic {self.auth}",
data={
"token": token.token,
},
)
self.assertEqual(res.status_code, 200)
def test_revoke_invalid(self):
"""Test revoke (invalid token)"""
res = self.client.post(
reverse("authentik_providers_oauth2:token-revoke"),
HTTP_AUTHORIZATION=f"Basic {self.auth}",
data={
"token": generate_id(),
},
)
self.assertEqual(res.status_code, 200)
def test_revoke_invalid_auth(self):
"""Test revoke (invalid auth)"""
res = self.client.post(
reverse("authentik_providers_oauth2:token-revoke"),
HTTP_AUTHORIZATION="Basic aaa",
data={
"token": generate_id(),
},
)
self.assertEqual(res.status_code, 401)
def test_revoke_invalid_auth_secret(self):
"""Test revoke (invalid secret)"""
invalid_auth = b64encode(f"{self.provider.client_id}:aaa".encode()).decode()
res = self.client.post(
reverse("authentik_providers_oauth2:token-revoke"),
HTTP_AUTHORIZATION=f"Basic {invalid_auth}",
data={
"token": generate_id(),
},
)
self.assertEqual(res.status_code, 401)
def test_revoke_public(self):
"""Test revoke public client"""
self.provider.client_type = ClientType.PUBLIC
self.provider.save()
token = AccessToken.objects.create(
provider=self.provider,
user=self.user,
token=generate_id(),
auth_time=timezone.now(),
_scope="openid user profile",
_id_token=json.dumps(
asdict(
IDToken("foo", "bar"),
)
),
)
auth_public = b64encode(f"{self.provider.client_id}:{generate_id()}".encode()).decode()
res = self.client.post(
reverse("authentik_providers_oauth2:token-revoke"),
HTTP_AUTHORIZATION=f"Basic {auth_public}",
data={
"token": token.token,
},
)
self.assertEqual(res.status_code, 200)
def test_revoke_logout(self):
"""Test revoke on logout"""
self.client.force_login(self.user)
AccessToken.objects.create(
provider=self.provider,
user=self.user,
session=self.client.session["authenticatedsession"],
token=generate_id(),
auth_time=timezone.now(),
_scope="openid user profile",
_id_token=json.dumps(
asdict(
IDToken("foo", "bar"),
)
),
)
self.client.logout()
self.assertEqual(AccessToken.objects.including_expired().all().count(), 0)
def test_revoke_session_delete(self):
"""Test revoke on logout"""
session = AuthenticatedSession.objects.create(
session=Session.objects.create(
session_key=generate_id(),
last_ip=ClientIPMiddleware.default_ip,
),
user=self.user,
)
AccessToken.objects.create(
provider=self.provider,
user=self.user,
session=session,
token=generate_id(),
auth_time=timezone.now(),
_scope="openid user profile",
_id_token=json.dumps(
asdict(
IDToken("foo", "bar"),
)
),
)
session.delete()
self.assertEqual(AccessToken.objects.including_expired().all().count(), 0)
def test_revoke_user_deactivated(self):
"""Test revoke on logout"""
AccessToken.objects.create(
provider=self.provider,
user=self.user,
token=generate_id(),
auth_time=timezone.now(),
_scope="openid user profile",
_id_token=json.dumps(
asdict(
IDToken("foo", "bar"),
)
),
)
RefreshToken.objects.create(
provider=self.provider,
user=self.user,
token=generate_id(),
auth_time=timezone.now(),
_scope="openid user profile",
_id_token=json.dumps(
asdict(
IDToken("foo", "bar"),
)
),
)
DeviceToken.objects.create(
provider=self.provider,
user=self.user,
_scope="openid user profile",
)
self.user.is_active = False
self.user.save()
self.assertEqual(AccessToken.objects.including_expired().all().count(), 0)
self.assertEqual(RefreshToken.objects.including_expired().all().count(), 0)
self.assertEqual(DeviceToken.objects.including_expired().all().count(), 0)
def test_revoke_user_deactivated_inhibited(self):
"""Test tokens are kept when deactivation cleanup is inhibited"""
AccessToken.objects.create(
provider=self.provider,
user=self.user,
token=generate_id(),
auth_time=timezone.now(),
_scope="openid user profile",
_id_token=json.dumps(
asdict(
IDToken("foo", "bar"),
)
),
)
RefreshToken.objects.create(
provider=self.provider,
user=self.user,
token=generate_id(),
auth_time=timezone.now(),
_scope="openid user profile",
_id_token=json.dumps(
asdict(
IDToken("foo", "bar"),
)
),
)
DeviceToken.objects.create(
provider=self.provider,
user=self.user,
_scope="openid user profile",
)
self.user.is_active = False
with deactivation_inhibit_cleanup():
self.user.save()
self.assertEqual(AccessToken.objects.including_expired().all().count(), 1)
self.assertEqual(RefreshToken.objects.including_expired().all().count(), 1)
self.assertEqual(DeviceToken.objects.including_expired().all().count(), 1)
def test_revoke_user_deactivated_inhibit_sessions_only(self):
"""Test tokens are still revoked when only session cleanup is inhibited"""
AccessToken.objects.create(
provider=self.provider,
user=self.user,
token=generate_id(),
auth_time=timezone.now(),
_scope="openid user profile",
_id_token=json.dumps(
asdict(
IDToken("foo", "bar"),
)
),
)
self.user.is_active = False
with deactivation_inhibit_cleanup(sessions=True, tokens=False):
self.user.save()
self.assertEqual(AccessToken.objects.including_expired().all().count(), 0)
def test_revoke_provider_fed(self):
"""Test revoke with federation. self.provider is a confidential
client and other_provider is a public client."""
other_provider = OAuth2Provider.objects.create(
name=generate_id(),
authorization_flow=create_test_flow(),
redirect_uris=[RedirectURI(RedirectURIMatchingMode.STRICT, "")],
signing_key=create_test_cert(),
client_type=ClientType.PUBLIC,
)
Application.objects.create(name=generate_id(), slug=generate_id(), provider=other_provider)
other_provider.jwt_federation_providers.add(self.provider)
token = AccessToken.objects.create(
provider=other_provider,
user=self.user,
token=generate_id(),
auth_time=timezone.now(),
_scope="openid user profile",
_id_token=json.dumps(
asdict(
IDToken("foo", "bar"),
)
),
)
res = self.client.post(
reverse("authentik_providers_oauth2:token-revoke"),
HTTP_AUTHORIZATION=f"Basic {self.auth}",
data={"token": token.token},
)
self.assertEqual(res.status_code, 200)
self.assertJSONEqual(res.content.decode(), {})
def test_revoke_provider_fed_public(self):
"""Test revoke with federation. self.provider is a public
client and other_provider is a public client."""
self.provider.client_type = ClientType.PUBLIC
self.provider.save()
other_provider = OAuth2Provider.objects.create(
name=generate_id(),
authorization_flow=create_test_flow(),
redirect_uris=[RedirectURI(RedirectURIMatchingMode.STRICT, "")],
signing_key=create_test_cert(),
client_type=ClientType.PUBLIC,
)
Application.objects.create(name=generate_id(), slug=generate_id(), provider=other_provider)
other_provider.jwt_federation_providers.add(self.provider)
token = AccessToken.objects.create(
provider=other_provider,
user=self.user,
token=generate_id(),
auth_time=timezone.now(),
_scope="openid user profile",
_id_token=json.dumps(
asdict(
IDToken("foo", "bar"),
)
),
)
auth_public = b64encode(f"{self.provider.client_id}:{generate_id()}".encode()).decode()
res = self.client.post(
reverse("authentik_providers_oauth2:token-revoke"),
HTTP_AUTHORIZATION=f"Basic {auth_public}",
data={"token": token.token},
)
self.assertEqual(res.status_code, 200)
self.assertTrue(AccessToken.objects.filter(token=token.token).exists())