Modify the role parent check logic to stay in the roles as much as possible

since the foreign keys to the roles from the resources can make us go
wrong almost immediately.
This commit is contained in:
Jeff Bradberry
2024-05-07 16:18:55 -04:00
parent 054cbe69d7
commit f613b76baa

View File

@@ -3,6 +3,7 @@ import json
import sys import sys
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.db.models.fields.related_descriptors import ManyToManyDescriptor
from awx.main.fields import ImplicitRoleField from awx.main.fields import ImplicitRoleField
from awx.main.models.rbac import Role from awx.main.models.rbac import Role
@@ -14,6 +15,20 @@ crosslinked = defaultdict(lambda: defaultdict(dict))
orphaned_roles = [] orphaned_roles = []
def resolve(obj, path):
fname, _, path = path.partition('.')
new_obj = getattr(obj, fname, None)
if new_obj is None:
return set()
if not path:
return {new_obj,}
if isinstance(new_obj, ManyToManyDescriptor):
return {x for o in new_obj.all() for x in resolve(o, path)}
return resolve(new_obj, path)
for ct in ContentType.objects.order_by('id'): for ct in ContentType.objects.order_by('id'):
cls = ct.model_class() cls = ct.model_class()
if cls is None: if cls is None:
@@ -62,19 +77,28 @@ for r in Role.objects.exclude(role_field__startswith='system_').order_by('id'):
continue continue
# Check the resource's role field parents for consistency with Role.parents.all(). # Check the resource's role field parents for consistency with Role.parents.all().
# f._resolve_parent_roles() walks the f.parent_role list, splitting on dots and recursively
# getting those resources as well, until we are down to just the Role ids at the end.
f = r.content_object._meta.get_field(r.role_field) f = r.content_object._meta.get_field(r.role_field)
parent_roles = f._resolve_parent_roles(r.content_object) f_parent = set(f.parent_role) if isinstance(f.parent_role, list) else {f.parent_role,}
minus = parent_roles - parents dotted = {x for p in f_parent if '.' in p for x in resolve(r.content_object, p)}
if minus: plus = set()
minus = [f"{x.content_type} {x.object_id} {x.role_field}" for x in Role.objects.filter(id__in=minus)] for p in r.parents.all():
sys.stderr.write(f"Role id={r.id} is missing parents: {minus}\n") if p.singleton_name:
plus = parents - parent_roles if f'singleton:{p.singleton_name}' not in f_parent:
plus.add(p)
elif (p.content_type, p.role_field) == (team_ct, 'member_role'):
# Team has been granted this role; probably legitimate.
continue
elif (p.content_type, p.object_id) == (r.content_type, r.object_id):
if p.role_field not in f_parent:
plus.add(p)
elif p in dotted:
continue
else:
plus.add(p)
if plus: if plus:
plus = [f"{x.content_type} {x.object_id} {x.role_field}" for x in Role.objects.filter(id__in=plus).exclude(content_type=team_ct, role_field='member_role')] plus = [f"{x.content_type!r} {x.object_id} {x.role_field}" for x in plus]
if plus: sys.stderr.write(f"Role id={r.id} has cross-linked parents: {plus}\n")
sys.stderr.write(f"Role id={r.id} has excess parents: {plus}\n")
rev = getattr(r.content_object, r.role_field, None) rev = getattr(r.content_object, r.role_field, None)
if rev is None or r.id != rev.id: if rev is None or r.id != rev.id: