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.
Files changed (142) hide show
  1. kinto/__init__.py +92 -0
  2. kinto/__main__.py +249 -0
  3. kinto/authorization.py +134 -0
  4. kinto/config/__init__.py +94 -0
  5. kinto/config/kinto.tpl +270 -0
  6. kinto/contribute.json +27 -0
  7. kinto/core/__init__.py +246 -0
  8. kinto/core/authentication.py +48 -0
  9. kinto/core/authorization.py +311 -0
  10. kinto/core/cache/__init__.py +131 -0
  11. kinto/core/cache/memcached.py +112 -0
  12. kinto/core/cache/memory.py +104 -0
  13. kinto/core/cache/postgresql/__init__.py +178 -0
  14. kinto/core/cache/postgresql/schema.sql +23 -0
  15. kinto/core/cache/testing.py +208 -0
  16. kinto/core/cornice/__init__.py +93 -0
  17. kinto/core/cornice/cors.py +144 -0
  18. kinto/core/cornice/errors.py +40 -0
  19. kinto/core/cornice/pyramidhook.py +373 -0
  20. kinto/core/cornice/renderer.py +89 -0
  21. kinto/core/cornice/resource.py +205 -0
  22. kinto/core/cornice/service.py +641 -0
  23. kinto/core/cornice/util.py +138 -0
  24. kinto/core/cornice/validators/__init__.py +94 -0
  25. kinto/core/cornice/validators/_colander.py +142 -0
  26. kinto/core/cornice/validators/_marshmallow.py +182 -0
  27. kinto/core/cornice_swagger/__init__.py +92 -0
  28. kinto/core/cornice_swagger/converters/__init__.py +21 -0
  29. kinto/core/cornice_swagger/converters/exceptions.py +6 -0
  30. kinto/core/cornice_swagger/converters/parameters.py +90 -0
  31. kinto/core/cornice_swagger/converters/schema.py +249 -0
  32. kinto/core/cornice_swagger/swagger.py +725 -0
  33. kinto/core/cornice_swagger/templates/index.html +73 -0
  34. kinto/core/cornice_swagger/templates/index_script_template.html +21 -0
  35. kinto/core/cornice_swagger/util.py +42 -0
  36. kinto/core/cornice_swagger/views.py +78 -0
  37. kinto/core/decorators.py +74 -0
  38. kinto/core/errors.py +216 -0
  39. kinto/core/events.py +301 -0
  40. kinto/core/initialization.py +738 -0
  41. kinto/core/listeners/__init__.py +9 -0
  42. kinto/core/metrics.py +94 -0
  43. kinto/core/openapi.py +115 -0
  44. kinto/core/permission/__init__.py +202 -0
  45. kinto/core/permission/memory.py +167 -0
  46. kinto/core/permission/postgresql/__init__.py +489 -0
  47. kinto/core/permission/postgresql/migrations/migration_001_002.sql +18 -0
  48. kinto/core/permission/postgresql/schema.sql +41 -0
  49. kinto/core/permission/testing.py +487 -0
  50. kinto/core/resource/__init__.py +1311 -0
  51. kinto/core/resource/model.py +412 -0
  52. kinto/core/resource/schema.py +502 -0
  53. kinto/core/resource/viewset.py +230 -0
  54. kinto/core/schema.py +119 -0
  55. kinto/core/scripts.py +50 -0
  56. kinto/core/statsd.py +1 -0
  57. kinto/core/storage/__init__.py +436 -0
  58. kinto/core/storage/exceptions.py +53 -0
  59. kinto/core/storage/generators.py +58 -0
  60. kinto/core/storage/memory.py +651 -0
  61. kinto/core/storage/postgresql/__init__.py +1131 -0
  62. kinto/core/storage/postgresql/client.py +120 -0
  63. kinto/core/storage/postgresql/migrations/migration_001_002.sql +10 -0
  64. kinto/core/storage/postgresql/migrations/migration_002_003.sql +33 -0
  65. kinto/core/storage/postgresql/migrations/migration_003_004.sql +18 -0
  66. kinto/core/storage/postgresql/migrations/migration_004_005.sql +20 -0
  67. kinto/core/storage/postgresql/migrations/migration_005_006.sql +11 -0
  68. kinto/core/storage/postgresql/migrations/migration_006_007.sql +74 -0
  69. kinto/core/storage/postgresql/migrations/migration_007_008.sql +66 -0
  70. kinto/core/storage/postgresql/migrations/migration_008_009.sql +41 -0
  71. kinto/core/storage/postgresql/migrations/migration_009_010.sql +98 -0
  72. kinto/core/storage/postgresql/migrations/migration_010_011.sql +14 -0
  73. kinto/core/storage/postgresql/migrations/migration_011_012.sql +9 -0
  74. kinto/core/storage/postgresql/migrations/migration_012_013.sql +71 -0
  75. kinto/core/storage/postgresql/migrations/migration_013_014.sql +14 -0
  76. kinto/core/storage/postgresql/migrations/migration_014_015.sql +95 -0
  77. kinto/core/storage/postgresql/migrations/migration_015_016.sql +4 -0
  78. kinto/core/storage/postgresql/migrations/migration_016_017.sql +81 -0
  79. kinto/core/storage/postgresql/migrations/migration_017_018.sql +25 -0
  80. kinto/core/storage/postgresql/migrations/migration_018_019.sql +8 -0
  81. kinto/core/storage/postgresql/migrations/migration_019_020.sql +7 -0
  82. kinto/core/storage/postgresql/migrations/migration_020_021.sql +68 -0
  83. kinto/core/storage/postgresql/migrations/migration_021_022.sql +62 -0
  84. kinto/core/storage/postgresql/migrations/migration_022_023.sql +5 -0
  85. kinto/core/storage/postgresql/migrations/migration_023_024.sql +6 -0
  86. kinto/core/storage/postgresql/migrations/migration_024_025.sql +6 -0
  87. kinto/core/storage/postgresql/migrator.py +98 -0
  88. kinto/core/storage/postgresql/pool.py +55 -0
  89. kinto/core/storage/postgresql/schema.sql +143 -0
  90. kinto/core/storage/testing.py +1857 -0
  91. kinto/core/storage/utils.py +37 -0
  92. kinto/core/testing.py +182 -0
  93. kinto/core/utils.py +553 -0
  94. kinto/core/views/__init__.py +0 -0
  95. kinto/core/views/batch.py +163 -0
  96. kinto/core/views/errors.py +145 -0
  97. kinto/core/views/heartbeat.py +106 -0
  98. kinto/core/views/hello.py +69 -0
  99. kinto/core/views/openapi.py +35 -0
  100. kinto/core/views/version.py +50 -0
  101. kinto/events.py +3 -0
  102. kinto/plugins/__init__.py +0 -0
  103. kinto/plugins/accounts/__init__.py +94 -0
  104. kinto/plugins/accounts/authentication.py +63 -0
  105. kinto/plugins/accounts/scripts.py +61 -0
  106. kinto/plugins/accounts/utils.py +13 -0
  107. kinto/plugins/accounts/views.py +136 -0
  108. kinto/plugins/admin/README.md +3 -0
  109. kinto/plugins/admin/VERSION +1 -0
  110. kinto/plugins/admin/__init__.py +40 -0
  111. kinto/plugins/admin/build/VERSION +1 -0
  112. kinto/plugins/admin/build/assets/index-CYFwtKtL.css +6 -0
  113. kinto/plugins/admin/build/assets/index-DJ0m93zA.js +149 -0
  114. kinto/plugins/admin/build/assets/logo-VBRiKSPX.png +0 -0
  115. kinto/plugins/admin/build/index.html +18 -0
  116. kinto/plugins/admin/public/help.html +25 -0
  117. kinto/plugins/admin/views.py +42 -0
  118. kinto/plugins/default_bucket/__init__.py +191 -0
  119. kinto/plugins/flush.py +28 -0
  120. kinto/plugins/history/__init__.py +65 -0
  121. kinto/plugins/history/listener.py +181 -0
  122. kinto/plugins/history/views.py +66 -0
  123. kinto/plugins/openid/__init__.py +131 -0
  124. kinto/plugins/openid/utils.py +14 -0
  125. kinto/plugins/openid/views.py +193 -0
  126. kinto/plugins/prometheus.py +300 -0
  127. kinto/plugins/statsd.py +85 -0
  128. kinto/schema_validation.py +135 -0
  129. kinto/views/__init__.py +34 -0
  130. kinto/views/admin.py +195 -0
  131. kinto/views/buckets.py +45 -0
  132. kinto/views/collections.py +58 -0
  133. kinto/views/contribute.py +39 -0
  134. kinto/views/groups.py +90 -0
  135. kinto/views/permissions.py +235 -0
  136. kinto/views/records.py +133 -0
  137. kinto-23.2.1.dist-info/METADATA +232 -0
  138. kinto-23.2.1.dist-info/RECORD +142 -0
  139. kinto-23.2.1.dist-info/WHEEL +5 -0
  140. kinto-23.2.1.dist-info/entry_points.txt +5 -0
  141. kinto-23.2.1.dist-info/licenses/LICENSE +13 -0
  142. 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
@@ -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
+ }