From d71f18fa44b71135c358f1f7b195eed7756f18e6 Mon Sep 17 00:00:00 2001 From: Stevenson Michel Date: Thu, 9 Apr 2026 16:24:03 -0400 Subject: [PATCH] [Devel] Config Endpoint Optimization (#16389) * Improved performance of the config endpoint by reducing database queries in GET /api/controller/v2/config/ --- awx/api/views/root.py | 29 +++++-- .../functional/api/test_config_endpoint.py | 84 +++++++++++++++++++ 2 files changed, 104 insertions(+), 9 deletions(-) create mode 100644 awx/main/tests/functional/api/test_config_endpoint.py diff --git a/awx/api/views/root.py b/awx/api/views/root.py index 5412951473..248f342d11 100644 --- a/awx/api/views/root.py +++ b/awx/api/views/root.py @@ -344,13 +344,22 @@ class ApiV2ConfigView(APIView): become_methods=PRIVILEGE_ESCALATION_METHODS, ) - if ( - request.user.is_superuser - or request.user.is_system_auditor - or Organization.accessible_objects(request.user, 'admin_role').exists() - or Organization.accessible_objects(request.user, 'auditor_role').exists() - or Organization.accessible_objects(request.user, 'project_admin_role').exists() - ): + # Check superuser/auditor first + if request.user.is_superuser or request.user.is_system_auditor: + has_org_access = True + else: + # Single query checking all three organization role types at once + has_org_access = ( + ( + Organization.access_qs(request.user, 'change') + | Organization.access_qs(request.user, 'audit') + | Organization.access_qs(request.user, 'add_project') + ) + .distinct() + .exists() + ) + + if has_org_access: data.update( dict( project_base_dir=settings.PROJECTS_ROOT, @@ -358,8 +367,10 @@ class ApiV2ConfigView(APIView): custom_virtualenvs=get_custom_venv_choices(), ) ) - elif JobTemplate.accessible_objects(request.user, 'admin_role').exists(): - data['custom_virtualenvs'] = get_custom_venv_choices() + else: + # Only check JobTemplate access if org check failed + if JobTemplate.accessible_objects(request.user, 'admin_role').exists(): + data['custom_virtualenvs'] = get_custom_venv_choices() return Response(data) diff --git a/awx/main/tests/functional/api/test_config_endpoint.py b/awx/main/tests/functional/api/test_config_endpoint.py new file mode 100644 index 0000000000..d31f96d1d7 --- /dev/null +++ b/awx/main/tests/functional/api/test_config_endpoint.py @@ -0,0 +1,84 @@ +import pytest +from awx.api.versioning import reverse +from rest_framework import status + +from awx.main.models.jobs import JobTemplate + + +@pytest.mark.django_db +class TestConfigEndpointFields: + def test_base_fields_all_users(self, get, rando): + url = reverse('api:api_v2_config_view') + response = get(url, rando, expect=200) + + assert 'time_zone' in response.data + assert 'license_info' in response.data + assert 'version' in response.data + assert 'eula' in response.data + assert 'analytics_status' in response.data + assert 'analytics_collectors' in response.data + assert 'become_methods' in response.data + + @pytest.mark.parametrize( + "role_type", + [ + "superuser", + "system_auditor", + "org_admin", + "org_auditor", + "org_project_admin", + ], + ) + def test_privileged_users_conditional_fields(self, get, user, organization, admin, role_type): + url = reverse('api:api_v2_config_view') + + if role_type == "superuser": + test_user = admin + elif role_type == "system_auditor": + test_user = user('system-auditor', is_superuser=False) + test_user.is_system_auditor = True + test_user.save() + elif role_type == "org_admin": + test_user = user('org-admin', is_superuser=False) + organization.admin_role.members.add(test_user) + elif role_type == "org_auditor": + test_user = user('org-auditor', is_superuser=False) + organization.auditor_role.members.add(test_user) + elif role_type == "org_project_admin": + test_user = user('org-project-admin', is_superuser=False) + organization.project_admin_role.members.add(test_user) + + response = get(url, test_user, expect=200) + + assert 'project_base_dir' in response.data + assert 'project_local_paths' in response.data + assert 'custom_virtualenvs' in response.data + + def test_job_template_admin_gets_venvs_only(self, get, user, organization, project, inventory): + """Test that JobTemplate admin without org access gets only custom_virtualenvs""" + jt_admin = user('jt-admin', is_superuser=False) + + jt = JobTemplate.objects.create(name='test-jt', organization=organization, project=project, inventory=inventory) + jt.admin_role.members.add(jt_admin) + + url = reverse('api:api_v2_config_view') + response = get(url, jt_admin, expect=200) + + assert 'custom_virtualenvs' in response.data + assert 'project_base_dir' not in response.data + assert 'project_local_paths' not in response.data + + def test_normal_user_no_conditional_fields(self, get, rando): + url = reverse('api:api_v2_config_view') + response = get(url, rando, expect=200) + + assert 'project_base_dir' not in response.data + assert 'project_local_paths' not in response.data + assert 'custom_virtualenvs' not in response.data + + def test_unauthenticated_denied(self, get): + """Test that unauthenticated requests are denied""" + url = reverse('api:api_v2_config_view') + response = get(url, None, expect=401) + + assert response.status_code == status.HTTP_401_UNAUTHORIZED