We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Invalid account_create_principals key
The `account:create` check_permission code is looking at `account_account:create_principals` settings key rather than `account_create_principals`

--- 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/core/authorization.py`
Content:
```
1 import functools
2 import logging
3 
4 from pyramid.settings import aslist
5 from pyramid.security import IAuthorizationPolicy, Authenticated
6 from zope.interface import implementer
7 
8 from kinto.core import utils
9 from kinto.core.storage import exceptions as storage_exceptions
10 
11 
12 logger = logging.getLogger(__name__)
13 
14 # A permission is called "dynamic" when it's computed at request time.
15 DYNAMIC = "dynamic"
16 
17 # When permission is set to "private", only the current user is allowed.
18 PRIVATE = "private"
19 
20 
21 def groupfinder(userid, request):
22     """Fetch principals from permission backend for the specified `userid`.
23 
24     This is plugged by default using the ``multiauth.groupfinder`` setting.
25     """
26     backend = getattr(request.registry, "permission", None)
27     # Permission backend not configured. Ignore.
28     if not backend:
29         return []
30 
31     # Safety check when Kinto-Core is used without pyramid_multiauth.
32     if request.prefixed_userid:
33         userid = request.prefixed_userid
34 
35     # Query the permission backend only once per request (e.g. batch).
36     reify_key = userid + "_principals"
37     if reify_key not in request.bound_data:
38         principals = backend.get_user_principals(userid)
39         request.bound_data[reify_key] = principals
40 
41     return request.bound_data[reify_key]
42 
43 
44 @implementer(IAuthorizationPolicy)
45 class AuthorizationPolicy:
46     """Default authorization class, that leverages the permission backend
47     for shareable resources.
48     """
49 
50     get_bound_permissions = None
51     """Callable that takes an object id and a permission and returns
52     a list of tuples (<object id>, <permission>). Useful when objects
53     permission depend on others."""
54 
55     def permits(self, context, principals, permission):
56         if permission == PRIVATE:
57             return Authenticated in principals
58 
59         principals = context.get_prefixed_principals()
60 
61         if permission == DYNAMIC:
62             permission = context.required_permission
63 
64         create_permission = "{}:create".format(context.resource_name)
65         if permission == "create":
66             permission = create_permission
67 
68         object_id = context.permission_object_id
69         bound_perms = self._get_bound_permissions(object_id, permission)
70 
71         allowed = context.check_permission(principals, bound_perms)
72 
73         # Here we consider that parent URI is one path level above.
74         parent_uri = "/".join(object_id.split("/")[:-1]) if object_id else None
75 
76         # If not allowed to delete/patch, and target object is missing, and
77         # allowed to read the parent, then view is permitted (will raise 404
78         # later anyway). See Kinto/kinto#918
79         is_record_unknown = not context.on_collection and context.current_record is None
80         if context.required_permission == "write" and is_record_unknown:
81             bound_perms = self._get_bound_permissions(parent_uri, "read")
82             allowed = context.check_permission(principals, bound_perms)
83 
84         # If not allowed on this collection, but some records are shared with
85         # the current user, then authorize.
86         # The ShareableResource class will take care of the filtering.
87         is_list_operation = context.on_collection and not permission.endswith("create")
88         if not allowed and is_list_operation:
89             allowed = bool(
90                 context.fetch_shared_records(permission, principals, self.get_bound_permissions)
91             )
92             if not allowed:
93                 # If allowed to create this kind of object on parent,
94                 # then allow to obtain the list.
95                 if len(bound_perms) > 0:
96                     bound_perms = [(parent_uri, create_permission)]
97                 else:
98                     bound_perms = [("", "create")]  # Root object.
99                 allowed = context.check_permission(principals, bound_perms)
100 
101         if not allowed:
102             logger.warn(
103                 "Permission %r on %r not granted to %r.",
104                 permission,
105                 object_id,
106                 principals[0],
107                 extra=dict(userid=principals[0], uri=object_id, perm=permission),
108             )
109 
110         return allowed
111 
112     def _get_bound_permissions(self, object_id, permission):
113         if self.get_bound_permissions is None:
114             return [(object_id, permission)]
115         return self.get_bound_permissions(object_id, permission)
116 
117     def principals_allowed_by_permission(self, context, permission):
118         raise NotImplementedError()  # PRAGMA NOCOVER
119 
120 
121 class RouteFactory:
122     resource_name = None
123     on_collection = False
124     required_permission = None
125     permission_object_id = None
126     current_record = None
127     shared_ids = None
128 
129     method_permissions = {
130         "head": "read",
131         "get": "read",
132         "post": "create",
133         "delete": "write",
134         "patch": "write",
135     }
136 
137     def __init__(self, request):
138         # Store some shortcuts.
139         permission = request.registry.permission
140         self._check_permission = permission.check_permission
141         self._get_accessible_objects = permission.get_accessible_objects
142 
143         self.get_prefixed_principals = functools.partial(utils.prefixed_principals, request)
144 
145         # Store current resource and required permission.
146         service = utils.current_service(request)
147         is_on_resource = (
148             service is not None and hasattr(service, "viewset") and hasattr(service, "resource")
149         )
150         if is_on_resource:
151             self.resource_name = request.current_resource_name
152             self.on_collection = getattr(service, "type", None) == "collection"
153 
154             # Try to fetch the target object. Its existence will affect permissions checking.
155             if not self.on_collection and request.method.lower() in ("put", "delete", "patch"):
156                 resource = service.resource(request=request, context=self)
157                 try:
158                     # Save a reference, to avoid refetching from storage in resource.
159                     self.current_record = resource.model.get_record(resource.record_id)
160                 except storage_exceptions.RecordNotFoundError:
161                     pass
162 
163             self.permission_object_id, self.required_permission = self._find_required_permission(
164                 request, service
165             )
166 
167             # To obtain shared records on a collection endpoint, use a match:
168             self._object_id_match = self.get_permission_object_id(request, "*")
169 
170         self._settings = request.registry.settings
171 
172     def check_permission(self, principals, bound_perms):
173         """Read allowed principals from settings, if not any, query the permission
174         backend to check if view is allowed.
175         """
176         if not bound_perms:
177             bound_perms = [(self.resource_name, self.required_permission)]
178         for (_, permission) in bound_perms:
179             setting = "{}_{}_principals".format(self.resource_name, permission)
180             allowed_principals = aslist(self._settings.get(setting, ""))
181             if allowed_principals:
182                 if bool(set(allowed_principals) & set(principals)):
183                     return True
184         return self._check_permission(principals, bound_perms)
185 
186     def fetch_shared_records(self, perm, principals, get_bound_permissions):
187         """Fetch records that are readable or writable for the current
188         principals.
189 
190         See :meth:`kinto.core.authorization.AuthorizationPolicy.permits`
191 
192         If no record is shared, it returns None.
193 
194         .. warning::
195             This sets the ``shared_ids`` attribute to the context with the
196             return value. The attribute is then read by
197             :class:`kinto.core.resource.ShareableResource`
198         """
199         if get_bound_permissions:
200             bound_perms = get_bound_permissions(self._object_id_match, perm)
201         else:
202             bound_perms = [(self._object_id_match, perm)]
203         by_obj_id = self._get_accessible_objects(principals, bound_perms, with_children=False)
204         ids = by_obj_id.keys()
205         # Store for later use in ``ShareableResource``.
206         self.shared_ids = [self._extract_object_id(id_) for id_ in ids]
207         return self.shared_ids
208 
209     def get_permission_object_id(self, request, object_id=None):
210         """Returns the permission object id for the current request.
211         In the nominal case, it is just the current URI without version prefix.
212         For collections, it is the related record URI using the specified
213         `object_id`.
214 
215         See :meth:`kinto.core.resource.model.SharableModel` and
216         :meth:`kinto.core.authorization.RouteFactory.__init__`
217         """
218         object_uri = utils.strip_uri_prefix(request.path)
219 
220         if self.on_collection and object_id is not None:
221             # With the current request on a collection, the record URI must
222             # be found out by inspecting the collection service and its sibling
223             # record service.
224             matchdict = {**request.matchdict, "id": object_id}
225             try:
226                 object_uri = utils.instance_uri(request, self.resource_name, **matchdict)
227                 object_uri = object_uri.replace("%2A", "*")
228             except KeyError:
229                 # Maybe the resource has no single record endpoint.
230                 # We consider that object URIs in permissions backend will
231                 # be stored naively:
232                 object_uri = "{}/{}".format(object_uri, object_id)
233 
234         return object_uri
235 
236     def _extract_object_id(self, object_uri):
237         # XXX: Rewrite using kinto.core.utils.view_lookup() and matchdict['id']
238         return object_uri.split("/")[-1]
239 
240     def _find_required_permission(self, request, service):
241         """Find out what is the permission object id and the required
242         permission.
243 
244         .. note::
245             This method saves an attribute ``self.current_record`` used
246             in :class:`kinto.core.resource.UserResource`.
247         """
248         # By default, it's a URI a and permission associated to the method.
249         permission_object_id = self.get_permission_object_id(request)
250         method = request.method.lower()
251         required_permission = self.method_permissions.get(method)
252 
253         # For create permission, the object id is the plural endpoint.
254         collection_path = str(service.collection_path)
255         collection_path = collection_path.format_map(request.matchdict)
256 
257         # In the case of a "PUT", check if the targetted record already
258         # exists, return "write" if it does, "create" otherwise.
259         if request.method.lower() == "put":
260             if self.current_record is None:
261                 # The record does not exist, the permission to create on
262                 # the related collection is required.
263                 permission_object_id = collection_path
264                 required_permission = "create"
265             else:
266                 # For safe creations, the user needs a create permission.
267                 # See Kinto/kinto#792
268                 if request.headers.get("If-None-Match") == "*":
269                     permission_object_id = collection_path
270                     required_permission = "create"
271                 else:
272                     required_permission = "write"
273 
274         return (permission_object_id, required_permission)
275
```
--- 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/core/authorization.py b/kinto/core/authorization.py
--- a/kinto/core/authorization.py
+++ b/kinto/core/authorization.py
@@ -176,7 +176,11 @@
         if not bound_perms:
             bound_perms = [(self.resource_name, self.required_permission)]
         for (_, permission) in bound_perms:
-            setting = "{}_{}_principals".format(self.resource_name, permission)
+            # With Kinto inheritance tree, we can have: `permission = "record:create"`
+            if self.resource_name and permission.startswith(self.resource_name):
+                setting = "{}_principals".format(permission.replace(":", "_"))
+            else:
+                setting = "{}_{}_principals".format(self.resource_name, permission)
             allowed_principals = aslist(self._settings.get(setting, ""))
             if allowed_principals:
                 if bool(set(allowed_principals) & set(principals)):

{"golden_diff": "diff --git a/kinto/core/authorization.py b/kinto/core/authorization.py\n--- a/kinto/core/authorization.py\n+++ b/kinto/core/authorization.py\n@@ -176,7 +176,11 @@\n         if not bound_perms:\n             bound_perms = [(self.resource_name, self.required_permission)]\n         for (_, permission) in bound_perms:\n-            setting = \"{}_{}_principals\".format(self.resource_name, permission)\n+            # With Kinto inheritance tree, we can have: `permission = \"record:create\"`\n+            if self.resource_name and permission.startswith(self.resource_name):\n+                setting = \"{}_principals\".format(permission.replace(\":\", \"_\"))\n+            else:\n+                setting = \"{}_{}_principals\".format(self.resource_name, permission)\n             allowed_principals = aslist(self._settings.get(setting, \"\"))\n             if allowed_principals:\n                 if bool(set(allowed_principals) & set(principals)):\n", "issue": "Invalid account_create_principals key\nThe `account:create` check_permission code is looking at `account_account:create_principals` settings key rather than `account_create_principals`\n", "before_files": [{"content": "import functools\nimport logging\n\nfrom pyramid.settings import aslist\nfrom pyramid.security import IAuthorizationPolicy, Authenticated\nfrom zope.interface import implementer\n\nfrom kinto.core import utils\nfrom kinto.core.storage import exceptions as storage_exceptions\n\n\nlogger = logging.getLogger(__name__)\n\n# A permission is called \"dynamic\" when it's computed at request time.\nDYNAMIC = \"dynamic\"\n\n# When permission is set to \"private\", only the current user is allowed.\nPRIVATE = \"private\"\n\n\ndef groupfinder(userid, request):\n    \"\"\"Fetch principals from permission backend for the specified `userid`.\n\n    This is plugged by default using the ``multiauth.groupfinder`` setting.\n    \"\"\"\n    backend = getattr(request.registry, \"permission\", None)\n    # Permission backend not configured. Ignore.\n    if not backend:\n        return []\n\n    # Safety check when Kinto-Core is used without pyramid_multiauth.\n    if request.prefixed_userid:\n        userid = request.prefixed_userid\n\n    # Query the permission backend only once per request (e.g. batch).\n    reify_key = userid + \"_principals\"\n    if reify_key not in request.bound_data:\n        principals = backend.get_user_principals(userid)\n        request.bound_data[reify_key] = principals\n\n    return request.bound_data[reify_key]\n\n\n@implementer(IAuthorizationPolicy)\nclass AuthorizationPolicy:\n    \"\"\"Default authorization class, that leverages the permission backend\n    for shareable resources.\n    \"\"\"\n\n    get_bound_permissions = None\n    \"\"\"Callable that takes an object id and a permission and returns\n    a list of tuples (<object id>, <permission>). Useful when objects\n    permission depend on others.\"\"\"\n\n    def permits(self, context, principals, permission):\n        if permission == PRIVATE:\n            return Authenticated in principals\n\n        principals = context.get_prefixed_principals()\n\n        if permission == DYNAMIC:\n            permission = context.required_permission\n\n        create_permission = \"{}:create\".format(context.resource_name)\n        if permission == \"create\":\n            permission = create_permission\n\n        object_id = context.permission_object_id\n        bound_perms = self._get_bound_permissions(object_id, permission)\n\n        allowed = context.check_permission(principals, bound_perms)\n\n        # Here we consider that parent URI is one path level above.\n        parent_uri = \"/\".join(object_id.split(\"/\")[:-1]) if object_id else None\n\n        # If not allowed to delete/patch, and target object is missing, and\n        # allowed to read the parent, then view is permitted (will raise 404\n        # later anyway). See Kinto/kinto#918\n        is_record_unknown = not context.on_collection and context.current_record is None\n        if context.required_permission == \"write\" and is_record_unknown:\n            bound_perms = self._get_bound_permissions(parent_uri, \"read\")\n            allowed = context.check_permission(principals, bound_perms)\n\n        # If not allowed on this collection, but some records are shared with\n        # the current user, then authorize.\n        # The ShareableResource class will take care of the filtering.\n        is_list_operation = context.on_collection and not permission.endswith(\"create\")\n        if not allowed and is_list_operation:\n            allowed = bool(\n                context.fetch_shared_records(permission, principals, self.get_bound_permissions)\n            )\n            if not allowed:\n                # If allowed to create this kind of object on parent,\n                # then allow to obtain the list.\n                if len(bound_perms) > 0:\n                    bound_perms = [(parent_uri, create_permission)]\n                else:\n                    bound_perms = [(\"\", \"create\")]  # Root object.\n                allowed = context.check_permission(principals, bound_perms)\n\n        if not allowed:\n            logger.warn(\n                \"Permission %r on %r not granted to %r.\",\n                permission,\n                object_id,\n                principals[0],\n                extra=dict(userid=principals[0], uri=object_id, perm=permission),\n            )\n\n        return allowed\n\n    def _get_bound_permissions(self, object_id, permission):\n        if self.get_bound_permissions is None:\n            return [(object_id, permission)]\n        return self.get_bound_permissions(object_id, permission)\n\n    def principals_allowed_by_permission(self, context, permission):\n        raise NotImplementedError()  # PRAGMA NOCOVER\n\n\nclass RouteFactory:\n    resource_name = None\n    on_collection = False\n    required_permission = None\n    permission_object_id = None\n    current_record = None\n    shared_ids = None\n\n    method_permissions = {\n        \"head\": \"read\",\n        \"get\": \"read\",\n        \"post\": \"create\",\n        \"delete\": \"write\",\n        \"patch\": \"write\",\n    }\n\n    def __init__(self, request):\n        # Store some shortcuts.\n        permission = request.registry.permission\n        self._check_permission = permission.check_permission\n        self._get_accessible_objects = permission.get_accessible_objects\n\n        self.get_prefixed_principals = functools.partial(utils.prefixed_principals, request)\n\n        # Store current resource and required permission.\n        service = utils.current_service(request)\n        is_on_resource = (\n            service is not None and hasattr(service, \"viewset\") and hasattr(service, \"resource\")\n        )\n        if is_on_resource:\n            self.resource_name = request.current_resource_name\n            self.on_collection = getattr(service, \"type\", None) == \"collection\"\n\n            # Try to fetch the target object. Its existence will affect permissions checking.\n            if not self.on_collection and request.method.lower() in (\"put\", \"delete\", \"patch\"):\n                resource = service.resource(request=request, context=self)\n                try:\n                    # Save a reference, to avoid refetching from storage in resource.\n                    self.current_record = resource.model.get_record(resource.record_id)\n                except storage_exceptions.RecordNotFoundError:\n                    pass\n\n            self.permission_object_id, self.required_permission = self._find_required_permission(\n                request, service\n            )\n\n            # To obtain shared records on a collection endpoint, use a match:\n            self._object_id_match = self.get_permission_object_id(request, \"*\")\n\n        self._settings = request.registry.settings\n\n    def check_permission(self, principals, bound_perms):\n        \"\"\"Read allowed principals from settings, if not any, query the permission\n        backend to check if view is allowed.\n        \"\"\"\n        if not bound_perms:\n            bound_perms = [(self.resource_name, self.required_permission)]\n        for (_, permission) in bound_perms:\n            setting = \"{}_{}_principals\".format(self.resource_name, permission)\n            allowed_principals = aslist(self._settings.get(setting, \"\"))\n            if allowed_principals:\n                if bool(set(allowed_principals) & set(principals)):\n                    return True\n        return self._check_permission(principals, bound_perms)\n\n    def fetch_shared_records(self, perm, principals, get_bound_permissions):\n        \"\"\"Fetch records that are readable or writable for the current\n        principals.\n\n        See :meth:`kinto.core.authorization.AuthorizationPolicy.permits`\n\n        If no record is shared, it returns None.\n\n        .. warning::\n            This sets the ``shared_ids`` attribute to the context with the\n            return value. The attribute is then read by\n            :class:`kinto.core.resource.ShareableResource`\n        \"\"\"\n        if get_bound_permissions:\n            bound_perms = get_bound_permissions(self._object_id_match, perm)\n        else:\n            bound_perms = [(self._object_id_match, perm)]\n        by_obj_id = self._get_accessible_objects(principals, bound_perms, with_children=False)\n        ids = by_obj_id.keys()\n        # Store for later use in ``ShareableResource``.\n        self.shared_ids = [self._extract_object_id(id_) for id_ in ids]\n        return self.shared_ids\n\n    def get_permission_object_id(self, request, object_id=None):\n        \"\"\"Returns the permission object id for the current request.\n        In the nominal case, it is just the current URI without version prefix.\n        For collections, it is the related record URI using the specified\n        `object_id`.\n\n        See :meth:`kinto.core.resource.model.SharableModel` and\n        :meth:`kinto.core.authorization.RouteFactory.__init__`\n        \"\"\"\n        object_uri = utils.strip_uri_prefix(request.path)\n\n        if self.on_collection and object_id is not None:\n            # With the current request on a collection, the record URI must\n            # be found out by inspecting the collection service and its sibling\n            # record service.\n            matchdict = {**request.matchdict, \"id\": object_id}\n            try:\n                object_uri = utils.instance_uri(request, self.resource_name, **matchdict)\n                object_uri = object_uri.replace(\"%2A\", \"*\")\n            except KeyError:\n                # Maybe the resource has no single record endpoint.\n                # We consider that object URIs in permissions backend will\n                # be stored naively:\n                object_uri = \"{}/{}\".format(object_uri, object_id)\n\n        return object_uri\n\n    def _extract_object_id(self, object_uri):\n        # XXX: Rewrite using kinto.core.utils.view_lookup() and matchdict['id']\n        return object_uri.split(\"/\")[-1]\n\n    def _find_required_permission(self, request, service):\n        \"\"\"Find out what is the permission object id and the required\n        permission.\n\n        .. note::\n            This method saves an attribute ``self.current_record`` used\n            in :class:`kinto.core.resource.UserResource`.\n        \"\"\"\n        # By default, it's a URI a and permission associated to the method.\n        permission_object_id = self.get_permission_object_id(request)\n        method = request.method.lower()\n        required_permission = self.method_permissions.get(method)\n\n        # For create permission, the object id is the plural endpoint.\n        collection_path = str(service.collection_path)\n        collection_path = collection_path.format_map(request.matchdict)\n\n        # In the case of a \"PUT\", check if the targetted record already\n        # exists, return \"write\" if it does, \"create\" otherwise.\n        if request.method.lower() == \"put\":\n            if self.current_record is None:\n                # The record does not exist, the permission to create on\n                # the related collection is required.\n                permission_object_id = collection_path\n                required_permission = \"create\"\n            else:\n                # For safe creations, the user needs a create permission.\n                # See Kinto/kinto#792\n                if request.headers.get(\"If-None-Match\") == \"*\":\n                    permission_object_id = collection_path\n                    required_permission = \"create\"\n                else:\n                    required_permission = \"write\"\n\n        return (permission_object_id, required_permission)\n", "path": "kinto/core/authorization.py"}], "after_files": [{"content": "import functools\nimport logging\n\nfrom pyramid.settings import aslist\nfrom pyramid.security import IAuthorizationPolicy, Authenticated\nfrom zope.interface import implementer\n\nfrom kinto.core import utils\nfrom kinto.core.storage import exceptions as storage_exceptions\n\n\nlogger = logging.getLogger(__name__)\n\n# A permission is called \"dynamic\" when it's computed at request time.\nDYNAMIC = \"dynamic\"\n\n# When permission is set to \"private\", only the current user is allowed.\nPRIVATE = \"private\"\n\n\ndef groupfinder(userid, request):\n    \"\"\"Fetch principals from permission backend for the specified `userid`.\n\n    This is plugged by default using the ``multiauth.groupfinder`` setting.\n    \"\"\"\n    backend = getattr(request.registry, \"permission\", None)\n    # Permission backend not configured. Ignore.\n    if not backend:\n        return []\n\n    # Safety check when Kinto-Core is used without pyramid_multiauth.\n    if request.prefixed_userid:\n        userid = request.prefixed_userid\n\n    # Query the permission backend only once per request (e.g. batch).\n    reify_key = userid + \"_principals\"\n    if reify_key not in request.bound_data:\n        principals = backend.get_user_principals(userid)\n        request.bound_data[reify_key] = principals\n\n    return request.bound_data[reify_key]\n\n\n@implementer(IAuthorizationPolicy)\nclass AuthorizationPolicy:\n    \"\"\"Default authorization class, that leverages the permission backend\n    for shareable resources.\n    \"\"\"\n\n    get_bound_permissions = None\n    \"\"\"Callable that takes an object id and a permission and returns\n    a list of tuples (<object id>, <permission>). Useful when objects\n    permission depend on others.\"\"\"\n\n    def permits(self, context, principals, permission):\n        if permission == PRIVATE:\n            return Authenticated in principals\n\n        principals = context.get_prefixed_principals()\n\n        if permission == DYNAMIC:\n            permission = context.required_permission\n\n        create_permission = \"{}:create\".format(context.resource_name)\n        if permission == \"create\":\n            permission = create_permission\n\n        object_id = context.permission_object_id\n        bound_perms = self._get_bound_permissions(object_id, permission)\n\n        allowed = context.check_permission(principals, bound_perms)\n\n        # Here we consider that parent URI is one path level above.\n        parent_uri = \"/\".join(object_id.split(\"/\")[:-1]) if object_id else None\n\n        # If not allowed to delete/patch, and target object is missing, and\n        # allowed to read the parent, then view is permitted (will raise 404\n        # later anyway). See Kinto/kinto#918\n        is_record_unknown = not context.on_collection and context.current_record is None\n        if context.required_permission == \"write\" and is_record_unknown:\n            bound_perms = self._get_bound_permissions(parent_uri, \"read\")\n            allowed = context.check_permission(principals, bound_perms)\n\n        # If not allowed on this collection, but some records are shared with\n        # the current user, then authorize.\n        # The ShareableResource class will take care of the filtering.\n        is_list_operation = context.on_collection and not permission.endswith(\"create\")\n        if not allowed and is_list_operation:\n            allowed = bool(\n                context.fetch_shared_records(permission, principals, self.get_bound_permissions)\n            )\n            if not allowed:\n                # If allowed to create this kind of object on parent,\n                # then allow to obtain the list.\n                if len(bound_perms) > 0:\n                    bound_perms = [(parent_uri, create_permission)]\n                else:\n                    bound_perms = [(\"\", \"create\")]  # Root object.\n                allowed = context.check_permission(principals, bound_perms)\n\n        if not allowed:\n            logger.warn(\n                \"Permission %r on %r not granted to %r.\",\n                permission,\n                object_id,\n                principals[0],\n                extra=dict(userid=principals[0], uri=object_id, perm=permission),\n            )\n\n        return allowed\n\n    def _get_bound_permissions(self, object_id, permission):\n        if self.get_bound_permissions is None:\n            return [(object_id, permission)]\n        return self.get_bound_permissions(object_id, permission)\n\n    def principals_allowed_by_permission(self, context, permission):\n        raise NotImplementedError()  # PRAGMA NOCOVER\n\n\nclass RouteFactory:\n    resource_name = None\n    on_collection = False\n    required_permission = None\n    permission_object_id = None\n    current_record = None\n    shared_ids = None\n\n    method_permissions = {\n        \"head\": \"read\",\n        \"get\": \"read\",\n        \"post\": \"create\",\n        \"delete\": \"write\",\n        \"patch\": \"write\",\n    }\n\n    def __init__(self, request):\n        # Store some shortcuts.\n        permission = request.registry.permission\n        self._check_permission = permission.check_permission\n        self._get_accessible_objects = permission.get_accessible_objects\n\n        self.get_prefixed_principals = functools.partial(utils.prefixed_principals, request)\n\n        # Store current resource and required permission.\n        service = utils.current_service(request)\n        is_on_resource = (\n            service is not None and hasattr(service, \"viewset\") and hasattr(service, \"resource\")\n        )\n        if is_on_resource:\n            self.resource_name = request.current_resource_name\n            self.on_collection = getattr(service, \"type\", None) == \"collection\"\n\n            # Try to fetch the target object. Its existence will affect permissions checking.\n            if not self.on_collection and request.method.lower() in (\"put\", \"delete\", \"patch\"):\n                resource = service.resource(request=request, context=self)\n                try:\n                    # Save a reference, to avoid refetching from storage in resource.\n                    self.current_record = resource.model.get_record(resource.record_id)\n                except storage_exceptions.RecordNotFoundError:\n                    pass\n\n            self.permission_object_id, self.required_permission = self._find_required_permission(\n                request, service\n            )\n\n            # To obtain shared records on a collection endpoint, use a match:\n            self._object_id_match = self.get_permission_object_id(request, \"*\")\n\n        self._settings = request.registry.settings\n\n    def check_permission(self, principals, bound_perms):\n        \"\"\"Read allowed principals from settings, if not any, query the permission\n        backend to check if view is allowed.\n        \"\"\"\n        if not bound_perms:\n            bound_perms = [(self.resource_name, self.required_permission)]\n        for (_, permission) in bound_perms:\n            # With Kinto inheritance tree, we can have: `permission = \"record:create\"`\n            if self.resource_name and permission.startswith(self.resource_name):\n                setting = \"{}_principals\".format(permission.replace(\":\", \"_\"))\n            else:\n                setting = \"{}_{}_principals\".format(self.resource_name, permission)\n            allowed_principals = aslist(self._settings.get(setting, \"\"))\n            if allowed_principals:\n                if bool(set(allowed_principals) & set(principals)):\n                    return True\n        return self._check_permission(principals, bound_perms)\n\n    def fetch_shared_records(self, perm, principals, get_bound_permissions):\n        \"\"\"Fetch records that are readable or writable for the current\n        principals.\n\n        See :meth:`kinto.core.authorization.AuthorizationPolicy.permits`\n\n        If no record is shared, it returns None.\n\n        .. warning::\n            This sets the ``shared_ids`` attribute to the context with the\n            return value. The attribute is then read by\n            :class:`kinto.core.resource.ShareableResource`\n        \"\"\"\n        if get_bound_permissions:\n            bound_perms = get_bound_permissions(self._object_id_match, perm)\n        else:\n            bound_perms = [(self._object_id_match, perm)]\n        by_obj_id = self._get_accessible_objects(principals, bound_perms, with_children=False)\n        ids = by_obj_id.keys()\n        # Store for later use in ``ShareableResource``.\n        self.shared_ids = [self._extract_object_id(id_) for id_ in ids]\n        return self.shared_ids\n\n    def get_permission_object_id(self, request, object_id=None):\n        \"\"\"Returns the permission object id for the current request.\n        In the nominal case, it is just the current URI without version prefix.\n        For collections, it is the related record URI using the specified\n        `object_id`.\n\n        See :meth:`kinto.core.resource.model.SharableModel` and\n        :meth:`kinto.core.authorization.RouteFactory.__init__`\n        \"\"\"\n        object_uri = utils.strip_uri_prefix(request.path)\n\n        if self.on_collection and object_id is not None:\n            # With the current request on a collection, the record URI must\n            # be found out by inspecting the collection service and its sibling\n            # record service.\n            matchdict = {**request.matchdict, \"id\": object_id}\n            try:\n                object_uri = utils.instance_uri(request, self.resource_name, **matchdict)\n                object_uri = object_uri.replace(\"%2A\", \"*\")\n            except KeyError:\n                # Maybe the resource has no single record endpoint.\n                # We consider that object URIs in permissions backend will\n                # be stored naively:\n                object_uri = \"{}/{}\".format(object_uri, object_id)\n\n        return object_uri\n\n    def _extract_object_id(self, object_uri):\n        # XXX: Rewrite using kinto.core.utils.view_lookup() and matchdict['id']\n        return object_uri.split(\"/\")[-1]\n\n    def _find_required_permission(self, request, service):\n        \"\"\"Find out what is the permission object id and the required\n        permission.\n\n        .. note::\n            This method saves an attribute ``self.current_record`` used\n            in :class:`kinto.core.resource.UserResource`.\n        \"\"\"\n        # By default, it's a URI a and permission associated to the method.\n        permission_object_id = self.get_permission_object_id(request)\n        method = request.method.lower()\n        required_permission = self.method_permissions.get(method)\n\n        # For create permission, the object id is the plural endpoint.\n        collection_path = str(service.collection_path)\n        collection_path = collection_path.format_map(request.matchdict)\n\n        # In the case of a \"PUT\", check if the targetted record already\n        # exists, return \"write\" if it does, \"create\" otherwise.\n        if request.method.lower() == \"put\":\n            if self.current_record is None:\n                # The record does not exist, the permission to create on\n                # the related collection is required.\n                permission_object_id = collection_path\n                required_permission = \"create\"\n            else:\n                # For safe creations, the user needs a create permission.\n                # See Kinto/kinto#792\n                if request.headers.get(\"If-None-Match\") == \"*\":\n                    permission_object_id = collection_path\n                    required_permission = \"create\"\n                else:\n                    required_permission = \"write\"\n\n        return (permission_object_id, required_permission)\n", "path": "kinto/core/authorization.py"}]}