kinto 23.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kinto/__init__.py +92 -0
- kinto/__main__.py +249 -0
- kinto/authorization.py +134 -0
- kinto/config/__init__.py +94 -0
- kinto/config/kinto.tpl +270 -0
- kinto/contribute.json +27 -0
- kinto/core/__init__.py +246 -0
- kinto/core/authentication.py +48 -0
- kinto/core/authorization.py +311 -0
- kinto/core/cache/__init__.py +131 -0
- kinto/core/cache/memcached.py +112 -0
- kinto/core/cache/memory.py +104 -0
- kinto/core/cache/postgresql/__init__.py +178 -0
- kinto/core/cache/postgresql/schema.sql +23 -0
- kinto/core/cache/testing.py +208 -0
- kinto/core/cornice/__init__.py +93 -0
- kinto/core/cornice/cors.py +144 -0
- kinto/core/cornice/errors.py +40 -0
- kinto/core/cornice/pyramidhook.py +373 -0
- kinto/core/cornice/renderer.py +89 -0
- kinto/core/cornice/resource.py +205 -0
- kinto/core/cornice/service.py +641 -0
- kinto/core/cornice/util.py +138 -0
- kinto/core/cornice/validators/__init__.py +94 -0
- kinto/core/cornice/validators/_colander.py +142 -0
- kinto/core/cornice/validators/_marshmallow.py +182 -0
- kinto/core/cornice_swagger/__init__.py +92 -0
- kinto/core/cornice_swagger/converters/__init__.py +21 -0
- kinto/core/cornice_swagger/converters/exceptions.py +6 -0
- kinto/core/cornice_swagger/converters/parameters.py +90 -0
- kinto/core/cornice_swagger/converters/schema.py +249 -0
- kinto/core/cornice_swagger/swagger.py +725 -0
- kinto/core/cornice_swagger/templates/index.html +73 -0
- kinto/core/cornice_swagger/templates/index_script_template.html +21 -0
- kinto/core/cornice_swagger/util.py +42 -0
- kinto/core/cornice_swagger/views.py +78 -0
- kinto/core/decorators.py +74 -0
- kinto/core/errors.py +216 -0
- kinto/core/events.py +301 -0
- kinto/core/initialization.py +738 -0
- kinto/core/listeners/__init__.py +9 -0
- kinto/core/metrics.py +94 -0
- kinto/core/openapi.py +115 -0
- kinto/core/permission/__init__.py +202 -0
- kinto/core/permission/memory.py +167 -0
- kinto/core/permission/postgresql/__init__.py +489 -0
- kinto/core/permission/postgresql/migrations/migration_001_002.sql +18 -0
- kinto/core/permission/postgresql/schema.sql +41 -0
- kinto/core/permission/testing.py +487 -0
- kinto/core/resource/__init__.py +1311 -0
- kinto/core/resource/model.py +412 -0
- kinto/core/resource/schema.py +502 -0
- kinto/core/resource/viewset.py +230 -0
- kinto/core/schema.py +119 -0
- kinto/core/scripts.py +50 -0
- kinto/core/statsd.py +1 -0
- kinto/core/storage/__init__.py +436 -0
- kinto/core/storage/exceptions.py +53 -0
- kinto/core/storage/generators.py +58 -0
- kinto/core/storage/memory.py +651 -0
- kinto/core/storage/postgresql/__init__.py +1131 -0
- kinto/core/storage/postgresql/client.py +120 -0
- kinto/core/storage/postgresql/migrations/migration_001_002.sql +10 -0
- kinto/core/storage/postgresql/migrations/migration_002_003.sql +33 -0
- kinto/core/storage/postgresql/migrations/migration_003_004.sql +18 -0
- kinto/core/storage/postgresql/migrations/migration_004_005.sql +20 -0
- kinto/core/storage/postgresql/migrations/migration_005_006.sql +11 -0
- kinto/core/storage/postgresql/migrations/migration_006_007.sql +74 -0
- kinto/core/storage/postgresql/migrations/migration_007_008.sql +66 -0
- kinto/core/storage/postgresql/migrations/migration_008_009.sql +41 -0
- kinto/core/storage/postgresql/migrations/migration_009_010.sql +98 -0
- kinto/core/storage/postgresql/migrations/migration_010_011.sql +14 -0
- kinto/core/storage/postgresql/migrations/migration_011_012.sql +9 -0
- kinto/core/storage/postgresql/migrations/migration_012_013.sql +71 -0
- kinto/core/storage/postgresql/migrations/migration_013_014.sql +14 -0
- kinto/core/storage/postgresql/migrations/migration_014_015.sql +95 -0
- kinto/core/storage/postgresql/migrations/migration_015_016.sql +4 -0
- kinto/core/storage/postgresql/migrations/migration_016_017.sql +81 -0
- kinto/core/storage/postgresql/migrations/migration_017_018.sql +25 -0
- kinto/core/storage/postgresql/migrations/migration_018_019.sql +8 -0
- kinto/core/storage/postgresql/migrations/migration_019_020.sql +7 -0
- kinto/core/storage/postgresql/migrations/migration_020_021.sql +68 -0
- kinto/core/storage/postgresql/migrations/migration_021_022.sql +62 -0
- kinto/core/storage/postgresql/migrations/migration_022_023.sql +5 -0
- kinto/core/storage/postgresql/migrations/migration_023_024.sql +6 -0
- kinto/core/storage/postgresql/migrations/migration_024_025.sql +6 -0
- kinto/core/storage/postgresql/migrator.py +98 -0
- kinto/core/storage/postgresql/pool.py +55 -0
- kinto/core/storage/postgresql/schema.sql +143 -0
- kinto/core/storage/testing.py +1857 -0
- kinto/core/storage/utils.py +37 -0
- kinto/core/testing.py +182 -0
- kinto/core/utils.py +553 -0
- kinto/core/views/__init__.py +0 -0
- kinto/core/views/batch.py +163 -0
- kinto/core/views/errors.py +145 -0
- kinto/core/views/heartbeat.py +106 -0
- kinto/core/views/hello.py +69 -0
- kinto/core/views/openapi.py +35 -0
- kinto/core/views/version.py +50 -0
- kinto/events.py +3 -0
- kinto/plugins/__init__.py +0 -0
- kinto/plugins/accounts/__init__.py +94 -0
- kinto/plugins/accounts/authentication.py +63 -0
- kinto/plugins/accounts/scripts.py +61 -0
- kinto/plugins/accounts/utils.py +13 -0
- kinto/plugins/accounts/views.py +136 -0
- kinto/plugins/admin/README.md +3 -0
- kinto/plugins/admin/VERSION +1 -0
- kinto/plugins/admin/__init__.py +40 -0
- kinto/plugins/admin/build/VERSION +1 -0
- kinto/plugins/admin/build/assets/index-CYFwtKtL.css +6 -0
- kinto/plugins/admin/build/assets/index-DJ0m93zA.js +149 -0
- kinto/plugins/admin/build/assets/logo-VBRiKSPX.png +0 -0
- kinto/plugins/admin/build/index.html +18 -0
- kinto/plugins/admin/public/help.html +25 -0
- kinto/plugins/admin/views.py +42 -0
- kinto/plugins/default_bucket/__init__.py +191 -0
- kinto/plugins/flush.py +28 -0
- kinto/plugins/history/__init__.py +65 -0
- kinto/plugins/history/listener.py +181 -0
- kinto/plugins/history/views.py +66 -0
- kinto/plugins/openid/__init__.py +131 -0
- kinto/plugins/openid/utils.py +14 -0
- kinto/plugins/openid/views.py +193 -0
- kinto/plugins/prometheus.py +300 -0
- kinto/plugins/statsd.py +85 -0
- kinto/schema_validation.py +135 -0
- kinto/views/__init__.py +34 -0
- kinto/views/admin.py +195 -0
- kinto/views/buckets.py +45 -0
- kinto/views/collections.py +58 -0
- kinto/views/contribute.py +39 -0
- kinto/views/groups.py +90 -0
- kinto/views/permissions.py +235 -0
- kinto/views/records.py +133 -0
- kinto-23.2.1.dist-info/METADATA +232 -0
- kinto-23.2.1.dist-info/RECORD +142 -0
- kinto-23.2.1.dist-info/WHEEL +5 -0
- kinto-23.2.1.dist-info/entry_points.txt +5 -0
- kinto-23.2.1.dist-info/licenses/LICENSE +13 -0
- kinto-23.2.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
import colander
|
|
4
|
+
from pyramid.settings import aslist
|
|
5
|
+
|
|
6
|
+
from kinto.authorization import PERMISSIONS_INHERITANCE_TREE
|
|
7
|
+
from kinto.core import resource
|
|
8
|
+
from kinto.core import utils as core_utils
|
|
9
|
+
from kinto.core.storage import Sort
|
|
10
|
+
from kinto.core.storage.memory import extract_object_set
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def allowed_from_settings(settings, principals):
|
|
14
|
+
"""Returns every permissions allowed from settings for the current user.
|
|
15
|
+
:param settings dict: app settings
|
|
16
|
+
:param principals list: list of principals of current user
|
|
17
|
+
:rtype: dict
|
|
18
|
+
|
|
19
|
+
Result example::
|
|
20
|
+
|
|
21
|
+
{
|
|
22
|
+
"bucket": {"write", "collection:create"},
|
|
23
|
+
"collection": {"read"}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
XXX: This helper will be useful for Kinto/kinto#894
|
|
27
|
+
"""
|
|
28
|
+
# Select settings about explicit permissions set on resources
|
|
29
|
+
# bucket_write_principals = ... --> {("bucket", "write"): ["account:admin"]}
|
|
30
|
+
perms_settings = {
|
|
31
|
+
tuple(k.split("_", 3)[:2]): aslist(v)
|
|
32
|
+
for k, v in settings.items()
|
|
33
|
+
if re.match("(.+)_(create|write|read)_principals", k)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
from_settings = {}
|
|
37
|
+
for (resource_name, permission), allowed_principals in perms_settings.items():
|
|
38
|
+
# Keep the known permissions only.
|
|
39
|
+
if resource_name not in PERMISSIONS_INHERITANCE_TREE.keys():
|
|
40
|
+
continue
|
|
41
|
+
# Keep the permissions of the current user only.
|
|
42
|
+
if not bool(set(principals) & set(allowed_principals)):
|
|
43
|
+
continue
|
|
44
|
+
# ``collection_create_principals`` means ``collection:create`` in bucket.
|
|
45
|
+
if permission == "create":
|
|
46
|
+
permission = f"{resource_name}:{permission}"
|
|
47
|
+
resource_name = { # resource parents.
|
|
48
|
+
"collection": "bucket",
|
|
49
|
+
"group": "bucket",
|
|
50
|
+
"record": "collection",
|
|
51
|
+
"bucket": "root",
|
|
52
|
+
"account": "root",
|
|
53
|
+
}.get(resource_name, "")
|
|
54
|
+
# Store them in a convenient way.
|
|
55
|
+
from_settings.setdefault(resource_name, set()).add(permission)
|
|
56
|
+
return from_settings
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class PermissionsModel:
|
|
60
|
+
id_field = "id"
|
|
61
|
+
modified_field = "last_modified"
|
|
62
|
+
deleted_field = "deleted"
|
|
63
|
+
|
|
64
|
+
def __init__(self, request):
|
|
65
|
+
self.request = request
|
|
66
|
+
|
|
67
|
+
def timestamp(self, parent_id=None):
|
|
68
|
+
return 0
|
|
69
|
+
|
|
70
|
+
def get_objects(
|
|
71
|
+
self,
|
|
72
|
+
filters=None,
|
|
73
|
+
sorting=None,
|
|
74
|
+
pagination_rules=None,
|
|
75
|
+
limit=None,
|
|
76
|
+
include_deleted=False,
|
|
77
|
+
parent_id=None,
|
|
78
|
+
):
|
|
79
|
+
objects, _ = self._get_objects(
|
|
80
|
+
filters=filters,
|
|
81
|
+
sorting=sorting,
|
|
82
|
+
pagination_rules=pagination_rules,
|
|
83
|
+
limit=limit,
|
|
84
|
+
include_deleted=include_deleted,
|
|
85
|
+
parent_id=parent_id,
|
|
86
|
+
)
|
|
87
|
+
return objects
|
|
88
|
+
|
|
89
|
+
def count_objects(self, filters=None, parent_id=None):
|
|
90
|
+
_, count = self._get_objects(filters=filters, parent_id=parent_id)
|
|
91
|
+
return count
|
|
92
|
+
|
|
93
|
+
def _get_objects(
|
|
94
|
+
self,
|
|
95
|
+
filters=None,
|
|
96
|
+
sorting=None,
|
|
97
|
+
pagination_rules=None,
|
|
98
|
+
limit=None,
|
|
99
|
+
include_deleted=False,
|
|
100
|
+
parent_id=None,
|
|
101
|
+
):
|
|
102
|
+
# Invert the permissions inheritance tree.
|
|
103
|
+
perms_descending_tree = {}
|
|
104
|
+
for on_resource, tree in PERMISSIONS_INHERITANCE_TREE.items():
|
|
105
|
+
for obtained_perm, obtained_from in tree.items():
|
|
106
|
+
for from_resource, perms in obtained_from.items():
|
|
107
|
+
for perm in perms:
|
|
108
|
+
perms_descending_tree.setdefault(from_resource, {}).setdefault(
|
|
109
|
+
perm, {}
|
|
110
|
+
).setdefault(on_resource, set()).add(obtained_perm)
|
|
111
|
+
|
|
112
|
+
# Obtain current principals.
|
|
113
|
+
principals = self.request.prefixed_principals
|
|
114
|
+
|
|
115
|
+
# Query every possible permission of the current user from backend.
|
|
116
|
+
backend = self.request.registry.permission
|
|
117
|
+
perms_by_object_uri = backend.get_accessible_objects(principals)
|
|
118
|
+
|
|
119
|
+
# Check settings for every allowed resources.
|
|
120
|
+
from_settings = allowed_from_settings(self.request.registry.settings, principals)
|
|
121
|
+
|
|
122
|
+
# Add additional resources and permissions defined in settings/plugins
|
|
123
|
+
for root_perm in from_settings.get("root", []):
|
|
124
|
+
resource_name, _ = root_perm.split(":")
|
|
125
|
+
perms_by_object_uri.setdefault("/", set()).add(root_perm)
|
|
126
|
+
perms_descending_tree.setdefault("root", {}).update({root_perm: {"root": {root_perm}}})
|
|
127
|
+
|
|
128
|
+
# Expand permissions obtained from backend with the object URIs that
|
|
129
|
+
# correspond to permissions allowed from settings.
|
|
130
|
+
allowed_resources = {"bucket", "collection", "group"} & set(from_settings.keys())
|
|
131
|
+
if allowed_resources:
|
|
132
|
+
storage = self.request.registry.storage
|
|
133
|
+
every_bucket = storage.list_all(parent_id="", resource_name="bucket")
|
|
134
|
+
for bucket in every_bucket:
|
|
135
|
+
bucket_uri = "/buckets/{id}".format_map(bucket)
|
|
136
|
+
for res in allowed_resources:
|
|
137
|
+
resource_perms = from_settings[res]
|
|
138
|
+
# Bucket is always fetched.
|
|
139
|
+
if res == "bucket":
|
|
140
|
+
perms_by_object_uri.setdefault(bucket_uri, set()).update(resource_perms)
|
|
141
|
+
continue
|
|
142
|
+
# Fetch bucket collections and groups.
|
|
143
|
+
# XXX: wrong approach: query in a loop!
|
|
144
|
+
every_subobjects = storage.list_all(parent_id=bucket_uri, resource_name=res)
|
|
145
|
+
for subobject in every_subobjects:
|
|
146
|
+
subobj_uri = bucket_uri + f"/{res}s/{subobject['id']}"
|
|
147
|
+
perms_by_object_uri.setdefault(subobj_uri, set()).update(resource_perms)
|
|
148
|
+
|
|
149
|
+
entries = []
|
|
150
|
+
for object_uri, perms in perms_by_object_uri.items():
|
|
151
|
+
try:
|
|
152
|
+
# Obtain associated res from object URI
|
|
153
|
+
resource_name, matchdict = core_utils.view_lookup(self.request, object_uri)
|
|
154
|
+
except ValueError:
|
|
155
|
+
# Skip permissions entries that are not linked to an object URI
|
|
156
|
+
continue
|
|
157
|
+
|
|
158
|
+
# For consistency with event payloads, if resource has an id,
|
|
159
|
+
# prefix it with its resource name
|
|
160
|
+
if "id" in matchdict:
|
|
161
|
+
matchdict[resource_name + "_id"] = matchdict["id"]
|
|
162
|
+
|
|
163
|
+
# The imaginary "root" resource gets mapped to the hello
|
|
164
|
+
# view. Handle it explicitly.
|
|
165
|
+
if resource_name == "hello":
|
|
166
|
+
resource_name = "root"
|
|
167
|
+
|
|
168
|
+
# Expand implicit permissions using descending tree.
|
|
169
|
+
permissions = set(perms)
|
|
170
|
+
for perm in perms:
|
|
171
|
+
obtained = perms_descending_tree[resource_name][perm]
|
|
172
|
+
# Related to same resource only and not every sub-objects.
|
|
173
|
+
# (e.g "bucket:write" gives "bucket:read" but not "group:read")
|
|
174
|
+
permissions |= obtained[resource_name]
|
|
175
|
+
|
|
176
|
+
entry = dict(
|
|
177
|
+
uri=object_uri,
|
|
178
|
+
resource_name=resource_name,
|
|
179
|
+
permissions=list(permissions),
|
|
180
|
+
**matchdict,
|
|
181
|
+
)
|
|
182
|
+
entries.append(entry)
|
|
183
|
+
|
|
184
|
+
return extract_object_set(
|
|
185
|
+
entries,
|
|
186
|
+
filters=filters,
|
|
187
|
+
sorting=sorting,
|
|
188
|
+
id_field="uri",
|
|
189
|
+
pagination_rules=pagination_rules,
|
|
190
|
+
limit=limit,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class PermissionsSchema(resource.ResourceSchema):
|
|
195
|
+
uri = colander.SchemaNode(colander.String())
|
|
196
|
+
resource_name = colander.SchemaNode(colander.String())
|
|
197
|
+
permissions = colander.Sequence(colander.SchemaNode(colander.String()))
|
|
198
|
+
bucket_id = colander.SchemaNode(colander.String())
|
|
199
|
+
collection_id = colander.SchemaNode(colander.String(), missing=colander.drop)
|
|
200
|
+
group_id = colander.SchemaNode(colander.String(), missing=colander.drop)
|
|
201
|
+
record_id = colander.SchemaNode(colander.String(), missing=colander.drop)
|
|
202
|
+
|
|
203
|
+
class Options:
|
|
204
|
+
preserve_unknown = False
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
@resource.register(
|
|
208
|
+
name="permissions",
|
|
209
|
+
description="List of user permissions",
|
|
210
|
+
plural_path="/permissions",
|
|
211
|
+
object_path=None,
|
|
212
|
+
plural_methods=("HEAD", "GET"),
|
|
213
|
+
)
|
|
214
|
+
class Permissions(resource.Resource):
|
|
215
|
+
schema = PermissionsSchema
|
|
216
|
+
|
|
217
|
+
def __init__(self, request, context=None):
|
|
218
|
+
super().__init__(request, context)
|
|
219
|
+
self.model = PermissionsModel(request)
|
|
220
|
+
|
|
221
|
+
def _extract_sorting(self, limit):
|
|
222
|
+
# Permissions entries are not stored with timestamp, so do not
|
|
223
|
+
# force it.
|
|
224
|
+
result = super()._extract_sorting(limit)
|
|
225
|
+
without_last_modified = [s for s in result if s.field != self.model.modified_field]
|
|
226
|
+
# For pagination, there must be at least one sort criteria.
|
|
227
|
+
# We use ``uri`` because its values are unique.
|
|
228
|
+
if "uri" not in [s.field for s in without_last_modified]:
|
|
229
|
+
without_last_modified.append(Sort("uri", -1))
|
|
230
|
+
return without_last_modified
|
|
231
|
+
|
|
232
|
+
def _extract_filters(self):
|
|
233
|
+
result = super()._extract_filters()
|
|
234
|
+
without_last_modified = [s for s in result if s.field != self.model.modified_field]
|
|
235
|
+
return without_last_modified
|
kinto/views/records.py
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
from pyramid.authorization import Authenticated
|
|
2
|
+
from pyramid.settings import asbool
|
|
3
|
+
|
|
4
|
+
from kinto.core import resource, utils
|
|
5
|
+
from kinto.core.errors import raise_invalid
|
|
6
|
+
from kinto.schema_validation import (
|
|
7
|
+
RefResolutionError,
|
|
8
|
+
ValidationError,
|
|
9
|
+
validate_from_bucket_schema_or_400,
|
|
10
|
+
validate_schema,
|
|
11
|
+
)
|
|
12
|
+
from kinto.views import object_exists_or_404
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
_parent_path = "/buckets/{{bucket_id}}/collections/{{collection_id}}"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@resource.register(
|
|
19
|
+
name="record",
|
|
20
|
+
plural_path=_parent_path + "/records",
|
|
21
|
+
object_path=_parent_path + "/records/{{id}}",
|
|
22
|
+
)
|
|
23
|
+
class Record(resource.Resource):
|
|
24
|
+
schema_field = "schema"
|
|
25
|
+
|
|
26
|
+
def __init__(self, request, **kwargs):
|
|
27
|
+
# Before all, first check that the parent collection exists.
|
|
28
|
+
# Check if already fetched before (in batch).
|
|
29
|
+
collections = request.bound_data.setdefault("collections", {})
|
|
30
|
+
collection_uri = self.get_parent_id(request)
|
|
31
|
+
if collection_uri not in collections:
|
|
32
|
+
# Unknown yet, fetch from storage.
|
|
33
|
+
bucket_uri = utils.instance_uri(request, "bucket", id=self.bucket_id)
|
|
34
|
+
collection = object_exists_or_404(
|
|
35
|
+
request,
|
|
36
|
+
resource_name="collection",
|
|
37
|
+
parent_id=bucket_uri,
|
|
38
|
+
object_id=self.collection_id,
|
|
39
|
+
)
|
|
40
|
+
collections[collection_uri] = collection
|
|
41
|
+
self._collection = collections[collection_uri]
|
|
42
|
+
|
|
43
|
+
super().__init__(request, **kwargs)
|
|
44
|
+
|
|
45
|
+
def get_parent_id(self, request):
|
|
46
|
+
self.bucket_id = request.matchdict["bucket_id"]
|
|
47
|
+
self.collection_id = request.matchdict["collection_id"]
|
|
48
|
+
return utils.instance_uri(
|
|
49
|
+
request, "collection", bucket_id=self.bucket_id, id=self.collection_id
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
def process_object(self, new, old=None):
|
|
53
|
+
"""Validate records against collection or bucket schema, if any."""
|
|
54
|
+
new = super().process_object(new, old)
|
|
55
|
+
|
|
56
|
+
# Is schema validation enabled?
|
|
57
|
+
settings = self.request.registry.settings
|
|
58
|
+
schema_validation = "experimental_collection_schema_validation"
|
|
59
|
+
if not asbool(settings.get(schema_validation)):
|
|
60
|
+
return new
|
|
61
|
+
|
|
62
|
+
# Remove internal and auto-assigned fields from schemas and record.
|
|
63
|
+
ignored_fields = (
|
|
64
|
+
self.model.modified_field,
|
|
65
|
+
self.schema_field,
|
|
66
|
+
self.model.permissions_field,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# The schema defined on the collection will be validated first.
|
|
70
|
+
if "schema" in self._collection:
|
|
71
|
+
schema = self._collection["schema"]
|
|
72
|
+
try:
|
|
73
|
+
validate_schema(
|
|
74
|
+
new, schema, ignore_fields=ignored_fields, id_field=self.model.id_field
|
|
75
|
+
)
|
|
76
|
+
except ValidationError as e:
|
|
77
|
+
raise_invalid(self.request, name=e.field, description=e.message)
|
|
78
|
+
except RefResolutionError as e:
|
|
79
|
+
raise_invalid(self.request, name="schema", description=str(e))
|
|
80
|
+
|
|
81
|
+
# Assign the schema version to the record.
|
|
82
|
+
schema_timestamp = self._collection[self.model.modified_field]
|
|
83
|
+
new[self.schema_field] = schema_timestamp
|
|
84
|
+
|
|
85
|
+
# Validate also from the record:schema field defined on the bucket.
|
|
86
|
+
validate_from_bucket_schema_or_400(
|
|
87
|
+
new,
|
|
88
|
+
resource_name="record",
|
|
89
|
+
request=self.request,
|
|
90
|
+
ignore_fields=ignored_fields,
|
|
91
|
+
id_field=self.model.id_field,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
return new
|
|
95
|
+
|
|
96
|
+
def plural_get(self):
|
|
97
|
+
result = super().plural_get()
|
|
98
|
+
self._handle_cache_expires(self.request.response)
|
|
99
|
+
return result
|
|
100
|
+
|
|
101
|
+
def get(self):
|
|
102
|
+
result = super().get()
|
|
103
|
+
self._handle_cache_expires(self.request.response)
|
|
104
|
+
return result
|
|
105
|
+
|
|
106
|
+
def _handle_cache_expires(self, response):
|
|
107
|
+
"""If the parent collection defines a ``cache_expires`` attribute,
|
|
108
|
+
then cache-control response headers are sent.
|
|
109
|
+
|
|
110
|
+
.. note::
|
|
111
|
+
|
|
112
|
+
Those headers are also sent if the
|
|
113
|
+
``kinto.record_cache_expires_seconds`` setting is defined.
|
|
114
|
+
"""
|
|
115
|
+
is_anonymous = Authenticated not in self.request.effective_principals
|
|
116
|
+
if not is_anonymous:
|
|
117
|
+
return
|
|
118
|
+
|
|
119
|
+
cache_expires = self._collection.get("cache_expires")
|
|
120
|
+
if cache_expires is None:
|
|
121
|
+
by_collection = f"{self.bucket_id}.{self.collection_id}.record_cache_expires_seconds"
|
|
122
|
+
by_bucket = f"{self.bucket_id}.record_cache_expires_seconds"
|
|
123
|
+
by_collection_legacy = (
|
|
124
|
+
f"{self.bucket_id}_{self.collection_id}_record_cache_expires_seconds"
|
|
125
|
+
)
|
|
126
|
+
by_bucket_legacy = f"{self.bucket_id}_record_cache_expires_seconds"
|
|
127
|
+
settings = self.request.registry.settings
|
|
128
|
+
for s in (by_collection, by_bucket, by_collection_legacy, by_bucket_legacy):
|
|
129
|
+
cache_expires = settings.get(s)
|
|
130
|
+
if cache_expires is not None:
|
|
131
|
+
break
|
|
132
|
+
if cache_expires is not None:
|
|
133
|
+
response.cache_expires(seconds=int(cache_expires))
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kinto
|
|
3
|
+
Version: 23.2.1
|
|
4
|
+
Summary: Kinto Web Service - Store, Sync, Share, and Self-Host.
|
|
5
|
+
Author-email: Mozilla Services <developers@kinto-storage.org>
|
|
6
|
+
License: Copyright 2012 - Mozilla Foundation
|
|
7
|
+
|
|
8
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
9
|
+
you may not use this file except in compliance with the License.
|
|
10
|
+
You may obtain a copy of the License at
|
|
11
|
+
|
|
12
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
13
|
+
|
|
14
|
+
Unless required by applicable law or agreed to in writing, software
|
|
15
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
16
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
17
|
+
See the License for the specific language governing permissions and
|
|
18
|
+
limitations under the License.
|
|
19
|
+
|
|
20
|
+
Project-URL: Repository, https://github.com/Kinto/kinto
|
|
21
|
+
Keywords: web,sync,json,storage,services
|
|
22
|
+
Classifier: Programming Language :: Python
|
|
23
|
+
Classifier: Programming Language :: Python :: 3
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
26
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
27
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
28
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
29
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
30
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
|
|
31
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
32
|
+
Description-Content-Type: text/x-rst
|
|
33
|
+
License-File: LICENSE
|
|
34
|
+
Requires-Dist: bcrypt
|
|
35
|
+
Requires-Dist: colander
|
|
36
|
+
Requires-Dist: dockerflow
|
|
37
|
+
Requires-Dist: jsonschema
|
|
38
|
+
Requires-Dist: jsonpatch
|
|
39
|
+
Requires-Dist: logging-color-formatter
|
|
40
|
+
Requires-Dist: python-dateutil
|
|
41
|
+
Requires-Dist: pyramid
|
|
42
|
+
Requires-Dist: pyramid_multiauth
|
|
43
|
+
Requires-Dist: transaction
|
|
44
|
+
Requires-Dist: pyramid_tm
|
|
45
|
+
Requires-Dist: requests
|
|
46
|
+
Requires-Dist: waitress
|
|
47
|
+
Requires-Dist: python-rapidjson
|
|
48
|
+
Provides-Extra: redis
|
|
49
|
+
Requires-Dist: kinto_redis; extra == "redis"
|
|
50
|
+
Provides-Extra: memcached
|
|
51
|
+
Requires-Dist: python-memcached; extra == "memcached"
|
|
52
|
+
Provides-Extra: postgresql
|
|
53
|
+
Requires-Dist: SQLAlchemy<3; extra == "postgresql"
|
|
54
|
+
Requires-Dist: psycopg2-binary; extra == "postgresql"
|
|
55
|
+
Requires-Dist: zope.sqlalchemy; extra == "postgresql"
|
|
56
|
+
Provides-Extra: monitoring
|
|
57
|
+
Requires-Dist: newrelic; extra == "monitoring"
|
|
58
|
+
Requires-Dist: sentry-sdk[sqlalchemy]; extra == "monitoring"
|
|
59
|
+
Requires-Dist: statsd; extra == "monitoring"
|
|
60
|
+
Requires-Dist: werkzeug; extra == "monitoring"
|
|
61
|
+
Requires-Dist: prometheus-client; extra == "monitoring"
|
|
62
|
+
Provides-Extra: test
|
|
63
|
+
Requires-Dist: bravado; extra == "test"
|
|
64
|
+
Requires-Dist: pytest; extra == "test"
|
|
65
|
+
Requires-Dist: pytest-cache; extra == "test"
|
|
66
|
+
Requires-Dist: pytest-cov; extra == "test"
|
|
67
|
+
Requires-Dist: playwright; extra == "test"
|
|
68
|
+
Requires-Dist: webtest; extra == "test"
|
|
69
|
+
Provides-Extra: dev
|
|
70
|
+
Requires-Dist: build; extra == "dev"
|
|
71
|
+
Requires-Dist: ruff; extra == "dev"
|
|
72
|
+
Requires-Dist: twine; extra == "dev"
|
|
73
|
+
Requires-Dist: uwsgi; extra == "dev"
|
|
74
|
+
Dynamic: license-file
|
|
75
|
+
|
|
76
|
+
Kinto
|
|
77
|
+
=====
|
|
78
|
+
|
|
79
|
+
|coc| |gitter| |readthedocs| |pypi| |ci| |main-coverage|
|
|
80
|
+
|
|
81
|
+
.. |coc| image:: https://img.shields.io/badge/%E2%9D%A4-code%20of%20conduct-blue.svg
|
|
82
|
+
:target: https://github.com/Kinto/kinto/blob/main/.github/CODE_OF_CONDUCT.md
|
|
83
|
+
:alt: Code of conduct
|
|
84
|
+
|
|
85
|
+
.. |gitter| image:: https://badges.gitter.im/Kinto/kinto.svg
|
|
86
|
+
:target: https://gitter.im/Kinto/kinto
|
|
87
|
+
|
|
88
|
+
.. |ci| image:: https://github.com/Kinto/kinto/actions/workflows/test.yml/badge.svg
|
|
89
|
+
:target: https://github.com/Kinto/kinto/actions
|
|
90
|
+
|
|
91
|
+
.. |readthedocs| image:: https://readthedocs.org/projects/kinto/badge/?version=latest
|
|
92
|
+
:target: https://kinto.readthedocs.io/en/latest/
|
|
93
|
+
:alt: Documentation Status
|
|
94
|
+
|
|
95
|
+
.. |main-coverage| image::
|
|
96
|
+
https://coveralls.io/repos/Kinto/kinto/badge.svg?branch=main
|
|
97
|
+
:alt: Coverage
|
|
98
|
+
:target: https://coveralls.io/r/Kinto/kinto
|
|
99
|
+
|
|
100
|
+
.. |pypi| image:: https://img.shields.io/pypi/v/kinto.svg
|
|
101
|
+
:target: https://pypi.python.org/pypi/kinto
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
Kinto is a minimalist JSON storage service with synchronisation and sharing abilities.
|
|
105
|
+
|
|
106
|
+
* `Online documentation <https://kinto.readthedocs.io/en/latest/>`_
|
|
107
|
+
* `Tutorial <https://kinto.readthedocs.io/en/latest/tutorials/first-steps.html>`_
|
|
108
|
+
* `Issue tracker <https://github.com/Kinto/kinto/issues>`_
|
|
109
|
+
* `Contributing <https://kinto.readthedocs.io/en/latest/community.html#how-to-contribute>`_
|
|
110
|
+
* `Docker Hub <https://hub.docker.com/r/kinto/kinto-server>`_
|
|
111
|
+
|
|
112
|
+
Requirements
|
|
113
|
+
------------
|
|
114
|
+
|
|
115
|
+
* **Python**: 3.9+
|
|
116
|
+
* **Backends**: In-memory (development), PostgreSQL 9.5+ (production)
|
|
117
|
+
|
|
118
|
+
Contributors
|
|
119
|
+
============
|
|
120
|
+
|
|
121
|
+
* Aaron Egaas <me@aaronegaas.com>
|
|
122
|
+
* Adam Chainz <adam@adamj.eu>
|
|
123
|
+
* Aditya Bhasin <conlini@gmail.com>
|
|
124
|
+
* Aiman Parvaiz <aimanparvaiz@gmail.com>
|
|
125
|
+
* Ajey B. Kulkarni <bkajey@gmail.com>
|
|
126
|
+
* Anh <anh.trinhtrung@gmail.com>
|
|
127
|
+
* Alexander Ryabkov <alexryabkov@gmail.com>
|
|
128
|
+
* Alexis Metaireau <alexis@mozilla.com>
|
|
129
|
+
* Alex Cottner <acottner@mozilla.com>
|
|
130
|
+
* Andy McKay <amckay@mozilla.com>
|
|
131
|
+
* Anthony Garuccio <garuccio124@gmail.com>
|
|
132
|
+
* Aymeric Faivre <miho@miho-stories.com>
|
|
133
|
+
* Ayush Sharma <ayush.aceit@gmail.com>
|
|
134
|
+
* Balthazar Rouberol <br@imap.cc>
|
|
135
|
+
* Boris Feld <lothiraldan@gmail.com>
|
|
136
|
+
* Brady Dufresne <dufresnebrady@gmail.com>
|
|
137
|
+
* Can Berk Güder <cbguder@mozilla.com>
|
|
138
|
+
* Castro
|
|
139
|
+
* Chirag B. Jadwani <chirag.jadwani@gmail.com>
|
|
140
|
+
* Christophe Gragnic <cgragnic@protonmail.com>
|
|
141
|
+
* Clément Villain <choclatefr@gmail.com>
|
|
142
|
+
* Dan Phrawzty <phrawzty+github@gmail.com>
|
|
143
|
+
* David Larlet <david@larlet.fr>
|
|
144
|
+
* Emamurho Ugherughe <emamurho@gmail.com>
|
|
145
|
+
* Enguerran Colson <enguerran@ticabri.com>
|
|
146
|
+
* Eric Bréhault <ebrehault@gmail.com>
|
|
147
|
+
* Eric Le Lay <elelay@macports.org>
|
|
148
|
+
* Éric Lemoine <eric.lemoine@gmail.com>
|
|
149
|
+
* Ethan Glasser-Camp <ethan@betacantrips.com>
|
|
150
|
+
* Étienne <@Étienne>
|
|
151
|
+
* Eugene Kulak <kulak.eugene@gmail.com>
|
|
152
|
+
* Fil <fil@rezo.net>
|
|
153
|
+
* FooBarQuaxx
|
|
154
|
+
* Francisco J. Piedrahita <@fpiedrah>
|
|
155
|
+
* Frank Bertsch <frank@mozilla.com>
|
|
156
|
+
* Greeshma <greeshmabalabadra@gmail.com>
|
|
157
|
+
* Gabriela Surita <gabsurita@gmail.com>
|
|
158
|
+
* George Smith <h3rmit@protonmail.com>
|
|
159
|
+
* Graham Beckley <gbeckley@mozilla.com>
|
|
160
|
+
* Greg Guthe <gguthe@mozilla.com>
|
|
161
|
+
* Haseeb Majid <hmajid2301@gmail.com>
|
|
162
|
+
* Heron Rossi <heron.rossi@hotmail.com>
|
|
163
|
+
* Hiromipaw <silvia@nopressure.co.uk>
|
|
164
|
+
* Indranil Dutta <duttaindranil497@gmail.com>
|
|
165
|
+
* Itai Steinherz <itaisteinherz@gmail.com>
|
|
166
|
+
* Jelmer van der Ploeg <jelmer@woovar.com>
|
|
167
|
+
* Joël Marty <@joelmarty>
|
|
168
|
+
* John Giannelos <johngiannelos@gmail.com>
|
|
169
|
+
* Joshua Bird <joshua.thomas.bird@gmail.com>
|
|
170
|
+
* Julien Bouquillon <contact@revolunet.com>
|
|
171
|
+
* Julien Lebunetel <julien@lebunetel.com>
|
|
172
|
+
* Kaloneh <kaloneh@gmail.com>
|
|
173
|
+
* Kulshekhar Kabra <@kulshekhar>
|
|
174
|
+
* Lavish Aggarwal <lucky.lavish@gmail.com>
|
|
175
|
+
* Maksym Shalenyi <supamaxy@gmail.com>
|
|
176
|
+
* Manas Mangaonkar <@Pac23>
|
|
177
|
+
* Mansimar Kaur <mansimarkaur.mks@gmail.com>
|
|
178
|
+
* Masataka Takeuchi <masataka.takeuchi@l-is-b.com>
|
|
179
|
+
* Mathieu Agopian <mathieu@agopian.info>
|
|
180
|
+
* Mathieu Leplatre <mathieu@mozilla.com>
|
|
181
|
+
* Matt Boris <mboris@mozilla.com>
|
|
182
|
+
* Maxime Warnier <marmax@gmail.com>
|
|
183
|
+
* Michael Charlton <m.charlton@mac.com>
|
|
184
|
+
* Michiel de Jong <michiel@unhosted.org>
|
|
185
|
+
* Mo Valipour <valipour@gmail.com>
|
|
186
|
+
* Mozillazg
|
|
187
|
+
* Nicolas Hoizey <nicolas@hoizey.com>
|
|
188
|
+
* Nicolas Perriault <nperriault@mozilla.com>
|
|
189
|
+
* Niraj <https://github.com/niraj8>
|
|
190
|
+
* Oron Gola <oron.golar@gmail.com>
|
|
191
|
+
* Palash Nigam <npalash25@gmail.com>
|
|
192
|
+
* Pascal Roessner <roessner.pascal@gmail.com>
|
|
193
|
+
* PeriGK <per.gkolias@gmail.com>
|
|
194
|
+
* Peter Bengtsson <mail@peterbe.com>
|
|
195
|
+
* Peter Rassias <ubcpeter@hotmail.com>
|
|
196
|
+
* realsumit <sumitsarinofficial@gmail.com>
|
|
197
|
+
* Rektide <rektide@voodoowarez.com>
|
|
198
|
+
* Rémy Hubscher <rhubscher@mozilla.com>
|
|
199
|
+
* Renisha Nellums <r.nellums@gmail.com>
|
|
200
|
+
* Ricardo <@rkleine>
|
|
201
|
+
* Rodolphe Quiédeville <rodolphe@quiedeville.org>
|
|
202
|
+
* Sahil Dua <sahildua2305@gmail.com>
|
|
203
|
+
* Sebastian Rodriguez <srodrigu85@gmail.com>
|
|
204
|
+
* Sergey Maranchuk <https://github.com/slav0nic/>
|
|
205
|
+
* Stanisław Wasiutyński <https://github.com/stanley>
|
|
206
|
+
* Stephen Daves <contact@stephendaves.com>
|
|
207
|
+
* Stephen Donner <stephen.donner@gmail.com>
|
|
208
|
+
* Stephen Martin <lockwood@opperline.com>
|
|
209
|
+
* Shweta Oak <oakshweta11@gmail.com>
|
|
210
|
+
* Sofia Utsch <sofia.utsch@gmail.com>
|
|
211
|
+
* Sumit Sarin <sumitsarinofficial@gmail.com>
|
|
212
|
+
* Sunakshi Tejwani <sunakshitejwani@gmail.com>
|
|
213
|
+
* Surya Prashanth <prashantsurya@ymail.com>
|
|
214
|
+
* SwhGo_oN <@swhgoon>
|
|
215
|
+
* Tarek Ziade <tarek@mozilla.com>
|
|
216
|
+
* Taus Brock-Nannestad <taus@semmle.com>
|
|
217
|
+
* Taylor Zane Glaeser <tzglaeser@gmail.com>
|
|
218
|
+
* Thomas Dressler <Thomas.Dressler1@gmail.com>
|
|
219
|
+
* Tiberiu Ichim <@tiberiuichim>
|
|
220
|
+
* Vamsi Sangam <vamsisangam@live.com>
|
|
221
|
+
* Varna Suresh <varna96@gmail.com>
|
|
222
|
+
* Vincent Fretin <@vincentfretin>
|
|
223
|
+
* Vitor Falcao <vitor.falcaor@gmail.com>
|
|
224
|
+
* Wil Clouser <wclouser@mozilla.com>
|
|
225
|
+
* Yann Klis <yann.klis@gmail.com>
|
|
226
|
+
* Jeff Schobelock <jswhatnot15@gmail.com>
|
|
227
|
+
* Shivasheesh Yadav <shivasheeshyadav@gmail.com>
|
|
228
|
+
* Fabian Chong <@feiming>
|
|
229
|
+
* Dan Milgram <danm@intervivo.com>
|
|
230
|
+
* Dex Devlon <@bxff>
|
|
231
|
+
* Varun Koranne <@varun-dhruv>
|
|
232
|
+
* Robin Sharma <robinrythm123@gmail.com>
|