We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Consistency on creation ? 403 versus 412
If we try to create an object that was already created by someone else, the server returns `403 Unauthorized`.

If we add the header `If-None-Match: *`, it also returns `403` even I have the permission to create new objects (and not `412 Precondition failed`), which can be confusing.
In the documentation we don't make those corner cases very clear.

Is that consistent by the way ? Knowing that we want to prevent Alice to know what objects ids Bob has created if she has no permission to read them.

Consistency on creation ? 403 versus 412
If we try to create an object that was already created by someone else, the server returns `403 Unauthorized`.

If we add the header `If-None-Match: *`, it also returns `403` even I have the permission to create new objects (and not `412 Precondition failed`), which can be confusing.
In the documentation we don't make those corner cases very clear.

Is that consistent by the way ? Knowing that we want to prevent Alice to know what objects ids Bob has created if she has no permission to read them.


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

{"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@@ -221,6 +221,9 @@\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 = service.collection_path.format(**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@@ -233,10 +236,15 @@\n             except storage_exceptions.RecordNotFoundError:\n                 # The record does not exist, the permission to create on\n                 # the related collection is required.\n-                permission_object_id = service.collection_path.format(\n-                    **request.matchdict)\n+                permission_object_id = collection_path\n                 required_permission = \"create\"\n             else:\n-                required_permission = \"write\"\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", "issue": "Consistency on creation ? 403 versus 412\nIf we try to create an object that was already created by someone else, the server returns `403 Unauthorized`.\n\nIf we add the header `If-None-Match: *`, it also returns `403` even I have the permission to create new objects (and not `412 Precondition failed`), which can be confusing.\nIn the documentation we don't make those corner cases very clear.\n\nIs that consistent by the way ? Knowing that we want to prevent Alice to know what objects ids Bob has created if she has no permission to read them.\n\nConsistency on creation ? 403 versus 412\nIf we try to create an object that was already created by someone else, the server returns `403 Unauthorized`.\n\nIf we add the header `If-None-Match: *`, it also returns `403` even I have the permission to create new objects (and not `412 Precondition failed`), which can be confusing.\nIn the documentation we don't make those corner cases very clear.\n\nIs that consistent by the way ? Knowing that we want to prevent Alice to know what objects ids Bob has created if she has no permission to read them.\n\n", "before_files": [{"content": "import functools\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\nfrom kinto.core.authentication import prefixed_userid\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(object):\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        # Add prefixed user id to principals.\n        prefixed_userid = context.get_prefixed_userid()\n        if prefixed_userid and ':' in prefixed_userid:\n            principals = principals + [prefixed_userid]\n            prefix, user_id = prefixed_userid.split(':', 1)\n            # Remove unprefixed user id to avoid conflicts.\n            # (it is added via Pyramid Authn policy effective principals)\n            if user_id in principals:\n                principals.remove(user_id)\n            # Retro-compatibility with cliquet 2.0 '_' user id prefixes.\n            # Just in case it was used in permissions definitions.\n            principals.append('%s_%s' % (prefix, user_id))\n\n        if permission == DYNAMIC:\n            permission = context.required_permission\n\n        if permission == 'create':\n            permission = '%s:%s' % (context.resource_name, permission)\n\n        if context.allowed_principals:\n            allowed = bool(set(context.allowed_principals) & set(principals))\n        else:\n            object_id = context.permission_object_id\n            if self.get_bound_permissions is None:\n                bound_perms = [(object_id, permission)]\n            else:\n                bound_perms = self.get_bound_permissions(object_id, permission)\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\n                             not permission.endswith('create'))\n        if not allowed and is_list_operation:\n            shared = context.fetch_shared_records(permission,\n                                                  principals,\n                                                  self.get_bound_permissions)\n            allowed = shared is not None\n\n        return allowed\n\n    def principals_allowed_by_permission(self, context, permission):\n        raise NotImplementedError()  # PRAGMA NOCOVER\n\n\nclass RouteFactory(object):\n    resource_name = None\n    on_collection = False\n    required_permission = None\n    allowed_principals = 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        # Make it available for the authorization policy.\n        self.get_prefixed_userid = functools.partial(prefixed_userid, request)\n\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        # Store current resource and required permission.\n        service = utils.current_service(request)\n        is_on_resource = (service is not None and\n                          hasattr(service, 'viewset') and\n                          hasattr(service, 'resource'))\n        if is_on_resource:\n            self.resource_name = request.current_resource_name\n            self.on_collection = getattr(service, \"type\", None) == \"collection\"\n\n            self.permission_object_id, self.required_permission = (\n                self._find_required_permission(request, service))\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            # Check if principals are allowed explicitly from settings.\n            settings = request.registry.settings\n            setting = '%s_%s_principals' % (self.resource_name,\n                                            self.required_permission)\n            self.allowed_principals = aslist(settings.get(setting, ''))\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)\n        ids = by_obj_id.keys()\n        if len(ids) > 0:\n            # Store for later use in ``ShareableResource``.\n            self.shared_ids = [self._extract_object_id(id_) for id_ in ids]\n        else:\n            self.shared_ids = None\n\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.copy()\n            matchdict['id'] = object_id\n            try:\n                object_uri = utils.instance_uri(request,\n                                                self.resource_name,\n                                                **matchdict)\n                if object_id == '*':\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 = 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        # 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            resource = service.resource(request=request, context=self)\n            try:\n                record = resource.model.get_record(resource.record_id)\n                # Save a reference, to avoid refetching from storage in\n                # resource.\n                self.current_record = record\n            except storage_exceptions.RecordNotFoundError:\n                # The record does not exist, the permission to create on\n                # the related collection is required.\n                permission_object_id = service.collection_path.format(\n                    **request.matchdict)\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\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\nfrom kinto.core.authentication import prefixed_userid\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(object):\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        # Add prefixed user id to principals.\n        prefixed_userid = context.get_prefixed_userid()\n        if prefixed_userid and ':' in prefixed_userid:\n            principals = principals + [prefixed_userid]\n            prefix, user_id = prefixed_userid.split(':', 1)\n            # Remove unprefixed user id to avoid conflicts.\n            # (it is added via Pyramid Authn policy effective principals)\n            if user_id in principals:\n                principals.remove(user_id)\n            # Retro-compatibility with cliquet 2.0 '_' user id prefixes.\n            # Just in case it was used in permissions definitions.\n            principals.append('%s_%s' % (prefix, user_id))\n\n        if permission == DYNAMIC:\n            permission = context.required_permission\n\n        if permission == 'create':\n            permission = '%s:%s' % (context.resource_name, permission)\n\n        if context.allowed_principals:\n            allowed = bool(set(context.allowed_principals) & set(principals))\n        else:\n            object_id = context.permission_object_id\n            if self.get_bound_permissions is None:\n                bound_perms = [(object_id, permission)]\n            else:\n                bound_perms = self.get_bound_permissions(object_id, permission)\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\n                             not permission.endswith('create'))\n        if not allowed and is_list_operation:\n            shared = context.fetch_shared_records(permission,\n                                                  principals,\n                                                  self.get_bound_permissions)\n            allowed = shared is not None\n\n        return allowed\n\n    def principals_allowed_by_permission(self, context, permission):\n        raise NotImplementedError()  # PRAGMA NOCOVER\n\n\nclass RouteFactory(object):\n    resource_name = None\n    on_collection = False\n    required_permission = None\n    allowed_principals = 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        # Make it available for the authorization policy.\n        self.get_prefixed_userid = functools.partial(prefixed_userid, request)\n\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        # Store current resource and required permission.\n        service = utils.current_service(request)\n        is_on_resource = (service is not None and\n                          hasattr(service, 'viewset') and\n                          hasattr(service, 'resource'))\n        if is_on_resource:\n            self.resource_name = request.current_resource_name\n            self.on_collection = getattr(service, \"type\", None) == \"collection\"\n\n            self.permission_object_id, self.required_permission = (\n                self._find_required_permission(request, service))\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            # Check if principals are allowed explicitly from settings.\n            settings = request.registry.settings\n            setting = '%s_%s_principals' % (self.resource_name,\n                                            self.required_permission)\n            self.allowed_principals = aslist(settings.get(setting, ''))\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)\n        ids = by_obj_id.keys()\n        if len(ids) > 0:\n            # Store for later use in ``ShareableResource``.\n            self.shared_ids = [self._extract_object_id(id_) for id_ in ids]\n        else:\n            self.shared_ids = None\n\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.copy()\n            matchdict['id'] = object_id\n            try:\n                object_uri = utils.instance_uri(request,\n                                                self.resource_name,\n                                                **matchdict)\n                if object_id == '*':\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 = 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 = service.collection_path.format(**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            resource = service.resource(request=request, context=self)\n            try:\n                record = resource.model.get_record(resource.record_id)\n                # Save a reference, to avoid refetching from storage in\n                # resource.\n                self.current_record = record\n            except storage_exceptions.RecordNotFoundError:\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"}]}