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

1import logging 

2from typing import Any 

3 

4import jwt 

5from django.conf import settings 

6from django.contrib.auth.backends import RemoteUserBackend 

7from authentication.user.models import User 

8 

9 

10def _set_email_from_header(request, user: User) -> bool: 

11 """Synchronise a user's email using the configured remote header. 

12 

13 Args: 

14 request: Incoming request containing remote-user headers. 

15 user: User instance being updated. 

16 

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) 

21 

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 

25 

26 old_email = user.email 

27 

28 # Only update if the email has changed 

29 if old_email != email: 

30 user.email = email 

31 return True 

32 

33 return False 

34 

35def _set_groups_from_header(request, user: User) -> bool: 

36 """Update the user's group flags based on remote group membership headers. 

37 

38 Args: 

39 request: Incoming request containing group membership information. 

40 user: User instance being updated. 

41 

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) 

46 

47 if groups_header_value is None: 

48 return False 

49 

50 groups = groups_header_value.split(",") 

51 

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) 

54 

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 

59 

60 return False 

61 

62def _set_name_from_header(request, user: User) -> bool: 

63 """Populate first and last names from the remote access token claims. 

64 

65 Args: 

66 request: Incoming request containing the access token header. 

67 user: User instance being updated. 

68 

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) 

73 

74 if not access_token: 

75 return False 

76 

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}) 

81 

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 

84 

85 first_name = access_token.get("given_name") 

86 last_name = access_token.get("family_name") 

87 

88 name_has_changed = False 

89 

90 if first_name and user.first_name != first_name: 

91 user.first_name = first_name 

92 name_has_changed = True 

93 

94 if last_name and user.last_name != last_name: 

95 user.last_name = last_name 

96 name_has_changed = True 

97 

98 return name_has_changed 

99 

100 

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 """ 

106 

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) 

112 

113 if email_changed or groups_changed or name_has_changed: 

114 user.save() 

115 

116 return user