Source code for swh.auth.django.models
# Copyright (C) 2020-2022  The Software Heritage developers
# See the AUTHORS file at the top-level directory of this distribution
# License: GNU Affero General Public License version 3, or any later version
# See top-level LICENSE file for more information
from datetime import datetime
from typing import Any, Dict, Optional, Set
from django.contrib.auth.models import Group, User
from django.db.models import Q
[docs]
class OIDCUser(User):
    """
    Custom User proxy model for remote users storing OpenID Connect
    related data: profile containing authentication tokens.
    The model is also not saved to database as all users are already stored
    in the Keycloak one.
    """
    # OIDC subject identifier
    sub: str = ""
    # OIDC tokens and session related data, only relevant when a user
    # authenticates from a web browser
    access_token: Optional[str] = None
    expires_in: Optional[int] = None
    expires_at: Optional[datetime] = None
    id_token: Optional[str] = None
    refresh_token: Optional[str] = None
    refresh_expires_in: Optional[int] = None
    refresh_expires_at: Optional[datetime] = None
    scope: Optional[str] = None
    session_state: Optional[str] = None
    # User permissions
    permissions: Set[str]
    # User groups
    group_names: Set[str]
    class Meta:
        # TODO: To redefine in subclass of this class
        # Forced to empty otherwise, django complains about it
        # "Model class swh.auth.django.OIDCUser doesn't declare an explicit app_label
        # and isn't in an application in INSTALLED_APPS"
        app_label = ""
        proxy = True
        auto_created = True  # prevent model to be created in database by migrations
[docs]
    def save(self, **kwargs):
        """
        Override django.db.models.Model.save to avoid saving the remote
        users to web application database.
        """
        pass 
[docs]
    def get_group_permissions(self, obj=None) -> Set[str]:
        """
        Override django.contrib.auth.models.PermissionsMixin.get_group_permissions
        to get permissions from OIDC
        """
        return self.get_all_permissions(obj) 
[docs]
    def get_all_permissions(self, obj=None) -> Set[str]:
        """
        Override django.contrib.auth.models.PermissionsMixin.get_all_permissions
        to get permissions from OIDC
        """
        return self.permissions 
[docs]
    def has_perm(self, perm, obj=None) -> bool:
        """
        Override django.contrib.auth.models.PermissionsMixin.has_perm
        to check permission from OIDC
        """
        if self.is_active and self.is_superuser:
            return True
        return perm in self.permissions 
[docs]
    def has_module_perms(self, app_label) -> bool:
        """
        Override django.contrib.auth.models.PermissionsMixin.has_module_perms
        to check permissions from OIDC.
        """
        if self.is_active and self.is_superuser:
            return True
        return any(perm.startswith(app_label) for perm in self.permissions) 
    @property
    def groups(self):
        """
        Override django.contrib.auth.models.PermissionsMixin.groups
        to get groups from OIDC.
        """
        search_query = Q()
        for group_name in self.group_names:
            search_query = search_query | Q(name=group_name)
        return Group.objects.filter(search_query)
    @property
    def oidc_profile(self) -> Dict[str, Any]:
        """
        Returns OpenID Connect profile associated to the user as a dictionary.
        """
        return {
            k: getattr(self, k)
            for k in (
                "access_token",
                "expires_in",
                "expires_at",
                "id_token",
                "refresh_token",
                "refresh_expires_in",
                "refresh_expires_at",
                "scope",
                "session_state",
            )
        }