Skip to content
Snippets Groups Projects
Commit 508b476d authored by Antoine Lambert's avatar Antoine Lambert
Browse files

django/backends: Handle error when user session is no longer active

When a user session has been terminated without using the logout view
(for instance a user can logout from all its authenticated sessions
using the Keycloak account UI), the expired OIDC profile is still
in webapp cache which causes errors and prevent new user logins.

So ensure to remove expired profile from cache when detecting Keycloak
session is no longer active in django authentication backend.

Related to T3496
parent 2da72f39
No related branches found
Tags v0.6.1
No related merge requests found
......@@ -106,7 +106,8 @@ class OIDCAuthorizationCodePKCEBackend:
def get_user(self, user_id: int) -> Optional[OIDCUser]:
# get oidc profile from cache
oidc_client = keycloak_oidc_client()
oidc_profile = cache.get(oidc_profile_cache_key(oidc_client, user_id))
cache_key = oidc_profile_cache_key(oidc_client, user_id)
oidc_profile = cache.get(cache_key)
if oidc_profile:
try:
user = oidc_user_from_profile(oidc_client, oidc_profile)
......@@ -115,6 +116,12 @@ class OIDCAuthorizationCodePKCEBackend:
# restore auth backend
setattr(user, "backend", f"{__name__}.{self.__class__.__name__}")
return user
except KeycloakError as ke:
error_msg = keycloak_error_message(ke)
if error_msg == "invalid_grant: Session not active":
# user session no longer active, remove oidc profile from cache
cache.delete(cache_key)
return None
except Exception as e:
sentry_sdk.capture_exception(e)
return None
......
......@@ -4,17 +4,19 @@
# See top-level LICENSE file for more information
from datetime import datetime, timedelta
import json
from unittest.mock import Mock
from django.conf import settings
from django.contrib.auth import authenticate, get_backends
from django.core.cache import cache
import pytest
from rest_framework.exceptions import AuthenticationFailed
from swh.auth.django.backends import OIDCBearerTokenAuthentication
from swh.auth.django.models import OIDCUser
from swh.auth.django.utils import reverse
from swh.auth.keycloak import ExpiredSignatureError
from swh.auth.django.utils import oidc_profile_cache_key, reverse
from swh.auth.keycloak import ExpiredSignatureError, KeycloakError
def _authenticate_user(request_factory):
......@@ -123,18 +125,43 @@ def test_oidc_code_pkce_auth_backend_refresh_token_failure(
"""
Checks access token renewal failure using refresh token.
"""
# authenticate user
user = _authenticate_user(request_factory)
assert user is not None
# OIDC profile should be in cache
cache_key = oidc_profile_cache_key(keycloak_oidc, user.id)
assert cache.get(cache_key) is not None
# simulate terminated OIDC session
keycloak_oidc.decode_token = Mock()
keycloak_oidc.decode_token.side_effect = ExpiredSignatureError(
"access token token has expired"
)
keycloak_oidc.refresh_token.side_effect = Exception("OIDC session has expired")
user = _authenticate_user(request_factory)
kc_error_dict = {
"error": "invalid_grant",
"error_description": "Session not active",
}
keycloak_oidc.refresh_token.side_effect = KeycloakError(
error_message=json.dumps(kc_error_dict).encode(), response_code=400
)
backend_path = "swh.auth.django.backends.OIDCAuthorizationCodePKCEBackend"
assert user.backend == backend_path
backend_idx = settings.AUTHENTICATION_BACKENDS.index(backend_path)
# try to authenticate user again from its id and cached OIDC profile
user = get_backends()[backend_idx].get_user(user.id)
# it should have tried to refresh token
oidc_profile = keycloak_oidc.login()
keycloak_oidc.refresh_token.assert_called_with(oidc_profile["refresh_token"])
# authentication failed
assert user is None
# invalid OIDC profile should have been removed from cache
assert cache.get(cache_key) is None
@pytest.mark.django_db
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment