From ea14ee1563c275b45d9479c2ce45514eaaf58e62 Mon Sep 17 00:00:00 2001 From: Dirk Julich Date: Fri, 17 Jul 2026 19:24:13 +0200 Subject: [PATCH] =?UTF-8?q?AAP-83163=20=E2=80=94=20Eliminate=20LEFT=20JOIN?= =?UTF-8?q?=20fan-out=20in=20user=20list=20RBAC=20query=20(#16546)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace legacy Role M2M traversal in UserAccess.filtered_queryset() with DAB's visible_users(), which queries organizational membership through RoleUserAssignment subqueries instead of LEFT OUTER JOINs through the objectrole/roleevaluation tables. Also replace role__in=actor.has_roles.all() in can_admin() with RoleEvaluation._actor_role_filter() to avoid the objectrole JOIN in per-object permission checks. Co-authored-by: Claude Opus 4.6 --- awx/main/access.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/awx/main/access.py b/awx/main/access.py index 4ecc0c1fd1..674e04ce39 100644 --- a/awx/main/access.py +++ b/awx/main/access.py @@ -20,6 +20,7 @@ from rest_framework.exceptions import ParseError, PermissionDenied # django-ansible-base from ansible_base.lib.utils.validation import to_python_boolean from ansible_base.rbac.models import RoleEvaluation +from ansible_base.rbac.policies import visible_users from ansible_base.rbac import permission_registry # AWX @@ -643,6 +644,8 @@ class UserAccess(BaseAccess): Organization.access_qs(self.user, 'change').exists() or Organization.access_qs(self.user, 'audit').exists() ): qs = User.objects.all() + elif settings.ANSIBLE_BASE_ROLE_SYSTEM_ACTIVATED: + qs = visible_users(self.user) else: qs = ( User.objects.filter(pk__in=Organization.access_qs(self.user, 'view').values('member_role__members')) @@ -706,12 +709,13 @@ class UserAccess(BaseAccess): # in these cases only superusers can modify orphan users return False if settings.ANSIBLE_BASE_ROLE_SYSTEM_ACTIVATED: - # Permission granted if the user has all permissions that the target user has target_perms = set( - RoleEvaluation.objects.filter(role__in=obj.has_roles.all()).values_list('object_id', 'content_type_id', 'codename').distinct() + RoleEvaluation.objects.filter(**RoleEvaluation._actor_role_filter(obj)).values_list('object_id', 'content_type_id', 'codename').distinct() ) user_perms = set( - RoleEvaluation.objects.filter(role__in=self.user.has_roles.all()).values_list('object_id', 'content_type_id', 'codename').distinct() + RoleEvaluation.objects.filter(**RoleEvaluation._actor_role_filter(self.user)) + .values_list('object_id', 'content_type_id', 'codename') + .distinct() ) return not (target_perms - user_perms) return not obj.roles.all().exclude(ancestors__in=self.user.roles.all()).exists()