from mozilla_django_oidc.auth import OIDCAuthenticationBackend from django.contrib.auth.models import Group # Keycloak group name → Django group name mapping. # Keycloak may send group paths with a leading slash (e.g. "/LabHelper Administrators"); # these are stripped before comparison. KEYCLOAK_GROUP_MAP = { 'LabHelper Administrators': 'LabHelper Administrators', 'LabHelper Staff': 'LabHelper Staff', 'LabHelper Viewers': 'LabHelper Viewers', } # Members of these groups receive is_staff=True (Django admin access) STAFF_GROUPS = {'LabHelper Administrators'} class KeycloakOIDCBackend(OIDCAuthenticationBackend): """OIDC backend that maps Keycloak groups to Django groups on every login.""" def get_username(self, claims): return claims.get('preferred_username') or super().get_username(claims) def create_user(self, claims): user = super().create_user(claims) self._sync_from_claims(user, claims) return user def update_user(self, user, claims): user = super().update_user(user, claims) self._sync_from_claims(user, claims) return user def _sync_from_claims(self, user, claims): """Sync user attributes and group memberships from Keycloak token claims.""" user.first_name = claims.get('given_name', user.first_name) user.last_name = claims.get('family_name', user.last_name) # Keycloak sends group paths like "/LabHelper Administrators"; normalise them. raw_groups = claims.get('groups', []) keycloak_groups = {g.lstrip('/') for g in raw_groups} user.is_staff = bool(keycloak_groups & STAFF_GROUPS) user.save() # Add/remove the user from each managed Django group to match Keycloak. for kc_group, django_group_name in KEYCLOAK_GROUP_MAP.items(): try: group = Group.objects.get(name=django_group_name) except Group.DoesNotExist: continue if kc_group in keycloak_groups: user.groups.add(group) else: user.groups.remove(group)