mirror of
https://github.com/ansible/awx.git
synced 2024-11-02 01:21:21 +03:00
allow deleting hosts and groups from inv src sublists
This commit is contained in:
parent
f2d46baf09
commit
d2e0b26287
@ -37,9 +37,10 @@ from awx.api.metadata import SublistAttachDetatchMetadata
|
||||
|
||||
__all__ = ['APIView', 'GenericAPIView', 'ListAPIView', 'SimpleListAPIView',
|
||||
'ListCreateAPIView', 'SubListAPIView', 'SubListCreateAPIView',
|
||||
'SubListDestroyAPIView',
|
||||
'SubListCreateAttachDetachAPIView', 'RetrieveAPIView',
|
||||
'RetrieveUpdateAPIView', 'RetrieveDestroyAPIView',
|
||||
'RetrieveUpdateDestroyAPIView', 'DestroyAPIView',
|
||||
'RetrieveUpdateDestroyAPIView',
|
||||
'SubDetailAPIView',
|
||||
'ResourceAccessList',
|
||||
'ParentMixin',
|
||||
@ -440,6 +441,41 @@ class SubListAPIView(ParentMixin, ListAPIView):
|
||||
return qs & sublist_qs
|
||||
|
||||
|
||||
class DestroyAPIView(generics.DestroyAPIView):
|
||||
|
||||
def has_delete_permission(self, obj):
|
||||
return self.request.user.can_access(self.model, 'delete', obj)
|
||||
|
||||
def perform_destroy(self, instance, check_permission=True):
|
||||
if check_permission and not self.has_delete_permission(instance):
|
||||
raise PermissionDenied()
|
||||
super(DestroyAPIView, self).perform_destroy(instance)
|
||||
|
||||
|
||||
class SubListDestroyAPIView(DestroyAPIView, SubListAPIView):
|
||||
"""
|
||||
Concrete view for deleting everything related by `relationship`.
|
||||
"""
|
||||
check_sub_obj_permission = True
|
||||
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
instance_list = self.get_queryset()
|
||||
if (not self.check_sub_obj_permission and
|
||||
not request.user.can_access(self.parent_model, 'delete', self.get_parent_object())):
|
||||
raise PermissionDenied()
|
||||
self.perform_list_destroy(instance_list)
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
def perform_list_destroy(self, instance_list):
|
||||
if self.check_sub_obj_permission:
|
||||
# Check permissions for all before deleting, avoiding half-deleted lists
|
||||
for instance in instance_list:
|
||||
if self.has_delete_permission(instance):
|
||||
raise PermissionDenied()
|
||||
for instance in instance_list:
|
||||
self.perform_destroy(instance, check_permission=False)
|
||||
|
||||
|
||||
class SubListCreateAPIView(SubListAPIView, ListCreateAPIView):
|
||||
# Base class for a sublist view that allows for creating subobjects
|
||||
# associated with the parent object.
|
||||
@ -678,22 +714,11 @@ class RetrieveUpdateAPIView(RetrieveAPIView, generics.RetrieveUpdateAPIView):
|
||||
pass
|
||||
|
||||
|
||||
class RetrieveDestroyAPIView(RetrieveAPIView, generics.RetrieveDestroyAPIView):
|
||||
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
# somewhat lame that delete has to call it's own permissions check
|
||||
obj = self.get_object()
|
||||
if not request.user.can_access(self.model, 'delete', obj):
|
||||
raise PermissionDenied()
|
||||
obj.delete()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
class RetrieveUpdateDestroyAPIView(RetrieveUpdateAPIView, RetrieveDestroyAPIView):
|
||||
class RetrieveDestroyAPIView(RetrieveAPIView, DestroyAPIView):
|
||||
pass
|
||||
|
||||
|
||||
class DestroyAPIView(GenericAPIView, generics.DestroyAPIView):
|
||||
class RetrieveUpdateDestroyAPIView(RetrieveUpdateAPIView, DestroyAPIView):
|
||||
pass
|
||||
|
||||
|
||||
|
6
awx/api/templates/api/sub_list_destroy_api_view.md
Normal file
6
awx/api/templates/api/sub_list_destroy_api_view.md
Normal file
@ -0,0 +1,6 @@
|
||||
{% include "api/sub_list_create_api_view.md" %}
|
||||
|
||||
# Delete all {{ model_verbose_name_plural }} of this {{ parent_model_verbose_name|title }}:
|
||||
|
||||
Make a DELETE request to this resource to delete all {{ model_verbose_name_plural }} show in the list.
|
||||
The {{ parent_model_verbose_name|title }} will not be deleted by this request.
|
@ -2616,23 +2616,25 @@ class InventorySourceNotificationTemplatesSuccessList(InventorySourceNotificatio
|
||||
relationship = 'notification_templates_success'
|
||||
|
||||
|
||||
class InventorySourceHostsList(SubListAPIView):
|
||||
class InventorySourceHostsList(SubListDestroyAPIView):
|
||||
|
||||
model = Host
|
||||
serializer_class = HostSerializer
|
||||
parent_model = InventorySource
|
||||
relationship = 'hosts'
|
||||
new_in_148 = True
|
||||
check_sub_obj_permission = False
|
||||
capabilities_prefetch = ['inventory.admin']
|
||||
|
||||
|
||||
class InventorySourceGroupsList(SubListAPIView):
|
||||
class InventorySourceGroupsList(SubListDestroyAPIView):
|
||||
|
||||
model = Group
|
||||
serializer_class = GroupSerializer
|
||||
parent_model = InventorySource
|
||||
relationship = 'groups'
|
||||
new_in_148 = True
|
||||
check_sub_obj_permission = False
|
||||
|
||||
|
||||
class InventorySourceUpdatesList(SubListAPIView):
|
||||
|
@ -60,3 +60,34 @@ def test_proxy_ip_whitelist(get, patch, admin):
|
||||
REMOTE_HOST='my.proxy.example.org',
|
||||
HTTP_X_FROM_THE_LOAD_BALANCER='some-actual-ip')
|
||||
assert middleware.environ['HTTP_X_FROM_THE_LOAD_BALANCER'] == 'some-actual-ip'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
class TestDeleteViews:
|
||||
def test_sublist_delete_permission_check(self, inventory_source, host, rando, delete):
|
||||
inventory_source.hosts.add(host)
|
||||
inventory_source.inventory.read_role.members.add(rando)
|
||||
delete(
|
||||
reverse(
|
||||
'api:inventory_source_hosts_list',
|
||||
kwargs={'version': 'v2', 'pk': inventory_source.pk}
|
||||
), user=rando, expect=403
|
||||
)
|
||||
|
||||
def test_sublist_delete_functionality(self, inventory_source, host, rando, delete):
|
||||
inventory_source.hosts.add(host)
|
||||
inventory_source.inventory.admin_role.members.add(rando)
|
||||
delete(
|
||||
reverse(
|
||||
'api:inventory_source_hosts_list',
|
||||
kwargs={'version': 'v2', 'pk': inventory_source.pk}
|
||||
), user=rando, expect=204
|
||||
)
|
||||
assert inventory_source.hosts.count() == 0
|
||||
|
||||
def test_destroy_permission_check(self, job_factory, system_auditor, delete):
|
||||
job = job_factory()
|
||||
resp = delete(
|
||||
job.get_absolute_url(), user=system_auditor
|
||||
)
|
||||
assert resp.status_code == 403
|
||||
|
@ -377,7 +377,7 @@ def admin(user):
|
||||
|
||||
@pytest.fixture
|
||||
def system_auditor(user):
|
||||
u = user(False)
|
||||
u = user('sys-auditor', False)
|
||||
Role.singleton('system_auditor').members.add(u)
|
||||
return u
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user