authentic/src/authentic2/a2_rbac/managers.py

174 lines
5.9 KiB
Python

# authentic2 - versatile identity manager
# Copyright (C) 2010-2019 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.contrib.contenttypes.models import ContentType
from django_rbac import utils as rbac_utils
from django_rbac.managers import AbstractBaseManager
from django_rbac.managers import RoleManager as BaseRoleManager
from django_rbac.models import ADMIN_OP
from django_rbac.utils import get_operation
class OrganizationalUnitManager(AbstractBaseManager):
def get_by_natural_key(self, slug):
return self.get(slug=slug)
class RoleManager(BaseRoleManager):
def get_admin_role(
self,
instance,
name,
slug,
ou=None,
operation=ADMIN_OP,
update_name=False,
update_slug=False,
permissions=(),
self_administered=False,
create=True,
):
'''Get or create the role of manager's of this object instance'''
kwargs = {}
assert not ou or isinstance(
instance, ContentType
), 'get_admin_role(ou=...) can only be used with ContentType instances: %s %s %s' % (
name,
ou,
instance,
)
# Does the permission need to be scoped by ou ? Yes if the target is a
# ContentType and ou is given. It's a general administration right upon
# all instance of a ContentType, eventually scoped to the given ou.
defaults = {}
if isinstance(instance, ContentType):
if ou:
kwargs['ou'] = ou
else:
kwargs['ou__isnull'] = True
else: # for non ContentType instances, OU must be set to NULL, always.
defaults['ou'] = None
# find an operation matching the template
op = get_operation(operation)
Permission = rbac_utils.get_permission_model()
if create:
perm, _ = Permission.objects.update_or_create(
operation=op,
target_ct=ContentType.objects.get_for_model(instance),
target_id=instance.pk,
defaults=defaults,
**kwargs,
)
else:
try:
perm = Permission.objects.get(
operation=op,
target_ct=ContentType.objects.get_for_model(instance),
target_id=instance.pk,
**kwargs,
)
except Permission.DoesNotExist:
return None
# in which ou do we put the role ?
if ou:
mirror_role_ou = ou
elif getattr(instance, 'ou', None):
mirror_role_ou = instance.ou
else:
mirror_role_ou = None
admin_role = self.get_mirror_role(
perm,
name,
slug,
ou=mirror_role_ou,
update_name=update_name,
update_slug=update_slug,
create=create,
)
if not admin_role:
return None
permissions = set(permissions)
permissions.add(perm)
if self_administered:
self_perm = admin_role.add_self_administration()
permissions.add(self_perm)
if set(admin_role.permissions.all()) != permissions:
admin_role.permissions.set(permissions)
return admin_role
def get_mirror_role(
self, instance, name, slug, ou=None, update_name=False, update_slug=False, create=True
):
"""Get or create a role which mirrors another model, for example a
permission.
"""
ct = ContentType.objects.get_for_model(instance)
update_fields = {}
kwargs = {}
if ou:
update_fields['ou'] = ou
else:
update_fields['ou'] = None
if update_name:
update_fields['name'] = name
if update_slug:
update_fields['slug'] = slug
if create:
role, _ = self.prefetch_related('permissions').update_or_create(
admin_scope_ct=ct, admin_scope_id=instance.pk, defaults=update_fields, **kwargs
)
else:
try:
role = self.prefetch_related('permissions').get(
admin_scope_ct=ct, admin_scope_id=instance.pk, **kwargs
)
except self.model.DoesNotExist:
return None
for field, value in update_fields.items():
setattr(role, field, value)
role.save(update_fields=update_fields)
return role
def get_by_natural_key(self, slug, ou_natural_key, service_natural_key):
kwargs = {'slug': slug}
if ou_natural_key is None:
kwargs['ou__isnull'] = True
else:
OU = rbac_utils.get_ou_model()
try:
ou = OU.objects.get_by_natural_key(*ou_natural_key)
except OU.DoesNotExist:
raise self.model.DoesNotExist
kwargs['ou'] = ou
if service_natural_key is None:
kwargs['service__isnull'] = True
else:
# XXX: prevent an import loop
from authentic2.models import Service
try:
service = Service.objects.get_by_natural_key(*service_natural_key)
except Service.DoesNotExist:
raise self.model.DoesNotExist
kwargs['service'] = service
return self.get(**kwargs)