AAP-96178: Fix unified_jobs pagination count to respect RBAC filtering (#16751)

* AAP-96178: Fix unified_jobs pagination count to respect RBAC filtering

Non-admin users saw the unfiltered table-wide count instead of the count
matching their RBAC-visible results. Use the same primary-key subquery
approach already proven on the /api/v2/jobs/ endpoint.


* AAP-96178: Add docstrings and mixed-role count test for UnifiedJobPaginator

Clarify the PK subquery approach with expanded docstring and inline
comments per review feedback. Add a functional test verifying that a user
with a mix of JT and inventory roles sees a count matching exactly
their permitted results.


* AAP-96178: Add deduplication test for multiple RBAC access routes

Verify that a job reachable through three overlapping permission paths
(direct JT role, org audit role, and team JT role) appears exactly once
in both results and count.

Assisted-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jake Jackson
2026-10-22 09:34:30 -04:00
committed by GitHub
parent a17f5c167b
commit 85c334e3a4
3 changed files with 113 additions and 20 deletions
+11 -3
View File
@@ -16,7 +16,7 @@ from django.utils.translation import gettext_lazy as _
from ansible_base.rest_filters.rest_framework.field_lookup_backend import (
FieldLookupBackend,
)
from awx.main.models import ActivityStream, UnifiedJob
from awx.main.models import ActivityStream
class DisabledPaginator(DjangoPaginator):
@@ -38,11 +38,19 @@ class ActivityStreamPaginator(DjangoPaginator):
class UnifiedJobPaginator(DjangoPaginator):
"""Use unfiltered table count for unified job pagination."""
"""Count only the unified jobs the requesting user can see.
Uses a PK subquery so the database can index-scan the RBAC-filtered
primary keys instead of counting the full UNION queryset directly.
Same approach as JobPaginator.
"""
@cached_property
def count(self):
return UnifiedJob.objects.count()
# Collect PKs from the RBAC-filtered queryset (stripping ORDER BY for efficiency),
# then COUNT against the base table using an index-only scan on the PK.
visible_pks = self.object_list.order_by().values("pk")
return self.object_list.model.objects.filter(pk__in=visible_pks).count()
class JobPaginator(DjangoPaginator):