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
@@ -0,0 +1,63 @@
1
+ import bcrypt
2
+ from pyramid import authentication as base_auth
3
+
4
+ from kinto.core import utils
5
+ from kinto.core.storage import exceptions as storage_exceptions
6
+
7
+ from .utils import (
8
+ ACCOUNT_CACHE_KEY,
9
+ ACCOUNT_POLICY_NAME,
10
+ )
11
+
12
+
13
+ def account_check(username, password, request):
14
+ settings = request.registry.settings
15
+ hmac_secret = settings["userid_hmac_secret"]
16
+ cache_key = utils.hmac_digest(hmac_secret, ACCOUNT_CACHE_KEY.format(username))
17
+ cache_ttl = int(settings.get("account_cache_ttl_seconds", 30))
18
+ hashed_password = utils.hmac_digest(cache_key, password)
19
+
20
+ # Check cache to see whether somebody has recently logged in with the same
21
+ # username and password.
22
+ cache = request.registry.cache
23
+ cache_result = cache.get(cache_key)
24
+
25
+ # Username and password have been verified previously. No need to compare hashes
26
+ if cache_result == hashed_password:
27
+ # Refresh the cache TTL.
28
+ cache.expire(cache_key, cache_ttl)
29
+ return True
30
+
31
+ # Back to standard procedure
32
+ parent_id = username
33
+ try:
34
+ existing = request.registry.storage.get(
35
+ parent_id=parent_id, resource_name="account", object_id=username
36
+ )
37
+ except storage_exceptions.ObjectNotFoundError:
38
+ return None
39
+
40
+ hashed = existing["password"].encode(encoding="utf-8")
41
+ pwd_str = password.encode(encoding="utf-8")
42
+ # Check if password is valid (it is a very expensive computation)
43
+ if bcrypt.checkpw(pwd_str, hashed):
44
+ cache.set(cache_key, hashed_password, ttl=cache_ttl)
45
+ return True
46
+
47
+
48
+ class AccountsAuthenticationPolicy(base_auth.BasicAuthAuthenticationPolicy):
49
+ """Accounts authentication policy.
50
+
51
+ It will check that the credentials exist in the account resource.
52
+ """
53
+
54
+ name = ACCOUNT_POLICY_NAME
55
+
56
+ def __init__(self, *args, **kwargs):
57
+ super().__init__(account_check, *args, **kwargs)
58
+
59
+ def effective_principals(self, request):
60
+ # Bypass default Pyramid construction of principals because
61
+ # Pyramid multiauth already adds userid, Authenticated and Everyone
62
+ # principals.
63
+ return []
@@ -0,0 +1,61 @@
1
+ import getpass
2
+ import logging
3
+
4
+ import transaction as current_transaction
5
+ from pyramid.settings import asbool
6
+
7
+ from .utils import hash_password
8
+ from .views import AccountIdGenerator
9
+
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ def create_user(env, username=None, password=None):
15
+ """Administrative command to create a new user."""
16
+ registry = env["registry"]
17
+ settings = registry.settings
18
+ readonly_mode = asbool(settings.get("readonly", False))
19
+ if readonly_mode:
20
+ message = "Cannot create a user with a readonly server."
21
+ logger.error(message)
22
+ return 51
23
+
24
+ if "kinto.plugins.accounts" not in settings["includes"]:
25
+ message = "Cannot create a user when the accounts plugin is not installed."
26
+ logger.error(message)
27
+ return 52
28
+
29
+ try:
30
+ validator = AccountIdGenerator()
31
+ if username is None:
32
+ username = input("Username: ")
33
+ while not validator.match(username):
34
+ print("{} is not a valid username.")
35
+ print(f"Username should match {validator.regexp}, please try again.")
36
+ username = input("Username: ")
37
+
38
+ if password is None:
39
+ while True: # The user didn't entered twice the same password
40
+ password = getpass.getpass(f"Please enter a password for {username}: ")
41
+ confirm = getpass.getpass("Please confirm the password: ")
42
+
43
+ if password == confirm:
44
+ break
45
+ print("Sorry, passwords do not match, please try again.")
46
+ except EOFError:
47
+ print("User creation aborted")
48
+ return 53
49
+
50
+ print(f"Creating user '{username}'")
51
+ entry = {"id": username, "password": hash_password(password)}
52
+ registry.storage.update(
53
+ resource_name="account", parent_id=username, object_id=username, obj=entry
54
+ )
55
+ registry.permission.add_principal_to_ace(
56
+ f"/accounts/{username}", "write", f"account:{username}"
57
+ )
58
+
59
+ current_transaction.commit()
60
+
61
+ return 0
@@ -0,0 +1,13 @@
1
+ import bcrypt
2
+
3
+
4
+ ACCOUNT_CACHE_KEY = "accounts:{}:verified"
5
+ ACCOUNT_POLICY_NAME = "account"
6
+
7
+
8
+ def hash_password(password):
9
+ # Store password safely in database as str
10
+ # (bcrypt.hashpw returns base64 bytes).
11
+ pwd_str = password.encode(encoding="utf-8")
12
+ hashed = bcrypt.hashpw(pwd_str, bcrypt.gensalt())
13
+ return hashed.decode(encoding="utf-8")
@@ -0,0 +1,136 @@
1
+ import colander
2
+ from pyramid import httpexceptions
3
+ from pyramid.authorization import Authenticated, Everyone
4
+ from pyramid.decorator import reify
5
+ from pyramid.events import subscriber
6
+ from pyramid.settings import aslist
7
+
8
+ from kinto.core import resource, utils
9
+ from kinto.core.errors import http_error, raise_invalid
10
+ from kinto.core.events import ACTIONS, ResourceChanged
11
+ from kinto.views import NameGenerator
12
+
13
+ from .utils import ACCOUNT_CACHE_KEY, ACCOUNT_POLICY_NAME, hash_password
14
+
15
+
16
+ def _extract_posted_body_id(request):
17
+ try:
18
+ # Anonymous creation with POST.
19
+ return request.json["data"]["id"]
20
+ except (ValueError, KeyError):
21
+ # Bad POST data.
22
+ if request.method.lower() == "post":
23
+ error_details = {"name": "data.id", "description": "data.id in body: Required"}
24
+ raise_invalid(request, **error_details)
25
+ # Anonymous GET
26
+ error_msg = "Cannot read accounts."
27
+ raise http_error(httpexceptions.HTTPUnauthorized(), error=error_msg)
28
+
29
+
30
+ class AccountIdGenerator(NameGenerator):
31
+ """Allow @ signs in account IDs."""
32
+
33
+ regexp = r"^[a-zA-Z0-9][+.@a-zA-Z0-9_-]*$"
34
+
35
+
36
+ class AccountSchema(resource.ResourceSchema):
37
+ password = colander.SchemaNode(colander.String())
38
+
39
+
40
+ @resource.register()
41
+ class Account(resource.Resource):
42
+ schema = AccountSchema
43
+
44
+ def __init__(self, request, context):
45
+ settings = request.registry.settings
46
+ # Store if current user is administrator (before accessing get_parent_id())
47
+ allowed_from_settings = settings.get("account_write_principals", [])
48
+ context.is_administrator = (
49
+ len(set(aslist(allowed_from_settings)) & set(request.prefixed_principals)) > 0
50
+ )
51
+ # Shortcut to check if current is anonymous (before get_parent_id()).
52
+ context.is_anonymous = Authenticated not in request.effective_principals
53
+
54
+ super().__init__(request, context)
55
+
56
+ # Overwrite the current principal set by Resource.
57
+ if self.model.current_principal == Everyone or context.is_administrator:
58
+ # Creation is anonymous, but author with write perm is this:
59
+ self.model.current_principal = f"{ACCOUNT_POLICY_NAME}:{self.model.parent_id}"
60
+
61
+ @reify
62
+ def id_generator(self):
63
+ # This generator is used for ID validation.
64
+ return AccountIdGenerator()
65
+
66
+ def get_parent_id(self, request):
67
+ # The whole challenge here is that we want to isolate what
68
+ # authenticated users can list, but give access to everything to
69
+ # administrators.
70
+ # Plus when anonymous create accounts, we have to set their parent id
71
+ # to the same value they would obtain when authenticated.
72
+ if self.context.is_administrator:
73
+ if self.context.on_plural_endpoint:
74
+ # Accounts created by admin should have userid as parent.
75
+ if request.method.lower() == "post":
76
+ return _extract_posted_body_id(request)
77
+ else:
78
+ # Admin see all accounts.
79
+ return "*"
80
+ else:
81
+ # No pattern matching for admin on single record.
82
+ return request.matchdict["id"]
83
+
84
+ if not self.context.is_anonymous:
85
+ # Authenticated users see their own account only.
86
+ return request.selected_userid
87
+
88
+ # Anonymous creation with PUT.
89
+ if "id" in request.matchdict:
90
+ return request.matchdict["id"]
91
+
92
+ return _extract_posted_body_id(request)
93
+
94
+ def process_object(self, new, old=None):
95
+ new = super(Account, self).process_object(new, old)
96
+
97
+ if "data" in self.request.json and "password" in self.request.json["data"]:
98
+ new["password"] = hash_password(new["password"])
99
+
100
+ # Do not let accounts be created without usernames.
101
+ if self.model.id_field not in new:
102
+ error_details = {"name": "data.id", "description": "Accounts must have an ID."}
103
+ raise_invalid(self.request, **error_details)
104
+
105
+ # Administrators can reach other accounts and anonymous have no
106
+ # selected_userid. So do not try to enforce.
107
+ if self.context.is_administrator or self.context.is_anonymous:
108
+ return new
109
+
110
+ # Otherwise, we force the id to match the authenticated username.
111
+ if new[self.model.id_field] != self.request.selected_userid:
112
+ error_details = {
113
+ "name": "data.id",
114
+ "description": "Username and account ID do not match.",
115
+ }
116
+ raise_invalid(self.request, **error_details)
117
+
118
+ return new
119
+
120
+
121
+ # Clear cache on account change
122
+ @subscriber(
123
+ ResourceChanged, for_resources=("account",), for_actions=(ACTIONS.UPDATE, ACTIONS.DELETE)
124
+ )
125
+ def on_account_changed(event):
126
+ request = event.request
127
+ cache = request.registry.cache
128
+ settings = request.registry.settings
129
+ hmac_secret = settings["userid_hmac_secret"]
130
+
131
+ for obj in event.impacted_objects:
132
+ # Extract username and password from current user
133
+ username = obj["old"]["id"]
134
+ cache_key = utils.hmac_digest(hmac_secret, ACCOUNT_CACHE_KEY.format(username))
135
+ # Delete cache
136
+ cache.delete(cache_key)
@@ -0,0 +1,3 @@
1
+ # kinto-admin plugin setup
2
+
3
+ Update the `kinto-admin` version you want in the VERSION file of this plugin
@@ -0,0 +1 @@
1
+ 5.0.1
@@ -0,0 +1,40 @@
1
+ from pathlib import Path
2
+
3
+ from pyramid.httpexceptions import HTTPTemporaryRedirect
4
+ from pyramid.static import static_view
5
+
6
+ from .views import admin_home_view
7
+
8
+
9
+ def includeme(config):
10
+ admin_assets_path = config.registry.settings["admin_assets_path"]
11
+ if not admin_assets_path:
12
+ # Use bundled admin.
13
+ admin_assets_path = "kinto.plugins.admin:build"
14
+ version_file_parent = Path(__file__).parent
15
+ else:
16
+ version_file_parent = Path(admin_assets_path)
17
+
18
+ admin_version = (version_file_parent / "VERSION").read_text().strip()
19
+
20
+ # Expose capability.
21
+ config.add_api_capability(
22
+ "admin",
23
+ version=admin_version,
24
+ description="Serves the admin console.",
25
+ url="https://github.com/Kinto/kinto-admin/",
26
+ )
27
+
28
+ config.add_route("admin_home", "/admin/")
29
+ config.add_view(admin_home_view, route_name="admin_home")
30
+
31
+ build_dir = static_view(admin_assets_path, use_subpath=True)
32
+ config.add_route("catchall_static", "/admin/*subpath")
33
+ config.add_view(build_dir, route_name="catchall_static")
34
+
35
+ # Setup redirect without trailing slash.
36
+ def admin_redirect_view(request):
37
+ raise HTTPTemporaryRedirect(request.path + "/")
38
+
39
+ config.add_route("admin_redirect", "/admin")
40
+ config.add_view(admin_redirect_view, route_name="admin_redirect")
@@ -0,0 +1 @@
1
+ 5.0.1