"""
django-model-permissions - simple object permissions for django.
Copyright (C) 2018 Mathias Stelzer
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
from django.conf import settings
from django.contrib.auth.backends import (
ModelBackend as DjangoModelBackend,
AllowAllUsersModelBackend as DjangoAllowAllUsersModelBackend,
RemoteUserBackend as DjangoRemoteUserBackend,
AllowAllUsersRemoteUserBackend as DjangoAllowAllUsersRemoteUserBackend,
)
from django.utils.module_loading import import_string
# mixins
[docs]class BaseObjectBackendMixin(object):
"""Base class for object permission backends."""
[docs] def get_permission_function(self, obj):
"""
Get the function to list the given objects permissions.
This checks the given object for a ``get_permissions`` method and,
if it doesn't exist, the ``MODEL_PERMISSIONS`` setting to find the
objects :ref:`manual-permission-function`.
:param obj: The model instance to get the function for.
:type obj: :class:`django.db.models.Model`
:return: The function to list the objects permissions.
:rtype: :class:`callable`
"""
get_permissions = getattr(obj, 'get_permissions', None)
if get_permissions is not None:
return self.model_permission_wrapper
model_permissions = getattr(settings, 'MODEL_PERMISSIONS', {})
f = model_permissions.get(obj._meta.label, None)
if f is None:
return
if callable(f):
return f
# assume string
return import_string(f)
[docs] def model_permission_wrapper(self, user_obj, obj):
"""Object permission wrapper for the `get_permissions` model method."""
return obj.get_permissions(user_obj)
[docs] def get_object_permissions(self, user_obj, obj=None):
"""
Get object permissions for the given user and object.
Exit early and return an empty set if:
* no object is given
* the user is
* inactive
* anonymous
* a superuser
* no permission function can be found
:param user_obj: User instance.
:type user_obj: :class:`django.contrib.auth.models.AbstractUser`
:param obj: Model instance.
:type obj: :class:`django.db.models.Model`
:return: Set of permissions.
:rtype: :class:`set`
"""
if obj is None:
return set()
get_permissions = self.get_permission_function(obj)
if get_permissions is None:
return set()
perm_cache_name = '_{}_{}_perm_cache'.format(obj._meta.label, obj.pk)
if not hasattr(user_obj, perm_cache_name):
perms = get_permissions(user_obj, obj)
if perms is None:
perms = set()
setattr(user_obj, perm_cache_name, perms)
return getattr(user_obj, perm_cache_name)
[docs]class ObjectModelBackendMixin(BaseObjectBackendMixin):
"""Mixin for dual permission authentication backends."""
[docs] def get_all_permissions(self, user_obj, obj=None):
"""Get all permissions for the given user and object."""
perms = super().get_all_permissions(user_obj)
perms.update(self.get_object_permissions(user_obj, obj=obj))
return perms
[docs]class ObjectBackendMixin(BaseObjectBackendMixin):
"""Mixin for object-only permission authentication backends."""
[docs] def get_all_permissions(self, user_obj, obj=None):
"""Get all permissions for the given user and object."""
return self.get_object_permissions(user_obj, obj=obj)
# backends
[docs]class ObjectBackend(ObjectBackendMixin, DjangoModelBackend):
"""An authentication backend with object permissions only."""
pass
[docs]class ObjectModelBackend(ObjectModelBackendMixin, DjangoModelBackend):
"""An authentication backend with default and object permissions."""
pass
[docs]class AllowAllUsersObjectBackend(ObjectBackendMixin, DjangoAllowAllUsersModelBackend):
"""Django's `AllowAllUsersModelBackend` with object permissions only."""
pass
[docs]class AllowAllUsersObjectModelBackend(ObjectModelBackendMixin, DjangoAllowAllUsersModelBackend):
"""Django's `AllowAllUsersModelBackend` with dual permissions."""
pass
[docs]class RemoteUserObjectBackend(ObjectBackendMixin, DjangoRemoteUserBackend):
"""Django's `RemoteUserBackend` with object permissions only."""
pass
[docs]class RemoteUserBackend(ObjectModelBackendMixin, DjangoRemoteUserBackend):
"""Django's `RemoteUserBackend` with dual permissions."""
pass
[docs]class AllowAllUsersRemoteUserObjectBackend(ObjectBackendMixin, DjangoAllowAllUsersRemoteUserBackend):
"""Django's `AllowAllUsersRemoteUserBackend` with object permissions only."""
pass
[docs]class AllowAllUsersRemoteUserBackend(ObjectModelBackendMixin, DjangoAllowAllUsersRemoteUserBackend):
"""Django's `AllowAllUsersRemoteUserBackend` with dual permissions."""
pass