def sign_collection_data()

in kinto-remote-settings/src/kinto_remote_settings/signer/listeners.py [0:0]


def sign_collection_data(event, resources, **kwargs):
    """
    Listen to resource change events, to check if a new signature is
    requested.

    When a source collection specified in settings is modified, and its
    new metadata ``status`` is set to ``"to-sign"``, then sign the data
    and update the destination.
    """
    payload = event.payload

    is_new_collection = payload["action"] == ACTIONS.CREATE.value

    current_user_id = event.request.prefixed_userid
    if current_user_id == PLUGIN_USERID:
        # Ignore changes made by plugin.
        return

    # Prevent recursivity, since the following operations will alter the current
    # collection.
    impacted_objects = list(event.impacted_objects)

    for impacted in impacted_objects:
        new_collection = impacted["new"]
        old_collection = impacted.get("old", {})

        # Only sign the configured resources.
        resource, signer = pick_resource_and_signer(
            event.request,
            resources,
            bucket_id=payload["bucket_id"],
            collection_id=new_collection["id"],
        )
        if resource is None:
            continue

        updater = LocalUpdater(
            signer=signer,
            storage=event.request.registry.storage,
            permission=event.request.registry.permission,
            source=resource["source"],
            destination=resource["destination"],
        )

        uri = instance_uri(
            event.request,
            "collection",
            bucket_id=payload["bucket_id"],
            id=new_collection["id"],
        )

        has_preview_collection = "preview" in resource

        payload = payload.copy()
        payload["uri"] = uri
        payload["collection_id"] = new_collection["id"]

        review_event_cls = None
        review_event_kw = dict(
            request=event.request,
            payload=payload,
            impacted_objects=[impacted],
            resource=resource,
            original_event=event,
        )

        new_status = new_collection.get("status")
        old_status = old_collection.get("status")

        # Autorize kinto-attachment metadata write access. #190
        event.request._attachment_auto_save = True

        # Logger JSON fields.
        logger_fields = {
            "user_id": current_user_id,
            "collection_id": new_collection["id"],
        }
        if is_new_collection:
            if has_preview_collection:
                updater.destination = resource["preview"]
                updater.sign_and_update_destination(
                    event.request,
                    source_attributes=new_collection,
                    # Do not update source attributes (done below).
                    next_source_status=None,
                )
            updater.destination = resource["destination"]
            updater.sign_and_update_destination(
                event.request,
                source_attributes=new_collection,
                # Prevents last_review_date to be set.
                previous_source_status=STATUS.SIGNED,
                # Signed by default.
                next_source_status=STATUS.SIGNED,
            )

        elif old_status == new_status:
            continue

        elif new_status == STATUS.TO_SIGN:
            # Run signature process (will set `last_reviewer` field).
            if has_preview_collection:
                updater.destination = resource["preview"]
                updater.sign_and_update_destination(
                    event.request,
                    source_attributes=new_collection,
                    previous_source_status=old_status,
                )

            updater.destination = resource["destination"]
            review_event_cls = signer_events.ReviewApproved
            changes_count = updater.sign_and_update_destination(
                event.request,
                source_attributes=new_collection,
                previous_source_status=old_status,
            )
            review_event_kw["changes_count"] = changes_count
            logger.info(
                "%s approved %s changes on %s",
                current_user_id,
                changes_count,
                new_collection["id"],
                extra={
                    **logger_fields,
                    "action": "approve",
                    "changes_count": changes_count,
                },
            )

        elif new_status == STATUS.TO_REVIEW:
            if has_preview_collection:
                # If preview collection: update and sign preview collection
                updater.destination = resource["preview"]
                changes_count = updater.sign_and_update_destination(
                    event.request,
                    source_attributes=new_collection,
                    next_source_status=STATUS.TO_REVIEW,
                )
            else:
                # If no preview collection: just track `last_editor`
                updater.update_source_review_request_by(event.request)
                changes_count = None
            review_event_cls = signer_events.ReviewRequested
            review_event_kw["changes_count"] = changes_count
            review_event_kw["comment"] = new_collection.get("last_editor_comment", "")
            logger.info(
                "%s requested review for %s changes on %s",
                current_user_id,
                changes_count,
                new_collection["id"],
                extra={
                    **logger_fields,
                    "action": "request",
                    "changes_count": changes_count,
                },
            )

        elif old_status == STATUS.TO_REVIEW and new_status == STATUS.WORK_IN_PROGRESS:
            review_event_cls = signer_events.ReviewRejected
            review_event_kw["comment"] = new_collection.get("last_reviewer_comment", "")
            logger.info(
                "%s rejected review on %s",
                current_user_id,
                new_collection["id"],
                extra={
                    **logger_fields,
                    "action": "reject",
                },
            )

        elif new_status == STATUS.TO_REFRESH:
            updater.refresh_signature(event.request, next_source_status=old_status)
            if has_preview_collection:
                updater.destination = resource["preview"]
                updater.refresh_signature(event.request, next_source_status=old_status)
            logger.info(
                "%s refreshed signature on %s",
                current_user_id,
                new_collection["id"],
                extra={
                    **logger_fields,
                    "action": "refresh",
                },
            )

        elif new_status == STATUS.TO_ROLLBACK:
            # Reset source with destination content, and set status to SIGNED.
            changes_count = updater.rollback_changes(event.request)
            if has_preview_collection:
                # Reset preview with destination content.
                updater.source = resource["preview"]
                changes_count += updater.rollback_changes(
                    event.request, refresh_last_edit=False
                )
                # Refresh signature for this new preview collection content.
                updater.destination = resource["preview"]
                # Without refreshing the source attributes.
                updater.refresh_signature(event.request, next_source_status=None)
            # If some changes were effectively rolledback, send an event.
            if changes_count > 0:
                review_event_cls = signer_events.ReviewCanceled
                review_event_kw["changes_count"] = changes_count
            logger.info(
                "%s rolledback %s changes on %s",
                current_user_id,
                changes_count,
                new_collection["id"],
                extra={
                    **logger_fields,
                    "action": "rollback",
                    "changes_count": changes_count,
                },
            )

        # Notify request of review.
        if review_event_cls:
            review_event = review_event_cls(**review_event_kw)
            event.request.bound_data.setdefault(
                "kinto_remote_settings.signer.events", []
            ).append(review_event)