prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  DELETE /v1/accounts raises a 500
  ```
  $ http DELETE https://natim.alwaysdata.net/v1/accounts --auth admin:admin
  HTTP/1.1 500 Internal Server Error
  Access-Control-Expose-Headers: Retry-After, Alert, Backoff, Content-Length
  Content-Length: 177
  Content-Type: application/json
  Date: Mon, 28 Jan 2019 20:45:56 GMT
  Via: 1.1 alproxy
  X-Content-Type-Options: nosniff
  ```
  
  ```
    File "/home/natim/kinto/kinto/kinto/plugins/accounts/views.py", line 221, in on_account_changed
      username = request.matchdict["id"]
  KeyError: 'id'
  ```
  DELETE /v1/accounts raises a 500
  ```
  $ http DELETE https://natim.alwaysdata.net/v1/accounts --auth admin:admin
  HTTP/1.1 500 Internal Server Error
  Access-Control-Expose-Headers: Retry-After, Alert, Backoff, Content-Length
  Content-Length: 177
  Content-Type: application/json
  Date: Mon, 28 Jan 2019 20:45:56 GMT
  Via: 1.1 alproxy
  X-Content-Type-Options: nosniff
  ```
  
  ```
    File "/home/natim/kinto/kinto/kinto/plugins/accounts/views.py", line 221, in on_account_changed
      username = request.matchdict["id"]
  KeyError: 'id'
  ```
  
  --- 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/accounts/views.py`
  Content:
  ```
  1 import colander
  2 from pyramid import httpexceptions
  3 from pyramid.decorator import reify
  4 from pyramid.security import Authenticated, Everyone
  5 from pyramid.settings import aslist
  6 from pyramid.events import subscriber
  7 
  8 from kinto.views import NameGenerator
  9 from kinto.core import resource, utils
  10 from kinto.core.errors import raise_invalid, http_error
  11 from kinto.core.events import ResourceChanged, ACTIONS
  12 
  13 from .utils import hash_password, ACCOUNT_CACHE_KEY, ACCOUNT_POLICY_NAME
  14 
  15 
  16 def _extract_posted_body_id(request):
  17     try:
  18         # Anonymous creation with POST.
  19         return request.json["data"]["id"]
  20     except (ValueError, KeyError):
  21         # Bad POST data.
  22         if request.method.lower() == "post":
  23             error_details = {"name": "data.id", "description": "data.id in body: Required"}
  24             raise_invalid(request, **error_details)
  25         # Anonymous GET
  26         error_msg = "Cannot read accounts."
  27         raise http_error(httpexceptions.HTTPUnauthorized(), error=error_msg)
  28 
  29 
  30 class AccountIdGenerator(NameGenerator):
  31     """Allow @ signs in account IDs."""
  32 
  33     regexp = r"^[a-zA-Z0-9][.@a-zA-Z0-9_-]*$"
  34 
  35 
  36 class AccountSchema(resource.ResourceSchema):
  37     password = colander.SchemaNode(colander.String())
  38 
  39 
  40 @resource.register()
  41 class Account(resource.Resource):
  42 
  43     schema = AccountSchema
  44 
  45     def __init__(self, request, context):
  46         # Store if current user is administrator (before accessing get_parent_id())
  47         allowed_from_settings = request.registry.settings.get("account_write_principals", [])
  48         context.is_administrator = (
  49             len(set(aslist(allowed_from_settings)) & set(request.prefixed_principals)) > 0
  50         )
  51         # Shortcut to check if current is anonymous (before get_parent_id()).
  52         context.is_anonymous = Authenticated not in request.effective_principals
  53 
  54         super().__init__(request, context)
  55 
  56         # Overwrite the current principal set by Resource.
  57         if self.model.current_principal == Everyone or context.is_administrator:
  58             # Creation is anonymous, but author with write perm is this:
  59             self.model.current_principal = f"{ACCOUNT_POLICY_NAME}:{self.model.parent_id}"
  60 
  61     @reify
  62     def id_generator(self):
  63         # This generator is used for ID validation.
  64         return AccountIdGenerator()
  65 
  66     def get_parent_id(self, request):
  67         # The whole challenge here is that we want to isolate what
  68         # authenticated users can list, but give access to everything to
  69         # administrators.
  70         # Plus when anonymous create accounts, we have to set their parent id
  71         # to the same value they would obtain when authenticated.
  72         if self.context.is_administrator:
  73             if self.context.on_plural_endpoint:
  74                 # Accounts created by admin should have userid as parent.
  75                 if request.method.lower() == "post":
  76                     return _extract_posted_body_id(request)
  77                 else:
  78                     # Admin see all accounts.
  79                     return "*"
  80             else:
  81                 # No pattern matching for admin on single record.
  82                 return request.matchdict["id"]
  83 
  84         if not self.context.is_anonymous:
  85             # Authenticated users see their own account only.
  86             return request.selected_userid
  87 
  88         # Anonymous creation with PUT.
  89         if "id" in request.matchdict:
  90             return request.matchdict["id"]
  91 
  92         return _extract_posted_body_id(request)
  93 
  94     def plural_post(self):
  95         result = super(Account, self).plural_post()
  96         if self.context.is_anonymous and self.request.response.status_code == 200:
  97             error_details = {"message": "Account ID %r already exists" % result["data"]["id"]}
  98             raise http_error(httpexceptions.HTTPForbidden(), **error_details)
  99         return result
  100 
  101     def process_object(self, new, old=None):
  102         new = super(Account, self).process_object(new, old)
  103 
  104         new["password"] = hash_password(new["password"])
  105 
  106         # Administrators can reach other accounts and anonymous have no
  107         # selected_userid. So do not try to enforce.
  108         if self.context.is_administrator or self.context.is_anonymous:
  109             return new
  110 
  111         # Do not let accounts be created without usernames.
  112         if self.model.id_field not in new:
  113             error_details = {"name": "data.id", "description": "Accounts must have an ID."}
  114             raise_invalid(self.request, **error_details)
  115 
  116         # Otherwise, we force the id to match the authenticated username.
  117         if new[self.model.id_field] != self.request.selected_userid:
  118             error_details = {
  119                 "name": "data.id",
  120                 "description": "Username and account ID do not match.",
  121             }
  122             raise_invalid(self.request, **error_details)
  123 
  124         return new
  125 
  126 
  127 # Clear cache on account change
  128 @subscriber(
  129     ResourceChanged, for_resources=("account",), for_actions=(ACTIONS.UPDATE, ACTIONS.DELETE)
  130 )
  131 def on_account_changed(event):
  132     request = event.request
  133     cache = request.registry.cache
  134     settings = request.registry.settings
  135     # Extract username and password from current user
  136     username = request.matchdict["id"]
  137     hmac_secret = settings["userid_hmac_secret"]
  138     cache_key = utils.hmac_digest(hmac_secret, ACCOUNT_CACHE_KEY.format(username))
  139     # Delete cache
  140     cache.delete(cache_key)
  141
  ```
  --- 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()
  
  ```
  
golden_diff: diff --git a/kinto/plugins/accounts/views.py b/kinto/plugins/accounts/views.py
  --- a/kinto/plugins/accounts/views.py
  +++ b/kinto/plugins/accounts/views.py
  @@ -132,9 +132,11 @@
       request = event.request
       cache = request.registry.cache
       settings = request.registry.settings
  -    # Extract username and password from current user
  -    username = request.matchdict["id"]
       hmac_secret = settings["userid_hmac_secret"]
  -    cache_key = utils.hmac_digest(hmac_secret, ACCOUNT_CACHE_KEY.format(username))
  -    # Delete cache
  -    cache.delete(cache_key)
  +
  +    for obj in event.impacted_objects:
  +        # Extract username and password from current user
  +        username = obj["old"]["id"]
  +        cache_key = utils.hmac_digest(hmac_secret, ACCOUNT_CACHE_KEY.format(username))
  +        # Delete cache
  +        cache.delete(cache_key)
  
verification_info: {"golden_diff": "diff --git a/kinto/plugins/accounts/views.py b/kinto/plugins/accounts/views.py\n--- a/kinto/plugins/accounts/views.py\n+++ b/kinto/plugins/accounts/views.py\n@@ -132,9 +132,11 @@\n     request = event.request\n     cache = request.registry.cache\n     settings = request.registry.settings\n-    # Extract username and password from current user\n-    username = request.matchdict[\"id\"]\n     hmac_secret = settings[\"userid_hmac_secret\"]\n-    cache_key = utils.hmac_digest(hmac_secret, ACCOUNT_CACHE_KEY.format(username))\n-    # Delete cache\n-    cache.delete(cache_key)\n+\n+    for obj in event.impacted_objects:\n+        # Extract username and password from current user\n+        username = obj[\"old\"][\"id\"]\n+        cache_key = utils.hmac_digest(hmac_secret, ACCOUNT_CACHE_KEY.format(username))\n+        # Delete cache\n+        cache.delete(cache_key)\n", "issue": "DELETE /v1/accounts raises a 500\n```\r\n$ http DELETE https://natim.alwaysdata.net/v1/accounts --auth admin:admin\r\nHTTP/1.1 500 Internal Server Error\r\nAccess-Control-Expose-Headers: Retry-After, Alert, Backoff, Content-Length\r\nContent-Length: 177\r\nContent-Type: application/json\r\nDate: Mon, 28 Jan 2019 20:45:56 GMT\r\nVia: 1.1 alproxy\r\nX-Content-Type-Options: nosniff\r\n```\r\n\r\n```\r\n  File \"/home/natim/kinto/kinto/kinto/plugins/accounts/views.py\", line 221, in on_account_changed\r\n    username = request.matchdict[\"id\"]\r\nKeyError: 'id'\r\n```\nDELETE /v1/accounts raises a 500\n```\r\n$ http DELETE https://natim.alwaysdata.net/v1/accounts --auth admin:admin\r\nHTTP/1.1 500 Internal Server Error\r\nAccess-Control-Expose-Headers: Retry-After, Alert, Backoff, Content-Length\r\nContent-Length: 177\r\nContent-Type: application/json\r\nDate: Mon, 28 Jan 2019 20:45:56 GMT\r\nVia: 1.1 alproxy\r\nX-Content-Type-Options: nosniff\r\n```\r\n\r\n```\r\n  File \"/home/natim/kinto/kinto/kinto/plugins/accounts/views.py\", line 221, in on_account_changed\r\n    username = request.matchdict[\"id\"]\r\nKeyError: 'id'\r\n```\n", "before_files": [{"content": "import colander\nfrom pyramid import httpexceptions\nfrom pyramid.decorator import reify\nfrom pyramid.security import Authenticated, Everyone\nfrom pyramid.settings import aslist\nfrom pyramid.events import subscriber\n\nfrom kinto.views import NameGenerator\nfrom kinto.core import resource, utils\nfrom kinto.core.errors import raise_invalid, http_error\nfrom kinto.core.events import ResourceChanged, ACTIONS\n\nfrom .utils import hash_password, ACCOUNT_CACHE_KEY, ACCOUNT_POLICY_NAME\n\n\ndef _extract_posted_body_id(request):\n    try:\n        # Anonymous creation with POST.\n        return request.json[\"data\"][\"id\"]\n    except (ValueError, KeyError):\n        # Bad POST data.\n        if request.method.lower() == \"post\":\n            error_details = {\"name\": \"data.id\", \"description\": \"data.id in body: Required\"}\n            raise_invalid(request, **error_details)\n        # Anonymous GET\n        error_msg = \"Cannot read accounts.\"\n        raise http_error(httpexceptions.HTTPUnauthorized(), error=error_msg)\n\n\nclass AccountIdGenerator(NameGenerator):\n    \"\"\"Allow @ signs in account IDs.\"\"\"\n\n    regexp = r\"^[a-zA-Z0-9][.@a-zA-Z0-9_-]*$\"\n\n\nclass AccountSchema(resource.ResourceSchema):\n    password = colander.SchemaNode(colander.String())\n\n\n@resource.register()\nclass Account(resource.Resource):\n\n    schema = AccountSchema\n\n    def __init__(self, request, context):\n        # Store if current user is administrator (before accessing get_parent_id())\n        allowed_from_settings = request.registry.settings.get(\"account_write_principals\", [])\n        context.is_administrator = (\n            len(set(aslist(allowed_from_settings)) & set(request.prefixed_principals)) > 0\n        )\n        # Shortcut to check if current is anonymous (before get_parent_id()).\n        context.is_anonymous = Authenticated not in request.effective_principals\n\n        super().__init__(request, context)\n\n        # Overwrite the current principal set by Resource.\n        if self.model.current_principal == Everyone or context.is_administrator:\n            # Creation is anonymous, but author with write perm is this:\n            self.model.current_principal = f\"{ACCOUNT_POLICY_NAME}:{self.model.parent_id}\"\n\n    @reify\n    def id_generator(self):\n        # This generator is used for ID validation.\n        return AccountIdGenerator()\n\n    def get_parent_id(self, request):\n        # The whole challenge here is that we want to isolate what\n        # authenticated users can list, but give access to everything to\n        # administrators.\n        # Plus when anonymous create accounts, we have to set their parent id\n        # to the same value they would obtain when authenticated.\n        if self.context.is_administrator:\n            if self.context.on_plural_endpoint:\n                # Accounts created by admin should have userid as parent.\n                if request.method.lower() == \"post\":\n                    return _extract_posted_body_id(request)\n                else:\n                    # Admin see all accounts.\n                    return \"*\"\n            else:\n                # No pattern matching for admin on single record.\n                return request.matchdict[\"id\"]\n\n        if not self.context.is_anonymous:\n            # Authenticated users see their own account only.\n            return request.selected_userid\n\n        # Anonymous creation with PUT.\n        if \"id\" in request.matchdict:\n            return request.matchdict[\"id\"]\n\n        return _extract_posted_body_id(request)\n\n    def plural_post(self):\n        result = super(Account, self).plural_post()\n        if self.context.is_anonymous and self.request.response.status_code == 200:\n            error_details = {\"message\": \"Account ID %r already exists\" % result[\"data\"][\"id\"]}\n            raise http_error(httpexceptions.HTTPForbidden(), **error_details)\n        return result\n\n    def process_object(self, new, old=None):\n        new = super(Account, self).process_object(new, old)\n\n        new[\"password\"] = hash_password(new[\"password\"])\n\n        # Administrators can reach other accounts and anonymous have no\n        # selected_userid. So do not try to enforce.\n        if self.context.is_administrator or self.context.is_anonymous:\n            return new\n\n        # Do not let accounts be created without usernames.\n        if self.model.id_field not in new:\n            error_details = {\"name\": \"data.id\", \"description\": \"Accounts must have an ID.\"}\n            raise_invalid(self.request, **error_details)\n\n        # Otherwise, we force the id to match the authenticated username.\n        if new[self.model.id_field] != self.request.selected_userid:\n            error_details = {\n                \"name\": \"data.id\",\n                \"description\": \"Username and account ID do not match.\",\n            }\n            raise_invalid(self.request, **error_details)\n\n        return new\n\n\n# Clear cache on account change\n@subscriber(\n    ResourceChanged, for_resources=(\"account\",), for_actions=(ACTIONS.UPDATE, ACTIONS.DELETE)\n)\ndef on_account_changed(event):\n    request = event.request\n    cache = request.registry.cache\n    settings = request.registry.settings\n    # Extract username and password from current user\n    username = request.matchdict[\"id\"]\n    hmac_secret = settings[\"userid_hmac_secret\"]\n    cache_key = utils.hmac_digest(hmac_secret, ACCOUNT_CACHE_KEY.format(username))\n    # Delete cache\n    cache.delete(cache_key)\n", "path": "kinto/plugins/accounts/views.py"}], "after_files": [{"content": "import colander\nfrom pyramid import httpexceptions\nfrom pyramid.decorator import reify\nfrom pyramid.security import Authenticated, Everyone\nfrom pyramid.settings import aslist\nfrom pyramid.events import subscriber\n\nfrom kinto.views import NameGenerator\nfrom kinto.core import resource, utils\nfrom kinto.core.errors import raise_invalid, http_error\nfrom kinto.core.events import ResourceChanged, ACTIONS\n\nfrom .utils import hash_password, ACCOUNT_CACHE_KEY, ACCOUNT_POLICY_NAME\n\n\ndef _extract_posted_body_id(request):\n    try:\n        # Anonymous creation with POST.\n        return request.json[\"data\"][\"id\"]\n    except (ValueError, KeyError):\n        # Bad POST data.\n        if request.method.lower() == \"post\":\n            error_details = {\"name\": \"data.id\", \"description\": \"data.id in body: Required\"}\n            raise_invalid(request, **error_details)\n        # Anonymous GET\n        error_msg = \"Cannot read accounts.\"\n        raise http_error(httpexceptions.HTTPUnauthorized(), error=error_msg)\n\n\nclass AccountIdGenerator(NameGenerator):\n    \"\"\"Allow @ signs in account IDs.\"\"\"\n\n    regexp = r\"^[a-zA-Z0-9][.@a-zA-Z0-9_-]*$\"\n\n\nclass AccountSchema(resource.ResourceSchema):\n    password = colander.SchemaNode(colander.String())\n\n\n@resource.register()\nclass Account(resource.Resource):\n\n    schema = AccountSchema\n\n    def __init__(self, request, context):\n        # Store if current user is administrator (before accessing get_parent_id())\n        allowed_from_settings = request.registry.settings.get(\"account_write_principals\", [])\n        context.is_administrator = (\n            len(set(aslist(allowed_from_settings)) & set(request.prefixed_principals)) > 0\n        )\n        # Shortcut to check if current is anonymous (before get_parent_id()).\n        context.is_anonymous = Authenticated not in request.effective_principals\n\n        super().__init__(request, context)\n\n        # Overwrite the current principal set by Resource.\n        if self.model.current_principal == Everyone or context.is_administrator:\n            # Creation is anonymous, but author with write perm is this:\n            self.model.current_principal = f\"{ACCOUNT_POLICY_NAME}:{self.model.parent_id}\"\n\n    @reify\n    def id_generator(self):\n        # This generator is used for ID validation.\n        return AccountIdGenerator()\n\n    def get_parent_id(self, request):\n        # The whole challenge here is that we want to isolate what\n        # authenticated users can list, but give access to everything to\n        # administrators.\n        # Plus when anonymous create accounts, we have to set their parent id\n        # to the same value they would obtain when authenticated.\n        if self.context.is_administrator:\n            if self.context.on_plural_endpoint:\n                # Accounts created by admin should have userid as parent.\n                if request.method.lower() == \"post\":\n                    return _extract_posted_body_id(request)\n                else:\n                    # Admin see all accounts.\n                    return \"*\"\n            else:\n                # No pattern matching for admin on single record.\n                return request.matchdict[\"id\"]\n\n        if not self.context.is_anonymous:\n            # Authenticated users see their own account only.\n            return request.selected_userid\n\n        # Anonymous creation with PUT.\n        if \"id\" in request.matchdict:\n            return request.matchdict[\"id\"]\n\n        return _extract_posted_body_id(request)\n\n    def plural_post(self):\n        result = super(Account, self).plural_post()\n        if self.context.is_anonymous and self.request.response.status_code == 200:\n            error_details = {\"message\": \"Account ID %r already exists\" % result[\"data\"][\"id\"]}\n            raise http_error(httpexceptions.HTTPForbidden(), **error_details)\n        return result\n\n    def process_object(self, new, old=None):\n        new = super(Account, self).process_object(new, old)\n\n        new[\"password\"] = hash_password(new[\"password\"])\n\n        # Administrators can reach other accounts and anonymous have no\n        # selected_userid. So do not try to enforce.\n        if self.context.is_administrator or self.context.is_anonymous:\n            return new\n\n        # Do not let accounts be created without usernames.\n        if self.model.id_field not in new:\n            error_details = {\"name\": \"data.id\", \"description\": \"Accounts must have an ID.\"}\n            raise_invalid(self.request, **error_details)\n\n        # Otherwise, we force the id to match the authenticated username.\n        if new[self.model.id_field] != self.request.selected_userid:\n            error_details = {\n                \"name\": \"data.id\",\n                \"description\": \"Username and account ID do not match.\",\n            }\n            raise_invalid(self.request, **error_details)\n\n        return new\n\n\n# Clear cache on account change\n@subscriber(\n    ResourceChanged, for_resources=(\"account\",), for_actions=(ACTIONS.UPDATE, ACTIONS.DELETE)\n)\ndef on_account_changed(event):\n    request = event.request\n    cache = request.registry.cache\n    settings = request.registry.settings\n    hmac_secret = settings[\"userid_hmac_secret\"]\n\n    for obj in event.impacted_objects:\n        # Extract username and password from current user\n        username = obj[\"old\"][\"id\"]\n        cache_key = utils.hmac_digest(hmac_secret, ACCOUNT_CACHE_KEY.format(username))\n        # Delete cache\n        cache.delete(cache_key)\n", "path": "kinto/plugins/accounts/views.py"}]}