meerschaum 2.9.4__py3-none-any.whl → 3.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.
- meerschaum/__init__.py +5 -2
- meerschaum/_internal/__init__.py +1 -0
- meerschaum/_internal/arguments/_parse_arguments.py +4 -4
- meerschaum/_internal/arguments/_parser.py +33 -4
- meerschaum/_internal/cli/__init__.py +6 -0
- meerschaum/_internal/cli/daemons.py +103 -0
- meerschaum/_internal/cli/entry.py +220 -0
- meerschaum/_internal/cli/workers.py +435 -0
- meerschaum/_internal/docs/index.py +48 -2
- meerschaum/_internal/entry.py +50 -14
- meerschaum/_internal/shell/Shell.py +121 -29
- meerschaum/_internal/shell/__init__.py +4 -1
- meerschaum/_internal/static.py +359 -0
- meerschaum/_internal/term/TermPageHandler.py +1 -2
- meerschaum/_internal/term/__init__.py +40 -6
- meerschaum/_internal/term/tools.py +33 -8
- meerschaum/actions/__init__.py +6 -4
- meerschaum/actions/api.py +53 -13
- meerschaum/actions/attach.py +1 -0
- meerschaum/actions/bootstrap.py +8 -8
- meerschaum/actions/delete.py +4 -2
- meerschaum/actions/edit.py +171 -25
- meerschaum/actions/login.py +8 -8
- meerschaum/actions/register.py +143 -6
- meerschaum/actions/reload.py +22 -5
- meerschaum/actions/restart.py +14 -0
- meerschaum/actions/show.py +184 -31
- meerschaum/actions/start.py +166 -17
- meerschaum/actions/stop.py +38 -2
- meerschaum/actions/sync.py +7 -2
- meerschaum/actions/tag.py +9 -8
- meerschaum/actions/verify.py +5 -8
- meerschaum/api/__init__.py +45 -15
- meerschaum/api/_events.py +46 -4
- meerschaum/api/_oauth2.py +162 -9
- meerschaum/api/_tokens.py +102 -0
- meerschaum/api/dash/__init__.py +0 -3
- meerschaum/api/dash/callbacks/__init__.py +1 -0
- meerschaum/api/dash/callbacks/custom.py +4 -3
- meerschaum/api/dash/callbacks/dashboard.py +228 -117
- meerschaum/api/dash/callbacks/jobs.py +14 -7
- meerschaum/api/dash/callbacks/login.py +10 -1
- meerschaum/api/dash/callbacks/pipes.py +194 -14
- meerschaum/api/dash/callbacks/plugins.py +0 -1
- meerschaum/api/dash/callbacks/register.py +10 -3
- meerschaum/api/dash/callbacks/settings/password_reset.py +2 -2
- meerschaum/api/dash/callbacks/tokens.py +389 -0
- meerschaum/api/dash/components.py +36 -15
- meerschaum/api/dash/jobs.py +1 -1
- meerschaum/api/dash/keys.py +35 -93
- meerschaum/api/dash/pages/__init__.py +2 -1
- meerschaum/api/dash/pages/dashboard.py +1 -20
- meerschaum/api/dash/pages/{job.py → jobs.py} +10 -7
- meerschaum/api/dash/pages/login.py +2 -2
- meerschaum/api/dash/pages/pipes.py +16 -5
- meerschaum/api/dash/pages/settings/password_reset.py +1 -1
- meerschaum/api/dash/pages/tokens.py +53 -0
- meerschaum/api/dash/pipes.py +438 -88
- meerschaum/api/dash/sessions.py +12 -0
- meerschaum/api/dash/tokens.py +603 -0
- meerschaum/api/dash/websockets.py +1 -1
- meerschaum/api/dash/webterm.py +18 -6
- meerschaum/api/models/__init__.py +23 -3
- meerschaum/api/models/_actions.py +22 -0
- meerschaum/api/models/_pipes.py +91 -7
- meerschaum/api/models/_tokens.py +81 -0
- meerschaum/api/resources/static/css/dash.css +16 -0
- meerschaum/api/resources/static/js/terminado.js +3 -0
- meerschaum/api/resources/static/js/xterm-addon-unicode11.js +2 -0
- meerschaum/api/resources/templates/termpage.html +13 -0
- meerschaum/api/routes/__init__.py +1 -0
- meerschaum/api/routes/_actions.py +3 -4
- meerschaum/api/routes/_connectors.py +3 -7
- meerschaum/api/routes/_jobs.py +26 -35
- meerschaum/api/routes/_login.py +120 -15
- meerschaum/api/routes/_misc.py +5 -10
- meerschaum/api/routes/_pipes.py +178 -143
- meerschaum/api/routes/_plugins.py +38 -28
- meerschaum/api/routes/_tokens.py +236 -0
- meerschaum/api/routes/_users.py +47 -35
- meerschaum/api/routes/_version.py +3 -3
- meerschaum/api/routes/_webterm.py +3 -3
- meerschaum/config/__init__.py +100 -30
- meerschaum/config/_default.py +132 -64
- meerschaum/config/_edit.py +38 -32
- meerschaum/config/_formatting.py +2 -0
- meerschaum/config/_patch.py +10 -8
- meerschaum/config/_paths.py +133 -13
- meerschaum/config/_read_config.py +87 -36
- meerschaum/config/_sync.py +6 -3
- meerschaum/config/_version.py +1 -1
- meerschaum/config/environment.py +262 -0
- meerschaum/config/stack/__init__.py +37 -15
- meerschaum/config/static.py +18 -0
- meerschaum/connectors/_Connector.py +11 -6
- meerschaum/connectors/__init__.py +41 -22
- meerschaum/connectors/api/_APIConnector.py +34 -6
- meerschaum/connectors/api/_actions.py +2 -2
- meerschaum/connectors/api/_jobs.py +12 -1
- meerschaum/connectors/api/_login.py +33 -7
- meerschaum/connectors/api/_misc.py +2 -2
- meerschaum/connectors/api/_pipes.py +23 -32
- meerschaum/connectors/api/_plugins.py +2 -2
- meerschaum/connectors/api/_request.py +1 -1
- meerschaum/connectors/api/_tokens.py +146 -0
- meerschaum/connectors/api/_users.py +70 -58
- meerschaum/connectors/instance/_InstanceConnector.py +83 -0
- meerschaum/connectors/instance/__init__.py +10 -0
- meerschaum/connectors/instance/_pipes.py +442 -0
- meerschaum/connectors/instance/_plugins.py +159 -0
- meerschaum/connectors/instance/_tokens.py +317 -0
- meerschaum/connectors/instance/_users.py +188 -0
- meerschaum/connectors/parse.py +5 -2
- meerschaum/connectors/sql/_SQLConnector.py +22 -5
- meerschaum/connectors/sql/_cli.py +12 -11
- meerschaum/connectors/sql/_create_engine.py +12 -168
- meerschaum/connectors/sql/_fetch.py +2 -18
- meerschaum/connectors/sql/_pipes.py +295 -278
- meerschaum/connectors/sql/_plugins.py +29 -0
- meerschaum/connectors/sql/_sql.py +47 -22
- meerschaum/connectors/sql/_users.py +36 -2
- meerschaum/connectors/sql/tables/__init__.py +254 -122
- meerschaum/connectors/valkey/_ValkeyConnector.py +5 -7
- meerschaum/connectors/valkey/_pipes.py +60 -31
- meerschaum/connectors/valkey/_plugins.py +2 -26
- meerschaum/core/Pipe/__init__.py +115 -85
- meerschaum/core/Pipe/_attributes.py +425 -124
- meerschaum/core/Pipe/_bootstrap.py +54 -24
- meerschaum/core/Pipe/_cache.py +555 -0
- meerschaum/core/Pipe/_clear.py +0 -11
- meerschaum/core/Pipe/_data.py +96 -68
- meerschaum/core/Pipe/_deduplicate.py +0 -13
- meerschaum/core/Pipe/_delete.py +12 -21
- meerschaum/core/Pipe/_drop.py +11 -23
- meerschaum/core/Pipe/_dtypes.py +49 -19
- meerschaum/core/Pipe/_edit.py +14 -4
- meerschaum/core/Pipe/_fetch.py +1 -1
- meerschaum/core/Pipe/_index.py +8 -14
- meerschaum/core/Pipe/_show.py +5 -5
- meerschaum/core/Pipe/_sync.py +123 -204
- meerschaum/core/Pipe/_verify.py +4 -4
- meerschaum/{plugins → core/Plugin}/_Plugin.py +16 -12
- meerschaum/core/Plugin/__init__.py +1 -1
- meerschaum/core/Token/_Token.py +220 -0
- meerschaum/core/Token/__init__.py +12 -0
- meerschaum/core/User/_User.py +35 -10
- meerschaum/core/User/__init__.py +9 -1
- meerschaum/core/__init__.py +1 -0
- meerschaum/jobs/_Executor.py +88 -4
- meerschaum/jobs/_Job.py +149 -38
- meerschaum/jobs/__init__.py +3 -2
- meerschaum/jobs/systemd.py +8 -3
- meerschaum/models/__init__.py +35 -0
- meerschaum/models/pipes.py +247 -0
- meerschaum/models/tokens.py +38 -0
- meerschaum/models/users.py +26 -0
- meerschaum/plugins/__init__.py +301 -88
- meerschaum/plugins/bootstrap.py +510 -4
- meerschaum/utils/_get_pipes.py +97 -30
- meerschaum/utils/daemon/Daemon.py +199 -43
- meerschaum/utils/daemon/FileDescriptorInterceptor.py +0 -1
- meerschaum/utils/daemon/RotatingFile.py +63 -36
- meerschaum/utils/daemon/StdinFile.py +53 -13
- meerschaum/utils/daemon/__init__.py +47 -6
- meerschaum/utils/daemon/_names.py +6 -3
- meerschaum/utils/dataframe.py +480 -82
- meerschaum/utils/debug.py +49 -19
- meerschaum/utils/dtypes/__init__.py +478 -37
- meerschaum/utils/dtypes/sql.py +369 -29
- meerschaum/utils/formatting/__init__.py +5 -2
- meerschaum/utils/formatting/_jobs.py +1 -1
- meerschaum/utils/formatting/_pipes.py +52 -50
- meerschaum/utils/formatting/_pprint.py +1 -0
- meerschaum/utils/formatting/_shell.py +44 -18
- meerschaum/utils/misc.py +268 -186
- meerschaum/utils/packages/__init__.py +25 -40
- meerschaum/utils/packages/_packages.py +42 -34
- meerschaum/utils/pipes.py +213 -0
- meerschaum/utils/process.py +2 -2
- meerschaum/utils/prompt.py +175 -144
- meerschaum/utils/schedule.py +2 -1
- meerschaum/utils/sql.py +135 -49
- meerschaum/utils/threading.py +42 -0
- meerschaum/utils/typing.py +1 -4
- meerschaum/utils/venv/_Venv.py +2 -2
- meerschaum/utils/venv/__init__.py +7 -7
- meerschaum/utils/warnings.py +19 -13
- {meerschaum-2.9.4.dist-info → meerschaum-3.0.0.dist-info}/METADATA +94 -96
- meerschaum-3.0.0.dist-info/RECORD +289 -0
- {meerschaum-2.9.4.dist-info → meerschaum-3.0.0.dist-info}/WHEEL +1 -1
- meerschaum-3.0.0.dist-info/licenses/NOTICE +2 -0
- meerschaum/api/models/_interfaces.py +0 -15
- meerschaum/api/models/_locations.py +0 -15
- meerschaum/api/models/_metrics.py +0 -15
- meerschaum/config/_environment.py +0 -145
- meerschaum/config/static/__init__.py +0 -186
- meerschaum-2.9.4.dist-info/RECORD +0 -263
- {meerschaum-2.9.4.dist-info → meerschaum-3.0.0.dist-info}/entry_points.txt +0 -0
- {meerschaum-2.9.4.dist-info → meerschaum-3.0.0.dist-info}/licenses/LICENSE +0 -0
- {meerschaum-2.9.4.dist-info → meerschaum-3.0.0.dist-info}/top_level.txt +0 -0
- {meerschaum-2.9.4.dist-info → meerschaum-3.0.0.dist-info}/zip-safe +0 -0
@@ -0,0 +1,220 @@
|
|
1
|
+
#! /usr/bin/env python3
|
2
|
+
# vim:fenc=utf-8
|
3
|
+
|
4
|
+
"""
|
5
|
+
Define the properties of a long-lived access token.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from __future__ import annotations
|
9
|
+
|
10
|
+
import base64
|
11
|
+
import uuid
|
12
|
+
from random import randint
|
13
|
+
from typing import Optional, Union, List, Tuple
|
14
|
+
from datetime import datetime, timedelta, timezone
|
15
|
+
|
16
|
+
import meerschaum as mrsm
|
17
|
+
|
18
|
+
_PLACEHOLDER_EXPIRATION = datetime(2000, 1, 1)
|
19
|
+
|
20
|
+
class Token:
|
21
|
+
"""
|
22
|
+
Tokens (long lived access tokens) may be registered and revoked to provide easier authentication (e.g. IoT devices).
|
23
|
+
Tokens must be tied to a Meerschaum user account.
|
24
|
+
"""
|
25
|
+
|
26
|
+
def __init__(
|
27
|
+
self,
|
28
|
+
label: Optional[str] = None,
|
29
|
+
creation: Optional[datetime] = None,
|
30
|
+
expiration: Optional[datetime] = _PLACEHOLDER_EXPIRATION,
|
31
|
+
instance: Optional[str] = None,
|
32
|
+
user: Optional[mrsm.core.User] = None,
|
33
|
+
user_id: Union[int, str, uuid.UUID, None] = None,
|
34
|
+
scopes: Optional[List[str]] = None,
|
35
|
+
is_valid: bool = True,
|
36
|
+
id: Optional[uuid.UUID] = None,
|
37
|
+
secret: Optional[str] = None,
|
38
|
+
secret_hash: Optional[str] = None,
|
39
|
+
):
|
40
|
+
from meerschaum.utils.dtypes import coerce_timezone, round_time
|
41
|
+
from meerschaum.utils.daemon import get_new_daemon_name
|
42
|
+
from meerschaum._internal.static import STATIC_CONFIG
|
43
|
+
now = datetime.now(timezone.utc)
|
44
|
+
default_expiration_days = mrsm.get_config(
|
45
|
+
'api', 'tokens', 'default_expiration_days',
|
46
|
+
) or 366
|
47
|
+
default_expiration = round_time(
|
48
|
+
now + timedelta(days=default_expiration_days),
|
49
|
+
timedelta(days=1),
|
50
|
+
)
|
51
|
+
if expiration == _PLACEHOLDER_EXPIRATION:
|
52
|
+
expiration = default_expiration
|
53
|
+
self.creation = coerce_timezone(creation) if creation is not None else None
|
54
|
+
self.expiration = coerce_timezone(expiration) if expiration is not None else None
|
55
|
+
self._instance_keys = str(instance) if instance is not None else None
|
56
|
+
self.label = label or get_new_daemon_name()
|
57
|
+
self._user = user
|
58
|
+
self._user_id = user_id
|
59
|
+
self.scopes = scopes or list(STATIC_CONFIG['tokens']['scopes'])
|
60
|
+
self.is_valid = is_valid
|
61
|
+
self.id = id
|
62
|
+
self.secret = secret
|
63
|
+
self.secret_hash = secret_hash
|
64
|
+
|
65
|
+
def generate_credentials(self) -> Tuple[uuid.UUID, str]:
|
66
|
+
"""
|
67
|
+
Generate and return the client ID and secret values for this token.
|
68
|
+
"""
|
69
|
+
if self.id and self.secret:
|
70
|
+
return self.id, self.secret
|
71
|
+
|
72
|
+
from meerschaum.utils.misc import generate_password
|
73
|
+
from meerschaum._internal.static import STATIC_CONFIG
|
74
|
+
min_len = STATIC_CONFIG['tokens']['minimum_length']
|
75
|
+
max_len = STATIC_CONFIG['tokens']['maximum_length']
|
76
|
+
|
77
|
+
secret_len = randint(min_len, max_len + 1)
|
78
|
+
self.secret = generate_password(secret_len)
|
79
|
+
self.id = uuid.uuid4()
|
80
|
+
return self.id, self.secret
|
81
|
+
|
82
|
+
def get_api_key(self) -> str:
|
83
|
+
"""
|
84
|
+
Return the API key to be sent in the `Authorization` header.
|
85
|
+
"""
|
86
|
+
return 'mrsm-key:' + base64.b64encode(f"{self.id}:{self.secret}".encode('utf-8')).decode('utf-8')
|
87
|
+
|
88
|
+
@property
|
89
|
+
def instance_connector(self) -> mrsm.connectors.InstanceConnector:
|
90
|
+
"""
|
91
|
+
Return the instance connector to use for this token.
|
92
|
+
"""
|
93
|
+
from meerschaum.connectors.parse import parse_instance_keys
|
94
|
+
return parse_instance_keys(self._instance_keys)
|
95
|
+
|
96
|
+
@property
|
97
|
+
def user(self) -> Union[mrsm.core.User, None]:
|
98
|
+
"""
|
99
|
+
Return the `User` for this token.
|
100
|
+
"""
|
101
|
+
if self._user is not None:
|
102
|
+
return self._user
|
103
|
+
|
104
|
+
if self._user_id is not None:
|
105
|
+
username = self.instance_connector.get_username(self._user_id)
|
106
|
+
if not username:
|
107
|
+
return None
|
108
|
+
_user = mrsm.core.User(
|
109
|
+
username,
|
110
|
+
user_id=self._user_id,
|
111
|
+
instance=str(self.instance_connector),
|
112
|
+
)
|
113
|
+
self._user = _user
|
114
|
+
return _user
|
115
|
+
|
116
|
+
return None
|
117
|
+
|
118
|
+
def register(self, debug: bool = False) -> mrsm.SuccessTuple:
|
119
|
+
"""
|
120
|
+
Register the new token to the configured instance.
|
121
|
+
"""
|
122
|
+
if self.user is None:
|
123
|
+
return False, "Cannot register a token without a user."
|
124
|
+
|
125
|
+
return self.instance_connector.register_token(self, debug=debug)
|
126
|
+
|
127
|
+
def edit(self, debug: bool = False) -> mrsm.SuccessTuple:
|
128
|
+
"""
|
129
|
+
Edit some of the token's attributes (expiration, scopes).
|
130
|
+
"""
|
131
|
+
return self.instance_connector.edit_token(self, debug=debug)
|
132
|
+
|
133
|
+
def invalidate(self, debug: bool = False) -> mrsm.SuccessTuple:
|
134
|
+
"""
|
135
|
+
Set `is_valid` to False for this token.
|
136
|
+
"""
|
137
|
+
self.is_valid = False
|
138
|
+
return self.instance_connector.invalidate_token(self, debug=debug)
|
139
|
+
|
140
|
+
def delete(self, debug: bool = False) -> mrsm.SuccessTuple:
|
141
|
+
"""
|
142
|
+
Delete this token from the instance connector.
|
143
|
+
"""
|
144
|
+
return self.instance_connector.delete_token(self, debug=debug)
|
145
|
+
|
146
|
+
def exists(self, debug: bool = False) -> bool:
|
147
|
+
"""
|
148
|
+
Return `True` if a token's ID exists in the tokens pipe.
|
149
|
+
"""
|
150
|
+
if not self.id:
|
151
|
+
return False
|
152
|
+
return self.instance_connector.token_exists(self.id, debug=debug)
|
153
|
+
|
154
|
+
def to_model(self, refresh: bool = False, debug: bool = False) -> 'TokenModel':
|
155
|
+
"""
|
156
|
+
Export the current state to a `TokenModel`.
|
157
|
+
"""
|
158
|
+
from meerschaum.models import TokenModel
|
159
|
+
in_memory_doc = {
|
160
|
+
'id': self.id,
|
161
|
+
'label': self.label,
|
162
|
+
'creation': self.creation,
|
163
|
+
'expiration': self.expiration,
|
164
|
+
'is_valid': self.is_valid,
|
165
|
+
'user_id': self._user_id,
|
166
|
+
'scopes': self.scopes,
|
167
|
+
}
|
168
|
+
if not refresh:
|
169
|
+
return TokenModel(**in_memory_doc)
|
170
|
+
|
171
|
+
if not self.id:
|
172
|
+
raise ValueError(f"ID is not set for {self}.")
|
173
|
+
|
174
|
+
token_model = self.instance_connector.get_token_model(self.id, debug=debug)
|
175
|
+
if token_model is None:
|
176
|
+
raise ValueError(f"{self} does not exist on instance '{self.instance_connector}'.")
|
177
|
+
|
178
|
+
return token_model
|
179
|
+
|
180
|
+
def get_scopes(self, refresh: bool = False, debug: bool = False) -> List[str]:
|
181
|
+
"""
|
182
|
+
Return the scopes for this `Token`.
|
183
|
+
"""
|
184
|
+
if not refresh:
|
185
|
+
return self.scopes
|
186
|
+
|
187
|
+
self.scopes = self.instance_connector.get_token_scopes(self, debug=debug)
|
188
|
+
return self.scopes
|
189
|
+
|
190
|
+
def get_expiration_status(self, debug: bool = False) -> bool:
|
191
|
+
"""
|
192
|
+
Check the token's expiration against the current timestamp.
|
193
|
+
If it's expired, invalidate the token.
|
194
|
+
|
195
|
+
Returns
|
196
|
+
-------
|
197
|
+
A bool to indication whether the token has expired.
|
198
|
+
A value of `True` means the token is invalid,
|
199
|
+
and `False` indicates a valid token.
|
200
|
+
"""
|
201
|
+
expiration = self.expiration
|
202
|
+
if expiration is None:
|
203
|
+
return False
|
204
|
+
|
205
|
+
now = datetime.now(timezone.utc)
|
206
|
+
is_expired = expiration <= now
|
207
|
+
if is_expired:
|
208
|
+
self.is_valid = False
|
209
|
+
invalidate_success, invalidate_msg = self.invalidate(debug=debug)
|
210
|
+
if not invalidate_success:
|
211
|
+
from meerschaum.utils.warnings import warn
|
212
|
+
warn(f"Failed to invalidate {self}:\n{invalidate_msg}")
|
213
|
+
|
214
|
+
return is_expired
|
215
|
+
|
216
|
+
def __str__(self):
|
217
|
+
return self.label
|
218
|
+
|
219
|
+
def __repr__(self):
|
220
|
+
return self.to_model(refresh=False).__repr__().replace('TokenModel(', 'Token(')
|
meerschaum/core/User/_User.py
CHANGED
@@ -7,14 +7,15 @@ User class definition
|
|
7
7
|
"""
|
8
8
|
|
9
9
|
from __future__ import annotations
|
10
|
+
|
10
11
|
import os
|
11
12
|
import hashlib
|
12
13
|
import hmac
|
14
|
+
import uuid
|
13
15
|
from binascii import b2a_base64, a2b_base64, Error as _BinAsciiError
|
14
16
|
|
15
17
|
import meerschaum as mrsm
|
16
|
-
from meerschaum.utils.typing import Optional, Dict, Any, Union
|
17
|
-
from meerschaum.config.static import STATIC_CONFIG
|
18
|
+
from meerschaum.utils.typing import Optional, Dict, Any, Union, List
|
18
19
|
from meerschaum.utils.warnings import warn
|
19
20
|
|
20
21
|
|
@@ -40,7 +41,7 @@ def hash_password(
|
|
40
41
|
|
41
42
|
rounds: Optional[int], default None
|
42
43
|
If provided, use this number of rounds to generate the hash.
|
43
|
-
Defaults to
|
44
|
+
Defaults to 1,000,000.
|
44
45
|
|
45
46
|
Returns
|
46
47
|
-------
|
@@ -48,6 +49,7 @@ def hash_password(
|
|
48
49
|
See the `passlib` documentation on the string format:
|
49
50
|
https://passlib.readthedocs.io/en/stable/lib/passlib.hash.pbkdf2_digest.html#format-algorithm
|
50
51
|
"""
|
52
|
+
from meerschaum._internal.static import STATIC_CONFIG
|
51
53
|
hash_config = STATIC_CONFIG['users']['password_hash']
|
52
54
|
if password is None:
|
53
55
|
password = ''
|
@@ -64,7 +66,7 @@ def hash_password(
|
|
64
66
|
)
|
65
67
|
return (
|
66
68
|
f"$pbkdf2-{hash_config['algorithm_name']}"
|
67
|
-
+ f"${
|
69
|
+
+ f"${rounds}"
|
68
70
|
+ '$' + ab64_encode(salt).decode('utf-8')
|
69
71
|
+ '$' + ab64_encode(pw_hash).decode('utf-8')
|
70
72
|
)
|
@@ -89,16 +91,16 @@ def verify_password(
|
|
89
91
|
-------
|
90
92
|
A `bool` indicating whether `password` matches `password_hash`.
|
91
93
|
"""
|
94
|
+
from meerschaum._internal.static import STATIC_CONFIG
|
92
95
|
if password is None or password_hash is None:
|
93
96
|
return False
|
94
|
-
hash_config = STATIC_CONFIG['users']['password_hash']
|
95
97
|
try:
|
96
98
|
digest, rounds_str, encoded_salt, encoded_checksum = password_hash.split('$')[1:]
|
97
99
|
algorithm_name = digest.split('-')[-1]
|
98
100
|
salt = ab64_decode(encoded_salt)
|
99
101
|
checksum = ab64_decode(encoded_checksum)
|
100
102
|
rounds = int(rounds_str)
|
101
|
-
except Exception
|
103
|
+
except Exception:
|
102
104
|
warn(f"Failed to extract context from password hash '{password_hash}'. Is it corrupted?")
|
103
105
|
return False
|
104
106
|
|
@@ -177,7 +179,7 @@ class User:
|
|
177
179
|
type: Optional[str] = None,
|
178
180
|
email: Optional[str] = None,
|
179
181
|
attributes: Optional[Dict[str, Any]] = None,
|
180
|
-
user_id:
|
182
|
+
user_id: Union[int, str, uuid.UUID, None] = None,
|
181
183
|
instance: Optional[str] = None
|
182
184
|
):
|
183
185
|
if password is None:
|
@@ -188,7 +190,7 @@ class User:
|
|
188
190
|
self.type = type
|
189
191
|
self._attributes = attributes
|
190
192
|
self._user_id = user_id
|
191
|
-
self._instance_keys = str(instance)
|
193
|
+
self._instance_keys = str(instance) if instance is not None else None
|
192
194
|
|
193
195
|
def __repr__(self):
|
194
196
|
return str(self)
|
@@ -203,14 +205,14 @@ class User:
|
|
203
205
|
return self._attributes
|
204
206
|
|
205
207
|
@property
|
206
|
-
def instance_connector(self) ->
|
208
|
+
def instance_connector(self) -> mrsm.connectors.InstanceConnector:
|
207
209
|
from meerschaum.connectors.parse import parse_instance_keys
|
208
210
|
if '_instance_connector' not in self.__dict__:
|
209
211
|
self._instance_connector = parse_instance_keys(self._instance_keys)
|
210
212
|
return self._instance_connector
|
211
213
|
|
212
214
|
@property
|
213
|
-
def user_id(self) -> Union[int, str, None]:
|
215
|
+
def user_id(self) -> Union[int, str, uuid.UUID, None]:
|
214
216
|
"""NOTE: This causes recursion with the API,
|
215
217
|
so don't try to get fancy with read-only attributes.
|
216
218
|
"""
|
@@ -231,3 +233,26 @@ class User:
|
|
231
233
|
|
232
234
|
self._password_hash = hash_password(self.password)
|
233
235
|
return self._password_hash
|
236
|
+
|
237
|
+
def get_attributes(self, refresh: bool = False, debug: bool = False) -> Dict[str, Any]:
|
238
|
+
"""
|
239
|
+
Return the user's attributes.
|
240
|
+
"""
|
241
|
+
if not refresh:
|
242
|
+
return self.attributes
|
243
|
+
self._attributes = self.instance_connector.get_user_attributes(self, debug=debug) or {}
|
244
|
+
return self._attributes
|
245
|
+
|
246
|
+
def get_scopes(self, refresh: bool = False, debug: bool = False) -> List[str]:
|
247
|
+
"""
|
248
|
+
Return the scopes for this user.
|
249
|
+
"""
|
250
|
+
from meerschaum._internal.static import STATIC_CONFIG
|
251
|
+
_attributes = self.get_attributes(refresh=refresh, debug=debug)
|
252
|
+
return _attributes.get('scopes', None) or list(STATIC_CONFIG['tokens']['scopes'])
|
253
|
+
|
254
|
+
def register(self, debug: bool = False, **kwargs: Any) -> mrsm.SuccessTuple:
|
255
|
+
"""
|
256
|
+
Register a user to the instance connector.
|
257
|
+
"""
|
258
|
+
return self.instance_connector.register_user(self, debug=debug, **kwargs)
|
meerschaum/core/User/__init__.py
CHANGED
@@ -9,7 +9,15 @@ Manager users' metadata via the User class
|
|
9
9
|
from typing import Optional
|
10
10
|
|
11
11
|
import meerschaum as mrsm
|
12
|
-
from meerschaum.core.User._User import User
|
12
|
+
from meerschaum.core.User._User import User, hash_password, verify_password
|
13
|
+
|
14
|
+
|
15
|
+
__all__ = (
|
16
|
+
'User',
|
17
|
+
'hash_password',
|
18
|
+
'verify_password',
|
19
|
+
'is_user_allowed_to_execute',
|
20
|
+
)
|
13
21
|
|
14
22
|
|
15
23
|
def is_user_allowed_to_execute(
|
meerschaum/core/__init__.py
CHANGED
meerschaum/jobs/_Executor.py
CHANGED
@@ -10,28 +10,72 @@ from __future__ import annotations
|
|
10
10
|
from abc import abstractmethod
|
11
11
|
|
12
12
|
from meerschaum.connectors import Connector
|
13
|
-
from meerschaum.utils.typing import
|
13
|
+
from meerschaum.utils.typing import (
|
14
|
+
List, Dict, SuccessTuple, TYPE_CHECKING, Optional, Any, Union, Callable,
|
15
|
+
)
|
14
16
|
|
15
17
|
if TYPE_CHECKING:
|
16
18
|
from meerschaum.jobs import Job
|
19
|
+
from datetime import datetime
|
17
20
|
|
18
21
|
class Executor(Connector):
|
19
22
|
"""
|
20
23
|
Define the methods for managing jobs.
|
21
24
|
"""
|
22
25
|
|
26
|
+
@abstractmethod
|
27
|
+
def get_job_names(self, debug: bool = False) -> List[str]:
|
28
|
+
"""
|
29
|
+
Return a list of existing jobs, including hidden ones.
|
30
|
+
"""
|
31
|
+
|
23
32
|
@abstractmethod
|
24
33
|
def get_job_exists(self, name: str, debug: bool = False) -> bool:
|
25
34
|
"""
|
26
35
|
Return whether a job exists.
|
27
36
|
"""
|
28
|
-
|
37
|
+
|
38
|
+
@abstractmethod
|
39
|
+
def get_jobs(self, debug: bool = False) -> Dict[str, Job]:
|
40
|
+
"""
|
41
|
+
Return a dictionary of existing jobs.
|
42
|
+
"""
|
43
|
+
|
44
|
+
@abstractmethod
|
45
|
+
def get_job_metadata(self, name: str, debug: bool = False) -> Dict[str, Any]:
|
46
|
+
"""
|
47
|
+
Return a job's metadata.
|
48
|
+
"""
|
49
|
+
|
50
|
+
@abstractmethod
|
51
|
+
def get_job_properties(self, name: str, debug: bool = False) -> Dict[str, Any]:
|
52
|
+
"""
|
53
|
+
Return the underlying daemon's properties.
|
54
|
+
"""
|
29
55
|
@abstractmethod
|
30
|
-
def
|
56
|
+
def get_job_status(self, name: str, debug: bool = False) -> str:
|
31
57
|
"""
|
32
|
-
Return
|
58
|
+
Return the job's status.
|
33
59
|
"""
|
34
60
|
|
61
|
+
@abstractmethod
|
62
|
+
def get_job_began(self, name: str, debug: bool = False) -> Union[str, None]:
|
63
|
+
"""
|
64
|
+
Return when a job began running.
|
65
|
+
"""
|
66
|
+
|
67
|
+
@abstractmethod
|
68
|
+
def get_job_ended(self, name: str, debug: bool = False) -> Union[str, None]:
|
69
|
+
"""
|
70
|
+
Return when a job stopped running.
|
71
|
+
"""
|
72
|
+
|
73
|
+
@abstractmethod
|
74
|
+
def get_job_paused(self, name: str, debug: bool = False) -> Union[str, None]:
|
75
|
+
"""
|
76
|
+
Return a job's `paused` timestamp, if it exists.
|
77
|
+
"""
|
78
|
+
|
35
79
|
@abstractmethod
|
36
80
|
def create_job(
|
37
81
|
self,
|
@@ -73,3 +117,43 @@ class Executor(Connector):
|
|
73
117
|
"""
|
74
118
|
Return a job's log output.
|
75
119
|
"""
|
120
|
+
|
121
|
+
@abstractmethod
|
122
|
+
def get_job_stop_time(self, name: str, debug: bool = False) -> Union[datetime, None]:
|
123
|
+
"""
|
124
|
+
Return the job's manual stop time.
|
125
|
+
"""
|
126
|
+
|
127
|
+
@abstractmethod
|
128
|
+
async def monitor_logs_async(
|
129
|
+
self,
|
130
|
+
name: str,
|
131
|
+
callback_function: Callable[[Any], Any],
|
132
|
+
input_callback_function: Callable[[], str],
|
133
|
+
stop_callback_function: Callable[[SuccessTuple], str],
|
134
|
+
stop_on_exit: bool = False,
|
135
|
+
strip_timestamps: bool = False,
|
136
|
+
accept_input: bool = True,
|
137
|
+
debug: bool = False,
|
138
|
+
):
|
139
|
+
"""
|
140
|
+
Monitor a job's log files and await a callback with the changes.
|
141
|
+
"""
|
142
|
+
|
143
|
+
@abstractmethod
|
144
|
+
def monitor_logs(self, *args, **kwargs):
|
145
|
+
"""
|
146
|
+
Monitor a job's log files.
|
147
|
+
"""
|
148
|
+
|
149
|
+
@abstractmethod
|
150
|
+
def get_job_is_blocking_on_stdin(self, name: str, debug: bool = False) -> bool:
|
151
|
+
"""
|
152
|
+
Return whether a job is blocking on stdin.
|
153
|
+
"""
|
154
|
+
|
155
|
+
@abstractmethod
|
156
|
+
def get_job_prompt_kwargs(self, name: str, debug: bool = False) -> Dict[str, Any]:
|
157
|
+
"""
|
158
|
+
Return the kwargs to the blocking prompt.
|
159
|
+
"""
|