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
kinto/__init__.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
import pkg_resources
|
|
4
|
+
from pyramid.authorization import Authenticated, Everyone
|
|
5
|
+
from pyramid.config import Configurator
|
|
6
|
+
from pyramid.settings import asbool
|
|
7
|
+
|
|
8
|
+
import kinto.core
|
|
9
|
+
from kinto.authorization import RouteFactory
|
|
10
|
+
from kinto.core import utils
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# Module version, as defined in PEP-0396.
|
|
14
|
+
__version__ = pkg_resources.get_distribution(__package__).version
|
|
15
|
+
|
|
16
|
+
# Implemented HTTP API Version
|
|
17
|
+
HTTP_API_VERSION = "1.23"
|
|
18
|
+
|
|
19
|
+
# Main kinto logger
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
DEFAULT_SETTINGS = {
|
|
24
|
+
"retry_after_seconds": 3,
|
|
25
|
+
"cache_backend": "kinto.core.cache.memory",
|
|
26
|
+
"permission_backend": "kinto.core.permission.memory",
|
|
27
|
+
"storage_backend": "kinto.core.storage.memory",
|
|
28
|
+
"project_docs": "https://kinto.readthedocs.io/",
|
|
29
|
+
"bucket_create_principals": Authenticated,
|
|
30
|
+
"permissions_read_principals": Everyone,
|
|
31
|
+
"multiauth.authorization_policy": ("kinto.authorization.AuthorizationPolicy"),
|
|
32
|
+
"experimental_collection_schema_validation": False,
|
|
33
|
+
"experimental_permissions_endpoint": False,
|
|
34
|
+
"http_api_version": utils.json_serializer(HTTP_API_VERSION),
|
|
35
|
+
"bucket_id_generator": "kinto.views.NameGenerator",
|
|
36
|
+
"collection_id_generator": "kinto.views.NameGenerator",
|
|
37
|
+
"group_id_generator": "kinto.views.NameGenerator",
|
|
38
|
+
"record_id_generator": "kinto.views.RelaxedUUID",
|
|
39
|
+
"project_name": "kinto",
|
|
40
|
+
"admin_assets_path": None,
|
|
41
|
+
"metrics_matchdict_fields": ["bucket_id", "collection_id", "group_id", "record_id"],
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def main(global_config, config=None, **settings):
|
|
46
|
+
if not config:
|
|
47
|
+
config = Configurator(settings=settings, root_factory=RouteFactory)
|
|
48
|
+
|
|
49
|
+
config.registry.command = global_config and global_config.get("command", None)
|
|
50
|
+
|
|
51
|
+
# Force settings prefix.
|
|
52
|
+
config.add_settings({"kinto.settings_prefix": "kinto"})
|
|
53
|
+
|
|
54
|
+
kinto.core.initialize(config, version=__version__, default_settings=DEFAULT_SETTINGS)
|
|
55
|
+
|
|
56
|
+
settings = config.get_settings()
|
|
57
|
+
|
|
58
|
+
# Expose capability
|
|
59
|
+
schema_enabled = asbool(settings["experimental_collection_schema_validation"])
|
|
60
|
+
if schema_enabled:
|
|
61
|
+
config.add_api_capability(
|
|
62
|
+
"schema",
|
|
63
|
+
description="Validates collection records with JSON schemas.",
|
|
64
|
+
url="https://kinto.readthedocs.io/en/latest/api/1.x/"
|
|
65
|
+
"collections.html#collection-json-schema",
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# Scan Kinto views.
|
|
69
|
+
kwargs = {}
|
|
70
|
+
|
|
71
|
+
# Permissions endpoint enabled if permission backend is setup.
|
|
72
|
+
is_admin_enabled = "kinto.plugins.admin" in settings["includes"]
|
|
73
|
+
permissions_endpoint_enabled = (
|
|
74
|
+
is_admin_enabled or asbool(settings["experimental_permissions_endpoint"])
|
|
75
|
+
) and hasattr(config.registry, "permission")
|
|
76
|
+
if permissions_endpoint_enabled:
|
|
77
|
+
config.add_api_capability(
|
|
78
|
+
"permissions_endpoint",
|
|
79
|
+
description="The permissions endpoint can be used to list all "
|
|
80
|
+
"user objects permissions.",
|
|
81
|
+
url="https://kinto.readthedocs.io/en/latest/configuration/"
|
|
82
|
+
"settings.html#activating-the-permissions-endpoint",
|
|
83
|
+
)
|
|
84
|
+
else:
|
|
85
|
+
kwargs.setdefault("ignore", []).append("kinto.views.permissions")
|
|
86
|
+
|
|
87
|
+
config.scan("kinto.views", **kwargs)
|
|
88
|
+
|
|
89
|
+
app = config.make_wsgi_app()
|
|
90
|
+
|
|
91
|
+
# Install middleware (no-op if disabled)
|
|
92
|
+
return kinto.core.install_middlewares(app, settings)
|
kinto/__main__.py
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import logging
|
|
3
|
+
import logging.config
|
|
4
|
+
import os
|
|
5
|
+
import subprocess
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
from pyramid.paster import bootstrap
|
|
9
|
+
from pyramid.scripts import pserve
|
|
10
|
+
|
|
11
|
+
from kinto import __version__
|
|
12
|
+
from kinto.config import init
|
|
13
|
+
from kinto.core import scripts as core_scripts
|
|
14
|
+
from kinto.plugins.accounts import scripts as accounts_scripts
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
DEFAULT_CONFIG_FILE = os.getenv("KINTO_INI", "config/kinto.ini")
|
|
18
|
+
DEFAULT_PORT = 8888
|
|
19
|
+
DEFAULT_LOG_LEVEL = logging.INFO
|
|
20
|
+
DEFAULT_LOG_FORMAT = "%(levelname)-5.5s %(message)s"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def main(args=None):
|
|
24
|
+
"""The main routine."""
|
|
25
|
+
if args is None:
|
|
26
|
+
args = sys.argv[1:]
|
|
27
|
+
|
|
28
|
+
parser = argparse.ArgumentParser(description="Kinto Command-Line Interface")
|
|
29
|
+
commands = (
|
|
30
|
+
"init",
|
|
31
|
+
"start",
|
|
32
|
+
"migrate",
|
|
33
|
+
"flush-cache",
|
|
34
|
+
"version",
|
|
35
|
+
"create-user",
|
|
36
|
+
"purge-deleted",
|
|
37
|
+
)
|
|
38
|
+
subparsers = parser.add_subparsers(
|
|
39
|
+
title="subcommands",
|
|
40
|
+
description="Main Kinto CLI commands",
|
|
41
|
+
dest="subcommand",
|
|
42
|
+
help="Choose and run with --help",
|
|
43
|
+
)
|
|
44
|
+
subparsers.required = True
|
|
45
|
+
|
|
46
|
+
for command in commands:
|
|
47
|
+
subparser = subparsers.add_parser(command)
|
|
48
|
+
subparser.set_defaults(which=command)
|
|
49
|
+
|
|
50
|
+
subparser.add_argument(
|
|
51
|
+
"--ini",
|
|
52
|
+
help="Application configuration file",
|
|
53
|
+
dest="ini_file",
|
|
54
|
+
required=False,
|
|
55
|
+
default=DEFAULT_CONFIG_FILE,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
subparser.add_argument(
|
|
59
|
+
"-q",
|
|
60
|
+
"--quiet",
|
|
61
|
+
action="store_const",
|
|
62
|
+
const=logging.CRITICAL,
|
|
63
|
+
dest="verbosity",
|
|
64
|
+
help="Show only critical errors.",
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
subparser.add_argument(
|
|
68
|
+
"-v",
|
|
69
|
+
"--debug",
|
|
70
|
+
action="store_const",
|
|
71
|
+
const=logging.DEBUG,
|
|
72
|
+
dest="verbosity",
|
|
73
|
+
help="Show all messages, including debug messages.",
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
if command == "init":
|
|
77
|
+
subparser.add_argument(
|
|
78
|
+
"--backend",
|
|
79
|
+
help="{memory,postgresql}",
|
|
80
|
+
dest="backend",
|
|
81
|
+
required=False,
|
|
82
|
+
default=None,
|
|
83
|
+
)
|
|
84
|
+
subparser.add_argument(
|
|
85
|
+
"--cache-backend",
|
|
86
|
+
help="{memory,postgresql,memcached}",
|
|
87
|
+
dest="cache-backend",
|
|
88
|
+
required=False,
|
|
89
|
+
default=None,
|
|
90
|
+
)
|
|
91
|
+
subparser.add_argument(
|
|
92
|
+
"--host",
|
|
93
|
+
help="Host to listen() on.",
|
|
94
|
+
dest="host",
|
|
95
|
+
required=False,
|
|
96
|
+
default="127.0.0.1",
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
elif command == "migrate":
|
|
100
|
+
subparser.add_argument(
|
|
101
|
+
"--dry-run",
|
|
102
|
+
action="store_true",
|
|
103
|
+
help="Simulate the migration operations and show information",
|
|
104
|
+
dest="dry_run",
|
|
105
|
+
required=False,
|
|
106
|
+
default=False,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
elif command == "start":
|
|
110
|
+
subparser.add_argument(
|
|
111
|
+
"--reload",
|
|
112
|
+
action="store_true",
|
|
113
|
+
help="Restart when code or config changes",
|
|
114
|
+
required=False,
|
|
115
|
+
default=False,
|
|
116
|
+
)
|
|
117
|
+
subparser.add_argument(
|
|
118
|
+
"--port",
|
|
119
|
+
type=int,
|
|
120
|
+
help="Listening port number",
|
|
121
|
+
required=False,
|
|
122
|
+
default=DEFAULT_PORT,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
elif command == "create-user":
|
|
126
|
+
subparser.add_argument(
|
|
127
|
+
"-u", "--username", help="Superuser username", required=False, default=None
|
|
128
|
+
)
|
|
129
|
+
subparser.add_argument(
|
|
130
|
+
"-p", "--password", help="Superuser password", required=False, default=None
|
|
131
|
+
)
|
|
132
|
+
elif command == "purge-deleted":
|
|
133
|
+
subparser.add_argument(
|
|
134
|
+
"resources", # No '--' → positional
|
|
135
|
+
nargs="+", # Accepts one or more
|
|
136
|
+
help="List of resources (e.g. record bucket group)",
|
|
137
|
+
default=["record"],
|
|
138
|
+
)
|
|
139
|
+
subparser.add_argument(
|
|
140
|
+
"max-retained",
|
|
141
|
+
help="The maximum number of tombstones to keep per resource and per parent",
|
|
142
|
+
type=int,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
# Parse command-line arguments
|
|
146
|
+
parsed_args = vars(parser.parse_args(args))
|
|
147
|
+
|
|
148
|
+
config_file = parsed_args["ini_file"]
|
|
149
|
+
which_command = parsed_args["which"]
|
|
150
|
+
|
|
151
|
+
# Initialize logging from
|
|
152
|
+
level = parsed_args.get("verbosity") or DEFAULT_LOG_LEVEL
|
|
153
|
+
logging.basicConfig(level=level, format=DEFAULT_LOG_FORMAT)
|
|
154
|
+
|
|
155
|
+
if which_command == "init":
|
|
156
|
+
if os.path.exists(config_file):
|
|
157
|
+
print(f"{config_file} already exists.", file=sys.stderr)
|
|
158
|
+
return 1
|
|
159
|
+
|
|
160
|
+
backend = parsed_args["backend"]
|
|
161
|
+
cache_backend = parsed_args["cache-backend"]
|
|
162
|
+
if not backend:
|
|
163
|
+
while True:
|
|
164
|
+
prompt = (
|
|
165
|
+
"Select the backend you would like to use: (1 - postgresql, default - memory) "
|
|
166
|
+
)
|
|
167
|
+
answer = input(prompt).strip()
|
|
168
|
+
try:
|
|
169
|
+
backends = {"1": "postgresql", "": "memory"}
|
|
170
|
+
backend = backends[answer]
|
|
171
|
+
break
|
|
172
|
+
except KeyError:
|
|
173
|
+
pass
|
|
174
|
+
|
|
175
|
+
if not cache_backend:
|
|
176
|
+
while True:
|
|
177
|
+
prompt = (
|
|
178
|
+
"Select the cache backend you would like to use: "
|
|
179
|
+
"(1 - postgresql, 2 - memcached, default - memory) "
|
|
180
|
+
)
|
|
181
|
+
answer = input(prompt).strip()
|
|
182
|
+
try:
|
|
183
|
+
cache_backends = {
|
|
184
|
+
"1": "postgresql",
|
|
185
|
+
"2": "memcached",
|
|
186
|
+
"": "memory",
|
|
187
|
+
}
|
|
188
|
+
cache_backend = cache_backends[answer]
|
|
189
|
+
break
|
|
190
|
+
except KeyError:
|
|
191
|
+
pass
|
|
192
|
+
|
|
193
|
+
init(config_file, backend, cache_backend, parsed_args["host"])
|
|
194
|
+
|
|
195
|
+
# Install postgresql libraries if necessary
|
|
196
|
+
if backend == "postgresql" or cache_backend == "postgresql":
|
|
197
|
+
try:
|
|
198
|
+
import psycopg2 # NOQA
|
|
199
|
+
except ImportError:
|
|
200
|
+
subprocess.check_call(
|
|
201
|
+
[sys.executable, "-m", "pip", "install", "kinto[postgresql]"]
|
|
202
|
+
)
|
|
203
|
+
elif cache_backend == "memcached":
|
|
204
|
+
try:
|
|
205
|
+
import memcache # NOQA
|
|
206
|
+
except ImportError:
|
|
207
|
+
subprocess.check_call([sys.executable, "-m", "pip", "install", "kinto[memcached]"])
|
|
208
|
+
|
|
209
|
+
elif which_command == "migrate":
|
|
210
|
+
dry_run = parsed_args["dry_run"]
|
|
211
|
+
env = bootstrap(config_file, options={"command": "migrate"})
|
|
212
|
+
core_scripts.migrate(env, dry_run=dry_run)
|
|
213
|
+
|
|
214
|
+
elif which_command == "flush-cache":
|
|
215
|
+
env = bootstrap(config_file, options={"command": "flush-cache"})
|
|
216
|
+
core_scripts.flush_cache(env)
|
|
217
|
+
|
|
218
|
+
elif which_command == "create-user":
|
|
219
|
+
username = parsed_args["username"]
|
|
220
|
+
password = parsed_args["password"]
|
|
221
|
+
env = bootstrap(config_file, options={"command": "create-user"})
|
|
222
|
+
return accounts_scripts.create_user(env, username=username, password=password)
|
|
223
|
+
|
|
224
|
+
elif which_command == "purge-deleted":
|
|
225
|
+
env = bootstrap(config_file)
|
|
226
|
+
return core_scripts.purge_deleted(
|
|
227
|
+
env, parsed_args["resources"], parsed_args["max-retained"]
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
elif which_command == "start":
|
|
231
|
+
pserve_argv = ["pserve"]
|
|
232
|
+
|
|
233
|
+
if parsed_args["reload"]:
|
|
234
|
+
pserve_argv.append("--reload")
|
|
235
|
+
|
|
236
|
+
if level == logging.DEBUG:
|
|
237
|
+
pserve_argv.append("-v")
|
|
238
|
+
|
|
239
|
+
if level == logging.CRITICAL:
|
|
240
|
+
pserve_argv.append("-q")
|
|
241
|
+
|
|
242
|
+
pserve_argv.append(config_file)
|
|
243
|
+
pserve_argv.append(f"http_port={parsed_args['port']}")
|
|
244
|
+
pserve.main(argv=pserve_argv)
|
|
245
|
+
|
|
246
|
+
else:
|
|
247
|
+
print(__version__)
|
|
248
|
+
|
|
249
|
+
return 0
|
kinto/authorization.py
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
from pyramid.interfaces import IAuthorizationPolicy
|
|
2
|
+
from zope.interface import implementer
|
|
3
|
+
|
|
4
|
+
from kinto.core import authorization as core_authorization
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# Vocab really matters when you deal with permissions. Let's do a quick recap
|
|
8
|
+
# of the terms used here:
|
|
9
|
+
#
|
|
10
|
+
# Object URI:
|
|
11
|
+
# An unique identifier for an object.
|
|
12
|
+
# for instance, /buckets/blog/collections/articles/records/article1
|
|
13
|
+
#
|
|
14
|
+
# Object:
|
|
15
|
+
# A common denomination of an object (e.g. "collection" or "record")
|
|
16
|
+
#
|
|
17
|
+
# Unbound permission:
|
|
18
|
+
# A permission not bound to an object (e.g. "create")
|
|
19
|
+
#
|
|
20
|
+
# Bound permission:
|
|
21
|
+
# A permission bound to an object (e.g. "collection:create")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# Dictionary that associates single permissions to any other permission that
|
|
25
|
+
# automatically provides it
|
|
26
|
+
# Ex: bucket:read is granted by both bucket:write and bucket:read
|
|
27
|
+
PERMISSIONS_INHERITANCE_TREE = {
|
|
28
|
+
"root": {"bucket:create": {}, "write": {}, "read": {}}, # Granted via settings only.
|
|
29
|
+
"bucket": {
|
|
30
|
+
"write": {"bucket": ["write"]},
|
|
31
|
+
"read": {"bucket": ["write", "read"]},
|
|
32
|
+
"read:attributes": {"bucket": ["write", "read", "collection:create", "group:create"]},
|
|
33
|
+
"group:create": {"bucket": ["write", "group:create"]},
|
|
34
|
+
"collection:create": {"bucket": ["write", "collection:create"]},
|
|
35
|
+
},
|
|
36
|
+
"group": {
|
|
37
|
+
"write": {"bucket": ["write"], "group": ["write"]},
|
|
38
|
+
"read": {"bucket": ["write", "read"], "group": ["write", "read"]},
|
|
39
|
+
},
|
|
40
|
+
"collection": {
|
|
41
|
+
"write": {"bucket": ["write"], "collection": ["write"]},
|
|
42
|
+
"read": {"bucket": ["write", "read"], "collection": ["write", "read"]},
|
|
43
|
+
"read:attributes": {
|
|
44
|
+
"bucket": ["write", "read"],
|
|
45
|
+
"collection": ["write", "read", "record:create"],
|
|
46
|
+
},
|
|
47
|
+
"record:create": {"bucket": ["write"], "collection": ["write", "record:create"]},
|
|
48
|
+
},
|
|
49
|
+
"record": {
|
|
50
|
+
"write": {"bucket": ["write"], "collection": ["write"], "record": ["write"]},
|
|
51
|
+
"read": {
|
|
52
|
+
"bucket": ["write", "read"],
|
|
53
|
+
"collection": ["write", "read"],
|
|
54
|
+
"record": ["write", "read"],
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _resource_endpoint(object_uri):
|
|
61
|
+
"""Determine the resource name and whether it is the plural endpoint from
|
|
62
|
+
the specified `object_uri`. Returns `(None, None)` for the root URL plural
|
|
63
|
+
endpoint.
|
|
64
|
+
"""
|
|
65
|
+
obj_parts = object_uri.split("/")
|
|
66
|
+
plural_endpoint = len(obj_parts) % 2 == 0
|
|
67
|
+
if plural_endpoint:
|
|
68
|
+
# /buckets/bid/collections -> /buckets/bid
|
|
69
|
+
obj_parts = obj_parts[:-1]
|
|
70
|
+
|
|
71
|
+
if len(obj_parts) <= 2:
|
|
72
|
+
# Root URL /buckets -> ('', False)
|
|
73
|
+
return "", False
|
|
74
|
+
|
|
75
|
+
# /buckets/bid -> buckets
|
|
76
|
+
resource_name = obj_parts[-2]
|
|
77
|
+
# buckets -> bucket
|
|
78
|
+
resource_name = resource_name.rstrip("s")
|
|
79
|
+
return resource_name, plural_endpoint
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _relative_object_uri(resource_name, object_uri):
|
|
83
|
+
"""Returns object_uri"""
|
|
84
|
+
obj_parts = object_uri.split("/")
|
|
85
|
+
for length in range(len(obj_parts) + 1):
|
|
86
|
+
parent_uri = "/".join(obj_parts[:length])
|
|
87
|
+
parent_resource_name, _ = _resource_endpoint(parent_uri)
|
|
88
|
+
if resource_name == parent_resource_name:
|
|
89
|
+
return parent_uri
|
|
90
|
+
|
|
91
|
+
error_msg = f"Cannot get URL of resource '{resource_name}' from parent '{object_uri}'."
|
|
92
|
+
raise ValueError(error_msg)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _inherited_permissions(object_uri, permission):
|
|
96
|
+
"""Build the list of all permissions that can grant access to the given
|
|
97
|
+
object URI and permission.
|
|
98
|
+
|
|
99
|
+
>>> _inherited_permissions('/buckets/blog/collections/article', 'read')
|
|
100
|
+
[('/buckets/blog/collections/article', 'write'),
|
|
101
|
+
('/buckets/blog/collections/article', 'read'),
|
|
102
|
+
('/buckets/blog', 'write'),
|
|
103
|
+
('/buckets/blog', 'read')]
|
|
104
|
+
|
|
105
|
+
"""
|
|
106
|
+
resource_name, plural = _resource_endpoint(object_uri)
|
|
107
|
+
try:
|
|
108
|
+
object_perms_tree = PERMISSIONS_INHERITANCE_TREE[resource_name]
|
|
109
|
+
except KeyError:
|
|
110
|
+
return [] # URL that are not resources have no inherited perms.
|
|
111
|
+
|
|
112
|
+
# When requesting permissions for a single object, we check if they are any
|
|
113
|
+
# specific inherited permissions for the attributes.
|
|
114
|
+
attributes_permission = f"{permission}:attributes" if not plural else permission
|
|
115
|
+
inherited_perms = object_perms_tree.get(attributes_permission, object_perms_tree[permission])
|
|
116
|
+
|
|
117
|
+
granters = set()
|
|
118
|
+
for related_resource_name, implicit_permissions in inherited_perms.items():
|
|
119
|
+
for permission in implicit_permissions:
|
|
120
|
+
related_uri = _relative_object_uri(related_resource_name, object_uri)
|
|
121
|
+
granters.add((related_uri, permission))
|
|
122
|
+
|
|
123
|
+
# Sort by ascending URLs.
|
|
124
|
+
return sorted(granters, key=lambda uri_perm: len(uri_perm[0]), reverse=True)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@implementer(IAuthorizationPolicy)
|
|
128
|
+
class AuthorizationPolicy(core_authorization.AuthorizationPolicy):
|
|
129
|
+
def get_bound_permissions(self, *args, **kwargs):
|
|
130
|
+
return _inherited_permissions(*args, **kwargs)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class RouteFactory(core_authorization.RouteFactory):
|
|
134
|
+
pass
|
kinto/config/__init__.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import codecs
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from functools import lru_cache
|
|
6
|
+
from hashlib import sha256
|
|
7
|
+
from time import strftime
|
|
8
|
+
|
|
9
|
+
from kinto import __version__
|
|
10
|
+
from kinto.core import utils as core_utils
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
HERE = os.path.dirname(__file__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def render_template(template, destination, **kwargs):
|
|
19
|
+
template = os.path.join(HERE, template)
|
|
20
|
+
folder = os.path.dirname(destination)
|
|
21
|
+
|
|
22
|
+
if folder and not os.path.exists(folder):
|
|
23
|
+
os.makedirs(folder)
|
|
24
|
+
|
|
25
|
+
logger.info(f"Created config {os.path.abspath(destination)}")
|
|
26
|
+
|
|
27
|
+
with codecs.open(template, "r", encoding="utf-8") as f:
|
|
28
|
+
raw_template = f.read()
|
|
29
|
+
rendered = raw_template.format_map(kwargs)
|
|
30
|
+
with codecs.open(destination, "w+", encoding="utf-8") as output:
|
|
31
|
+
output.write(rendered)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
postgresql_url = "postgresql://postgres:postgres@localhost/postgres"
|
|
35
|
+
|
|
36
|
+
backend_to_values = {
|
|
37
|
+
"postgresql": {
|
|
38
|
+
"storage_backend": "kinto.core.storage.postgresql",
|
|
39
|
+
"storage_url": postgresql_url,
|
|
40
|
+
"permission_backend": "kinto.core.permission.postgresql",
|
|
41
|
+
"permission_url": postgresql_url,
|
|
42
|
+
},
|
|
43
|
+
"memory": {
|
|
44
|
+
"storage_backend": "kinto.core.storage.memory",
|
|
45
|
+
"storage_url": "",
|
|
46
|
+
"permission_backend": "kinto.core.permission.memory",
|
|
47
|
+
"permission_url": "",
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
cache_backend_to_values = {
|
|
52
|
+
"postgresql": {"cache_backend": "kinto.core.cache.postgresql", "cache_url": postgresql_url},
|
|
53
|
+
"memcached": {
|
|
54
|
+
"cache_backend": "kinto.core.cache.memcached",
|
|
55
|
+
"cache_url": "127.0.0.1:11211 127.0.0.2:11211",
|
|
56
|
+
},
|
|
57
|
+
"memory": {"cache_backend": "kinto.core.cache.memory", "cache_url": ""},
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def init(config_file, backend, cache_backend, host="127.0.0.1"):
|
|
62
|
+
values = {}
|
|
63
|
+
|
|
64
|
+
values["host"] = host
|
|
65
|
+
values["secret"] = core_utils.random_bytes_hex(32)
|
|
66
|
+
values["bucket_id_salt"] = core_utils.random_bytes_hex(32)
|
|
67
|
+
|
|
68
|
+
values["kinto_version"] = __version__
|
|
69
|
+
values["config_file_timestamp"] = str(strftime("%a, %d %b %Y %H:%M:%S %z"))
|
|
70
|
+
|
|
71
|
+
values.update(backend_to_values[backend])
|
|
72
|
+
values.update(cache_backend_to_values[cache_backend])
|
|
73
|
+
|
|
74
|
+
render_template("kinto.tpl", config_file, **values)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@lru_cache(maxsize=1)
|
|
78
|
+
def config_attributes():
|
|
79
|
+
"""
|
|
80
|
+
Returns a hash of the config `.ini` file content.
|
|
81
|
+
The path is only known from `app.wsgi`, so we have to read
|
|
82
|
+
the environment variable again. Since tests are not run through
|
|
83
|
+
WSGI, then the variable is not set.
|
|
84
|
+
"""
|
|
85
|
+
# WARNING: this default value should be the same as `app.wsgi`
|
|
86
|
+
ini_path = os.environ.get("KINTO_INI", os.path.join(".", "config", "kinto.ini"))
|
|
87
|
+
if not os.path.exists(ini_path):
|
|
88
|
+
logger.error(f"Could not find config file at {ini_path}")
|
|
89
|
+
return None
|
|
90
|
+
return {
|
|
91
|
+
"path": ini_path,
|
|
92
|
+
"hash": sha256(open(ini_path, "rb").read()).hexdigest(),
|
|
93
|
+
"modified": datetime.fromtimestamp(os.path.getmtime(ini_path)).isoformat(),
|
|
94
|
+
}
|