We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Crash in history with DELETE /buckets
```
ERROR  "DELETE /v1/buckets" ? (? ms) 'bucket_id' agent=node-fetch/1.0 (+https://github.com/bitinn/node-fetch) authn_type=basicauth collection_id=bucket collection_timestamp=1471536086687 errno=None exception=Traceback (most recent call last):
  File "/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/pyramid/tweens.py", line 22, in excview_tween
    response = handler(request)
  File "/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/pyramid_tm/__init__.py", line 101, in tm_tween
    reraise(*exc_info)
  File "/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/pyramid_tm/__init__.py", line 90, in tm_tween
    manager.commit()
  File "/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/transaction/_manager.py", line 111, in commit
    return self.get().commit()
  File "/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/transaction/_transaction.py", line 265, in commit
    self._callBeforeCommitHooks()
  File "/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/transaction/_transaction.py", line 337, in _callBeforeCommitHooks
    hook(*args, **kws)
  File "/home/mathieu/Code/Mozilla/kinto/kinto/core/events.py", line 76, in _notify_resource_events_before
    request.registry.notify(event)
  File "/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/pyramid/registry.py", line 91, in notify
    [ _ for _ in self.subscribers(events, None) ]
  File "/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/zope/interface/registry.py", line 328, in subscribers
    return self.adapters.subscribers(objects, provided)
  File "/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/zope/interface/adapter.py", line 596, in subscribers
    subscription(*objects)
  File "/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/pyramid/config/adapters.py", line 130, in subscriber_wrapper
    return derived_subscriber(*arg)
  File "/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/pyramid/config/adapters.py", line 103, in derived_subscriber
    return subscriber(arg[0])
  File "/home/mathieu/Code/Mozilla/kinto/kinto/plugins/history/listener.py", line 16, in on_resource_changed
    bucket_id = payload.pop('bucket_id')
KeyError: 'bucket_id' lang=None uid=a9d81e1b56f2b77103e87601621a99b3d82a15abf7c72ce57a032d0964317c7a
```

Crash in history with DELETE /buckets
```
ERROR  "DELETE /v1/buckets" ? (? ms) 'bucket_id' agent=node-fetch/1.0 (+https://github.com/bitinn/node-fetch) authn_type=basicauth collection_id=bucket collection_timestamp=1471536086687 errno=None exception=Traceback (most recent call last):
  File "/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/pyramid/tweens.py", line 22, in excview_tween
    response = handler(request)
  File "/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/pyramid_tm/__init__.py", line 101, in tm_tween
    reraise(*exc_info)
  File "/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/pyramid_tm/__init__.py", line 90, in tm_tween
    manager.commit()
  File "/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/transaction/_manager.py", line 111, in commit
    return self.get().commit()
  File "/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/transaction/_transaction.py", line 265, in commit
    self._callBeforeCommitHooks()
  File "/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/transaction/_transaction.py", line 337, in _callBeforeCommitHooks
    hook(*args, **kws)
  File "/home/mathieu/Code/Mozilla/kinto/kinto/core/events.py", line 76, in _notify_resource_events_before
    request.registry.notify(event)
  File "/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/pyramid/registry.py", line 91, in notify
    [ _ for _ in self.subscribers(events, None) ]
  File "/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/zope/interface/registry.py", line 328, in subscribers
    return self.adapters.subscribers(objects, provided)
  File "/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/zope/interface/adapter.py", line 596, in subscribers
    subscription(*objects)
  File "/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/pyramid/config/adapters.py", line 130, in subscriber_wrapper
    return derived_subscriber(*arg)
  File "/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/pyramid/config/adapters.py", line 103, in derived_subscriber
    return subscriber(arg[0])
  File "/home/mathieu/Code/Mozilla/kinto/kinto/plugins/history/listener.py", line 16, in on_resource_changed
    bucket_id = payload.pop('bucket_id')
KeyError: 'bucket_id' lang=None uid=a9d81e1b56f2b77103e87601621a99b3d82a15abf7c72ce57a032d0964317c7a
```


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `kinto/plugins/history/listener.py`
Content:
```
1 from kinto.core.utils import instance_uri
2 import copy
3 from datetime import datetime
4 
5 
6 def on_resource_changed(event):
7     """
8     Everytime an object is created/changed/deleted, we create an entry in the
9     ``history`` resource. The entries are served as read-only in the
10     :mod:`kinto.plugins.history.views` module.
11     """
12     payload = copy.deepcopy(event.payload)
13     resource_name = payload['resource_name']
14     event_uri = payload['uri']
15 
16     bucket_id = payload.pop('bucket_id')
17     bucket_uri = instance_uri(event.request, 'bucket', id=bucket_id)
18     collection_id = None
19     collection_uri = None
20     if 'collection_id' in payload:
21         collection_id = payload['collection_id']
22         collection_uri = instance_uri(event.request,
23                                       'collection',
24                                       bucket_id=bucket_id,
25                                       id=collection_id)
26 
27     storage = event.request.registry.storage
28     permission = event.request.registry.permission
29 
30     targets = []
31     for impacted in event.impacted_records:
32         # On POST .../records, the URI does not contain the newly created
33         # record id.
34         target = impacted['new']
35         obj_id = target['id']
36         parts = event_uri.split('/')
37         if resource_name in parts[-1]:
38             parts.append(obj_id)
39         else:
40             # Make sure the id is correct on grouped events.
41             parts[-1] = obj_id
42         uri = '/'.join(parts)
43         targets.append((uri, target))
44 
45     # Prepare a list of object ids to be fetched from permission backend,
46     # and fetch them all at once. Use a mapping for later convenience.
47     all_perms_objects_ids = [oid for (oid, _) in targets]
48     all_perms_objects_ids.append(bucket_uri)
49     if collection_uri is not None:
50         all_perms_objects_ids.append(collection_uri)
51     all_perms_objects_ids = list(set(all_perms_objects_ids))
52     all_permissions = permission.get_objects_permissions(all_perms_objects_ids)
53     perms_by_object_id = dict(zip(all_perms_objects_ids, all_permissions))
54 
55     bucket_perms = perms_by_object_id[bucket_uri]
56     collection_perms = {}
57     if collection_uri is not None:
58         collection_perms = perms_by_object_id[collection_uri]
59 
60     # The principals allowed to read the bucket and collection.
61     # (Note: ``write`` means ``read``)
62     read_principals = set(bucket_perms.get('read', []))
63     read_principals.update(bucket_perms.get('write', []))
64     read_principals.update(collection_perms.get('read', []))
65     read_principals.update(collection_perms.get('write', []))
66 
67     # Create a history entry for each impacted record.
68     for (uri, target) in targets:
69         obj_id = target['id']
70         # Prepare the history entry attributes.
71         perms = {k: list(v) for k, v in perms_by_object_id[uri].items()}
72         eventattrs = dict(**payload)
73         eventattrs.setdefault('%s_id' % resource_name, obj_id)
74         eventattrs['uri'] = uri
75         attrs = dict(date=datetime.now().isoformat(),
76                      target={'data': target, 'permissions': perms},
77                      **eventattrs)
78 
79         # Create a record for the 'history' resource, whose parent_id is
80         # the bucket URI (c.f. views.py).
81         # Note: this will be rolledback if the transaction is rolledback.
82         entry = storage.create(parent_id=bucket_uri,
83                                collection_id='history',
84                                record=attrs)
85 
86         # The read permission on the newly created history entry is the union
87         # of the record permissions with the one from bucket and collection.
88         entry_principals = set(read_principals)
89         entry_principals.update(perms.get('read', []))
90         entry_principals.update(perms.get('write', []))
91         entry_perms = {'read': list(entry_principals)}
92         # /buckets/{id}/history is the URI for the list of history entries.
93         entry_perm_id = '/buckets/%s/history/%s' % (bucket_id, entry['id'])
94         permission.replace_object_permissions(entry_perm_id, entry_perms)
95
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/kinto/plugins/history/listener.py b/kinto/plugins/history/listener.py
--- a/kinto/plugins/history/listener.py
+++ b/kinto/plugins/history/listener.py
@@ -1,5 +1,4 @@
 from kinto.core.utils import instance_uri
-import copy
 from datetime import datetime
 
 
@@ -9,30 +8,38 @@
     ``history`` resource. The entries are served as read-only in the
     :mod:`kinto.plugins.history.views` module.
     """
-    payload = copy.deepcopy(event.payload)
+    payload = event.payload
     resource_name = payload['resource_name']
     event_uri = payload['uri']
 
-    bucket_id = payload.pop('bucket_id')
-    bucket_uri = instance_uri(event.request, 'bucket', id=bucket_id)
-    collection_id = None
+    bucket_id = None
+    bucket_uri = None
     collection_uri = None
-    if 'collection_id' in payload:
-        collection_id = payload['collection_id']
-        collection_uri = instance_uri(event.request,
-                                      'collection',
-                                      bucket_id=bucket_id,
-                                      id=collection_id)
 
     storage = event.request.registry.storage
     permission = event.request.registry.permission
 
     targets = []
     for impacted in event.impacted_records:
-        # On POST .../records, the URI does not contain the newly created
-        # record id.
         target = impacted['new']
         obj_id = target['id']
+
+        try:
+            bucket_id = payload['bucket_id']
+        except KeyError:
+            # e.g. DELETE /buckets
+            bucket_id = obj_id
+        bucket_uri = instance_uri(event.request, 'bucket', id=bucket_id)
+
+        if 'collection_id' in payload:
+            collection_id = payload['collection_id']
+            collection_uri = instance_uri(event.request,
+                                          'collection',
+                                          bucket_id=bucket_id,
+                                          id=collection_id)
+
+        # On POST .../records, the URI does not contain the newly created
+        # record id.
         parts = event_uri.split('/')
         if resource_name in parts[-1]:
             parts.append(obj_id)
@@ -70,6 +77,7 @@
         # Prepare the history entry attributes.
         perms = {k: list(v) for k, v in perms_by_object_id[uri].items()}
         eventattrs = dict(**payload)
+        eventattrs.pop('bucket_id', None)
         eventattrs.setdefault('%s_id' % resource_name, obj_id)
         eventattrs['uri'] = uri
         attrs = dict(date=datetime.now().isoformat(),

{"golden_diff": "diff --git a/kinto/plugins/history/listener.py b/kinto/plugins/history/listener.py\n--- a/kinto/plugins/history/listener.py\n+++ b/kinto/plugins/history/listener.py\n@@ -1,5 +1,4 @@\n from kinto.core.utils import instance_uri\n-import copy\n from datetime import datetime\n \n \n@@ -9,30 +8,38 @@\n     ``history`` resource. The entries are served as read-only in the\n     :mod:`kinto.plugins.history.views` module.\n     \"\"\"\n-    payload = copy.deepcopy(event.payload)\n+    payload = event.payload\n     resource_name = payload['resource_name']\n     event_uri = payload['uri']\n \n-    bucket_id = payload.pop('bucket_id')\n-    bucket_uri = instance_uri(event.request, 'bucket', id=bucket_id)\n-    collection_id = None\n+    bucket_id = None\n+    bucket_uri = None\n     collection_uri = None\n-    if 'collection_id' in payload:\n-        collection_id = payload['collection_id']\n-        collection_uri = instance_uri(event.request,\n-                                      'collection',\n-                                      bucket_id=bucket_id,\n-                                      id=collection_id)\n \n     storage = event.request.registry.storage\n     permission = event.request.registry.permission\n \n     targets = []\n     for impacted in event.impacted_records:\n-        # On POST .../records, the URI does not contain the newly created\n-        # record id.\n         target = impacted['new']\n         obj_id = target['id']\n+\n+        try:\n+            bucket_id = payload['bucket_id']\n+        except KeyError:\n+            # e.g. DELETE /buckets\n+            bucket_id = obj_id\n+        bucket_uri = instance_uri(event.request, 'bucket', id=bucket_id)\n+\n+        if 'collection_id' in payload:\n+            collection_id = payload['collection_id']\n+            collection_uri = instance_uri(event.request,\n+                                          'collection',\n+                                          bucket_id=bucket_id,\n+                                          id=collection_id)\n+\n+        # On POST .../records, the URI does not contain the newly created\n+        # record id.\n         parts = event_uri.split('/')\n         if resource_name in parts[-1]:\n             parts.append(obj_id)\n@@ -70,6 +77,7 @@\n         # Prepare the history entry attributes.\n         perms = {k: list(v) for k, v in perms_by_object_id[uri].items()}\n         eventattrs = dict(**payload)\n+        eventattrs.pop('bucket_id', None)\n         eventattrs.setdefault('%s_id' % resource_name, obj_id)\n         eventattrs['uri'] = uri\n         attrs = dict(date=datetime.now().isoformat(),\n", "issue": "Crash in history with DELETE /buckets\n```\nERROR  \"DELETE /v1/buckets\" ? (? ms) 'bucket_id' agent=node-fetch/1.0 (+https://github.com/bitinn/node-fetch) authn_type=basicauth collection_id=bucket collection_timestamp=1471536086687 errno=None exception=Traceback (most recent call last):\n  File \"/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/pyramid/tweens.py\", line 22, in excview_tween\n    response = handler(request)\n  File \"/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/pyramid_tm/__init__.py\", line 101, in tm_tween\n    reraise(*exc_info)\n  File \"/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/pyramid_tm/__init__.py\", line 90, in tm_tween\n    manager.commit()\n  File \"/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/transaction/_manager.py\", line 111, in commit\n    return self.get().commit()\n  File \"/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/transaction/_transaction.py\", line 265, in commit\n    self._callBeforeCommitHooks()\n  File \"/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/transaction/_transaction.py\", line 337, in _callBeforeCommitHooks\n    hook(*args, **kws)\n  File \"/home/mathieu/Code/Mozilla/kinto/kinto/core/events.py\", line 76, in _notify_resource_events_before\n    request.registry.notify(event)\n  File \"/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/pyramid/registry.py\", line 91, in notify\n    [ _ for _ in self.subscribers(events, None) ]\n  File \"/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/zope/interface/registry.py\", line 328, in subscribers\n    return self.adapters.subscribers(objects, provided)\n  File \"/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/zope/interface/adapter.py\", line 596, in subscribers\n    subscription(*objects)\n  File \"/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/pyramid/config/adapters.py\", line 130, in subscriber_wrapper\n    return derived_subscriber(*arg)\n  File \"/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/pyramid/config/adapters.py\", line 103, in derived_subscriber\n    return subscriber(arg[0])\n  File \"/home/mathieu/Code/Mozilla/kinto/kinto/plugins/history/listener.py\", line 16, in on_resource_changed\n    bucket_id = payload.pop('bucket_id')\nKeyError: 'bucket_id' lang=None uid=a9d81e1b56f2b77103e87601621a99b3d82a15abf7c72ce57a032d0964317c7a\n```\n\nCrash in history with DELETE /buckets\n```\nERROR  \"DELETE /v1/buckets\" ? (? ms) 'bucket_id' agent=node-fetch/1.0 (+https://github.com/bitinn/node-fetch) authn_type=basicauth collection_id=bucket collection_timestamp=1471536086687 errno=None exception=Traceback (most recent call last):\n  File \"/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/pyramid/tweens.py\", line 22, in excview_tween\n    response = handler(request)\n  File \"/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/pyramid_tm/__init__.py\", line 101, in tm_tween\n    reraise(*exc_info)\n  File \"/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/pyramid_tm/__init__.py\", line 90, in tm_tween\n    manager.commit()\n  File \"/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/transaction/_manager.py\", line 111, in commit\n    return self.get().commit()\n  File \"/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/transaction/_transaction.py\", line 265, in commit\n    self._callBeforeCommitHooks()\n  File \"/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/transaction/_transaction.py\", line 337, in _callBeforeCommitHooks\n    hook(*args, **kws)\n  File \"/home/mathieu/Code/Mozilla/kinto/kinto/core/events.py\", line 76, in _notify_resource_events_before\n    request.registry.notify(event)\n  File \"/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/pyramid/registry.py\", line 91, in notify\n    [ _ for _ in self.subscribers(events, None) ]\n  File \"/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/zope/interface/registry.py\", line 328, in subscribers\n    return self.adapters.subscribers(objects, provided)\n  File \"/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/zope/interface/adapter.py\", line 596, in subscribers\n    subscription(*objects)\n  File \"/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/pyramid/config/adapters.py\", line 130, in subscriber_wrapper\n    return derived_subscriber(*arg)\n  File \"/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/pyramid/config/adapters.py\", line 103, in derived_subscriber\n    return subscriber(arg[0])\n  File \"/home/mathieu/Code/Mozilla/kinto/kinto/plugins/history/listener.py\", line 16, in on_resource_changed\n    bucket_id = payload.pop('bucket_id')\nKeyError: 'bucket_id' lang=None uid=a9d81e1b56f2b77103e87601621a99b3d82a15abf7c72ce57a032d0964317c7a\n```\n\n", "before_files": [{"content": "from kinto.core.utils import instance_uri\nimport copy\nfrom datetime import datetime\n\n\ndef on_resource_changed(event):\n    \"\"\"\n    Everytime an object is created/changed/deleted, we create an entry in the\n    ``history`` resource. The entries are served as read-only in the\n    :mod:`kinto.plugins.history.views` module.\n    \"\"\"\n    payload = copy.deepcopy(event.payload)\n    resource_name = payload['resource_name']\n    event_uri = payload['uri']\n\n    bucket_id = payload.pop('bucket_id')\n    bucket_uri = instance_uri(event.request, 'bucket', id=bucket_id)\n    collection_id = None\n    collection_uri = None\n    if 'collection_id' in payload:\n        collection_id = payload['collection_id']\n        collection_uri = instance_uri(event.request,\n                                      'collection',\n                                      bucket_id=bucket_id,\n                                      id=collection_id)\n\n    storage = event.request.registry.storage\n    permission = event.request.registry.permission\n\n    targets = []\n    for impacted in event.impacted_records:\n        # On POST .../records, the URI does not contain the newly created\n        # record id.\n        target = impacted['new']\n        obj_id = target['id']\n        parts = event_uri.split('/')\n        if resource_name in parts[-1]:\n            parts.append(obj_id)\n        else:\n            # Make sure the id is correct on grouped events.\n            parts[-1] = obj_id\n        uri = '/'.join(parts)\n        targets.append((uri, target))\n\n    # Prepare a list of object ids to be fetched from permission backend,\n    # and fetch them all at once. Use a mapping for later convenience.\n    all_perms_objects_ids = [oid for (oid, _) in targets]\n    all_perms_objects_ids.append(bucket_uri)\n    if collection_uri is not None:\n        all_perms_objects_ids.append(collection_uri)\n    all_perms_objects_ids = list(set(all_perms_objects_ids))\n    all_permissions = permission.get_objects_permissions(all_perms_objects_ids)\n    perms_by_object_id = dict(zip(all_perms_objects_ids, all_permissions))\n\n    bucket_perms = perms_by_object_id[bucket_uri]\n    collection_perms = {}\n    if collection_uri is not None:\n        collection_perms = perms_by_object_id[collection_uri]\n\n    # The principals allowed to read the bucket and collection.\n    # (Note: ``write`` means ``read``)\n    read_principals = set(bucket_perms.get('read', []))\n    read_principals.update(bucket_perms.get('write', []))\n    read_principals.update(collection_perms.get('read', []))\n    read_principals.update(collection_perms.get('write', []))\n\n    # Create a history entry for each impacted record.\n    for (uri, target) in targets:\n        obj_id = target['id']\n        # Prepare the history entry attributes.\n        perms = {k: list(v) for k, v in perms_by_object_id[uri].items()}\n        eventattrs = dict(**payload)\n        eventattrs.setdefault('%s_id' % resource_name, obj_id)\n        eventattrs['uri'] = uri\n        attrs = dict(date=datetime.now().isoformat(),\n                     target={'data': target, 'permissions': perms},\n                     **eventattrs)\n\n        # Create a record for the 'history' resource, whose parent_id is\n        # the bucket URI (c.f. views.py).\n        # Note: this will be rolledback if the transaction is rolledback.\n        entry = storage.create(parent_id=bucket_uri,\n                               collection_id='history',\n                               record=attrs)\n\n        # The read permission on the newly created history entry is the union\n        # of the record permissions with the one from bucket and collection.\n        entry_principals = set(read_principals)\n        entry_principals.update(perms.get('read', []))\n        entry_principals.update(perms.get('write', []))\n        entry_perms = {'read': list(entry_principals)}\n        # /buckets/{id}/history is the URI for the list of history entries.\n        entry_perm_id = '/buckets/%s/history/%s' % (bucket_id, entry['id'])\n        permission.replace_object_permissions(entry_perm_id, entry_perms)\n", "path": "kinto/plugins/history/listener.py"}], "after_files": [{"content": "from kinto.core.utils import instance_uri\nfrom datetime import datetime\n\n\ndef on_resource_changed(event):\n    \"\"\"\n    Everytime an object is created/changed/deleted, we create an entry in the\n    ``history`` resource. The entries are served as read-only in the\n    :mod:`kinto.plugins.history.views` module.\n    \"\"\"\n    payload = event.payload\n    resource_name = payload['resource_name']\n    event_uri = payload['uri']\n\n    bucket_id = None\n    bucket_uri = None\n    collection_uri = None\n\n    storage = event.request.registry.storage\n    permission = event.request.registry.permission\n\n    targets = []\n    for impacted in event.impacted_records:\n        target = impacted['new']\n        obj_id = target['id']\n\n        try:\n            bucket_id = payload['bucket_id']\n        except KeyError:\n            # e.g. DELETE /buckets\n            bucket_id = obj_id\n        bucket_uri = instance_uri(event.request, 'bucket', id=bucket_id)\n\n        if 'collection_id' in payload:\n            collection_id = payload['collection_id']\n            collection_uri = instance_uri(event.request,\n                                          'collection',\n                                          bucket_id=bucket_id,\n                                          id=collection_id)\n\n        # On POST .../records, the URI does not contain the newly created\n        # record id.\n        parts = event_uri.split('/')\n        if resource_name in parts[-1]:\n            parts.append(obj_id)\n        else:\n            # Make sure the id is correct on grouped events.\n            parts[-1] = obj_id\n        uri = '/'.join(parts)\n        targets.append((uri, target))\n\n    # Prepare a list of object ids to be fetched from permission backend,\n    # and fetch them all at once. Use a mapping for later convenience.\n    all_perms_objects_ids = [oid for (oid, _) in targets]\n    all_perms_objects_ids.append(bucket_uri)\n    if collection_uri is not None:\n        all_perms_objects_ids.append(collection_uri)\n    all_perms_objects_ids = list(set(all_perms_objects_ids))\n    all_permissions = permission.get_objects_permissions(all_perms_objects_ids)\n    perms_by_object_id = dict(zip(all_perms_objects_ids, all_permissions))\n\n    bucket_perms = perms_by_object_id[bucket_uri]\n    collection_perms = {}\n    if collection_uri is not None:\n        collection_perms = perms_by_object_id[collection_uri]\n\n    # The principals allowed to read the bucket and collection.\n    # (Note: ``write`` means ``read``)\n    read_principals = set(bucket_perms.get('read', []))\n    read_principals.update(bucket_perms.get('write', []))\n    read_principals.update(collection_perms.get('read', []))\n    read_principals.update(collection_perms.get('write', []))\n\n    # Create a history entry for each impacted record.\n    for (uri, target) in targets:\n        obj_id = target['id']\n        # Prepare the history entry attributes.\n        perms = {k: list(v) for k, v in perms_by_object_id[uri].items()}\n        eventattrs = dict(**payload)\n        eventattrs.pop('bucket_id', None)\n        eventattrs.setdefault('%s_id' % resource_name, obj_id)\n        eventattrs['uri'] = uri\n        attrs = dict(date=datetime.now().isoformat(),\n                     target={'data': target, 'permissions': perms},\n                     **eventattrs)\n\n        # Create a record for the 'history' resource, whose parent_id is\n        # the bucket URI (c.f. views.py).\n        # Note: this will be rolledback if the transaction is rolledback.\n        entry = storage.create(parent_id=bucket_uri,\n                               collection_id='history',\n                               record=attrs)\n\n        # The read permission on the newly created history entry is the union\n        # of the record permissions with the one from bucket and collection.\n        entry_principals = set(read_principals)\n        entry_principals.update(perms.get('read', []))\n        entry_principals.update(perms.get('write', []))\n        entry_perms = {'read': list(entry_principals)}\n        # /buckets/{id}/history is the URI for the list of history entries.\n        entry_perm_id = '/buckets/%s/history/%s' % (bucket_id, entry['id'])\n        permission.replace_object_permissions(entry_perm_id, entry_perms)\n", "path": "kinto/plugins/history/listener.py"}]}