def test_worker(self):
        request_id = uuid.uuid4()

        def mock_send(_, request, **kwargs):
            """
            Mocked implementation of Session.send() that always returns a 200 HTTP
            status code.
            """
            webhook = Webhook.objects.get(type_create=True)
            signature = generate_signature(request.body, webhook.secret)

            # Validate the outgoing request headers
            self.assertEqual(request.headers["Content-Type"],
                             webhook.http_content_type)
            self.assertEqual(request.headers["X-Hook-Signature"], signature)

            # Validate the outgoing request body
            body = json.loads(request.body)
            self.assertEqual(body["event"], ObjectChangeAction.CREATE)
            self.assertEqual(body["timestamp"], job.args[4])
            self.assertEqual(body["model"], "autonomoussystem")
            self.assertEqual(body["username"], "testuser")
            self.assertEqual(body["request_id"], str(request_id))
            self.assertEqual(body["data"]["name"], "Guillaume Mazoyer")

            return HttpResponse()

        # Enqueue a webhook for processing
        a_s = AutonomousSystem.objects.create(asn=201281,
                                              name="Guillaume Mazoyer")
        enqueue_webhooks(a_s, self.user, request_id, ObjectChangeAction.CREATE)

        job = self.queue.jobs[0]
        with patch.object(Session, "send", mock_send):
            process_webhook(*job.args)
    def __call__(self, request):
        # Prepare to collect objects that have been changed
        local_thread.changed_objects = []

        # Assign an ID to the given request in case we have to handle multiple objects
        request.id = uuid.uuid4()

        # Listen for objects being saved (created/updated) and deleted
        post_save.connect(cache_changed_object, dispatch_uid="log_object_being_changed")
        pre_delete.connect(
            cache_deleted_object, dispatch_uid="log_object_being_deleted"
        )

        # Process the request
        response = self.get_response(request)

        # Nothing to do as there are no changes to process
        if not local_thread.changed_objects:
            return response

        # Stop listening for object changes
        post_save.disconnect(
            cache_changed_object, dispatch_uid="log_object_being_changed"
        )
        pre_delete.disconnect(
            cache_deleted_object, dispatch_uid="log_object_being_deleted"
        )

        # Record change for each object that need to be tracked
        has_redis_failed = False
        for changed_object, action in local_thread.changed_objects:
            if hasattr(changed_object, "log_change"):
                changed_object.log_change(request.user, request.id, action)

            try:
                enqueue_webhooks(changed_object, request.user, request.id, action)
            except RedisError as e:
                if not has_redis_failed:
                    messages.error(
                        request,
                        f"An error has occured while processing webhooks for this request. Check that the Redis service is running and reachable. The full error details were: {e}",
                    )
                    has_redis_failed = True

        # Cleanup object changes that are too old (based on changelog retention)
        if local_thread.changed_objects and settings.CHANGELOG_RETENTION:
            date_limit = timezone.now() - timedelta(days=settings.CHANGELOG_RETENTION)
            ObjectChange.objects.filter(time__lt=date_limit).delete()

        return response
Exemple #3
0
def _handle_deleted_object(request, sender, instance, **kwargs):
    """
    Fires when an object is deleted.
    """
    # Record an ObjectChange if applicable
    if hasattr(instance, "get_change"):
        change = instance.get_change(ObjectChangeAction.DELETE)
        change.user = request.user
        change.request_id = request.id
        change.save()

    # Enqueue webhooks
    enqueue_webhooks(instance, request.user, request.id,
                     ObjectChangeAction.DELETE)

    # Increment metric counters
    model_deletes.labels(instance._meta.model_name).inc()
Exemple #4
0
def _handle_changed_object(request, sender, instance, **kwargs):
    """
    Fires when an object is created or updated.
    """
    # Queue the object for processing once the request completes
    if kwargs.get("created"):
        action = ObjectChangeAction.CREATE
    elif "created" in kwargs:
        action = ObjectChangeAction.UPDATE
    elif kwargs.get("action") in ["post_add", "post_remove"
                                  ] and kwargs["pk_set"]:
        # m2m_changed with objects added or removed
        action = ObjectChangeAction.UPDATE
    else:
        return

    # Record an ObjectChange if applicable
    if hasattr(instance, "get_change"):
        change = instance.get_change(action)
        change.user = request.user
        change.request_id = request.id
        change.save()

    # Enqueue webhooks
    enqueue_webhooks(instance, request.user, request.id, action)

    # Increment metric counters
    if action == ObjectChangeAction.CREATE:
        model_inserts.labels(instance._meta.model_name).inc()
    elif action == ObjectChangeAction.UPDATE:
        model_updates.labels(instance._meta.model_name).inc()

    # Housekeeping: 0.1% chance of clearing out expired ObjectChanges
    if settings.CHANGELOG_RETENTION and random.randint(1, 1000) == 1:
        date_limit = timezone.now() - timedelta(
            days=settings.CHANGELOG_RETENTION)
        ObjectChange.objects.filter(time__lt=date_limit).delete()