In my project I have both Group and Custom User Based Permissions.
I have a custom authentication backend which essentially checks to see if the user has group permissions and then sees if they have any revoked permissions which need to be removed from the checked perms.
I am running into an optimization issue now that I am testing the implementation of said revoked permission, because my CustomUser model has an M2M field that holds these revoked permissions which is a relation to auth_permissions
, and my BackendAuthentication checks for it, I am getting crazy amounts of DB hits on page load.
How can I pass a prefetched object to my AuthBackend?
Here is my AuthBackend:
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.models import Permission
class UsersAuthenticationBackend(ModelBackend):
def _get_revoked_perms(self, user_obj):
if user_obj.is_superuser or user_obj.is_admin:
revoked_perms = Permission.objects.none()
elif hasattr(user_obj, 'revoked_permissions'):
# this causes the issue, I need to pass in the prefetch related to my model backend...HOW?
# this should be something like CustomUser.objects.prefetch_related('revoked_permissions')
revoked_perms = getattr(user_obj, 'revoked_permissions').values_list('content_type__app_label', 'codename')
else:
revoked_perms = Permission.objects.none()
revoked_perms = ["{}.{}".format(perm[0], perm[1]) for perm in revoked_perms]
print(revoked_perms)
return revoked_perms
def has_perm(self, user_obj, perm, obj=None):
if not user_obj.is_active:
return False
revoked_perms = self._get_revoked_perms(user_obj)
all_perms = self.get_all_permissions(user_obj)
allowed_perms = [p for p in all_perms if not p in revoked_perms]
if isinstance(perm, str):
return perm in allowed_perms
elif isinstance(perm, Permission):
return '{}.{}'.format(perm.content_type.app_label, perm.codename) in allowed_perms
else:
return False
Here is the relevant part of CustomUser if you need to see it
class CustomUser(AbstractUser, SafeDeleteModel):
...
revoked_permissions = models.ManyToManyField(Permission, blank=True)
I figured out a solution to accomplish what I needed, although it was a somewhat decent workaround...
I changed the "Revoked Permissions" to "Extra Applied Permissions" (for requirement reasons) and changed the Model Field from a M2M relationship to a JSONField(list) which stores the permission content_type.app_label
and codename
as a concatenated string, which I then use for comparison.
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.models import Permission
class UsersAuthenticationBackend(ModelBackend):
def _get_allowed_perms(self, user_obj):
if user_obj.is_superuser or user_obj.is_admin:
allowed_perms = []
elif hasattr(user_obj, 'extra_allowed_permissions'):
allowed_perms = user_obj.extra_allowed_permissions
else:
allowed_perms = []
# 'content_type__app_label'.'codename'
allowed_perms = [perm for perm in allowed_perms]
# print(allowed_perms)
return allowed_perms
def has_perm(self, user_obj, perm, obj=None):
if not user_obj.is_active:
return False
allowed_perms = self._get_allowed_perms(user_obj)
group_perms = self.get_group_permissions(user_obj)
# concat the perms for comparison
combined_perms = list(group_perms) + allowed_perms
if isinstance(perm, str):
return perm in combined_perms
elif isinstance(perm, Permission):
perm_string = '{}.{}'.format(perm.content_type.app_label, perm.codename)
return perm_string in combined_perms
else:
return False
Here is the Model Change:
class CustomUser(AbstractUser, SafeDeleteModel):
...
extra_allowed_permissions = JSONField(default=list, null=True, blank=True)