mirror of
https://github.com/ansible/awx.git
synced 2026-03-25 12:55:04 -02:30
ORMified RBAC classes; Added GenericForeignKey backref for convenience
The RoleHierarchy table has been eliminated in favor of just using a ManyToMany map, which is what we should have been using all along. ORMifications still need improvement, in particular filtering on ResourceMixin.accessible_by should reduce permission calculation overhead, but with the current implemenation this is not true. ResourceMixin.get_permission performs adequately but not as good as it can yet.
This commit is contained in:
@@ -7,11 +7,13 @@ import logging
|
||||
# Django
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
|
||||
# AWX
|
||||
from awx.main.models.base import * # noqa
|
||||
|
||||
__all__ = ['Role', 'RolePermission', 'Resource', 'RoleHierarchy']
|
||||
__all__ = ['Role', 'RolePermission', 'Resource']
|
||||
|
||||
logger = logging.getLogger('awx.main.models.rbac')
|
||||
|
||||
@@ -28,32 +30,41 @@ class Role(CommonModelNameNotUnique):
|
||||
|
||||
singleton_name = models.TextField(null=True, default=None, db_index=True, unique=True)
|
||||
parents = models.ManyToManyField('Role', related_name='children')
|
||||
ancestors = models.ManyToManyField('Role', related_name='descendents') # auto-generated by `rebuild_role_ancestor_list`
|
||||
members = models.ManyToManyField('auth.User', related_name='roles')
|
||||
content_type = models.ForeignKey(ContentType, null=True, default=None)
|
||||
object_id = models.PositiveIntegerField(null=True, default=None)
|
||||
content_object = GenericForeignKey('content_type', 'object_id')
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
super(Role, self).save(*args, **kwargs)
|
||||
self.rebuild_role_hierarchy_cache()
|
||||
self.rebuild_role_ancestor_list()
|
||||
|
||||
def rebuild_role_hierarchy_cache(self):
|
||||
'Rebuilds the associated entries in the RoleHierarchy model'
|
||||
def rebuild_role_ancestor_list(self):
|
||||
'''
|
||||
Updates our `ancestors` map to accurately reflect all of the ancestors for a role
|
||||
|
||||
# Compute what our hierarchy should be. (Note: this depends on our
|
||||
# parent's cached hierarchy being correct)
|
||||
parent_ids = set([parent.id for parent in self.parents.all()])
|
||||
actual_ancestors = set([r.ancestor.id for r in RoleHierarchy.objects.filter(role__id__in=parent_ids)])
|
||||
You should never need to call this. Signal handlers should be calling
|
||||
this method when the role hierachy changes automatically.
|
||||
|
||||
Note that this method relies on any parents' ancestor list being correct.
|
||||
'''
|
||||
|
||||
actual_ancestors = set(Role.objects.filter(id=self.id).values_list('parents__ancestors__id', flat=True))
|
||||
actual_ancestors.add(self.id)
|
||||
|
||||
# Compute what we have stored
|
||||
stored_ancestors = set([r.ancestor.id for r in RoleHierarchy.objects.filter(role__id=self.id)])
|
||||
if None in actual_ancestors:
|
||||
actual_ancestors.remove(None)
|
||||
stored_ancestors = set(self.ancestors.all().values_list('id', flat=True))
|
||||
|
||||
# If it differs, update, and then update all of our children
|
||||
if actual_ancestors != stored_ancestors:
|
||||
RoleHierarchy.objects.filter(role__id=self.id).delete()
|
||||
for id in actual_ancestors:
|
||||
rh = RoleHierarchy(role=self, ancestor=Role.objects.get(id=id))
|
||||
rh.save()
|
||||
for id in actual_ancestors - stored_ancestors:
|
||||
self.ancestors.add(Role.objects.get(id=id))
|
||||
for id in stored_ancestors - actual_ancestors:
|
||||
self.ancestors.remove(Role.objects.get(id=id))
|
||||
|
||||
for child in self.children.all():
|
||||
child.rebuild_role_hierarchy_cache()
|
||||
child.rebuild_role_ancestor_list()
|
||||
|
||||
def grant(self, resource, permissions):
|
||||
# take either the raw Resource or something that includes the ResourceMixin
|
||||
@@ -85,22 +96,7 @@ class Role(CommonModelNameNotUnique):
|
||||
return ret
|
||||
|
||||
def is_ancestor_of(self, role):
|
||||
return RoleHierarchy.objects.filter(role_id=role.id, ancestor_id=self.id).count() > 0
|
||||
|
||||
|
||||
|
||||
class RoleHierarchy(CreatedModifiedModel):
|
||||
'''
|
||||
Stores a flattened relation map of all roles in the system for easy joining
|
||||
'''
|
||||
|
||||
class Meta:
|
||||
app_label = 'main'
|
||||
verbose_name_plural = _('role_ancestors')
|
||||
db_table = 'main_rbac_role_hierarchy'
|
||||
|
||||
role = models.ForeignKey('Role', related_name='+', on_delete=models.CASCADE)
|
||||
ancestor = models.ForeignKey('Role', related_name='+', on_delete=models.CASCADE)
|
||||
return role.ancestors.filter(id=self.id).exists()
|
||||
|
||||
|
||||
class Resource(CommonModelNameNotUnique):
|
||||
@@ -113,6 +109,10 @@ class Resource(CommonModelNameNotUnique):
|
||||
verbose_name_plural = _('resources')
|
||||
db_table = 'main_rbac_resources'
|
||||
|
||||
content_type = models.ForeignKey(ContentType, null=True, default=None)
|
||||
object_id = models.PositiveIntegerField(null=True, default=None)
|
||||
content_object = GenericForeignKey('content_type', 'object_id')
|
||||
|
||||
|
||||
class RolePermission(CreatedModifiedModel):
|
||||
'''
|
||||
|
||||
Reference in New Issue
Block a user