Skip to content

Commit

Permalink
Merge pull request ansible#490 from AlanCoding/many_deleter_320
Browse files Browse the repository at this point in the history
[3.2.2] Delete all hosts or groups from inventory source
  • Loading branch information
AlanCoding authored and matburt committed Oct 10, 2017
2 parents bb628c5 + d2e0b26 commit 5fffdec
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 17 deletions.
53 changes: 39 additions & 14 deletions awx/api/generics.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,10 @@

__all__ = ['APIView', 'GenericAPIView', 'ListAPIView', 'SimpleListAPIView',
'ListCreateAPIView', 'SubListAPIView', 'SubListCreateAPIView',
'SubListDestroyAPIView',
'SubListCreateAttachDetachAPIView', 'RetrieveAPIView',
'RetrieveUpdateAPIView', 'RetrieveDestroyAPIView',
'RetrieveUpdateDestroyAPIView', 'DestroyAPIView',
'RetrieveUpdateDestroyAPIView',
'SubDetailAPIView',
'ResourceAccessList',
'ParentMixin',
Expand Down Expand Up @@ -440,6 +441,41 @@ def get_queryset(self):
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.
Expand Down Expand Up @@ -678,22 +714,11 @@ def update_filter(self, request, *args, **kwargs):
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


Expand Down
6 changes: 6 additions & 0 deletions awx/api/templates/api/sub_list_destroy_api_view.md
Original file line number Diff line number Diff line change
@@ -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.
6 changes: 4 additions & 2 deletions awx/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
31 changes: 31 additions & 0 deletions awx/main/tests/functional/api/test_generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,34 @@ def process_response(self, request, response):
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
2 changes: 1 addition & 1 deletion awx/main/tests/functional/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down

0 comments on commit 5fffdec

Please sign in to comment.