mirror of
https://github.com/ansible/awx.git
synced 2026-01-11 18:09:57 -03:30
199 lines
7.5 KiB
Python
199 lines
7.5 KiB
Python
# Copyright (c) 2015 Ansible, Inc.
|
|
# All Rights Reserved.
|
|
|
|
# Django
|
|
from django.db.models.signals import post_save
|
|
from django.db import models
|
|
from django.db.models.fields.related import SingleRelatedObjectDescriptor
|
|
from django.db.models.fields.related import ReverseSingleRelatedObjectDescriptor
|
|
from django.core.exceptions import FieldError
|
|
|
|
# AWX
|
|
from awx.main.models.rbac import Resource, RolePermission, Role
|
|
|
|
|
|
__all__ = ['AutoOneToOneField', 'ImplicitResourceField', 'ImplicitRoleField']
|
|
|
|
# Based on AutoOneToOneField from django-annoying:
|
|
# https://bitbucket.org/offline/django-annoying/src/a0de8b294db3/annoying/fields.py
|
|
|
|
class AutoSingleRelatedObjectDescriptor(SingleRelatedObjectDescriptor):
|
|
"""Descriptor for access to the object from its related class."""
|
|
|
|
def __get__(self, instance, instance_type=None):
|
|
try:
|
|
return super(AutoSingleRelatedObjectDescriptor,
|
|
self).__get__(instance, instance_type)
|
|
except self.related.related_model.DoesNotExist:
|
|
obj = self.related.related_model(**{self.related.field.name: instance})
|
|
if self.related.field.rel.parent_link:
|
|
raise NotImplementedError('not supported with polymorphic!')
|
|
for f in instance._meta.local_fields:
|
|
setattr(obj, f.name, getattr(instance, f.name))
|
|
obj.save()
|
|
return obj
|
|
|
|
class AutoOneToOneField(models.OneToOneField):
|
|
"""OneToOneField that creates related object if it doesn't exist."""
|
|
|
|
def contribute_to_related_class(self, cls, related):
|
|
setattr(cls, related.get_accessor_name(),
|
|
AutoSingleRelatedObjectDescriptor(related))
|
|
|
|
|
|
|
|
|
|
|
|
def resolve_field(obj, field):
|
|
for f in field.split('.'):
|
|
if hasattr(obj, f):
|
|
obj = getattr(obj, f)
|
|
else:
|
|
obj = None
|
|
return obj
|
|
|
|
class ResourceFieldDescriptor(ReverseSingleRelatedObjectDescriptor):
|
|
"""Descriptor for access to the object from its related class."""
|
|
|
|
def __init__(self, parent_resource, *args, **kwargs):
|
|
self.parent_resource = parent_resource
|
|
super(ResourceFieldDescriptor, self).__init__(*args, **kwargs)
|
|
|
|
def __get__(self, instance, instance_type=None):
|
|
resource = super(ResourceFieldDescriptor, self).__get__(instance, instance_type)
|
|
if resource:
|
|
return resource
|
|
resource = Resource._default_manager.create()
|
|
if self.parent_resource:
|
|
# Take first non null parent resource
|
|
parent = None
|
|
if type(self.parent_resource) is list:
|
|
for path in self.parent_resource:
|
|
parent = resolve_field(instance, path)
|
|
if parent:
|
|
break
|
|
else:
|
|
parent = resolve_field(instance, self.parent_resource)
|
|
resource.parent = parent
|
|
resource.save()
|
|
setattr(instance, self.field.name, resource)
|
|
instance.save(update_fields=[self.field.name,])
|
|
return resource
|
|
|
|
|
|
class ImplicitResourceField(models.ForeignKey):
|
|
"""Creates an associated resource object if one doesn't already exist"""
|
|
|
|
def __init__(self, parent_resource=None, *args, **kwargs):
|
|
self.parent_resource = parent_resource
|
|
kwargs.setdefault('to', 'Resource')
|
|
kwargs.setdefault('related_name', '+')
|
|
kwargs.setdefault('null', 'True')
|
|
super(ImplicitResourceField, self).__init__(*args, **kwargs)
|
|
|
|
def contribute_to_class(self, cls, name):
|
|
super(ImplicitResourceField, self).contribute_to_class(cls, name)
|
|
setattr(cls, self.name, ResourceFieldDescriptor(self.parent_resource, self))
|
|
post_save.connect(self._save, cls, True)
|
|
|
|
def _save(self, instance, *args, **kwargs):
|
|
# Ensure that our field gets initialized after our first save
|
|
getattr(instance, self.name)
|
|
|
|
|
|
class ImplicitRoleDescriptor(ReverseSingleRelatedObjectDescriptor):
|
|
"""Descriptor Implict Role Fields. Auto-creates the appropriate role entry on first access"""
|
|
|
|
def __init__(self, role_name, resource_field, permissions, parent_role, *args, **kwargs):
|
|
self.role_name = role_name
|
|
self.resource_field = resource_field
|
|
self.permissions = permissions
|
|
self.parent_role = parent_role
|
|
|
|
super(ImplicitRoleDescriptor, self).__init__(*args, **kwargs)
|
|
|
|
def __get__(self, instance, instance_type=None):
|
|
role = super(ImplicitRoleDescriptor, self).__get__(instance, instance_type)
|
|
if role:
|
|
return role
|
|
|
|
if not self.role_name:
|
|
raise FieldError('Implicit role missing `role_name`')
|
|
|
|
role = Role._default_manager.create(name=self.role_name)
|
|
if self.parent_role:
|
|
# Add all non-null parent roles as parents
|
|
if type(self.parent_role) is list:
|
|
for path in self.parent_role:
|
|
if path.startswith("singleton:"):
|
|
parent = Role.singleton(path[10:])
|
|
else:
|
|
parent = resolve_field(instance, path)
|
|
if parent:
|
|
role.parents.add(parent)
|
|
else:
|
|
if self.parent_role.startswith("singleton:"):
|
|
parent = Role.singleton(self.parent_role[10:])
|
|
else:
|
|
parent = resolve_field(instance, self.parent_role)
|
|
if parent:
|
|
role.parents.add(parent)
|
|
setattr(instance, self.field.name, role)
|
|
instance.save(update_fields=[self.field.name,])
|
|
|
|
if self.resource_field and self.permissions:
|
|
permissions = RolePermission(
|
|
role=role,
|
|
resource=getattr(instance, self.resource_field)
|
|
)
|
|
|
|
if 'all' in self.permissions and self.permissions['all']:
|
|
del self.permissions['all']
|
|
self.permissions['create'] = True
|
|
self.permissions['read'] = True
|
|
self.permissions['write'] = True
|
|
self.permissions['update'] = True
|
|
self.permissions['delete'] = True
|
|
self.permissions['scm_update'] = True
|
|
self.permissions['use'] = True
|
|
self.permissions['execute'] = True
|
|
|
|
for k,v in self.permissions.items():
|
|
setattr(permissions, k, v)
|
|
permissions.save()
|
|
|
|
return role
|
|
|
|
|
|
class ImplicitRoleField(models.ForeignKey):
|
|
"""Implicitly creates a role entry for a resource"""
|
|
|
|
def __init__(self, role_name=None, resource_field=None, permissions=None, parent_role=None, *args, **kwargs):
|
|
self.role_name = role_name
|
|
self.resource_field = resource_field
|
|
self.permissions = permissions
|
|
self.parent_role = parent_role
|
|
|
|
kwargs.setdefault('to', 'Role')
|
|
kwargs.setdefault('related_name', '+')
|
|
kwargs.setdefault('null', 'True')
|
|
super(ImplicitRoleField, self).__init__(*args, **kwargs)
|
|
|
|
def contribute_to_class(self, cls, name):
|
|
super(ImplicitRoleField, self).contribute_to_class(cls, name)
|
|
setattr(cls,
|
|
self.name,
|
|
ImplicitRoleDescriptor(
|
|
self.role_name,
|
|
self.resource_field,
|
|
self.permissions,
|
|
self.parent_role,
|
|
self
|
|
)
|
|
)
|
|
post_save.connect(self._save, cls, True)
|
|
|
|
def _save(self, instance, *args, **kwargs):
|
|
# Ensure that our field gets initialized after our first save
|
|
getattr(instance, self.name)
|