{"action":"created","comment":{"url":"https://api.github.com/repos/Kinto/kinto-signer/pulls/comments/75719403","id":75719403,"diff_hunk":"@@ -0,0 +1,176 @@\n+import transaction\n+from kinto.core import errors\n+\n+from kinto import logger\n+from pyramid import httpexceptions\n+\n+from kinto_signer.updater import LocalUpdater\n+from kinto_signer.utils import STATUS\n+\n+\n+def raise_invalid(**kwargs):\n+    # A ``400`` error response does not natively rollback the transaction.\n+    transaction.doom()\n+    raise errors.http_error(httpexceptions.HTTPBadRequest(), **kwargs)\n+\n+\n+def raise_forbidden(**kwargs):\n+    # A ``403`` error response does not natively rollback the transaction.\n+    transaction.doom()\n+    raise errors.http_error(httpexceptions.HTTPForbidden(), **kwargs)\n+\n+\n+def sign_collection_data(event, resources):\n+    \"\"\"\n+    Listen to resource change events, to check if a new signature is\n+    requested.\n+\n+    When a source collection specified in settings is modified, and its\n+    new metadata ``status`` is set to ``\"to-sign\"``, then sign the data\n+    and update the destination.\n+    \"\"\"\n+    payload = event.payload\n+\n+    for impacted in event.impacted_records:\n+        new_collection = impacted['new']\n+\n+        key = \"/buckets/{bucket_id}/collections/{collection_id}\".format(\n+            collection_id=new_collection['id'],\n+            bucket_id=payload['bucket_id'])\n+\n+        resource = resources.get(key)\n+\n+        # Only sign the configured resources.\n+        if resource is None:\n+            continue\n+\n+        registry = event.request.registry\n+        updater = LocalUpdater(signer=registry.signers[key],\n+                               storage=registry.storage,\n+                               permission=registry.permission,\n+                               source=resource['source'],\n+                               destination=resource['destination'])\n+\n+        new_status = new_collection.get(\"status\")\n+        if new_status == STATUS.TO_SIGN:\n+            # Run signature process (will set `last_reviewer` field).\n+            try:\n+                updater.sign_and_update_destination(event.request)\n+            except Exception:\n+                logger.exception(\"Could not sign '{0}'\".format(key))\n+                event.request.response.status = 503\n+\n+        elif new_status == STATUS.TO_REVIEW:\n+            # Track `last_editor`\n+            updater.update_source_editor(event.request)\n+\n+\n+def check_collection_status(event, resources, force_groups, force_review,\n+                            editors_group, reviewers_group):\n+    \"\"\"Make sure status changes are allowed.\n+    \"\"\"\n+    payload = event.payload\n+\n+    editors_group = \"/buckets/{bucket_id}/groups/{group_name}\".format(\n+        group_name=editors_group, **payload)\n+    reviewers_group = \"/buckets/{bucket_id}/groups/{group_name}\".format(\n+        group_name=reviewers_group, **payload)\n+\n+    current_user_id = event.request.prefixed_userid\n+    current_principals = event.request.effective_principals\n+\n+    for impacted in event.impacted_records:\n+        old_collection = impacted.get(\"old\", {}).copy()\n+        old_status = old_collection.get(\"status\")\n+        new_collection = impacted[\"new\"].copy()\n+        new_status = new_collection.get(\"status\")\n+\n+        key = \"/buckets/{bucket_id}/collections/{collection_id}\".format(\n+            bucket_id=payload[\"bucket_id\"],\n+            collection_id=new_collection[\"id\"])\n+\n+        # Skip if resource is not configured.\n+        if key not in resources:\n+            continue\n+\n+        if old_status == new_status:\n+            continue\n+\n+        # Only these status can be set manually.\n+        if new_status in (STATUS.SIGNED,):\n+            raise_invalid(message=\"Cannot set status to '%s'\" % new_status)\n+\n+        # 1. None -> work-in-progress\n+        # 2. work-in-progress -> to-review\n+        elif new_status == STATUS.TO_REVIEW:\n+            if editors_group not in current_principals and force_groups:\n+                raise_forbidden(message=\"Not in editors group\")\n+\n+        # 3. to-review -> work-in-progress\n+        elif new_status == STATUS.WORK_IN_PROGRESS:\n+            pass\n+\n+        # 3. to-review -> to-sign\n+        elif new_status == STATUS.TO_SIGN:\n+            # Only allow to-sign from to-review if reviewer and no-editor\n+            if reviewers_group not in current_principals and force_groups:\n+                raise_forbidden(message=\"Not in reviewers group\")\n+\n+            requires_review = old_status not in (STATUS.TO_REVIEW,\n+                                                 STATUS.SIGNED)\n+            if requires_review and force_review:\n+                raise_invalid(message=\"Collection not reviewed\")\n+\n+            if old_collection.get(\"last_editor\") == current_user_id:\n+                raise_forbidden(message=\"Editor cannot review\")\n+\n+        # 4. to-sign -> signed","path":"kinto_signer/listeners.py","position":131,"original_position":127,"commit_id":"2908adde37ce6700b15d46b8b4979269e676aef3","original_commit_id":"762ace00a2bedc01ee5dd0121f97d64cccdfe460","user":{"login":"leplatrem","id":546692,"avatar_url":"https://avatars.githubusercontent.com/u/546692?v=3","gravatar_id":"","url":"https://api.github.com/users/leplatrem","html_url":"https://github.com/leplatrem","followers_url":"https://api.github.com/users/leplatrem/followers","following_url":"https://api.github.com/users/leplatrem/following{/other_user}","gists_url":"https://api.github.com/users/leplatrem/gists{/gist_id}","starred_url":"https://api.github.com/users/leplatrem/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/leplatrem/subscriptions","organizations_url":"https://api.github.com/users/leplatrem/orgs","repos_url":"https://api.github.com/users/leplatrem/repos","events_url":"https://api.github.com/users/leplatrem/events{/privacy}","received_events_url":"https://api.github.com/users/leplatrem/received_events","type":"User","site_admin":false},"body":"Very good catch! There was a bug preventing the right event to be triggered. I added some checks to escape the listener when a change is done by the update (`user_id == 'plugin:kinto-signer'`)","created_at":"2016-08-22T17:20:45Z","updated_at":"2016-08-22T17:20:45Z","html_url":"https://github.com/Kinto/kinto-signer/pull/96#discussion_r75719403","pull_request_url":"https://api.github.com/repos/Kinto/kinto-signer/pulls/96","_links":{"self":{"href":"https://api.github.com/repos/Kinto/kinto-signer/pulls/comments/75719403"},"html":{"href":"https://github.com/Kinto/kinto-signer/pull/96#discussion_r75719403"},"pull_request":{"href":"https://api.github.com/repos/Kinto/kinto-signer/pulls/96"}}},"pull_request":{"url":"https://api.github.com/repos/Kinto/kinto-signer/pulls/96","id":76573155,"html_url":"https://github.com/Kinto/kinto-signer/pull/96","diff_url":"https://github.com/Kinto/kinto-signer/pull/96.diff","patch_url":"https://github.com/Kinto/kinto-signer/pull/96.patch","issue_url":"https://api.github.com/repos/Kinto/kinto-signer/issues/96","number":96,"state":"open","locked":false,"title":"Sign-off workflow (fixes #95)","user":{"login":"Natim","id":229453,"avatar_url":"https://avatars.githubusercontent.com/u/229453?v=3","gravatar_id":"","url":"https://api.github.com/users/Natim","html_url":"https://github.com/Natim","followers_url":"https://api.github.com/users/Natim/followers","following_url":"https://api.github.com/users/Natim/following{/other_user}","gists_url":"https://api.github.com/users/Natim/gists{/gist_id}","starred_url":"https://api.github.com/users/Natim/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Natim/subscriptions","organizations_url":"https://api.github.com/users/Natim/orgs","repos_url":"https://api.github.com/users/Natim/repos","events_url":"https://api.github.com/users/Natim/events{/privacy}","received_events_url":"https://api.github.com/users/Natim/received_events","type":"User","site_admin":false},"body":"Starting off the sign-off feature\r\n\r\nFixes #95","created_at":"2016-07-07T10:34:01Z","updated_at":"2016-08-22T17:20:45Z","closed_at":null,"merged_at":null,"merge_commit_sha":"c689e5bbf9b2023f0f432e2a9d40abdfdfd6fb88","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/Kinto/kinto-signer/pulls/96/commits","review_comments_url":"https://api.github.com/repos/Kinto/kinto-signer/pulls/96/comments","review_comment_url":"https://api.github.com/repos/Kinto/kinto-signer/pulls/comments{/number}","comments_url":"https://api.github.com/repos/Kinto/kinto-signer/issues/96/comments","statuses_url":"https://api.github.com/repos/Kinto/kinto-signer/statuses/2908adde37ce6700b15d46b8b4979269e676aef3","head":{"label":"Kinto:signoff-feature","ref":"signoff-feature","sha":"2908adde37ce6700b15d46b8b4979269e676aef3","user":{"login":"Kinto","id":13413813,"avatar_url":"https://avatars.githubusercontent.com/u/13413813?v=3","gravatar_id":"","url":"https://api.github.com/users/Kinto","html_url":"https://github.com/Kinto","followers_url":"https://api.github.com/users/Kinto/followers","following_url":"https://api.github.com/users/Kinto/following{/other_user}","gists_url":"https://api.github.com/users/Kinto/gists{/gist_id}","starred_url":"https://api.github.com/users/Kinto/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Kinto/subscriptions","organizations_url":"https://api.github.com/users/Kinto/orgs","repos_url":"https://api.github.com/users/Kinto/repos","events_url":"https://api.github.com/users/Kinto/events{/privacy}","received_events_url":"https://api.github.com/users/Kinto/received_events","type":"Organization","site_admin":false},"repo":{"id":42935019,"name":"kinto-signer","full_name":"Kinto/kinto-signer","owner":{"login":"Kinto","id":13413813,"avatar_url":"https://avatars.githubusercontent.com/u/13413813?v=3","gravatar_id":"","url":"https://api.github.com/users/Kinto","html_url":"https://github.com/Kinto","followers_url":"https://api.github.com/users/Kinto/followers","following_url":"https://api.github.com/users/Kinto/following{/other_user}","gists_url":"https://api.github.com/users/Kinto/gists{/gist_id}","starred_url":"https://api.github.com/users/Kinto/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Kinto/subscriptions","organizations_url":"https://api.github.com/users/Kinto/orgs","repos_url":"https://api.github.com/users/Kinto/repos","events_url":"https://api.github.com/users/Kinto/events{/privacy}","received_events_url":"https://api.github.com/users/Kinto/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/Kinto/kinto-signer","description":"Digital signatures to guarantee integrity and authenticity of collections of records.","fork":false,"url":"https://api.github.com/repos/Kinto/kinto-signer","forks_url":"https://api.github.com/repos/Kinto/kinto-signer/forks","keys_url":"https://api.github.com/repos/Kinto/kinto-signer/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Kinto/kinto-signer/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Kinto/kinto-signer/teams","hooks_url":"https://api.github.com/repos/Kinto/kinto-signer/hooks","issue_events_url":"https://api.github.com/repos/Kinto/kinto-signer/issues/events{/number}","events_url":"https://api.github.com/repos/Kinto/kinto-signer/events","assignees_url":"https://api.github.com/repos/Kinto/kinto-signer/assignees{/user}","branches_url":"https://api.github.com/repos/Kinto/kinto-signer/branches{/branch}","tags_url":"https://api.github.com/repos/Kinto/kinto-signer/tags","blobs_url":"https://api.github.com/repos/Kinto/kinto-signer/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Kinto/kinto-signer/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Kinto/kinto-signer/git/refs{/sha}","trees_url":"https://api.github.com/repos/Kinto/kinto-signer/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Kinto/kinto-signer/statuses/{sha}","languages_url":"https://api.github.com/repos/Kinto/kinto-signer/languages","stargazers_url":"https://api.github.com/repos/Kinto/kinto-signer/stargazers","contributors_url":"https://api.github.com/repos/Kinto/kinto-signer/contributors","subscribers_url":"https://api.github.com/repos/Kinto/kinto-signer/subscribers","subscription_url":"https://api.github.com/repos/Kinto/kinto-signer/subscription","commits_url":"https://api.github.com/repos/Kinto/kinto-signer/commits{/sha}","git_commits_url":"https://api.github.com/repos/Kinto/kinto-signer/git/commits{/sha}","comments_url":"https://api.github.com/repos/Kinto/kinto-signer/comments{/number}","issue_comment_url":"https://api.github.com/repos/Kinto/kinto-signer/issues/comments{/number}","contents_url":"https://api.github.com/repos/Kinto/kinto-signer/contents/{+path}","compare_url":"https://api.github.com/repos/Kinto/kinto-signer/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Kinto/kinto-signer/merges","archive_url":"https://api.github.com/repos/Kinto/kinto-signer/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Kinto/kinto-signer/downloads","issues_url":"https://api.github.com/repos/Kinto/kinto-signer/issues{/number}","pulls_url":"https://api.github.com/repos/Kinto/kinto-signer/pulls{/number}","milestones_url":"https://api.github.com/repos/Kinto/kinto-signer/milestones{/number}","notifications_url":"https://api.github.com/repos/Kinto/kinto-signer/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Kinto/kinto-signer/labels{/name}","releases_url":"https://api.github.com/repos/Kinto/kinto-signer/releases{/id}","deployments_url":"https://api.github.com/repos/Kinto/kinto-signer/deployments","created_at":"2015-09-22T13:21:26Z","updated_at":"2016-07-21T08:57:04Z","pushed_at":"2016-08-22T17:18:35Z","git_url":"git://github.com/Kinto/kinto-signer.git","ssh_url":"git@github.com:Kinto/kinto-signer.git","clone_url":"https://github.com/Kinto/kinto-signer.git","svn_url":"https://github.com/Kinto/kinto-signer","homepage":"","size":512,"stargazers_count":3,"watchers_count":3,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":4,"mirror_url":null,"open_issues_count":13,"forks":4,"open_issues":13,"watchers":3,"default_branch":"master"}},"base":{"label":"Kinto:master","ref":"master","sha":"b29b5b3a4acd0d655b2fa59f22e81e43c36c6126","user":{"login":"Kinto","id":13413813,"avatar_url":"https://avatars.githubusercontent.com/u/13413813?v=3","gravatar_id":"","url":"https://api.github.com/users/Kinto","html_url":"https://github.com/Kinto","followers_url":"https://api.github.com/users/Kinto/followers","following_url":"https://api.github.com/users/Kinto/following{/other_user}","gists_url":"https://api.github.com/users/Kinto/gists{/gist_id}","starred_url":"https://api.github.com/users/Kinto/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Kinto/subscriptions","organizations_url":"https://api.github.com/users/Kinto/orgs","repos_url":"https://api.github.com/users/Kinto/repos","events_url":"https://api.github.com/users/Kinto/events{/privacy}","received_events_url":"https://api.github.com/users/Kinto/received_events","type":"Organization","site_admin":false},"repo":{"id":42935019,"name":"kinto-signer","full_name":"Kinto/kinto-signer","owner":{"login":"Kinto","id":13413813,"avatar_url":"https://avatars.githubusercontent.com/u/13413813?v=3","gravatar_id":"","url":"https://api.github.com/users/Kinto","html_url":"https://github.com/Kinto","followers_url":"https://api.github.com/users/Kinto/followers","following_url":"https://api.github.com/users/Kinto/following{/other_user}","gists_url":"https://api.github.com/users/Kinto/gists{/gist_id}","starred_url":"https://api.github.com/users/Kinto/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Kinto/subscriptions","organizations_url":"https://api.github.com/users/Kinto/orgs","repos_url":"https://api.github.com/users/Kinto/repos","events_url":"https://api.github.com/users/Kinto/events{/privacy}","received_events_url":"https://api.github.com/users/Kinto/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/Kinto/kinto-signer","description":"Digital signatures to guarantee integrity and authenticity of collections of records.","fork":false,"url":"https://api.github.com/repos/Kinto/kinto-signer","forks_url":"https://api.github.com/repos/Kinto/kinto-signer/forks","keys_url":"https://api.github.com/repos/Kinto/kinto-signer/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Kinto/kinto-signer/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Kinto/kinto-signer/teams","hooks_url":"https://api.github.com/repos/Kinto/kinto-signer/hooks","issue_events_url":"https://api.github.com/repos/Kinto/kinto-signer/issues/events{/number}","events_url":"https://api.github.com/repos/Kinto/kinto-signer/events","assignees_url":"https://api.github.com/repos/Kinto/kinto-signer/assignees{/user}","branches_url":"https://api.github.com/repos/Kinto/kinto-signer/branches{/branch}","tags_url":"https://api.github.com/repos/Kinto/kinto-signer/tags","blobs_url":"https://api.github.com/repos/Kinto/kinto-signer/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Kinto/kinto-signer/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Kinto/kinto-signer/git/refs{/sha}","trees_url":"https://api.github.com/repos/Kinto/kinto-signer/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Kinto/kinto-signer/statuses/{sha}","languages_url":"https://api.github.com/repos/Kinto/kinto-signer/languages","stargazers_url":"https://api.github.com/repos/Kinto/kinto-signer/stargazers","contributors_url":"https://api.github.com/repos/Kinto/kinto-signer/contributors","subscribers_url":"https://api.github.com/repos/Kinto/kinto-signer/subscribers","subscription_url":"https://api.github.com/repos/Kinto/kinto-signer/subscription","commits_url":"https://api.github.com/repos/Kinto/kinto-signer/commits{/sha}","git_commits_url":"https://api.github.com/repos/Kinto/kinto-signer/git/commits{/sha}","comments_url":"https://api.github.com/repos/Kinto/kinto-signer/comments{/number}","issue_comment_url":"https://api.github.com/repos/Kinto/kinto-signer/issues/comments{/number}","contents_url":"https://api.github.com/repos/Kinto/kinto-signer/contents/{+path}","compare_url":"https://api.github.com/repos/Kinto/kinto-signer/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Kinto/kinto-signer/merges","archive_url":"https://api.github.com/repos/Kinto/kinto-signer/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Kinto/kinto-signer/downloads","issues_url":"https://api.github.com/repos/Kinto/kinto-signer/issues{/number}","pulls_url":"https://api.github.com/repos/Kinto/kinto-signer/pulls{/number}","milestones_url":"https://api.github.com/repos/Kinto/kinto-signer/milestones{/number}","notifications_url":"https://api.github.com/repos/Kinto/kinto-signer/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Kinto/kinto-signer/labels{/name}","releases_url":"https://api.github.com/repos/Kinto/kinto-signer/releases{/id}","deployments_url":"https://api.github.com/repos/Kinto/kinto-signer/deployments","created_at":"2015-09-22T13:21:26Z","updated_at":"2016-07-21T08:57:04Z","pushed_at":"2016-08-22T17:18:35Z","git_url":"git://github.com/Kinto/kinto-signer.git","ssh_url":"git@github.com:Kinto/kinto-signer.git","clone_url":"https://github.com/Kinto/kinto-signer.git","svn_url":"https://github.com/Kinto/kinto-signer","homepage":"","size":512,"stargazers_count":3,"watchers_count":3,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":4,"mirror_url":null,"open_issues_count":13,"forks":4,"open_issues":13,"watchers":3,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/Kinto/kinto-signer/pulls/96"},"html":{"href":"https://github.com/Kinto/kinto-signer/pull/96"},"issue":{"href":"https://api.github.com/repos/Kinto/kinto-signer/issues/96"},"comments":{"href":"https://api.github.com/repos/Kinto/kinto-signer/issues/96/comments"},"review_comments":{"href":"https://api.github.com/repos/Kinto/kinto-signer/pulls/96/comments"},"review_comment":{"href":"https://api.github.com/repos/Kinto/kinto-signer/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/Kinto/kinto-signer/pulls/96/commits"},"statuses":{"href":"https://api.github.com/repos/Kinto/kinto-signer/statuses/2908adde37ce6700b15d46b8b4979269e676aef3"}}}}
{"actor":{"display_login":"leplatrem"}}