kinto 19.6.0__py3-none-any.whl → 20.0.0__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.
Potentially problematic release.
This version of kinto might be problematic. Click here for more details.
- kinto/__main__.py +0 -17
- kinto/config/kinto.tpl +0 -13
- kinto/contribute.json +27 -0
- kinto/core/initialization.py +0 -14
- kinto/core/storage/postgresql/pool.py +1 -1
- kinto/core/views/errors.py +2 -0
- kinto/plugins/accounts/__init__.py +2 -19
- kinto/plugins/accounts/authentication.py +8 -54
- kinto/plugins/accounts/utils.py +0 -133
- kinto/plugins/accounts/{views/__init__.py → views.py} +7 -62
- kinto/plugins/admin/VERSION +1 -1
- kinto/plugins/admin/build/VERSION +1 -1
- kinto/plugins/admin/build/assets/asn1-EdZsLKOL.js +1 -0
- kinto/plugins/admin/build/assets/index-Bq62Gei8.js +165 -0
- kinto/plugins/admin/build/assets/{index-BdpYyatM.css → index-Cs7JVwIg.css} +1 -1
- kinto/plugins/admin/build/assets/javascript-qCveANmP.js +1 -0
- kinto/plugins/admin/build/assets/mllike-CXdrOF99.js +1 -0
- kinto/plugins/admin/build/assets/sql-D0XecflT.js +1 -0
- kinto/plugins/admin/build/assets/ttcn-cfg-B9xdYoR4.js +1 -0
- kinto/plugins/admin/build/index.html +2 -2
- kinto/views/contribute.py +12 -12
- {kinto-19.6.0.dist-info → kinto-20.0.0.dist-info}/METADATA +1 -3
- {kinto-19.6.0.dist-info → kinto-20.0.0.dist-info}/RECORD +27 -33
- {kinto-19.6.0.dist-info → kinto-20.0.0.dist-info}/WHEEL +1 -1
- kinto/plugins/accounts/mails.py +0 -96
- kinto/plugins/accounts/views/validation.py +0 -136
- kinto/plugins/admin/build/assets/asn1-CGOzndHr.js +0 -1
- kinto/plugins/admin/build/assets/index-n-QM_iZE.js +0 -165
- kinto/plugins/admin/build/assets/javascript-iSgyE4tI.js +0 -1
- kinto/plugins/admin/build/assets/mllike-C_8OmSiT.js +0 -1
- kinto/plugins/admin/build/assets/sql-C4g8LzGK.js +0 -1
- kinto/plugins/admin/build/assets/ttcn-cfg-BIkV9KBc.js +0 -1
- kinto/plugins/quotas/__init__.py +0 -21
- kinto/plugins/quotas/listener.py +0 -226
- kinto/plugins/quotas/scripts.py +0 -80
- kinto/plugins/quotas/utils.py +0 -7
- kinto/scripts.py +0 -41
- {kinto-19.6.0.dist-info → kinto-20.0.0.dist-info}/LICENSE +0 -0
- {kinto-19.6.0.dist-info → kinto-20.0.0.dist-info}/entry_points.txt +0 -0
- {kinto-19.6.0.dist-info → kinto-20.0.0.dist-info}/top_level.txt +0 -0
kinto/__main__.py
CHANGED
|
@@ -9,7 +9,6 @@ from pyramid.paster import bootstrap
|
|
|
9
9
|
from pyramid.scripts import pserve
|
|
10
10
|
|
|
11
11
|
from kinto import __version__
|
|
12
|
-
from kinto import scripts as kinto_scripts
|
|
13
12
|
from kinto.config import init
|
|
14
13
|
from kinto.core import scripts as core_scripts
|
|
15
14
|
from kinto.plugins.accounts import scripts as accounts_scripts
|
|
@@ -33,7 +32,6 @@ def main(args=None):
|
|
|
33
32
|
"migrate",
|
|
34
33
|
"flush-cache",
|
|
35
34
|
"version",
|
|
36
|
-
"rebuild-quotas",
|
|
37
35
|
"create-user",
|
|
38
36
|
)
|
|
39
37
|
subparsers = parser.add_subparsers(
|
|
@@ -107,16 +105,6 @@ def main(args=None):
|
|
|
107
105
|
default=False,
|
|
108
106
|
)
|
|
109
107
|
|
|
110
|
-
elif command == "rebuild-quotas":
|
|
111
|
-
subparser.add_argument(
|
|
112
|
-
"--dry-run",
|
|
113
|
-
action="store_true",
|
|
114
|
-
help="Simulate the rebuild operation and show information",
|
|
115
|
-
dest="dry_run",
|
|
116
|
-
required=False,
|
|
117
|
-
default=False,
|
|
118
|
-
)
|
|
119
|
-
|
|
120
108
|
elif command == "start":
|
|
121
109
|
subparser.add_argument(
|
|
122
110
|
"--reload",
|
|
@@ -214,11 +202,6 @@ def main(args=None):
|
|
|
214
202
|
env = bootstrap(config_file, options={"command": "flush-cache"})
|
|
215
203
|
core_scripts.flush_cache(env)
|
|
216
204
|
|
|
217
|
-
elif which_command == "rebuild-quotas":
|
|
218
|
-
dry_run = parsed_args["dry_run"]
|
|
219
|
-
env = bootstrap(config_file, options={"command": "rebuild-quotas"})
|
|
220
|
-
return kinto_scripts.rebuild_quotas(env, dry_run=dry_run)
|
|
221
|
-
|
|
222
205
|
elif which_command == "create-user":
|
|
223
206
|
username = parsed_args["username"]
|
|
224
207
|
password = parsed_args["password"]
|
kinto/config/kinto.tpl
CHANGED
|
@@ -36,7 +36,6 @@ kinto.includes = kinto.plugins.default_bucket
|
|
|
36
36
|
kinto.plugins.admin
|
|
37
37
|
kinto.plugins.accounts
|
|
38
38
|
# kinto.plugins.history
|
|
39
|
-
# kinto.plugins.quotas
|
|
40
39
|
|
|
41
40
|
# Backends
|
|
42
41
|
# https://kinto.readthedocs.io/en/latest/configuration/settings.html#storage
|
|
@@ -104,18 +103,6 @@ kinto.account_create_principals = system.Everyone
|
|
|
104
103
|
kinto.account_write_principals = account:admin
|
|
105
104
|
# Allow administrators to create buckets
|
|
106
105
|
kinto.bucket_create_principals = account:admin
|
|
107
|
-
# Enable the "account_validation" option.
|
|
108
|
-
# kinto.account_validation = true
|
|
109
|
-
# Set the sender for the validation email.
|
|
110
|
-
# kinto.account_validation.email_sender = "admin@example.com"
|
|
111
|
-
# Set the regular expression used to validate a proper email address.
|
|
112
|
-
# kinto.account_validation.email_regexp = "^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$"
|
|
113
|
-
|
|
114
|
-
# Mail configuration (needed for the account validation option), see https://docs.pylonsproject.org/projects/pyramid_mailer/en/latest/#configuration
|
|
115
|
-
# mail.host = localhost
|
|
116
|
-
# mail.port = 25
|
|
117
|
-
# mail.username = someusername
|
|
118
|
-
# mail.password = somepassword
|
|
119
106
|
|
|
120
107
|
# Notifications
|
|
121
108
|
# https://kinto.readthedocs.io/en/latest/configuration/settings.html#notifications
|
kinto/contribute.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Kinto",
|
|
3
|
+
"description": "Kinto is a minimalist JSON storage service with synchronisation and sharing abilities.",
|
|
4
|
+
"repository": {
|
|
5
|
+
"url": "https://github.com/Kinto/kinto",
|
|
6
|
+
"license": "Apache License (2.0)",
|
|
7
|
+
"tests": "https://github.com/Kinto/kinto/actions"
|
|
8
|
+
},
|
|
9
|
+
"participate": {
|
|
10
|
+
"home": "https://kinto.readthedocs.io/en/latest/community.html#how-to-contribute",
|
|
11
|
+
"docs": "https://kinto.readthedocs.io/",
|
|
12
|
+
"irc": "irc://irc.freenode.org/#kinto",
|
|
13
|
+
"irc-contacts": ["alexis", "leplatrem", "magopian", "natim", "NiKo`"]
|
|
14
|
+
},
|
|
15
|
+
"bugs": {
|
|
16
|
+
"list": "https://github.com/Kinto/kinto/issues",
|
|
17
|
+
"report": "https://github.com/Kinto/kinto/issues/new"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"JSON",
|
|
21
|
+
"Python",
|
|
22
|
+
"Pyramid",
|
|
23
|
+
"REST",
|
|
24
|
+
"API",
|
|
25
|
+
"Database"
|
|
26
|
+
]
|
|
27
|
+
}
|
kinto/core/initialization.py
CHANGED
|
@@ -334,16 +334,6 @@ def setup_sentry(config):
|
|
|
334
334
|
config.add_subscriber(on_app_created, ApplicationCreated)
|
|
335
335
|
|
|
336
336
|
|
|
337
|
-
def setup_statsd(config):
|
|
338
|
-
# It would be pretty rare to find users that have a custom ``kinto.initialization_sequence`` setting.
|
|
339
|
-
# But just in case, warn that it will be removed in next major.
|
|
340
|
-
warnings.warn(
|
|
341
|
-
"``setup_statsd()`` is now deprecated. Use ``kinto.core.initialization.setup_metrics()`` instead.",
|
|
342
|
-
DeprecationWarning,
|
|
343
|
-
)
|
|
344
|
-
setup_metrics(config)
|
|
345
|
-
|
|
346
|
-
|
|
347
337
|
def install_middlewares(app, settings):
|
|
348
338
|
"Install a set of middlewares defined in the ini file on the given app."
|
|
349
339
|
# Setup new-relic.
|
|
@@ -544,10 +534,6 @@ def setup_metrics(config):
|
|
|
544
534
|
|
|
545
535
|
config.add_subscriber(on_new_response, NewResponse)
|
|
546
536
|
|
|
547
|
-
# While statsd is deprecated, we include its plugin by default for retro-compability.
|
|
548
|
-
if settings["statsd_url"]:
|
|
549
|
-
config.include("kinto.plugins.statsd")
|
|
550
|
-
|
|
551
537
|
|
|
552
538
|
class EventActionFilter:
|
|
553
539
|
def __init__(self, actions, config):
|
kinto/core/views/errors.py
CHANGED
|
@@ -22,6 +22,8 @@ def authorization_required(response, request):
|
|
|
22
22
|
"""
|
|
23
23
|
if Authenticated not in request.effective_principals:
|
|
24
24
|
if response.content_type != "application/json":
|
|
25
|
+
# This is always the case when `HTTPForbidden` is raised by Pyramid
|
|
26
|
+
# on protected views with unauthenticated requests.
|
|
25
27
|
error_msg = "Please authenticate yourself to use this endpoint."
|
|
26
28
|
response = http_error(
|
|
27
29
|
httpexceptions.HTTPUnauthorized(),
|
|
@@ -6,19 +6,12 @@ from pyramid.exceptions import ConfigurationError
|
|
|
6
6
|
from kinto.authorization import PERMISSIONS_INHERITANCE_TREE
|
|
7
7
|
|
|
8
8
|
from .authentication import AccountsAuthenticationPolicy as AccountsPolicy
|
|
9
|
-
from .utils import
|
|
10
|
-
ACCOUNT_CACHE_KEY,
|
|
11
|
-
ACCOUNT_POLICY_NAME,
|
|
12
|
-
ACCOUNT_RESET_PASSWORD_CACHE_KEY,
|
|
13
|
-
ACCOUNT_VALIDATION_CACHE_KEY,
|
|
14
|
-
)
|
|
9
|
+
from .utils import ACCOUNT_CACHE_KEY, ACCOUNT_POLICY_NAME
|
|
15
10
|
|
|
16
11
|
|
|
17
12
|
__all__ = [
|
|
18
13
|
"ACCOUNT_CACHE_KEY",
|
|
19
14
|
"ACCOUNT_POLICY_NAME",
|
|
20
|
-
"ACCOUNT_RESET_PASSWORD_CACHE_KEY",
|
|
21
|
-
"ACCOUNT_VALIDATION_CACHE_KEY",
|
|
22
15
|
"AccountsPolicy",
|
|
23
16
|
]
|
|
24
17
|
|
|
@@ -27,16 +20,13 @@ DOCS_URL = "https://kinto.readthedocs.io/en/stable/api/1.x/accounts.html"
|
|
|
27
20
|
|
|
28
21
|
def includeme(config):
|
|
29
22
|
settings = config.get_settings()
|
|
30
|
-
validation_enabled = settings.get("account_validation", False)
|
|
31
23
|
config.add_api_capability(
|
|
32
24
|
"accounts",
|
|
33
25
|
description="Manage user accounts.",
|
|
34
26
|
url="https://kinto.readthedocs.io/en/latest/api/1.x/accounts.html",
|
|
35
|
-
validation_enabled=
|
|
27
|
+
validation_enabled=False,
|
|
36
28
|
)
|
|
37
29
|
kwargs = {}
|
|
38
|
-
if not validation_enabled:
|
|
39
|
-
kwargs["ignore"] = "kinto.plugins.accounts.views.validation"
|
|
40
30
|
config.scan("kinto.plugins.accounts.views", **kwargs)
|
|
41
31
|
|
|
42
32
|
PERMISSIONS_INHERITANCE_TREE["root"].update({"account:create": {}})
|
|
@@ -45,13 +35,6 @@ def includeme(config):
|
|
|
45
35
|
"read": {"account": ["write", "read"]},
|
|
46
36
|
}
|
|
47
37
|
|
|
48
|
-
if validation_enabled:
|
|
49
|
-
# Valid mailers other than the default are `debug` and `testing`
|
|
50
|
-
# according to
|
|
51
|
-
# https://docs.pylonsproject.org/projects/pyramid_mailer/en/latest/#debugging
|
|
52
|
-
mailer = settings.get("mail.mailer", "")
|
|
53
|
-
config.include("pyramid_mailer" + (f".{mailer}" if mailer else ""))
|
|
54
|
-
|
|
55
38
|
# Check that the account policy is mentioned in config if included.
|
|
56
39
|
accountClass = "AccountsPolicy"
|
|
57
40
|
policy = None
|
|
@@ -5,31 +5,27 @@ from kinto.core import utils
|
|
|
5
5
|
from kinto.core.storage import exceptions as storage_exceptions
|
|
6
6
|
|
|
7
7
|
from .utils import (
|
|
8
|
+
ACCOUNT_CACHE_KEY,
|
|
8
9
|
ACCOUNT_POLICY_NAME,
|
|
9
|
-
cache_account,
|
|
10
|
-
delete_cached_reset_password,
|
|
11
|
-
get_account_cache_key,
|
|
12
|
-
get_cached_account,
|
|
13
|
-
get_cached_reset_password,
|
|
14
|
-
is_validated,
|
|
15
|
-
refresh_cached_account,
|
|
16
10
|
)
|
|
17
11
|
|
|
18
12
|
|
|
19
13
|
def account_check(username, password, request):
|
|
20
14
|
settings = request.registry.settings
|
|
21
|
-
|
|
22
|
-
cache_key =
|
|
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))
|
|
23
18
|
hashed_password = utils.hmac_digest(cache_key, password)
|
|
24
19
|
|
|
25
20
|
# Check cache to see whether somebody has recently logged in with the same
|
|
26
21
|
# username and password.
|
|
27
|
-
|
|
22
|
+
cache = request.registry.cache
|
|
23
|
+
cache_result = cache.get(cache_key)
|
|
28
24
|
|
|
29
25
|
# Username and password have been verified previously. No need to compare hashes
|
|
30
26
|
if cache_result == hashed_password:
|
|
31
27
|
# Refresh the cache TTL.
|
|
32
|
-
|
|
28
|
+
cache.expire(cache_key, cache_ttl)
|
|
33
29
|
return True
|
|
34
30
|
|
|
35
31
|
# Back to standard procedure
|
|
@@ -41,53 +37,11 @@ def account_check(username, password, request):
|
|
|
41
37
|
except storage_exceptions.ObjectNotFoundError:
|
|
42
38
|
return None
|
|
43
39
|
|
|
44
|
-
if validation_enabled and not is_validated(existing):
|
|
45
|
-
return None
|
|
46
|
-
|
|
47
40
|
hashed = existing["password"].encode(encoding="utf-8")
|
|
48
41
|
pwd_str = password.encode(encoding="utf-8")
|
|
49
42
|
# Check if password is valid (it is a very expensive computation)
|
|
50
43
|
if bcrypt.checkpw(pwd_str, hashed):
|
|
51
|
-
|
|
52
|
-
return True
|
|
53
|
-
|
|
54
|
-
# Last chance, is this a "reset password" flow?
|
|
55
|
-
return reset_password_flow(username, password, request)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def reset_password_flow(username, password, request):
|
|
59
|
-
cache_key = get_account_cache_key(username, request.registry)
|
|
60
|
-
hashed_password = utils.hmac_digest(cache_key, password)
|
|
61
|
-
pwd_str = password.encode(encoding="utf-8")
|
|
62
|
-
|
|
63
|
-
cached_password = get_cached_reset_password(username, request.registry)
|
|
64
|
-
if not cached_password:
|
|
65
|
-
return None
|
|
66
|
-
|
|
67
|
-
# The temporary reset password is only available for changing a user's password.
|
|
68
|
-
if request.method.lower() not in ["post", "put", "patch"]:
|
|
69
|
-
return None
|
|
70
|
-
|
|
71
|
-
# Only allow modifying a user account, no other resource.
|
|
72
|
-
uri = utils.strip_uri_prefix(request.path)
|
|
73
|
-
resource_name, _ = utils.view_lookup(request, uri)
|
|
74
|
-
if resource_name != "account":
|
|
75
|
-
return None
|
|
76
|
-
|
|
77
|
-
try:
|
|
78
|
-
data = request.json["data"]
|
|
79
|
-
except (ValueError, KeyError):
|
|
80
|
-
return None
|
|
81
|
-
|
|
82
|
-
# Request one and only one data field: the `password`.
|
|
83
|
-
if not data or "password" not in data or len(data.keys()) > 1:
|
|
84
|
-
return None
|
|
85
|
-
|
|
86
|
-
cached_password_str = cached_password.encode(encoding="utf-8")
|
|
87
|
-
if bcrypt.checkpw(pwd_str, cached_password_str):
|
|
88
|
-
# Remove the temporary reset password from the cache.
|
|
89
|
-
delete_cached_reset_password(username, request.registry)
|
|
90
|
-
cache_account(hashed_password, username, request.registry)
|
|
44
|
+
cache.set(cache_key, hashed_password, ttl=cache_ttl)
|
|
91
45
|
return True
|
|
92
46
|
|
|
93
47
|
|
kinto/plugins/accounts/utils.py
CHANGED
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
import bcrypt
|
|
2
2
|
|
|
3
|
-
from kinto.core import utils
|
|
4
|
-
|
|
5
3
|
|
|
6
4
|
ACCOUNT_CACHE_KEY = "accounts:{}:verified"
|
|
7
5
|
ACCOUNT_POLICY_NAME = "account"
|
|
8
|
-
ACCOUNT_RESET_PASSWORD_CACHE_KEY = "accounts:{}:reset-password"
|
|
9
|
-
ACCOUNT_VALIDATION_CACHE_KEY = "accounts:{}:validation-key"
|
|
10
|
-
DEFAULT_RESET_PASSWORD_CACHE_TTL_SECONDS = 7 * 24 * 60 * 60
|
|
11
|
-
DEFAULT_VALIDATION_KEY_CACHE_TTL_SECONDS = 7 * 24 * 60 * 60
|
|
12
6
|
|
|
13
7
|
|
|
14
8
|
def hash_password(password):
|
|
@@ -17,130 +11,3 @@ def hash_password(password):
|
|
|
17
11
|
pwd_str = password.encode(encoding="utf-8")
|
|
18
12
|
hashed = bcrypt.hashpw(pwd_str, bcrypt.gensalt())
|
|
19
13
|
return hashed.decode(encoding="utf-8")
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def is_validated(user):
|
|
23
|
-
"""Is this user record validated?"""
|
|
24
|
-
# An account is "validated" if it has the `validated` field set to True, or
|
|
25
|
-
# no `validated` field at all (for accounts created before the "account
|
|
26
|
-
# validation option" was enabled).
|
|
27
|
-
return user.get("validated", True)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def get_account_cache_key(username, registry):
|
|
31
|
-
"""Given a username, return the cache key for this account."""
|
|
32
|
-
settings = registry.settings
|
|
33
|
-
hmac_secret = settings["userid_hmac_secret"]
|
|
34
|
-
cache_key = utils.hmac_digest(hmac_secret, ACCOUNT_CACHE_KEY.format(username))
|
|
35
|
-
return cache_key
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def cache_reset_password(reset_password, username, registry):
|
|
39
|
-
"""Store a reset-password in the cache."""
|
|
40
|
-
settings = registry.settings
|
|
41
|
-
hmac_secret = settings["userid_hmac_secret"]
|
|
42
|
-
cache_key = utils.hmac_digest(hmac_secret, ACCOUNT_RESET_PASSWORD_CACHE_KEY.format(username))
|
|
43
|
-
# Store a reset password for 7 days by default.
|
|
44
|
-
cache_ttl = int(
|
|
45
|
-
settings.get(
|
|
46
|
-
"account_validation.reset_password_cache_ttl_seconds",
|
|
47
|
-
DEFAULT_RESET_PASSWORD_CACHE_TTL_SECONDS,
|
|
48
|
-
)
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
cache = registry.cache
|
|
52
|
-
cache_result = cache.set(cache_key, reset_password, ttl=cache_ttl)
|
|
53
|
-
return cache_result
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def get_cached_reset_password(username, registry):
|
|
57
|
-
"""Given a username, get the reset-password from the cache."""
|
|
58
|
-
hmac_secret = registry.settings["userid_hmac_secret"]
|
|
59
|
-
cache_key = utils.hmac_digest(hmac_secret, ACCOUNT_RESET_PASSWORD_CACHE_KEY.format(username))
|
|
60
|
-
|
|
61
|
-
cache = registry.cache
|
|
62
|
-
cache_result = cache.get(cache_key)
|
|
63
|
-
return cache_result
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def delete_cached_reset_password(username, registry):
|
|
67
|
-
"""Given a username, delete the reset-password from the cache."""
|
|
68
|
-
hmac_secret = registry.settings["userid_hmac_secret"]
|
|
69
|
-
cache_key = utils.hmac_digest(hmac_secret, ACCOUNT_RESET_PASSWORD_CACHE_KEY.format(username))
|
|
70
|
-
|
|
71
|
-
cache = registry.cache
|
|
72
|
-
cache_result = cache.delete(cache_key)
|
|
73
|
-
return cache_result
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
def cache_validation_key(activation_key, username, registry):
|
|
77
|
-
"""Store a validation_key in the cache."""
|
|
78
|
-
settings = registry.settings
|
|
79
|
-
hmac_secret = settings["userid_hmac_secret"]
|
|
80
|
-
cache_key = utils.hmac_digest(hmac_secret, ACCOUNT_VALIDATION_CACHE_KEY.format(username))
|
|
81
|
-
# Store an activation key for 7 days by default.
|
|
82
|
-
cache_ttl = int(
|
|
83
|
-
settings.get(
|
|
84
|
-
"account_validation.validation_key_cache_ttl_seconds",
|
|
85
|
-
DEFAULT_VALIDATION_KEY_CACHE_TTL_SECONDS,
|
|
86
|
-
)
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
cache = registry.cache
|
|
90
|
-
cache_result = cache.set(cache_key, activation_key, ttl=cache_ttl)
|
|
91
|
-
return cache_result
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
def get_cached_validation_key(username, registry):
|
|
95
|
-
"""Given a username, get the validation key from the cache."""
|
|
96
|
-
hmac_secret = registry.settings["userid_hmac_secret"]
|
|
97
|
-
cache_key = utils.hmac_digest(hmac_secret, ACCOUNT_VALIDATION_CACHE_KEY.format(username))
|
|
98
|
-
cache = registry.cache
|
|
99
|
-
activation_key = cache.get(cache_key)
|
|
100
|
-
return activation_key
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
def delete_cached_validation_key(username, registry):
|
|
104
|
-
"""Given a username, delete the validation key from the cache."""
|
|
105
|
-
hmac_secret = registry.settings["userid_hmac_secret"]
|
|
106
|
-
cache_key = utils.hmac_digest(hmac_secret, ACCOUNT_VALIDATION_CACHE_KEY.format(username))
|
|
107
|
-
cache = registry.cache
|
|
108
|
-
cache_result = cache.delete(cache_key)
|
|
109
|
-
return cache_result
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
def cache_account(hashed_password, username, registry):
|
|
113
|
-
"""Store an authenticated account in the cache."""
|
|
114
|
-
settings = registry.settings
|
|
115
|
-
cache_ttl = int(settings.get("account_cache_ttl_seconds", 30))
|
|
116
|
-
cache_key = get_account_cache_key(username, registry)
|
|
117
|
-
cache = registry.cache
|
|
118
|
-
cache_result = cache.set(cache_key, hashed_password, ttl=cache_ttl)
|
|
119
|
-
return cache_result
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
def get_cached_account(username, registry):
|
|
123
|
-
"""Given a username, get the account from the cache."""
|
|
124
|
-
cache_key = get_account_cache_key(username, registry)
|
|
125
|
-
cache = registry.cache
|
|
126
|
-
cached_account = cache.get(cache_key)
|
|
127
|
-
return cached_account
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
def refresh_cached_account(username, registry):
|
|
131
|
-
"""Given a username, refresh the cache TTL."""
|
|
132
|
-
settings = registry.settings
|
|
133
|
-
cache_ttl = int(settings.get("account_cache_ttl_seconds", 30))
|
|
134
|
-
cache_key = get_account_cache_key(username, registry)
|
|
135
|
-
cache = registry.cache
|
|
136
|
-
cache_result = cache.expire(cache_key, cache_ttl)
|
|
137
|
-
return cache_result
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
def delete_cached_account(username, registry):
|
|
141
|
-
"""Given a username, delete the account key from the cache."""
|
|
142
|
-
hmac_secret = registry.settings["userid_hmac_secret"]
|
|
143
|
-
cache_key = utils.hmac_digest(hmac_secret, ACCOUNT_CACHE_KEY.format(username))
|
|
144
|
-
cache = registry.cache
|
|
145
|
-
cache_result = cache.delete(cache_key)
|
|
146
|
-
return cache_result
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import re
|
|
2
|
-
import uuid
|
|
3
|
-
|
|
4
1
|
import colander
|
|
5
2
|
from pyramid import httpexceptions
|
|
6
3
|
from pyramid.authorization import Authenticated, Everyone
|
|
@@ -8,22 +5,12 @@ from pyramid.decorator import reify
|
|
|
8
5
|
from pyramid.events import subscriber
|
|
9
6
|
from pyramid.settings import aslist
|
|
10
7
|
|
|
11
|
-
from kinto.core import resource
|
|
8
|
+
from kinto.core import resource, utils
|
|
12
9
|
from kinto.core.errors import http_error, raise_invalid
|
|
13
10
|
from kinto.core.events import ACTIONS, ResourceChanged
|
|
14
11
|
from kinto.views import NameGenerator
|
|
15
12
|
|
|
16
|
-
from
|
|
17
|
-
from ..utils import (
|
|
18
|
-
ACCOUNT_POLICY_NAME,
|
|
19
|
-
cache_validation_key,
|
|
20
|
-
delete_cached_account,
|
|
21
|
-
get_cached_validation_key,
|
|
22
|
-
hash_password,
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
DEFAULT_EMAIL_REGEXP = "^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$"
|
|
13
|
+
from .utils import ACCOUNT_CACHE_KEY, ACCOUNT_POLICY_NAME, hash_password
|
|
27
14
|
|
|
28
15
|
|
|
29
16
|
def _extract_posted_body_id(request):
|
|
@@ -63,13 +50,6 @@ class Account(resource.Resource):
|
|
|
63
50
|
)
|
|
64
51
|
# Shortcut to check if current is anonymous (before get_parent_id()).
|
|
65
52
|
context.is_anonymous = Authenticated not in request.effective_principals
|
|
66
|
-
# Is the "accounts validation" setting set?
|
|
67
|
-
context.validation_enabled = settings.get("account_validation", False)
|
|
68
|
-
# Account validation requires the user id to be an email.
|
|
69
|
-
validation_email_regexp = settings.get(
|
|
70
|
-
"account_validation.email_regexp", DEFAULT_EMAIL_REGEXP
|
|
71
|
-
)
|
|
72
|
-
context.validation_email_regexp = re.compile(validation_email_regexp)
|
|
73
53
|
|
|
74
54
|
super().__init__(request, context)
|
|
75
55
|
|
|
@@ -122,26 +102,6 @@ class Account(resource.Resource):
|
|
|
122
102
|
error_details = {"name": "data.id", "description": "Accounts must have an ID."}
|
|
123
103
|
raise_invalid(self.request, **error_details)
|
|
124
104
|
|
|
125
|
-
# Account validation requires that the record ID is an email address.
|
|
126
|
-
# TODO: this might be better suited for a schema. Do we have a way to
|
|
127
|
-
# dynamically change the schema according to the settings?
|
|
128
|
-
if self.context.validation_enabled and old is None:
|
|
129
|
-
email_regexp = self.context.validation_email_regexp
|
|
130
|
-
# Account validation requires that the record ID is an email address.
|
|
131
|
-
user_email = new[self.model.id_field]
|
|
132
|
-
if not email_regexp.match(user_email):
|
|
133
|
-
error_details = {
|
|
134
|
-
"name": "data.id",
|
|
135
|
-
"description": f"Account validation is enabled, and user id should match {email_regexp}",
|
|
136
|
-
}
|
|
137
|
-
raise_invalid(self.request, **error_details)
|
|
138
|
-
|
|
139
|
-
activation_key = str(uuid.uuid4())
|
|
140
|
-
new["validated"] = False
|
|
141
|
-
|
|
142
|
-
# Store the activation key in the cache to be used in the `validate` endpoint.
|
|
143
|
-
cache_validation_key(activation_key, new["id"], self.request.registry)
|
|
144
|
-
|
|
145
105
|
# Administrators can reach other accounts and anonymous have no
|
|
146
106
|
# selected_userid. So do not try to enforce.
|
|
147
107
|
if self.context.is_administrator or self.context.is_anonymous:
|
|
@@ -164,28 +124,13 @@ class Account(resource.Resource):
|
|
|
164
124
|
)
|
|
165
125
|
def on_account_changed(event):
|
|
166
126
|
request = event.request
|
|
127
|
+
cache = request.registry.cache
|
|
128
|
+
settings = request.registry.settings
|
|
129
|
+
hmac_secret = settings["userid_hmac_secret"]
|
|
167
130
|
|
|
168
131
|
for obj in event.impacted_objects:
|
|
169
132
|
# Extract username and password from current user
|
|
170
133
|
username = obj["old"]["id"]
|
|
134
|
+
cache_key = utils.hmac_digest(hmac_secret, ACCOUNT_CACHE_KEY.format(username))
|
|
171
135
|
# Delete cache
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
# Send activation code by email on account creation if account validation is enabled.
|
|
176
|
-
@subscriber(ResourceChanged, for_resources=("account",), for_actions=(ACTIONS.CREATE,))
|
|
177
|
-
def on_account_created(event):
|
|
178
|
-
request = event.request
|
|
179
|
-
settings = request.registry.settings
|
|
180
|
-
if not settings.get("account_validation", False):
|
|
181
|
-
return
|
|
182
|
-
|
|
183
|
-
for impacted_object in event.impacted_objects:
|
|
184
|
-
account = impacted_object["new"]
|
|
185
|
-
user_email = account["id"]
|
|
186
|
-
activation_key = get_cached_validation_key(user_email, request.registry)
|
|
187
|
-
if activation_key is None:
|
|
188
|
-
continue
|
|
189
|
-
|
|
190
|
-
# Send an email to the user with the link to activate their account.
|
|
191
|
-
Emailer(request, account).send_activation(activation_key)
|
|
136
|
+
cache.delete(cache_key)
|
kinto/plugins/admin/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.
|
|
1
|
+
3.6.0
|
|
@@ -1 +1 @@
|
|
|
1
|
-
3.
|
|
1
|
+
3.6.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function u(i){for(var s={},c=i.split(" "),T=0;T<c.length;++T)s[c[T]]=!0;return s}const o={keywords:u("DEFINITIONS OBJECTS IF DERIVED INFORMATION ACTION REPLY ANY NAMED CHARACTERIZED BEHAVIOUR REGISTERED WITH AS IDENTIFIED CONSTRAINED BY PRESENT BEGIN IMPORTS FROM UNITS SYNTAX MIN-ACCESS MAX-ACCESS MINACCESS MAXACCESS REVISION STATUS DESCRIPTION SEQUENCE SET COMPONENTS OF CHOICE DistinguishedName ENUMERATED SIZE MODULE END INDEX AUGMENTS EXTENSIBILITY IMPLIED EXPORTS"),cmipVerbs:u("ACTIONS ADD GET NOTIFICATIONS REPLACE REMOVE"),compareTypes:u("OPTIONAL DEFAULT MANAGED MODULE-TYPE MODULE_IDENTITY MODULE-COMPLIANCE OBJECT-TYPE OBJECT-IDENTITY OBJECT-COMPLIANCE MODE CONFIRMED CONDITIONAL SUBORDINATE SUPERIOR CLASS TRUE FALSE NULL TEXTUAL-CONVENTION"),status:u("current deprecated mandatory obsolete"),tags:u("APPLICATION AUTOMATIC EXPLICIT IMPLICIT PRIVATE TAGS UNIVERSAL"),storage:u("BOOLEAN INTEGER OBJECT IDENTIFIER BIT OCTET STRING UTCTime InterfaceIndex IANAifType CMIP-Attribute REAL PACKAGE PACKAGES IpAddress PhysAddress NetworkAddress BITS BMPString TimeStamp TimeTicks TruthValue RowStatus DisplayString GeneralString GraphicString IA5String NumericString PrintableString SnmpAdminString TeletexString UTF8String VideotexString VisibleString StringStore ISO646String T61String UniversalString Unsigned32 Integer32 Gauge Gauge32 Counter Counter32 Counter64"),modifier:u("ATTRIBUTE ATTRIBUTES MANDATORY-GROUP MANDATORY-GROUPS GROUP GROUPS ELEMENTS EQUALITY ORDERING SUBSTRINGS DEFINED"),accessTypes:u("not-accessible accessible-for-notify read-only read-create read-write"),multiLineStrings:!0};function g(i){var s=i.keywords||o.keywords,c=i.cmipVerbs||o.cmipVerbs,T=i.compareTypes||o.compareTypes,N=i.status||o.status,d=i.tags||o.tags,f=i.storage||o.storage,m=i.modifier||o.modifier,C=i.accessTypes||o.accessTypes;i.multiLineStrings||o.multiLineStrings;var R=i.indentStatements!==!1,A=/[\|\^]/,E;function y(e,n){var t=e.next();if(t=='"'||t=="'")return n.tokenize=D(t),n.tokenize(e,n);if(/[\[\]\(\){}:=,;]/.test(t))return E=t,"punctuation";if(t=="-"&&e.eat("-"))return e.skipToEnd(),"comment";if(/\d/.test(t))return e.eatWhile(/[\w\.]/),"number";if(A.test(t))return e.eatWhile(A),"operator";e.eatWhile(/[\w\-]/);var r=e.current();return s.propertyIsEnumerable(r)?"keyword":c.propertyIsEnumerable(r)?"variableName":T.propertyIsEnumerable(r)?"atom":N.propertyIsEnumerable(r)?"comment":d.propertyIsEnumerable(r)?"typeName":f.propertyIsEnumerable(r)||m.propertyIsEnumerable(r)||C.propertyIsEnumerable(r)?"modifier":"variableName"}function D(e){return function(n,t){for(var r=!1,S,O=!1;(S=n.next())!=null;){if(S==e&&!r){var I=n.peek();I&&(I=I.toLowerCase(),(I=="b"||I=="h"||I=="o")&&n.next()),O=!0;break}r=!r&&S=="\\"}return O&&(t.tokenize=null),"string"}}function p(e,n,t,r,S){this.indented=e,this.column=n,this.type=t,this.align=r,this.prev=S}function a(e,n,t){var r=e.indented;return e.context&&e.context.type=="statement"&&(r=e.context.indented),e.context=new p(r,n,t,null,e.context)}function l(e){var n=e.context.type;return(n==")"||n=="]"||n=="}")&&(e.indented=e.context.indented),e.context=e.context.prev}return{name:"asn1",startState:function(){return{tokenize:null,context:new p(-2,0,"top",!1),indented:0,startOfLine:!0}},token:function(e,n){var t=n.context;if(e.sol()&&(t.align==null&&(t.align=!1),n.indented=e.indentation(),n.startOfLine=!0),e.eatSpace())return null;E=null;var r=(n.tokenize||y)(e,n);if(r=="comment")return r;if(t.align==null&&(t.align=!0),(E==";"||E==":"||E==",")&&t.type=="statement")l(n);else if(E=="{")a(n,e.column(),"}");else if(E=="[")a(n,e.column(),"]");else if(E=="(")a(n,e.column(),")");else if(E=="}"){for(;t.type=="statement";)t=l(n);for(t.type=="}"&&(t=l(n));t.type=="statement";)t=l(n)}else E==t.type?l(n):R&&((t.type=="}"||t.type=="top")&&E!=";"||t.type=="statement"&&E=="newstatement")&&a(n,e.column(),"statement");return n.startOfLine=!1,r},languageData:{indentOnInput:/^\s*[{}]$/,commentTokens:{line:"--"}}}}export{g as asn1};
|