From 1f7506e9827d2ed05be4aa340f10ca7e8ccf0a2d Mon Sep 17 00:00:00 2001 From: chris meyers Date: Mon, 12 Mar 2018 16:44:06 -0400 Subject: [PATCH] prevent tower group delete and update * related to https://github.com/ansible/ansible-tower/issues/7931 * The Tower Instance group is special. It should always exist, so prevent any delete to it. * Only allow super users to associate/disassociate instances the 'tower' instance group. * Do not allow fields of tower instance group to be changed. --- awx/api/permissions.py | 11 ++++++++++- awx/api/views.py | 11 +++++++++-- .../tests/functional/api/test_instance_group.py | 17 +++++++++++++++++ 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/awx/api/permissions.py b/awx/api/permissions.py index 1146e012f1..c9a7f68d13 100644 --- a/awx/api/permissions.py +++ b/awx/api/permissions.py @@ -17,7 +17,7 @@ logger = logging.getLogger('awx.api.permissions') __all__ = ['ModelAccessPermission', 'JobTemplateCallbackPermission', 'TaskPermission', 'ProjectUpdatePermission', 'InventoryInventorySourcesUpdatePermission', - 'UserPermission', 'IsSuperUser'] + 'UserPermission', 'IsSuperUser', 'InstanceGroupTowerPermission',] class ModelAccessPermission(permissions.BasePermission): @@ -227,3 +227,12 @@ class IsSuperUser(permissions.BasePermission): def has_permission(self, request, view): return request.user and request.user.is_superuser + + +class InstanceGroupTowerPermission(ModelAccessPermission): + def has_object_permission(self, request, view, obj): + if request.method not in permissions.SAFE_METHODS: + if obj.name == "tower": + return False + return super(InstanceGroupTowerPermission, self).has_object_permission(request, view, obj) + diff --git a/awx/api/views.py b/awx/api/views.py index 91df862f4c..a1cad8a469 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -77,8 +77,14 @@ from awx.main.utils import ( from awx.main.utils.encryption import encrypt_value from awx.main.utils.filters import SmartFilter from awx.main.utils.insights import filter_insights_api_response - -from awx.api.permissions import * # noqa +from awx.api.permissions import ( + JobTemplateCallbackPermission, + TaskPermission, + ProjectUpdatePermission, + InventoryInventorySourcesUpdatePermission, + UserPermission, + InstanceGroupTowerPermission, +) from awx.api.renderers import * # noqa from awx.api.serializers import * # noqa from awx.api.metadata import RoleMetadata, JobTypeMetadata @@ -651,6 +657,7 @@ class InstanceGroupDetail(RelatedJobsPreventDeleteMixin, RetrieveUpdateDestroyAP view_name = _("Instance Group Detail") model = InstanceGroup serializer_class = InstanceGroupSerializer + permission_classes = (InstanceGroupTowerPermission,) class InstanceGroupUnifiedJobsList(SubListAPIView): diff --git a/awx/main/tests/functional/api/test_instance_group.py b/awx/main/tests/functional/api/test_instance_group.py index 7f5c8d5ba8..6e4b8f89cb 100644 --- a/awx/main/tests/functional/api/test_instance_group.py +++ b/awx/main/tests/functional/api/test_instance_group.py @@ -14,6 +14,13 @@ def instance_group(job_factory): return ig +@pytest.fixture +def tower_instance_group(): + ig = InstanceGroup(name='tower') + ig.save() + return ig + + @pytest.fixture def create_job_factory(job_factory, instance_group): def fn(status='running'): @@ -71,3 +78,13 @@ def test_delete_instance_group_jobs_running(delete, instance_group_jobs_running, assert response.data['error'] == u"Resource is being used by running jobs." assert response_sorted == expect_sorted + +@pytest.mark.django_db +def test_delete_tower_instance_group_prevented(delete, options, tower_instance_group, admin): + url = reverse("api:instance_group_detail", kwargs={'pk': tower_instance_group.pk}) + delete(url, None, admin, expect=403) + resp = options(url, None, admin, expect=200) + actions = ['DELETE', 'PATCH', 'PUT'] + for action in actions: + assert action not in resp.data['actions'] + assert 'GET' in resp.data['actions']