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)