From 55cc23a71267356860081500523fdea0b8241bd6 Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Tue, 25 Jul 2017 10:54:37 -0400 Subject: [PATCH] impersonate requesting user in inventory deletion task --- awx/api/views.py | 2 +- awx/main/models/inventory.py | 4 ++-- awx/main/signals.py | 17 +++++++++++------ awx/main/tasks.py | 18 ++++++++++++++---- .../functional/models/test_activity_stream.py | 11 +++++++++++ 5 files changed, 39 insertions(+), 13 deletions(-) diff --git a/awx/api/views.py b/awx/api/views.py index d8c9d535d9..e4085cdf19 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -1848,7 +1848,7 @@ class InventoryDetail(ControlledByScmMixin, RetrieveUpdateDestroyAPIView): if not request.user.can_access(self.model, 'delete', obj): raise PermissionDenied() try: - obj.schedule_deletion() + obj.schedule_deletion(getattr(request.user, 'id', None)) return Response(status=status.HTTP_202_ACCEPTED) except RuntimeError, e: return Response(dict(error=_("{0}".format(e))), status=status.HTTP_400_BAD_REQUEST) diff --git a/awx/main/models/inventory.py b/awx/main/models/inventory.py index 8cf291a86d..f1d252a64a 100644 --- a/awx/main/models/inventory.py +++ b/awx/main/models/inventory.py @@ -376,14 +376,14 @@ class Inventory(CommonModelNameNotUnique, ResourceMixin): return self.insights_credential @transaction.atomic - def schedule_deletion(self): + def schedule_deletion(self, user_id=None): from awx.main.tasks import delete_inventory if self.pending_deletion is True: raise RuntimeError("Inventory is already pending deletion.") self.pending_deletion = True self.save(update_fields=['pending_deletion']) self.websocket_emit_status('pending_deletion') - delete_inventory.delay(self.pk) + delete_inventory.delay(self.pk, user_id) def _update_host_smart_inventory_memeberships(self): if self.kind == 'smart' and settings.AWX_REBUILD_SMART_MEMBERSHIP: diff --git a/awx/main/signals.py b/awx/main/signals.py index da477706e2..7befd1545a 100644 --- a/awx/main/signals.py +++ b/awx/main/signals.py @@ -13,7 +13,7 @@ from django.db.models.signals import post_save, pre_delete, post_delete, m2m_cha from django.dispatch import receiver # Django-CRUM -from crum import get_current_request +from crum import get_current_request, get_current_user from crum.signals import current_user_getter # AWX @@ -385,7 +385,8 @@ def activity_stream_create(sender, instance, created, **kwargs): activity_entry = ActivityStream( operation='create', object1=object1, - changes=json.dumps(changes)) + changes=json.dumps(changes), + actor=get_current_user()) activity_entry.save() #TODO: Weird situation where cascade SETNULL doesn't work # it might actually be a good idea to remove all of these FK references since @@ -412,7 +413,8 @@ def activity_stream_update(sender, instance, **kwargs): activity_entry = ActivityStream( operation='update', object1=object1, - changes=json.dumps(changes)) + changes=json.dumps(changes), + actor=get_current_user()) activity_entry.save() if instance._meta.model_name != 'setting': # Is not conf.Setting instance getattr(activity_entry, object1).add(instance) @@ -430,7 +432,8 @@ def activity_stream_delete(sender, instance, **kwargs): activity_entry = ActivityStream( operation='delete', changes=json.dumps(changes), - object1=object1) + object1=object1, + actor=get_current_user()) activity_entry.save() @@ -477,7 +480,8 @@ def activity_stream_associate(sender, instance, **kwargs): operation=action, object1=object1, object2=object2, - object_relationship_type=obj_rel) + object_relationship_type=obj_rel, + actor=get_current_user()) activity_entry.save() getattr(activity_entry, object1).add(obj1) getattr(activity_entry, object2).add(obj2_actual) @@ -515,8 +519,9 @@ def get_current_user_from_drf_request(sender, **kwargs): @receiver(pre_delete, sender=Organization) def delete_inventory_for_org(sender, instance, **kwargs): inventories = Inventory.objects.filter(organization__pk=instance.pk) + user = get_current_user() for inventory in inventories: try: - inventory.schedule_deletion() + inventory.schedule_deletion(user_id=getattr(user, 'id', None)) except RuntimeError, e: logger.debug(e) diff --git a/awx/main/tasks.py b/awx/main/tasks.py index 370e7762ea..9858d1db0f 100644 --- a/awx/main/tasks.py +++ b/awx/main/tasks.py @@ -42,6 +42,9 @@ from django.utils.translation import ugettext_lazy as _ from django.core.cache import cache from django.core.exceptions import ObjectDoesNotExist +# Django-CRUM +from crum import impersonate + # AWX from awx import __version__ as awx_application_version from awx.main.constants import CLOUD_PROVIDERS, PRIVILEGE_ESCALATION_METHODS @@ -410,9 +413,16 @@ def update_host_smart_inventory_memberships(): @task(bind=True, queue='tower', base=LogErrorsTask, max_retries=5) -def delete_inventory(self, inventory_id): - with ignore_inventory_computed_fields(), \ - ignore_inventory_group_removal(): +def delete_inventory(self, inventory_id, user_id): + # Delete inventory as user + if user_id is None: + user = None + else: + try: + user = User.objects.get(id=user_id) + except: + user = None + with ignore_inventory_computed_fields(), ignore_inventory_group_removal(), impersonate(user): try: i = Inventory.objects.get(id=inventory_id) i.delete() @@ -420,7 +430,7 @@ def delete_inventory(self, inventory_id): 'inventories-status_changed', {'group_name': 'inventories', 'inventory_id': inventory_id, 'status': 'deleted'} ) - logger.debug('Deleted inventory: %s' % inventory_id) + logger.debug('Deleted inventory %s as user %s.' % (inventory_id, user_id)) except OperationalError: logger.warning('Database error deleting inventory {}, but will retry.'.format(inventory_id)) self.retry(countdown=10) diff --git a/awx/main/tests/functional/models/test_activity_stream.py b/awx/main/tests/functional/models/test_activity_stream.py index bdee4b80c1..0af3582b79 100644 --- a/awx/main/tests/functional/models/test_activity_stream.py +++ b/awx/main/tests/functional/models/test_activity_stream.py @@ -16,6 +16,9 @@ from awx.main.models import ( from awx.main.utils import model_to_dict from awx.api.serializers import InventorySourceSerializer +# Django-CRUM +from crum import impersonate + model_serializer_mapping = { InventorySource: InventorySourceSerializer @@ -157,3 +160,11 @@ def test_missing_related_on_delete(inventory_source): inventory_source.inventory.delete() d = model_to_dict(old_is, serializer_mapping=model_serializer_mapping) assert d['inventory'] == '-{}'.format(old_is.inventory_id) + + +@pytest.mark.django_db +def test_activity_stream_actor(admin_user): + with impersonate(admin_user): + o = Organization.objects.create(name='test organization') + entry = o.activitystream_set.get(operation='create') + assert entry.actor == admin_user