authentic/src/django_rbac/backends.py

245 lines
9.3 KiB
Python

import copy
import functools
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.db.models.query import Q
try:
from django.core.exceptions import FieldDoesNotExist
except ImportError:
# Django < 1.8
from django.db.models.fields import FieldDoesNotExist
from . import utils
def get_fk_model(model, fieldname):
try:
field = model._meta.get_field('ou')
except FieldDoesNotExist:
return None
else:
if not field.is_relation or not field.many_to_one:
return None
return field.related_model
class DjangoRBACBackend(object):
_DEFAULT_DJANGO_RBAC_PERMISSIONS_HIERARCHY = {
'view': ['search'],
'admin': ['change', 'delete', 'add', 'view', 'search'],
'change': ['view', 'search'],
'delete': ['view', 'search'],
'add': ['view', 'search'],
}
def authenticate(self, request=None, **kwargs):
# this method is mandatory
pass
def get_permission_cache(self, user_obj):
"""Returns the permission cache for an user
The permission cache is a dictionnary, key can be of many types:
- `'ou.<ou.id>'` ; contains a list of permissions owner by this user as strings
(<app_label>.<permission>_<model_name>), those permissions are restricted to objects in
this organizational unit,
- `'__all__'`: contains a list of global permissions (applicable to any object in any
organizaton unit) owner by this user,
- `'<content_type.id>.<object.pk>'`: contains permissions restricted to a specific
object,
- `'<app_label>'`: contains a boolean, it indicates that the user own at least on
permision on a model of this application.
"""
if not hasattr(user_obj, '_rbac_perms_cache'):
perms_cache = {}
Permission = utils.get_permission_model()
qs = Permission.objects.for_user(user_obj)
ct_ct = ContentType.objects.get_for_model(ContentType)
qs = qs.select_related('operation')
for permission in qs:
target_ct = ContentType.objects.get_for_id(permission.target_ct_id)
if target_ct == ct_ct:
target = ContentType.objects.get_for_id(permission.target_id)
app_label = target.app_label
model = target.model
if permission.ou_id:
key = 'ou.%s' % permission.ou_id
else:
key = '__all__'
else:
app_label = target_ct.app_label
model = target_ct.model
key = '%s.%s' % (permission.target_ct_id, permission.target_id)
slug = permission.operation.slug
perms = [str('%s.%s_%s' % (app_label, slug, model))]
perm_hierarchy = getattr(
settings,
'DJANGO_RBAC_PERMISSIONS_HIERARCHY',
self._DEFAULT_DJANGO_RBAC_PERMISSIONS_HIERARCHY,
)
if slug in perm_hierarchy:
for other_perm in perm_hierarchy[slug]:
perms.append(str('%s.%s_%s' % (app_label, other_perm, model)))
permissions = perms_cache.setdefault(key, set())
permissions.update(perms)
# optimization for has_module_perms
perms_cache[app_label] = True
user_obj._rbac_perms_cache = perms_cache
return user_obj._rbac_perms_cache
def get_all_permissions(self, user_obj, obj=None):
if user_obj.is_anonymous:
return ()
perms_cache = self.get_permission_cache(user_obj)
if obj:
permissions = set()
ct = ContentType.objects.get_for_model(obj)
key = '%s.%s' % (ct.id, obj.pk)
if key in perms_cache:
permissions.update(perms_cache[key])
for permission in perms_cache.get('__all__', set([])):
if permission.startswith('%s.' % ct.app_label) and permission.endswith('_%s' % ct.model):
permissions.add(permission)
if hasattr(obj, 'ou_id') and obj.ou_id:
key = 'ou.%s' % obj.ou_id
for permission in perms_cache.get(key, ()):
if permission.startswith('%s.' % ct.app_label) and permission.endswith('_%s' % ct.model):
permissions.add(permission)
return permissions
else:
return perms_cache.get('__all__', [])
def has_perm(self, user_obj, perm, obj=None):
if user_obj.is_anonymous:
return False
if not user_obj.is_active:
return False
if user_obj.is_superuser:
return True
return perm in self.get_all_permissions(user_obj, obj=obj)
def has_perms(self, user_obj, perm_list, obj=None):
if user_obj.is_anonymous:
return False
if not user_obj.is_active:
return False
all_permissions = self.get_all_permissions(user_obj, obj=obj)
return all(perm in all_permissions for perm in perm_list)
def has_module_perms(self, user_obj, package_name):
if user_obj.is_anonymous:
return False
if not user_obj.is_active:
return False
if user_obj.is_superuser:
return True
return package_name in self.get_permission_cache(user_obj)
def has_perm_any(self, user_obj, perm_or_perms):
'''Return True if user has any perm on any object'''
if user_obj.is_anonymous:
return False
if not user_obj.is_active:
return False
if user_obj.is_superuser:
return True
if isinstance(perm_or_perms, str):
perm_or_perms = [perm_or_perms]
perm_or_perms = set(perm_or_perms)
cache = self.get_permission_cache(user_obj)
if perm_or_perms & cache.get('__all__', set()):
return True
for key, value in cache.items():
if isinstance(value, bool):
continue
elif key == '__all__':
continue
elif key.startswith('ou.'):
if perm_or_perms & value:
return True
elif perm_or_perms & value:
return True
return False
def filter_by_perm_query(self, user_obj, perm_or_perms, qs):
"""Create a filter for a queryset for the objects on which the user has
the given permission. Permissions can be set on individual objects, globally on
a content type or locally for all objects of an organizational unit.
"""
if user_obj.is_anonymous:
return False
if not user_obj.is_active:
return False
if user_obj.is_superuser:
return True
if isinstance(perm_or_perms, str):
perm_or_perms = [perm_or_perms]
perm_or_perms = set(perm_or_perms)
cache = self.get_permission_cache(user_obj)
model = qs.model
OU = utils.get_ou_model()
has_ou_field = get_fk_model(model, 'ou') == OU
if perm_or_perms & cache.get('__all__', set()):
return True
q = []
for key, value in cache.items():
if isinstance(value, bool):
continue
elif key == '__all__':
continue
elif key.startswith('ou.'):
if has_ou_field and perm_or_perms & value:
q.append(Q(ou_id=int(key[3:])))
continue
elif perm_or_perms & value:
ct_id, fk = key.split('.')
q.append(Q(pk=int(fk)))
if q:
return functools.reduce(Q.__or__, q)
return False
def filter_by_perm(self, user_obj, perm_or_perms, qs):
"""Filter a queryset for the objects on which the user has
the given permission. Permissions can be set on individual objects, globally on
a content type or locally for all objects of an organizational unit.
"""
query = self.filter_by_perm_query(user_obj, perm_or_perms, qs)
if query is True:
return copy.deepcopy(qs)
elif query is False:
return qs.none()
else:
return qs.filter(query)
def has_ou_perm(self, user_obj, perm, ou):
if user_obj.is_anonymous:
return False
if not user_obj.is_active:
return False
if user_obj.is_superuser:
return True
if self.has_perm(user_obj, perm):
return True
return perm in self.get_permission_cache(user_obj).get('ou.%s' % ou.pk, ())
def ous_with_perm(self, user_obj, perm, queryset=None):
OU = utils.get_ou_model()
qs = queryset or OU.objects.all()
if user_obj.is_anonymous:
return qs.none()
if not user_obj.is_active:
return qs.none()
if user_obj.is_superuser:
return qs
cache = self.get_permission_cache(user_obj)
ou_ids = []
for key in cache:
if key == '__all__' and perm in cache[key]:
return qs
if key.startswith('ou.') and perm in cache[key]:
ou_ids.append(int(key.split('.')[1]))
return qs.filter(id__in=ou_ids)