Coverage for backend/django/authentication/remote_user_backend.py: 94%
53 statements
« prev ^ index » next coverage.py v7.10.7, created at 2025-12-18 04:00 +0000
« prev ^ index » next coverage.py v7.10.7, created at 2025-12-18 04:00 +0000
1import logging
2from typing import Any
4import jwt
5from django.conf import settings
6from django.contrib.auth.backends import RemoteUserBackend
7from authentication.user.models import User
10def _set_email_from_header(request, user: User) -> bool:
11 """Synchronise a user's email using the configured remote header.
13 Args:
14 request: Incoming request containing remote-user headers.
15 user: User instance being updated.
17 Returns:
18 True if the email field changed and should be persisted, otherwise False.
19 """
20 email = request.META.get(settings.REMOTE_USER_EMAIL_HEADER)
22 if not email:
23 logging.warning(f"Email header not found in request for user {user.get_username()}. User email will not be updated.")
24 return False
26 old_email = user.email
28 # Only update if the email has changed
29 if old_email != email:
30 user.email = email
31 return True
33 return False
35def _set_groups_from_header(request, user: User) -> bool:
36 """Update the user's group flags based on remote group membership headers.
38 Args:
39 request: Incoming request containing group membership information.
40 user: User instance being updated.
42 Returns:
43 True if a group/role flag has changed, otherwise False.
44 """
45 groups_header_value: str = request.META.get(settings.REMOTE_USER_GROUPS_HEADER)
47 if groups_header_value is None:
48 return False
50 groups = groups_header_value.split(",")
52 is_platform_admin = any(group == settings.PLATFORM_ADMINISTRATORS_GROUP for group in groups)
53 is_platform_tester = any(group == settings.PLATFORM_TESTERS_GROUP for group in groups)
55 if user.is_staff != is_platform_admin or user.is_tester != is_platform_tester: 55 ↛ 60line 55 didn't jump to line 60 because the condition on line 55 was always true
56 user.is_tester = is_platform_tester
57 user.is_staff = is_platform_admin
58 return True
60 return False
62def _set_name_from_header(request, user: User) -> bool:
63 """Populate first and last names from the remote access token claims.
65 Args:
66 request: Incoming request containing the access token header.
67 user: User instance being updated.
69 Returns:
70 True if either the first or last name fields changed, otherwise False.
71 """
72 access_token: str = request.META.get(settings.REMOTE_USER_ACCESS_TOKEN_HEADER)
74 if not access_token:
75 return False
77 # The access token is passed by OAuth2 Proxy and can not be set directly by the user.
78 # This is safe from external manipulation. This is only vulnerable to internal cluster traffic.
79 # We should do public key verification in future.
80 access_token: dict[str, Any] = jwt.decode(access_token, options={"verify_signature": False})
82 if not access_token: 82 ↛ 83line 82 didn't jump to line 83 because the condition on line 82 was never true
83 return False
85 first_name = access_token.get("given_name")
86 last_name = access_token.get("family_name")
88 name_has_changed = False
90 if first_name and user.first_name != first_name:
91 user.first_name = first_name
92 name_has_changed = True
94 if last_name and user.last_name != last_name:
95 user.last_name = last_name
96 name_has_changed = True
98 return name_has_changed
101class RemoteUserBackendWithEmail(RemoteUserBackend):
102 """
103 A custom RemoteUserBackend that updates the email, group and name fields
104 of the user based on remote headers upon authentication.
105 """
107 def configure_user(self, request, user: User, created=True) -> User:
108 """Apply remote-header derived fields to the newly authenticated user."""
109 email_changed = _set_email_from_header(request, user)
110 groups_changed = _set_groups_from_header(request, user)
111 name_has_changed = _set_name_from_header(request, user)
113 if email_changed or groups_changed or name_has_changed:
114 user.save()
116 return user