from typing import Iterable from django.contrib.auth.models import Permission from django.db import models from django.utils.translation import gettext_lazy as _ from django.utils.translation import pgettext_lazy from core.models import Group, User class ApiClient(models.Model): name = models.CharField(_("name"), max_length=64) owner = models.ForeignKey( User, verbose_name=_("owner"), related_name="api_clients", on_delete=models.CASCADE, ) groups = models.ManyToManyField( Group, verbose_name=_("groups"), related_name="api_clients", blank=True ) client_permissions = models.ManyToManyField( Permission, verbose_name=_("client permissions"), blank=True, help_text=_("Specific permissions for this api client."), related_name="clients", ) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) _perm_cache: set[str] | None = None class Meta: verbose_name = _("api client") verbose_name_plural = _("api clients") def __str__(self): return self.name def has_perm(self, perm: str): """Return True if the client has the specified permission.""" if self._perm_cache is None: group_permissions = ( Permission.objects.filter(group__group__in=self.groups.all()) .values_list("content_type__app_label", "codename") .order_by() ) client_permissions = self.client_permissions.values_list( "content_type__app_label", "codename" ).order_by() self._perm_cache = { f"{content_type}.{name}" for content_type, name in (*group_permissions, *client_permissions) } return perm in self._perm_cache def has_perms(self, perm_list): """ Return True if the client has each of the specified permissions. If object is passed, check if the client has all required perms for it. """ if not isinstance(perm_list, Iterable) or isinstance(perm_list, str): raise ValueError("perm_list must be an iterable of permissions.") return all(self.has_perm(perm) for perm in perm_list) class ApiKey(models.Model): PREFIX_LENGTH = 5 KEY_LENGTH = 72 HASHED_KEY_LENGTH = 136 name = models.CharField(_("name"), blank=True, default="") prefix = models.CharField(_("prefix"), max_length=PREFIX_LENGTH, editable=False) hashed_key = models.CharField( _("hashed key"), max_length=HASHED_KEY_LENGTH, db_index=True, editable=False ) client = models.ForeignKey( ApiClient, verbose_name=_("api client"), related_name="api_keys", on_delete=models.CASCADE, ) revoked = models.BooleanField(pgettext_lazy("api key", "revoked"), default=False) created_at = models.DateTimeField(auto_now_add=True) class Meta: verbose_name = _("api key") verbose_name_plural = _("api keys") permissions = [("revoke_apikey", "Revoke API keys")] def __str__(self): return f"{self.name} ({self.prefix}***)"