Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from django.contrib.auth.models import User
from django.contrib.auth import logout
from django.db import transaction
from django.core.cache import cache
from django.conf import settings
from keycloak.exceptions import KeycloakGetError

from cloudharness.auth.exceptions import InvalidToken
Expand All @@ -14,6 +16,9 @@
from psycopg2.errors import UniqueViolation


USER_CACHE_TTL = getattr(settings, "BEARER_TOKEN_USER_CACHE_TTL", 60)


def _get_user(kc_user_id: str) -> User:
"""
Get or create a Django user for the given Keycloak user ID.
Expand Down Expand Up @@ -108,7 +113,7 @@ def __init__(self, get_response=None):

@transaction.atomic
def __call__(self, request):
user = getattr(request, "user", None)

authentication_token = get_authentication_token()
if not authentication_token or authentication_token == 'Bearer undefined':
return self.get_response(request)
Expand All @@ -121,6 +126,16 @@ def __call__(self, request):
response.delete_cookie('kc-access')
return response

if kc_user_id:
cache_key = f"bearer_token_user:{kc_user_id}"
cached_user = cache.get(cache_key)
if cached_user:
request.user = cached_user
request._cached_user = cached_user
return self.get_response(request)
Comment on lines +133 to +135
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cached_user retrieved from cache bypasses all the safety checks that ensure a user has a valid Member relationship. The _get_user function and subsequent code have extensive safety guarantees to never assign a user without a Member, but cached users skip these verifications. This could lead to AttributeError exceptions when code tries to access user.member. Consider adding a safety check to verify the cached user still has a valid Member relationship before using it, similar to the checks at lines 146-156.

Suggested change
request.user = cached_user
request._cached_user = cached_user
return self.get_response(request)
# SAFETY CHECK: Ensure cached user still has a valid Member with matching kc_id
try:
member = cached_user.member
except Member.DoesNotExist:
log.error(
"Cached user %s has no Member; ignoring cache and falling back to _get_user.",
getattr(cached_user, "id", None),
)
else:
if member and getattr(member, "kc_id", None) == kc_user_id:
request.user = cached_user
request._cached_user = cached_user
return self.get_response(request)

Copilot uses AI. Check for mistakes.

user = getattr(request, "user", None)

if kc_user:
if not user or user.is_anonymous or getattr(user, "member", None) is None or user.member.kc_id != kc_user_id:
user = _get_user(kc_user_id)
Expand All @@ -132,6 +147,7 @@ def __call__(self, request):
# Safe to assign - user has a valid Member
request.user = user
request._cached_user = user

except:
# This should NEVER happen due to _get_user safety checks,
# but if it does, DO NOT assign the user - keep anonymous
Expand All @@ -140,7 +156,8 @@ def __call__(self, request):
# Don't assign user - request will remain anonymous
# elif not request.path.startswith('/admin/'):
# logout(request)

if kc_user_id:
cache.set(cache_key, user, timeout=USER_CACHE_TTL)
return self.get_response(request)


Expand Down
Loading