meerschaum 2.9.5__py3-none-any.whl → 3.0.0rc1__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 +17 -1
- meerschaum/_internal/entry.py +6 -6
- meerschaum/_internal/shell/Shell.py +1 -1
- meerschaum/_internal/static.py +372 -0
- meerschaum/actions/api.py +12 -2
- meerschaum/actions/bootstrap.py +7 -7
- meerschaum/actions/edit.py +142 -18
- meerschaum/actions/register.py +137 -6
- meerschaum/actions/show.py +117 -29
- meerschaum/actions/stop.py +4 -1
- meerschaum/actions/sync.py +1 -1
- meerschaum/actions/tag.py +9 -8
- meerschaum/api/__init__.py +9 -2
- meerschaum/api/_events.py +39 -2
- meerschaum/api/_oauth2.py +118 -8
- meerschaum/api/_tokens.py +102 -0
- meerschaum/api/dash/__init__.py +0 -1
- meerschaum/api/dash/callbacks/custom.py +2 -2
- meerschaum/api/dash/callbacks/dashboard.py +102 -18
- meerschaum/api/dash/callbacks/plugins.py +0 -1
- meerschaum/api/dash/callbacks/register.py +1 -1
- meerschaum/api/dash/callbacks/settings/__init__.py +1 -0
- meerschaum/api/dash/callbacks/settings/password_reset.py +2 -2
- meerschaum/api/dash/callbacks/settings/tokens.py +388 -0
- meerschaum/api/dash/components.py +30 -8
- meerschaum/api/dash/keys.py +19 -93
- meerschaum/api/dash/pages/dashboard.py +1 -20
- meerschaum/api/dash/pages/settings/__init__.py +1 -0
- meerschaum/api/dash/pages/settings/password_reset.py +1 -1
- meerschaum/api/dash/pages/settings/tokens.py +55 -0
- meerschaum/api/dash/pipes.py +94 -59
- meerschaum/api/dash/sessions.py +12 -0
- meerschaum/api/dash/tokens.py +606 -0
- meerschaum/api/dash/websockets.py +1 -1
- meerschaum/api/dash/webterm.py +4 -0
- meerschaum/api/models/__init__.py +23 -3
- meerschaum/api/models/_actions.py +22 -0
- meerschaum/api/models/_pipes.py +85 -7
- meerschaum/api/models/_tokens.py +81 -0
- meerschaum/api/resources/templates/termpage.html +12 -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 +14 -35
- meerschaum/api/routes/_login.py +49 -12
- meerschaum/api/routes/_misc.py +5 -10
- meerschaum/api/routes/_pipes.py +134 -111
- 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/config/__init__.py +43 -20
- meerschaum/config/_default.py +32 -5
- meerschaum/config/_edit.py +28 -24
- meerschaum/config/_environment.py +1 -1
- meerschaum/config/_patch.py +6 -6
- meerschaum/config/_paths.py +5 -1
- meerschaum/config/_read_config.py +65 -34
- meerschaum/config/_sync.py +6 -3
- meerschaum/config/_version.py +1 -1
- meerschaum/config/stack/__init__.py +24 -5
- meerschaum/config/static.py +18 -0
- meerschaum/connectors/_Connector.py +10 -4
- meerschaum/connectors/__init__.py +4 -20
- meerschaum/connectors/api/_APIConnector.py +34 -6
- meerschaum/connectors/api/_actions.py +2 -2
- meerschaum/connectors/api/_jobs.py +1 -1
- meerschaum/connectors/api/_login.py +33 -7
- meerschaum/connectors/api/_misc.py +2 -2
- meerschaum/connectors/api/_pipes.py +15 -14
- 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 +151 -0
- meerschaum/connectors/instance/_tokens.py +296 -0
- meerschaum/connectors/instance/_users.py +181 -0
- meerschaum/connectors/parse.py +4 -1
- meerschaum/connectors/sql/_SQLConnector.py +8 -5
- meerschaum/connectors/sql/_cli.py +12 -11
- meerschaum/connectors/sql/_create_engine.py +6 -154
- meerschaum/connectors/sql/_fetch.py +2 -18
- meerschaum/connectors/sql/_pipes.py +42 -31
- meerschaum/connectors/sql/_plugins.py +29 -0
- meerschaum/connectors/sql/_sql.py +8 -1
- meerschaum/connectors/sql/_users.py +29 -2
- meerschaum/connectors/sql/tables/__init__.py +1 -1
- meerschaum/connectors/valkey/_ValkeyConnector.py +2 -4
- meerschaum/connectors/valkey/_pipes.py +9 -10
- meerschaum/connectors/valkey/_plugins.py +2 -26
- meerschaum/core/Pipe/__init__.py +31 -14
- meerschaum/core/Pipe/_attributes.py +156 -58
- meerschaum/core/Pipe/_bootstrap.py +54 -24
- meerschaum/core/Pipe/_data.py +41 -1
- meerschaum/core/Pipe/_dtypes.py +29 -14
- meerschaum/core/Pipe/_edit.py +12 -4
- meerschaum/core/Pipe/_show.py +5 -5
- meerschaum/core/Pipe/_sync.py +48 -53
- meerschaum/core/Pipe/_verify.py +1 -1
- meerschaum/{plugins → core/Plugin}/_Plugin.py +9 -11
- meerschaum/core/Plugin/__init__.py +1 -1
- meerschaum/core/Token/_Token.py +221 -0
- meerschaum/core/Token/__init__.py +12 -0
- meerschaum/core/User/_User.py +34 -8
- meerschaum/core/User/__init__.py +9 -1
- meerschaum/core/__init__.py +1 -0
- meerschaum/jobs/_Job.py +3 -2
- meerschaum/jobs/__init__.py +3 -2
- meerschaum/jobs/systemd.py +1 -1
- 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 +22 -7
- meerschaum/plugins/bootstrap.py +2 -1
- meerschaum/utils/_get_pipes.py +68 -27
- meerschaum/utils/daemon/Daemon.py +2 -1
- meerschaum/utils/daemon/__init__.py +30 -2
- meerschaum/utils/dataframe.py +95 -14
- meerschaum/utils/dtypes/__init__.py +91 -18
- meerschaum/utils/dtypes/sql.py +44 -0
- meerschaum/utils/formatting/__init__.py +1 -1
- meerschaum/utils/formatting/_pipes.py +5 -4
- meerschaum/utils/formatting/_shell.py +11 -9
- meerschaum/utils/misc.py +237 -80
- meerschaum/utils/packages/__init__.py +3 -6
- meerschaum/utils/packages/_packages.py +34 -32
- meerschaum/utils/pipes.py +181 -0
- meerschaum/utils/process.py +1 -1
- meerschaum/utils/prompt.py +3 -1
- meerschaum/utils/schedule.py +1 -0
- meerschaum/utils/sql.py +114 -37
- meerschaum/utils/typing.py +1 -4
- meerschaum/utils/venv/_Venv.py +2 -2
- meerschaum/utils/venv/__init__.py +5 -7
- {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc1.dist-info}/METADATA +88 -80
- meerschaum-3.0.0rc1.dist-info/RECORD +282 -0
- {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc1.dist-info}/WHEEL +1 -1
- meerschaum/api/models/_interfaces.py +0 -15
- meerschaum/api/models/_locations.py +0 -15
- meerschaum/api/models/_metrics.py +0 -15
- meerschaum/config/static/__init__.py +0 -186
- meerschaum-2.9.5.dist-info/RECORD +0 -263
- {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc1.dist-info}/entry_points.txt +0 -0
- {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc1.dist-info}/licenses/LICENSE +0 -0
- {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc1.dist-info}/top_level.txt +0 -0
- {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc1.dist-info}/zip-safe +0 -0
meerschaum/api/models/_pipes.py
CHANGED
@@ -8,14 +8,92 @@ Pydantic model for a pipe's keys.
|
|
8
8
|
|
9
9
|
from __future__ import annotations
|
10
10
|
|
11
|
+
from typing import Optional, List, Tuple, Dict, Union, Any
|
12
|
+
|
11
13
|
import meerschaum as mrsm
|
12
|
-
from meerschaum.utils.typing import Optional
|
13
14
|
|
14
|
-
|
15
|
+
from meerschaum.models.pipes import (
|
16
|
+
PipeModel as BasePipeModel,
|
17
|
+
ConnectorKeysModel,
|
18
|
+
MetricKeyModel,
|
19
|
+
LocationKeyModel,
|
20
|
+
)
|
21
|
+
|
22
|
+
pydantic = mrsm.attempt_import('pydantic', lazy=False)
|
23
|
+
from pydantic import (
|
24
|
+
BaseModel,
|
25
|
+
RootModel,
|
26
|
+
field_validator,
|
27
|
+
ValidationInfo,
|
28
|
+
ConfigDict,
|
29
|
+
)
|
30
|
+
|
31
|
+
|
32
|
+
class PipeModel(BasePipeModel):
|
33
|
+
"""
|
34
|
+
A `Pipe`'s model to be used in API responses.
|
35
|
+
"""
|
36
|
+
parameters: Optional[dict] = None
|
37
|
+
model_config = ConfigDict(
|
38
|
+
json_schema_extra={
|
39
|
+
'example': {
|
40
|
+
'connector_keys': 'sql:main',
|
41
|
+
'metric_key': 'weather',
|
42
|
+
'location_key': 'us.co.denver',
|
43
|
+
'instance_keys': 'sql:main',
|
44
|
+
'parameters': {
|
45
|
+
'columns': {
|
46
|
+
'datetime': 'dt',
|
47
|
+
'id': 'id',
|
48
|
+
'value': 'val',
|
49
|
+
},
|
50
|
+
},
|
51
|
+
},
|
52
|
+
},
|
53
|
+
)
|
54
|
+
|
55
|
+
|
56
|
+
class FetchPipesKeysResponseModel(
|
57
|
+
RootModel[List[Tuple[ConnectorKeysModel, MetricKeyModel, LocationKeyModel]]]
|
58
|
+
):
|
59
|
+
"""
|
60
|
+
A list of tuples containing connector, metric, and location keys.
|
61
|
+
"""
|
62
|
+
model_config = ConfigDict(
|
63
|
+
json_schema_extra={
|
64
|
+
'example': [
|
65
|
+
['sql:main', 'weather', 'greenville'],
|
66
|
+
['plugin:noaa', 'weather', 'greenville'],
|
67
|
+
],
|
68
|
+
},
|
69
|
+
)
|
15
70
|
|
16
71
|
|
17
|
-
class
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
72
|
+
class SyncPipeRequestModel(
|
73
|
+
RootModel[
|
74
|
+
Union[
|
75
|
+
List[Dict[str, Any]],
|
76
|
+
Dict[str, List[Any]],
|
77
|
+
str
|
78
|
+
]
|
79
|
+
]
|
80
|
+
):
|
81
|
+
"""
|
82
|
+
The accepted formats of dataframes to be synced.
|
83
|
+
"""
|
84
|
+
model_config = ConfigDict(
|
85
|
+
json_schema_extra={
|
86
|
+
'example': [
|
87
|
+
{
|
88
|
+
'timestamp': '2026-01-01',
|
89
|
+
'id': 1,
|
90
|
+
'value': 100.1,
|
91
|
+
},
|
92
|
+
{
|
93
|
+
'timestamp': '2026-01-02',
|
94
|
+
'id': 1,
|
95
|
+
'value': 200.2,
|
96
|
+
}
|
97
|
+
],
|
98
|
+
}
|
99
|
+
)
|
@@ -0,0 +1,81 @@
|
|
1
|
+
#! /usr/bin/env python3
|
2
|
+
# vim:fenc=utf-8
|
3
|
+
|
4
|
+
"""
|
5
|
+
Response models for tokens.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import uuid
|
9
|
+
from datetime import datetime
|
10
|
+
from typing import Optional, List, Union
|
11
|
+
|
12
|
+
import meerschaum as mrsm
|
13
|
+
from meerschaum._internal.static import STATIC_CONFIG
|
14
|
+
|
15
|
+
from pydantic import BaseModel, RootModel, Field, ConfigDict
|
16
|
+
|
17
|
+
|
18
|
+
class RegisterTokenRequestModel(BaseModel):
|
19
|
+
label: Optional[str] = None
|
20
|
+
expiration: Optional[datetime] = None
|
21
|
+
scopes: List[str] = Field(default_factory=lambda: list(STATIC_CONFIG['tokens']['scopes']))
|
22
|
+
model_config = ConfigDict(
|
23
|
+
json_schema_extra = {
|
24
|
+
'examples': [
|
25
|
+
{
|
26
|
+
'label': 'my-iot-device',
|
27
|
+
'expiration': '2026-01-01T00:00:00Z',
|
28
|
+
'scopes': list(STATIC_CONFIG['tokens']['scopes']),
|
29
|
+
}
|
30
|
+
]
|
31
|
+
}
|
32
|
+
)
|
33
|
+
|
34
|
+
|
35
|
+
class RegisterTokenResponseModel(BaseModel):
|
36
|
+
label: str
|
37
|
+
secret: str
|
38
|
+
id: uuid.UUID
|
39
|
+
api_key: str
|
40
|
+
expiration: Optional[datetime]
|
41
|
+
model_config = ConfigDict(
|
42
|
+
json_schema_extra = {
|
43
|
+
'examples': [
|
44
|
+
{
|
45
|
+
'label': 'my-iot-device',
|
46
|
+
'secret': 'a_very_long_secret_string_that_is_only_shown_once',
|
47
|
+
'id': '1540c2f6-a99d-463c-bfab-47d361200123',
|
48
|
+
'expiration': '2026-01-01T00:00:00Z',
|
49
|
+
'api_key': 'mrsm-key:MTU0MGMyZjYtYTk5ZC00NjNjLWJmYWItNDdkMzYxMjAwMTIzOmFfdmVyeV9sb25nX3NlY3JldF9zdHJpbmdfdGhhdF9pc19vbmx5X3Nob3duX29uY2U=',
|
50
|
+
}
|
51
|
+
]
|
52
|
+
}
|
53
|
+
)
|
54
|
+
|
55
|
+
|
56
|
+
class GetTokenResponseModel(BaseModel):
|
57
|
+
id: Optional[uuid.UUID] = Field(default=None)
|
58
|
+
creation: datetime = Field()
|
59
|
+
expiration: Optional[datetime] = Field()
|
60
|
+
label: str = Field()
|
61
|
+
user_id: Optional[Union[int, str, uuid.UUID]] = Field(default=None)
|
62
|
+
scopes: List[str] = Field(default=list(STATIC_CONFIG['tokens']['scopes']))
|
63
|
+
is_valid: bool = Field(default=True)
|
64
|
+
|
65
|
+
|
66
|
+
class GetTokensResponseModel(RootModel[List[GetTokenResponseModel]]):
|
67
|
+
model_config = ConfigDict(
|
68
|
+
json_schema_extra={
|
69
|
+
'example': [
|
70
|
+
{
|
71
|
+
'label': 'my-iot-device',
|
72
|
+
'id': '1540c2f6-a99d-463c-bfab-47d361200123',
|
73
|
+
'user_id': 1,
|
74
|
+
'scopes': ['pipes:write'],
|
75
|
+
'creation': '2025-07-01T00:00:00Z',
|
76
|
+
'expiration': '2026-01-01T00:00:00Z',
|
77
|
+
'is_valid': True,
|
78
|
+
},
|
79
|
+
],
|
80
|
+
}
|
81
|
+
)
|
@@ -30,6 +30,7 @@ window.addEventListener(
|
|
30
30
|
let connector_keys = event.data['connector_keys'] ? event.data['connector_keys'] : [];
|
31
31
|
let metric_keys = event.data['metric_keys'] ? event.data['metric_keys'] : [];
|
32
32
|
let location_keys = event.data['location_keys'] ? event.data['location_keys'] : [];
|
33
|
+
let tags = event.data['tags'] ? event.data['tags'] : [];
|
33
34
|
let connector_keys_str = " -c";
|
34
35
|
for (let ck of connector_keys){
|
35
36
|
if (typeof ck === "string"){
|
@@ -62,6 +63,16 @@ window.addEventListener(
|
|
62
63
|
if (location_keys_str === " -l"){
|
63
64
|
location_keys_str = "";
|
64
65
|
}
|
66
|
+
let tags_str = " -t";
|
67
|
+
for (tag of tags){
|
68
|
+
if (typeof tag === "string"){
|
69
|
+
quote_str = tag.includes(" ") ? "'" : "";
|
70
|
+
tags_str += " " + quote_str + tag + quote_str;
|
71
|
+
}
|
72
|
+
}
|
73
|
+
if (tags_str === " -t"){
|
74
|
+
tags_str = "";
|
75
|
+
}
|
65
76
|
|
66
77
|
let instance = event.data['instance'] ? event.data['instance'] : '';
|
67
78
|
let flags_str = "";
|
@@ -105,6 +116,7 @@ window.addEventListener(
|
|
105
116
|
+ connector_keys_str
|
106
117
|
+ metric_keys_str
|
107
118
|
+ location_keys_str
|
119
|
+
+ tags_str
|
108
120
|
+ flags_str
|
109
121
|
+ '\r'
|
110
122
|
);
|
@@ -15,6 +15,7 @@ import meerschaum.api.routes._misc
|
|
15
15
|
import meerschaum.api.routes._pipes
|
16
16
|
import meerschaum.api.routes._plugins
|
17
17
|
import meerschaum.api.routes._users
|
18
|
+
import meerschaum.api.routes._tokens
|
18
19
|
import meerschaum.api.routes._version
|
19
20
|
|
20
21
|
from meerschaum.api import _include_dash
|
@@ -18,6 +18,7 @@ from meerschaum.api import (
|
|
18
18
|
manager,
|
19
19
|
private,
|
20
20
|
no_auth,
|
21
|
+
ScopedAuth,
|
21
22
|
)
|
22
23
|
from meerschaum.actions import actions
|
23
24
|
from meerschaum.core.User import is_user_allowed_to_execute
|
@@ -28,7 +29,7 @@ actions_endpoint = endpoints['actions']
|
|
28
29
|
@app.get(actions_endpoint, tags=['Actions'])
|
29
30
|
def get_actions(
|
30
31
|
curr_user = (
|
31
|
-
fastapi.Depends(
|
32
|
+
fastapi.Depends(ScopedAuth(['actions:execute'])) if private else None
|
32
33
|
),
|
33
34
|
) -> List[str]:
|
34
35
|
"""
|
@@ -41,9 +42,7 @@ def get_actions(
|
|
41
42
|
def do_action_legacy(
|
42
43
|
action: str,
|
43
44
|
keywords: Dict[str, Any] = fastapi.Body(...),
|
44
|
-
curr_user = (
|
45
|
-
fastapi.Depends(manager) if not no_auth else None
|
46
|
-
),
|
45
|
+
curr_user = fastapi.Depends(ScopedAuth(['actions:execute'])),
|
47
46
|
) -> SuccessTuple:
|
48
47
|
"""
|
49
48
|
Perform a Meerschaum action (if permissions allow).
|
@@ -10,7 +10,7 @@ import fastapi
|
|
10
10
|
from fastapi import HTTPException
|
11
11
|
|
12
12
|
import meerschaum as mrsm
|
13
|
-
from meerschaum.api import app, endpoints,
|
13
|
+
from meerschaum.api import app, endpoints, ScopedAuth
|
14
14
|
from meerschaum.utils.typing import Optional, Dict, List, Union
|
15
15
|
|
16
16
|
endpoint = endpoints['connectors']
|
@@ -19,9 +19,7 @@ endpoint = endpoints['connectors']
|
|
19
19
|
@app.get(endpoint, tags=['Connectors'])
|
20
20
|
def get_connectors(
|
21
21
|
type: Optional[str] = None,
|
22
|
-
curr_user
|
23
|
-
fastapi.Depends(manager) if not no_auth else None
|
24
|
-
),
|
22
|
+
curr_user=fastapi.Depends(ScopedAuth(['connectors:read'])),
|
25
23
|
) -> Union[Dict[str, List[str]], List[str]]:
|
26
24
|
"""
|
27
25
|
Return the keys of the registered connectors.
|
@@ -54,9 +52,7 @@ def get_connectors(
|
|
54
52
|
@app.get(endpoint + "/{type}", tags=['Connectors'])
|
55
53
|
def get_connectors_by_type(
|
56
54
|
type: str,
|
57
|
-
curr_user
|
58
|
-
fastapi.Depends(manager) if not no_auth else None
|
59
|
-
),
|
55
|
+
curr_user=fastapi.Depends(ScopedAuth(['connectors:read']))
|
60
56
|
):
|
61
57
|
"""
|
62
58
|
Convenience method for `get_connectors()`.
|
meerschaum/api/routes/_jobs.py
CHANGED
@@ -28,11 +28,12 @@ from meerschaum.api import (
|
|
28
28
|
fastapi,
|
29
29
|
app,
|
30
30
|
endpoints,
|
31
|
-
|
31
|
+
ScopedAuth,
|
32
32
|
no_auth,
|
33
|
+
manager,
|
33
34
|
debug,
|
34
35
|
)
|
35
|
-
from meerschaum.
|
36
|
+
from meerschaum._internal.static import STATIC_CONFIG
|
36
37
|
from meerschaum.core.User import is_user_allowed_to_execute
|
37
38
|
|
38
39
|
|
@@ -53,9 +54,7 @@ def _get_job(name: str, sysargs: Union[str, List[str], None] = None):
|
|
53
54
|
|
54
55
|
@app.get(endpoints['jobs'], tags=['Jobs'])
|
55
56
|
def get_jobs(
|
56
|
-
curr_user=(
|
57
|
-
fastapi.Depends(manager) if not no_auth else None
|
58
|
-
),
|
57
|
+
curr_user=fastapi.Depends(ScopedAuth(['jobs:read'])),
|
59
58
|
) -> Dict[str, Dict[str, Any]]:
|
60
59
|
"""
|
61
60
|
Return metadata about the current jobs.
|
@@ -84,9 +83,7 @@ def get_jobs(
|
|
84
83
|
@app.get(endpoints['jobs'] + '/{name}', tags=['Jobs'])
|
85
84
|
def get_job(
|
86
85
|
name: str,
|
87
|
-
curr_user=(
|
88
|
-
fastapi.Depends(manager) if not no_auth else None
|
89
|
-
),
|
86
|
+
curr_user=fastapi.Depends(ScopedAuth(['jobs:read'])),
|
90
87
|
) -> Dict[str, Any]:
|
91
88
|
"""
|
92
89
|
Return metadata for a single job.
|
@@ -137,9 +134,7 @@ def clean_sysargs(sysargs: List[str]) -> List[str]:
|
|
137
134
|
def create_job(
|
138
135
|
name: str,
|
139
136
|
metadata: Union[List[str], Dict[str, Any]],
|
140
|
-
curr_user=(
|
141
|
-
fastapi.Depends(manager) if not no_auth else None
|
142
|
-
),
|
137
|
+
curr_user=fastapi.Depends(ScopedAuth(['jobs:execute', 'jobs:write'])),
|
143
138
|
) -> SuccessTuple:
|
144
139
|
"""
|
145
140
|
Create and start a new job.
|
@@ -172,9 +167,7 @@ def create_job(
|
|
172
167
|
@app.delete(endpoints['jobs'] + '/{name}', tags=['Jobs'])
|
173
168
|
def delete_job(
|
174
169
|
name: str,
|
175
|
-
curr_user=(
|
176
|
-
fastapi.Depends(manager) if not no_auth else None
|
177
|
-
),
|
170
|
+
curr_user=fastapi.Depends(ScopedAuth(['jobs:delete'])),
|
178
171
|
) -> SuccessTuple:
|
179
172
|
"""
|
180
173
|
Delete a job.
|
@@ -189,9 +182,7 @@ def delete_job(
|
|
189
182
|
@app.get(endpoints['jobs'] + '/{name}/exists', tags=['Jobs'])
|
190
183
|
def get_job_exists(
|
191
184
|
name: str,
|
192
|
-
curr_user=(
|
193
|
-
fastapi.Depends(manager) if not no_auth else None
|
194
|
-
),
|
185
|
+
curr_user=fastapi.Depends(ScopedAuth(['jobs:read'])),
|
195
186
|
) -> bool:
|
196
187
|
"""
|
197
188
|
Return whether a job exists.
|
@@ -203,9 +194,7 @@ def get_job_exists(
|
|
203
194
|
@app.get(endpoints['logs'] + '/{name}', tags=['Jobs'])
|
204
195
|
def get_logs(
|
205
196
|
name: str,
|
206
|
-
curr_user=(
|
207
|
-
fastapi.Depends(manager) if not no_auth else None
|
208
|
-
),
|
197
|
+
curr_user=fastapi.Depends(ScopedAuth(['jobs:read', 'logs:read'])),
|
209
198
|
) -> Union[str, None]:
|
210
199
|
"""
|
211
200
|
Return a job's log text.
|
@@ -224,9 +213,7 @@ def get_logs(
|
|
224
213
|
@app.post(endpoints['jobs'] + '/{name}/start', tags=['Jobs'])
|
225
214
|
def start_job(
|
226
215
|
name: str,
|
227
|
-
curr_user=(
|
228
|
-
fastapi.Depends(manager) if not no_auth else None
|
229
|
-
),
|
216
|
+
curr_user=fastapi.Depends(ScopedAuth(['jobs:execute'])),
|
230
217
|
) -> SuccessTuple:
|
231
218
|
"""
|
232
219
|
Start a job if stopped.
|
@@ -247,9 +234,7 @@ def start_job(
|
|
247
234
|
@app.post(endpoints['jobs'] + '/{name}/stop', tags=['Jobs'])
|
248
235
|
def stop_job(
|
249
236
|
name: str,
|
250
|
-
curr_user=(
|
251
|
-
fastapi.Depends(manager) if not no_auth else None
|
252
|
-
),
|
237
|
+
curr_user=fastapi.Depends(ScopedAuth(['jobs:execute', 'josb:stop'])),
|
253
238
|
) -> SuccessTuple:
|
254
239
|
"""
|
255
240
|
Stop a job if running.
|
@@ -270,9 +255,7 @@ def stop_job(
|
|
270
255
|
@app.post(endpoints['jobs'] + '/{name}/pause', tags=['Jobs'])
|
271
256
|
def pause_job(
|
272
257
|
name: str,
|
273
|
-
curr_user=(
|
274
|
-
fastapi.Depends(manager) if not no_auth else None
|
275
|
-
),
|
258
|
+
curr_user=fastapi.Depends(ScopedAuth(['jobs:execute', 'jobs:pause'])),
|
276
259
|
) -> SuccessTuple:
|
277
260
|
"""
|
278
261
|
Pause a job if running.
|
@@ -293,9 +276,7 @@ def pause_job(
|
|
293
276
|
@app.get(endpoints['jobs'] + '/{name}/stop_time', tags=['Jobs'])
|
294
277
|
def get_stop_time(
|
295
278
|
name: str,
|
296
|
-
curr_user=(
|
297
|
-
fastapi.Depends(manager) if not no_auth else None
|
298
|
-
),
|
279
|
+
curr_user=fastapi.Depends(ScopedAuth(['jobs:read'])),
|
299
280
|
) -> Union[datetime, None]:
|
300
281
|
"""
|
301
282
|
Get the timestamp when the job was manually stopped.
|
@@ -307,9 +288,7 @@ def get_stop_time(
|
|
307
288
|
@app.get(endpoints['jobs'] + '/{name}/is_blocking_on_stdin', tags=['Jobs'])
|
308
289
|
def get_is_blocking_on_stdin(
|
309
290
|
name: str,
|
310
|
-
curr_user=(
|
311
|
-
fastapi.Depends(manager) if not no_auth else None
|
312
|
-
),
|
291
|
+
curr_user=fastapi.Depends(ScopedAuth(['jobs:read'])),
|
313
292
|
) -> bool:
|
314
293
|
"""
|
315
294
|
Return whether a job is blocking on stdin.
|
meerschaum/api/routes/_login.py
CHANGED
@@ -6,19 +6,22 @@
|
|
6
6
|
Manage access and refresh tokens.
|
7
7
|
"""
|
8
8
|
|
9
|
+
import uuid
|
9
10
|
from datetime import datetime, timedelta, timezone
|
11
|
+
from typing import Union
|
10
12
|
|
11
13
|
import fastapi
|
12
|
-
from fastapi import Request, status
|
14
|
+
from fastapi import Request, status, Response
|
13
15
|
from fastapi_login.exceptions import InvalidCredentialsException
|
14
16
|
from fastapi.exceptions import RequestValidationError
|
15
17
|
from starlette.responses import JSONResponse
|
16
18
|
|
17
19
|
from meerschaum.api import endpoints, get_api_connector, app, debug, manager, no_auth
|
18
20
|
from meerschaum.core import User
|
19
|
-
from meerschaum.
|
21
|
+
from meerschaum._internal.static import STATIC_CONFIG
|
20
22
|
from meerschaum.utils.typing import Dict, Any
|
21
|
-
from meerschaum.
|
23
|
+
from meerschaum.utils.misc import is_uuid
|
24
|
+
from meerschaum.core.User import verify_password
|
22
25
|
from meerschaum.utils.warnings import warn
|
23
26
|
from meerschaum.api._oauth2 import CustomOAuth2PasswordRequestForm
|
24
27
|
|
@@ -39,26 +42,60 @@ def login(
|
|
39
42
|
Login and set the session token.
|
40
43
|
"""
|
41
44
|
username, password = (
|
42
|
-
(data
|
45
|
+
(data.get('username', None), data.get('password', None))
|
43
46
|
if isinstance(data, dict)
|
44
47
|
else (data.username, data.password)
|
45
|
-
) if not no_auth else ('no-auth', 'no-auth')
|
46
|
-
|
47
|
-
user = User(username, password)
|
48
|
-
correct_password = no_auth or verify_password(
|
49
|
-
password,
|
50
|
-
get_api_connector().get_user_password_hash(user, debug=debug)
|
51
48
|
)
|
52
|
-
|
49
|
+
client_id, client_secret = (
|
50
|
+
(data.get('client_id', None), data.get('client_secret', None))
|
51
|
+
if isinstance(data, dict)
|
52
|
+
else (data.client_id, data.client_secret)
|
53
|
+
)
|
54
|
+
grant_type = (
|
55
|
+
data.get('grant_type', None)
|
56
|
+
if isinstance(data, dict)
|
57
|
+
else data.grant_type
|
58
|
+
)
|
59
|
+
if not grant_type:
|
60
|
+
grant_type = (
|
61
|
+
'password'
|
62
|
+
if username and password
|
63
|
+
else 'client_credentials'
|
64
|
+
)
|
65
|
+
|
66
|
+
expires_dt: Union[datetime, None] = None
|
67
|
+
if grant_type == 'password':
|
68
|
+
user = User(str(username), str(password), instance=get_api_connector())
|
69
|
+
correct_password = no_auth or verify_password(
|
70
|
+
str(password),
|
71
|
+
get_api_connector().get_user_password_hash(user, debug=debug)
|
72
|
+
)
|
73
|
+
if not correct_password:
|
74
|
+
raise InvalidCredentialsException
|
75
|
+
|
76
|
+
elif grant_type == 'client_credentials':
|
77
|
+
if not is_uuid(str(client_id)):
|
78
|
+
raise InvalidCredentialsException
|
79
|
+
token_id = uuid.UUID(client_id)
|
80
|
+
correct_password = no_auth or verify_password(
|
81
|
+
str(client_secret),
|
82
|
+
str(get_api_connector().get_token_secret_hash(token_id, debug=debug))
|
83
|
+
)
|
84
|
+
if not correct_password:
|
85
|
+
raise InvalidCredentialsException
|
86
|
+
else:
|
53
87
|
raise InvalidCredentialsException
|
54
88
|
|
55
89
|
expires_minutes = STATIC_CONFIG['api']['oauth']['token_expires_minutes']
|
56
90
|
expires_delta = timedelta(minutes=expires_minutes)
|
57
91
|
expires_dt = datetime.now(timezone.utc).replace(tzinfo=None) + expires_delta
|
58
92
|
access_token = manager.create_access_token(
|
59
|
-
data={
|
93
|
+
data={
|
94
|
+
'sub': (username if grant_type == 'password' else client_id),
|
95
|
+
},
|
60
96
|
expires=expires_delta
|
61
97
|
)
|
98
|
+
|
62
99
|
return {
|
63
100
|
'access_token': access_token,
|
64
101
|
'token_type': 'bearer',
|
meerschaum/api/routes/_misc.py
CHANGED
@@ -19,7 +19,8 @@ from meerschaum.api import (
|
|
19
19
|
debug,
|
20
20
|
get_api_connector,
|
21
21
|
private,
|
22
|
-
manager
|
22
|
+
manager,
|
23
|
+
ScopedAuth,
|
23
24
|
)
|
24
25
|
from meerschaum.config.paths import API_STATIC_PATH
|
25
26
|
from meerschaum import __version__ as version
|
@@ -38,9 +39,7 @@ def get_favicon() -> Any:
|
|
38
39
|
|
39
40
|
@app.get(endpoints['chaining'], tags=['Misc'])
|
40
41
|
def get_chaining_status(
|
41
|
-
curr_user = (
|
42
|
-
fastapi.Depends(manager) if private else None
|
43
|
-
),
|
42
|
+
curr_user = fastapi.Depends(ScopedAuth(['instance:read'])) if private else None,
|
44
43
|
) -> bool:
|
45
44
|
"""
|
46
45
|
Return whether this API instance may be chained.
|
@@ -50,9 +49,7 @@ def get_chaining_status(
|
|
50
49
|
|
51
50
|
@app.get(endpoints['info'], tags=['Misc'])
|
52
51
|
def get_instance_info(
|
53
|
-
curr_user = (
|
54
|
-
fastapi.Depends(manager) if private else None
|
55
|
-
),
|
52
|
+
curr_user = fastapi.Depends(ScopedAuth(['instance:read'])) if private else None,
|
56
53
|
instance_keys: Optional[str] = None,
|
57
54
|
) -> Dict[str, Union[str, int]]:
|
58
55
|
"""
|
@@ -87,9 +84,7 @@ def get_healtheck(instance_keys: Optional[str] = None) -> Dict[str, Any]:
|
|
87
84
|
if debug:
|
88
85
|
@app.get('/id', tags=['Misc'])
|
89
86
|
def get_ids(
|
90
|
-
curr_user = (
|
91
|
-
fastapi.Depends(manager) if private else None
|
92
|
-
),
|
87
|
+
curr_user = fastapi.Depends(ScopedAuth(['instance:read'])) if private else None,
|
93
88
|
) -> Dict[str, Union[int, str]]:
|
94
89
|
return {
|
95
90
|
'server': SERVER_ID,
|