from __future__ import annotations
from typing import Any
from django.conf import settings
from django.contrib.auth.models import PermissionsMixin
from django.db import models
from django.utils.module_loading import import_string
from django.utils.translation import gettext_lazy as _
from tenant_users.permissions.functional import tenant_cached_property
[docs]
class PermissionsMixinFacade:
"""A facade for Django's PermissionMixin to handle multi-tenant permissions.
Adapts Django's PermissionMixin to work seamlessly with django-tenant-users, by
delegating permission-related functionalities to the tenant-specific permissions model.
It ensures that permissions are correctly managed according to the tenant context,
rather than using Django's default user-based permission system.
Note:
This class is abstract and should be inherited by AUTH_USER_MODEL.
"""
pk: Any
# This will throw a DoesNotExist exception if there is no tenant
# permissions matching the current schema, which means that this
# user has no authorization, so we catch this exception and return
# the appropriate False or empty set
@tenant_cached_property
def tenant_perms(self) -> UserTenantPermissions:
queryset_fn = getattr(settings, "TENANT_USERS_PERMS_QUERYSET", None)
if queryset_fn:
# Import and call the custom queryset function
get_queryset = import_string(queryset_fn)
queryset = get_queryset()
else:
# Use the default queryset
queryset = UserTenantPermissions.objects
return queryset.get(profile_id=self.pk)
[docs]
def has_tenant_permissions(self) -> bool:
try:
_ = self.tenant_perms
except UserTenantPermissions.DoesNotExist:
return False
return True
@tenant_cached_property
def is_staff(self) -> bool:
try:
return self.tenant_perms.is_staff
except UserTenantPermissions.DoesNotExist:
return False
@tenant_cached_property
def is_superuser(self) -> bool:
try:
return self.tenant_perms.is_superuser
except UserTenantPermissions.DoesNotExist:
return False
[docs]
def get_group_permissions(self, obj=None) -> set[str]:
try:
return self.tenant_perms.get_group_permissions(obj)
except UserTenantPermissions.DoesNotExist:
return set()
[docs]
def get_all_permissions(self, obj=None) -> set[str]:
try:
return self.tenant_perms.get_all_permissions(obj)
except UserTenantPermissions.DoesNotExist:
return set()
[docs]
def has_perm(self, perm: str, obj=None) -> bool:
try:
return self.tenant_perms.has_perm(perm, obj)
except UserTenantPermissions.DoesNotExist:
return False
[docs]
def has_perms(self, perm_list: list[str], obj=None) -> bool:
try:
return self.tenant_perms.has_perms(perm_list, obj)
except UserTenantPermissions.DoesNotExist:
return False
[docs]
def has_module_perms(self, app_label: str) -> bool:
try:
return self.tenant_perms.has_module_perms(app_label)
except UserTenantPermissions.DoesNotExist:
return False
class AbstractBaseUserFacade:
"""A facade class bridging authorization and authentication models in a multi-tenant setup.
In django-tenant-users, authentication and authorization models are separated to enable
single authentication with per-tenant permissions. This class acts as a shim, aligning
functions typically found in the authentication model with those expected by Django's
auth backends. It ensures compatibility and functionality in scenarios where auth backends
expect a combined model structure.
"""
profile: Any
@property
def is_active(self) -> bool:
return self.profile.is_active
@property
def is_anonymous(self) -> bool:
return False
@property
def is_authenticated(self) -> bool:
return self.profile.is_authenticated
[docs]
class UserTenantPermissions(PermissionsMixin, AbstractBaseUserFacade):
"""Authorization model for managing per-tenant permissions in Django-tenant-users.
This class is responsible for handling the authorization aspects (permissions) for each tenant.
It complements the UserProfile model, which stores global user profile information and authentication
details in the public tenant schema. By separating authorization on a per-tenant basis, this model
supports a flexible and scalable approach to permissions management in a multi-tenant environment.
Inherits:
PermissionsMixin: Provides Django's built-in permissions framework.
AbstractBaseUserFacade: Bridges authorization with authentication models.
See Also:
UserProfile: For the model handling global user profile and authentication aspects.
"""
id = models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
)
# The profile stores all of the common information between
# tenants for a user
profile = models.OneToOneField(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
)
is_staff = models.BooleanField(
_("staff status"),
default=False,
help_text=_(
"Designates whether the user can log into this tenants admin site.",
),
)
# Timestamp tracking
created_at = models.DateTimeField(
auto_now_add=True,
null=True,
blank=True,
help_text=_("The date and time when the user was added to this tenant."),
)
modified_at = models.DateTimeField(
auto_now=True,
null=True,
blank=True,
help_text=_(
"The date and time when the user's permissions were last modified."
),
)
def __str__(self) -> str:
"""Return string representation."""
return str(self.profile)