moat-kv 0.70.24__py3-none-any.whl → 0.71.6__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.
- moat/kv/__init__.py +6 -7
- moat/kv/_cfg.yaml +5 -8
- moat/kv/actor/__init__.py +2 -1
- moat/kv/actor/deletor.py +4 -1
- moat/kv/auth/__init__.py +12 -13
- moat/kv/auth/_test.py +4 -1
- moat/kv/auth/password.py +11 -7
- moat/kv/backend/mqtt.py +4 -8
- moat/kv/client.py +20 -39
- moat/kv/code.py +3 -3
- moat/kv/command/data.py +4 -3
- moat/kv/command/dump/__init__.py +29 -29
- moat/kv/command/internal.py +2 -3
- moat/kv/command/job.py +1 -2
- moat/kv/command/type.py +3 -6
- moat/kv/data.py +9 -8
- moat/kv/errors.py +16 -8
- moat/kv/mock/__init__.py +2 -12
- moat/kv/model.py +28 -32
- moat/kv/obj/__init__.py +3 -3
- moat/kv/obj/command.py +3 -3
- moat/kv/runner.py +4 -5
- moat/kv/server.py +106 -126
- moat/kv/types.py +8 -6
- {moat_kv-0.70.24.dist-info → moat_kv-0.71.6.dist-info}/METADATA +7 -6
- moat_kv-0.71.6.dist-info/RECORD +47 -0
- {moat_kv-0.70.24.dist-info → moat_kv-0.71.6.dist-info}/WHEEL +1 -1
- moat_kv-0.71.6.dist-info/licenses/LICENSE +3 -0
- moat_kv-0.71.6.dist-info/licenses/LICENSE.APACHE2 +202 -0
- moat_kv-0.71.6.dist-info/licenses/LICENSE.MIT +20 -0
- moat_kv-0.71.6.dist-info/top_level.txt +1 -0
- build/lib/docs/source/conf.py +0 -201
- build/lib/examples/pathify.py +0 -45
- build/lib/moat/kv/__init__.py +0 -19
- build/lib/moat/kv/_cfg.yaml +0 -97
- build/lib/moat/kv/_main.py +0 -91
- build/lib/moat/kv/actor/__init__.py +0 -98
- build/lib/moat/kv/actor/deletor.py +0 -139
- build/lib/moat/kv/auth/__init__.py +0 -444
- build/lib/moat/kv/auth/_test.py +0 -166
- build/lib/moat/kv/auth/password.py +0 -234
- build/lib/moat/kv/auth/root.py +0 -58
- build/lib/moat/kv/backend/__init__.py +0 -67
- build/lib/moat/kv/backend/mqtt.py +0 -74
- build/lib/moat/kv/backend/serf.py +0 -45
- build/lib/moat/kv/client.py +0 -1025
- build/lib/moat/kv/code.py +0 -236
- build/lib/moat/kv/codec.py +0 -11
- build/lib/moat/kv/command/__init__.py +0 -1
- build/lib/moat/kv/command/acl.py +0 -180
- build/lib/moat/kv/command/auth.py +0 -261
- build/lib/moat/kv/command/code.py +0 -293
- build/lib/moat/kv/command/codec.py +0 -186
- build/lib/moat/kv/command/data.py +0 -265
- build/lib/moat/kv/command/dump/__init__.py +0 -143
- build/lib/moat/kv/command/error.py +0 -149
- build/lib/moat/kv/command/internal.py +0 -248
- build/lib/moat/kv/command/job.py +0 -433
- build/lib/moat/kv/command/log.py +0 -53
- build/lib/moat/kv/command/server.py +0 -114
- build/lib/moat/kv/command/type.py +0 -201
- build/lib/moat/kv/config.py +0 -46
- build/lib/moat/kv/data.py +0 -216
- build/lib/moat/kv/errors.py +0 -561
- build/lib/moat/kv/exceptions.py +0 -126
- build/lib/moat/kv/mock/__init__.py +0 -101
- build/lib/moat/kv/mock/mqtt.py +0 -159
- build/lib/moat/kv/mock/serf.py +0 -250
- build/lib/moat/kv/mock/tracer.py +0 -63
- build/lib/moat/kv/model.py +0 -1069
- build/lib/moat/kv/obj/__init__.py +0 -646
- build/lib/moat/kv/obj/command.py +0 -241
- build/lib/moat/kv/runner.py +0 -1347
- build/lib/moat/kv/server.py +0 -2809
- build/lib/moat/kv/types.py +0 -513
- debian/moat-kv/usr/lib/python3/dist-packages/docs/source/conf.py +0 -201
- debian/moat-kv/usr/lib/python3/dist-packages/examples/pathify.py +0 -45
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/__init__.py +0 -19
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/_cfg.yaml +0 -97
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/_main.py +0 -91
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/actor/__init__.py +0 -98
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/actor/deletor.py +0 -139
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/__init__.py +0 -444
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/_test.py +0 -166
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/password.py +0 -234
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/root.py +0 -58
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/backend/__init__.py +0 -67
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/backend/mqtt.py +0 -74
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/backend/serf.py +0 -45
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/client.py +0 -1025
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/code.py +0 -236
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/codec.py +0 -11
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/__init__.py +0 -1
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/acl.py +0 -180
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/auth.py +0 -261
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/code.py +0 -293
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/codec.py +0 -186
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/data.py +0 -265
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/dump/__init__.py +0 -143
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/error.py +0 -149
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/internal.py +0 -248
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/job.py +0 -433
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/log.py +0 -53
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/server.py +0 -114
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/type.py +0 -201
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/config.py +0 -46
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/data.py +0 -216
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/errors.py +0 -561
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/exceptions.py +0 -126
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/__init__.py +0 -101
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/mqtt.py +0 -159
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/serf.py +0 -250
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/tracer.py +0 -63
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/model.py +0 -1069
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/obj/__init__.py +0 -646
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/obj/command.py +0 -241
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/runner.py +0 -1347
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/server.py +0 -2809
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/types.py +0 -513
- docs/source/conf.py +0 -201
- examples/pathify.py +0 -45
- moat/kv/backend/serf.py +0 -45
- moat/kv/codec.py +0 -11
- moat/kv/mock/serf.py +0 -250
- moat_kv-0.70.24.dist-info/RECORD +0 -137
- moat_kv-0.70.24.dist-info/top_level.txt +0 -9
- {moat_kv-0.70.24.dist-info → moat_kv-0.71.6.dist-info}/licenses/LICENSE.txt +0 -0
@@ -1,98 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
This module implements a :class:`asyncactor.Actor` which works on top of
|
3
|
-
a MoaT-KV client.
|
4
|
-
"""
|
5
|
-
from __future__ import annotations
|
6
|
-
|
7
|
-
from asyncactor import Actor
|
8
|
-
from asyncactor.abc import MonitorStream, Transport
|
9
|
-
|
10
|
-
__all__ = [
|
11
|
-
"ClientActor",
|
12
|
-
"ActorState",
|
13
|
-
"BrokenState",
|
14
|
-
"DetachedState",
|
15
|
-
"PartialState",
|
16
|
-
"CompleteState",
|
17
|
-
]
|
18
|
-
|
19
|
-
|
20
|
-
class ClientActor(Actor):
|
21
|
-
def __init__(self, client, *a, topic, **kw):
|
22
|
-
super().__init__(ClientTransport(client, topic), *a, **kw)
|
23
|
-
|
24
|
-
|
25
|
-
class ClientTransport(Transport):
|
26
|
-
"""
|
27
|
-
This class exports the client's direct messaging interface to the
|
28
|
-
actor.
|
29
|
-
"""
|
30
|
-
|
31
|
-
def __init__(self, client, topic):
|
32
|
-
self.client = client
|
33
|
-
self.topic = topic
|
34
|
-
|
35
|
-
def monitor(self):
|
36
|
-
return ClientMonitor(self)
|
37
|
-
|
38
|
-
async def send(self, payload):
|
39
|
-
await self.client.msg_send(self.topic, payload)
|
40
|
-
|
41
|
-
|
42
|
-
class ClientMonitor(MonitorStream):
|
43
|
-
_mon1 = None
|
44
|
-
_mon2 = None
|
45
|
-
_it = None
|
46
|
-
|
47
|
-
async def __aenter__(self):
|
48
|
-
self._mon1 = self.transport.client.msg_monitor(self.transport.topic)
|
49
|
-
self._mon2 = await self._mon1.__aenter__()
|
50
|
-
return self
|
51
|
-
|
52
|
-
async def __aexit__(self, *tb):
|
53
|
-
return await self._mon1.__aexit__(*tb)
|
54
|
-
|
55
|
-
def __aiter__(self):
|
56
|
-
self._it = self._mon2.__aiter__()
|
57
|
-
return self
|
58
|
-
|
59
|
-
async def __anext__(self):
|
60
|
-
msg = await self._it.__anext__()
|
61
|
-
return msg.data
|
62
|
-
|
63
|
-
|
64
|
-
# The following events are used by Runner etc. to notify running jobs
|
65
|
-
# about the current connectivity state.
|
66
|
-
#
|
67
|
-
class ActorState:
|
68
|
-
"""base class for states"""
|
69
|
-
|
70
|
-
def __init__(self, msg=None):
|
71
|
-
self.msg = msg
|
72
|
-
|
73
|
-
def __repr__(self):
|
74
|
-
return "<%s:%r>" % (self.__class__.__name__, self.msg)
|
75
|
-
|
76
|
-
|
77
|
-
class BrokenState(ActorState):
|
78
|
-
"""I have no idea what's happening, probably nothing good"""
|
79
|
-
|
80
|
-
pass
|
81
|
-
|
82
|
-
|
83
|
-
class DetachedState(ActorState):
|
84
|
-
"""I am detached, my actor group is not visible"""
|
85
|
-
|
86
|
-
pass
|
87
|
-
|
88
|
-
|
89
|
-
class PartialState(ActorState):
|
90
|
-
"""Some but not all members of my actor group are visible"""
|
91
|
-
|
92
|
-
pass
|
93
|
-
|
94
|
-
|
95
|
-
class CompleteState(ActorState):
|
96
|
-
"""All members of my actor group are visible"""
|
97
|
-
|
98
|
-
pass
|
@@ -1,139 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
This module implements additional code for the server-side DeleteActor,
|
3
|
-
which is used to clean up the list of deleted nodes.
|
4
|
-
"""
|
5
|
-
|
6
|
-
from __future__ import annotations
|
7
|
-
|
8
|
-
import weakref
|
9
|
-
from collections import deque
|
10
|
-
|
11
|
-
import anyio
|
12
|
-
from asyncactor import Actor, PingEvent, TagEvent
|
13
|
-
from asyncactor.backend import get_transport
|
14
|
-
|
15
|
-
TAGS = 4
|
16
|
-
|
17
|
-
|
18
|
-
class DeleteActor:
|
19
|
-
_enabled = None
|
20
|
-
|
21
|
-
def __init__(self, server):
|
22
|
-
self._server = weakref.ref(server)
|
23
|
-
self.deleted = deque()
|
24
|
-
self.tags = []
|
25
|
-
self.actor = None
|
26
|
-
|
27
|
-
self.max_seen = 0
|
28
|
-
self.n_tags = 0
|
29
|
-
self.n_pings = 0
|
30
|
-
self.n_nodes = 0
|
31
|
-
|
32
|
-
@property
|
33
|
-
def server(self):
|
34
|
-
return self._server()
|
35
|
-
|
36
|
-
async def tock_me(self):
|
37
|
-
"""
|
38
|
-
Add the current tock to our buffer.
|
39
|
-
|
40
|
-
This is updated whenever a new leader is selected.
|
41
|
-
"""
|
42
|
-
self.tags.append(self.server.tock)
|
43
|
-
self.tags = self.tags[-TAGS:]
|
44
|
-
await self.actor.set_value((self.tags[0], self.tags[-1]))
|
45
|
-
|
46
|
-
def add_deleted(self, nodes: NodeSet): # noqa: F821
|
47
|
-
"""
|
48
|
-
These nodes are deleted. Remember them for some time.
|
49
|
-
"""
|
50
|
-
if self.n_nodes == 0:
|
51
|
-
return
|
52
|
-
self.deleted.append((self.server.tock, nodes))
|
53
|
-
|
54
|
-
def purge_to(self, tock):
|
55
|
-
"""
|
56
|
-
Sufficient time has passed since this tock was seen, while all
|
57
|
-
Delete actor nodes were active. Finally flush the entries that have
|
58
|
-
been deleted before it.
|
59
|
-
"""
|
60
|
-
while self.deleted and self.deleted[0][0] < tock:
|
61
|
-
d = self.deleted.popleft()
|
62
|
-
self.server.purge_deleted(d[1])
|
63
|
-
|
64
|
-
async def enable(self, n):
|
65
|
-
"""
|
66
|
-
Enable this actor, as a group of N.
|
67
|
-
"""
|
68
|
-
if self.actor is None:
|
69
|
-
self._enabled = True
|
70
|
-
else:
|
71
|
-
await self.actor.enable(n)
|
72
|
-
self.n_tags = 0
|
73
|
-
self.n_pings = 0
|
74
|
-
self.n_nodes = n
|
75
|
-
|
76
|
-
async def disable(self, n: int = 0):
|
77
|
-
"""
|
78
|
-
Disable this actor. It will still listen, and require N Delete
|
79
|
-
actor members in order to flush its deletion entries.
|
80
|
-
|
81
|
-
Completely disable deletion flushing by passing n=0.
|
82
|
-
"""
|
83
|
-
if self.actor is None:
|
84
|
-
self._enabled = False
|
85
|
-
else:
|
86
|
-
await self.actor.disable()
|
87
|
-
self.n_tags = 0
|
88
|
-
self.n_pings = 0
|
89
|
-
self.n_nodes = n
|
90
|
-
|
91
|
-
async def run(self, evt: anyio.abc.Event = None):
|
92
|
-
"""
|
93
|
-
The task that monitors the Delete actor.
|
94
|
-
"""
|
95
|
-
try:
|
96
|
-
T = get_transport("moat_kv")
|
97
|
-
async with Actor(
|
98
|
-
T(self.server.backend, *self.server.cfg.server.root, "del"),
|
99
|
-
name=self.server.node.name,
|
100
|
-
cfg=self.server.cfg.server.delete,
|
101
|
-
enabled=False,
|
102
|
-
) as actor:
|
103
|
-
self.actor = actor
|
104
|
-
if self._enabled is not None:
|
105
|
-
if self._enabled:
|
106
|
-
await actor.enable()
|
107
|
-
else:
|
108
|
-
await actor.disable()
|
109
|
-
if evt is not None:
|
110
|
-
evt.set()
|
111
|
-
async for evt in actor:
|
112
|
-
if isinstance(evt, PingEvent):
|
113
|
-
val = evt.value
|
114
|
-
if val is None:
|
115
|
-
self.n_pings = self.n_tags = 0
|
116
|
-
continue
|
117
|
-
if len(evt.msg.history) < self.n_nodes:
|
118
|
-
self.n_pings = self.n_tags = 0
|
119
|
-
continue
|
120
|
-
self.n_pings += 1
|
121
|
-
if self.n_pings > self.n_nodes:
|
122
|
-
mx, self.max_seen = (
|
123
|
-
self.max_seen,
|
124
|
-
max(self.max_seen, val[1]),
|
125
|
-
)
|
126
|
-
if val[0] > mx > 0:
|
127
|
-
await self.server.resync_deleted(evt.msg.history)
|
128
|
-
continue
|
129
|
-
self.purge_to(val[0])
|
130
|
-
self.max_seen = max(self.max_seen, val[1])
|
131
|
-
|
132
|
-
elif isinstance(evt, TagEvent):
|
133
|
-
if actor.history_size == self.n_nodes:
|
134
|
-
self.n_tags += 1
|
135
|
-
if self.n_tags > 2:
|
136
|
-
self.purge_to(self.tags[0])
|
137
|
-
await self.tock_me()
|
138
|
-
finally:
|
139
|
-
self.actor = None
|
@@ -1,444 +0,0 @@
|
|
1
|
-
# moat.kv.auth
|
2
|
-
# template for authorization
|
3
|
-
|
4
|
-
"""
|
5
|
-
This set of modules authenticates users.
|
6
|
-
|
7
|
-
A submodule is expected to export a "load(type:str, make:bool, server:bool)"
|
8
|
-
method that returns a class.
|
9
|
-
|
10
|
-
It must recognize, at minimum:
|
11
|
-
|
12
|
-
* load("stream", server:bool):
|
13
|
-
|
14
|
-
A filter used for logging in a user.
|
15
|
-
|
16
|
-
* load("user", server:bool, make:bool)
|
17
|
-
|
18
|
-
A class used to represent the user, or a way to create/manipulate a user record.
|
19
|
-
|
20
|
-
The client process is:
|
21
|
-
|
22
|
-
* create a user:
|
23
|
-
|
24
|
-
* Create a :class:`BaseUserMaker` by calling :meth:`BaseUserMaker.build`
|
25
|
-
with a record conforming to its schema.
|
26
|
-
|
27
|
-
* Export that and save it to the server, at (None,"auth","user",NAME).
|
28
|
-
|
29
|
-
* modify a user:
|
30
|
-
|
31
|
-
* Call :class:`BaseUserMaker.import` with the value from the server.
|
32
|
-
|
33
|
-
* Log in:
|
34
|
-
|
35
|
-
* Create a :class:`BaseUser` by calling :meth:`BaseUserMaker.build`
|
36
|
-
with a record conforming to its schema.
|
37
|
-
|
38
|
-
* Call :meth:`BaseUser.auth`.
|
39
|
-
|
40
|
-
The server process is:
|
41
|
-
|
42
|
-
* create a user:
|
43
|
-
|
44
|
-
* The server intercepts the write call and uses the data to call
|
45
|
-
:meth:`BaseServerAuthMaker.build`.
|
46
|
-
|
47
|
-
* It calls :meth:`BaseServerMaker.save` and stores the actual result.
|
48
|
-
|
49
|
-
* modify a user:
|
50
|
-
|
51
|
-
* the server calls :meth:`BaseServerAuth.read` with the stored data,
|
52
|
-
then sends a record created with :meth:`BaseServerAuth.save` to the client.
|
53
|
-
|
54
|
-
* verify a user:
|
55
|
-
|
56
|
-
* The server calls :meth:`BaseServerAuth.read` with the stored data.
|
57
|
-
|
58
|
-
* The server calls :meth:`BaseServerAuth.auth` with the record from the client.
|
59
|
-
|
60
|
-
"""
|
61
|
-
|
62
|
-
from __future__ import annotations
|
63
|
-
|
64
|
-
import io
|
65
|
-
from importlib import import_module
|
66
|
-
|
67
|
-
import jsonschema
|
68
|
-
from moat.util import NotGiven, Path, attrdict, split_arg, yload
|
69
|
-
|
70
|
-
from ..client import Client, NoData
|
71
|
-
from ..exceptions import NoAuthModuleError
|
72
|
-
from ..model import Entry
|
73
|
-
from ..server import ServerClient, StreamCommand
|
74
|
-
from ..types import ACLFinder, NullACL
|
75
|
-
|
76
|
-
# Empty schema
|
77
|
-
null_schema = {"type": "object", "additionalProperties": False}
|
78
|
-
|
79
|
-
# Additional schema data for specific types
|
80
|
-
add_schema = {
|
81
|
-
"user": {
|
82
|
-
"acl": {
|
83
|
-
"type": "object",
|
84
|
-
"additionalProperties": False,
|
85
|
-
"properties": {"key": {type: "string", "minLength": 1}},
|
86
|
-
},
|
87
|
-
"conv": {
|
88
|
-
"type": "object",
|
89
|
-
"additionalProperties": False,
|
90
|
-
"properties": {"key": {type: "string", "minLength": 1}},
|
91
|
-
},
|
92
|
-
},
|
93
|
-
}
|
94
|
-
|
95
|
-
|
96
|
-
def loader(method: str, typ: str, *a, **k):
|
97
|
-
m = method
|
98
|
-
if "." not in m:
|
99
|
-
m = "moat.kv.auth." + m
|
100
|
-
cls = import_module(m).load(typ, *a, **k)
|
101
|
-
cls._auth_method = method
|
102
|
-
cls._auth_typ = typ
|
103
|
-
cls.aux_schemas = add_schema.get(typ, null_schema)
|
104
|
-
return cls
|
105
|
-
|
106
|
-
|
107
|
-
def gen_auth(s: str):
|
108
|
-
"""
|
109
|
-
Generate auth data from parameters or YAML file (if first char is '=').
|
110
|
-
"""
|
111
|
-
if not isinstance(s, str):
|
112
|
-
return s # called twice. Oh well.
|
113
|
-
|
114
|
-
m, *p = s.split()
|
115
|
-
if len(p) == 0 and m[0] == "=":
|
116
|
-
with open(m[1:], encoding="utf-8") as f:
|
117
|
-
kw = yload(f)
|
118
|
-
m = kw.pop("type")
|
119
|
-
else:
|
120
|
-
kw = {}
|
121
|
-
for pp in p:
|
122
|
-
split_arg(pp, kw)
|
123
|
-
try:
|
124
|
-
m = loader(m, "user", server=False)
|
125
|
-
except ModuleNotFoundError:
|
126
|
-
raise NoAuthModuleError(m) from None
|
127
|
-
return m.build(kw)
|
128
|
-
|
129
|
-
|
130
|
-
def load(typ: str, *, make: bool = False, server: bool):
|
131
|
-
"""
|
132
|
-
This procedure is used to load and return a user management class.
|
133
|
-
|
134
|
-
Arguments:
|
135
|
-
typ: the type of module to load.
|
136
|
-
make: flag that the caller wants a record-generating, not a
|
137
|
-
record-using class.
|
138
|
-
server: flag that the class is to be used on the server, not on the
|
139
|
-
client.
|
140
|
-
|
141
|
-
Types:
|
142
|
-
stream: the filter used for authorizing the user.
|
143
|
-
user: represents a user record.
|
144
|
-
"""
|
145
|
-
raise NotImplementedError("You need to implement me") # pragma: no cover
|
146
|
-
|
147
|
-
|
148
|
-
async def null_server_login(stream):
|
149
|
-
return stream
|
150
|
-
|
151
|
-
|
152
|
-
async def null_client_login(stream, user: BaseClientAuth): # pylint: disable=unused-argument
|
153
|
-
return stream
|
154
|
-
|
155
|
-
|
156
|
-
def _load_example(typ: str, make: bool, server: bool): # pragma: no cover
|
157
|
-
"""example code for :proc:`load`"""
|
158
|
-
if typ == "client":
|
159
|
-
if server:
|
160
|
-
return null_server_login
|
161
|
-
else:
|
162
|
-
return null_client_login
|
163
|
-
if typ != "user":
|
164
|
-
raise NotImplementedError("This module only handles users")
|
165
|
-
if server:
|
166
|
-
if make:
|
167
|
-
return BaseServerAuthMaker
|
168
|
-
else:
|
169
|
-
return BaseServerAuth
|
170
|
-
else:
|
171
|
-
if make:
|
172
|
-
return BaseClientAuthMaker
|
173
|
-
else:
|
174
|
-
return BaseClientAuth
|
175
|
-
|
176
|
-
|
177
|
-
class _AuthLoaded:
|
178
|
-
# This class is mainly there to appease pylint
|
179
|
-
_auth_method = None
|
180
|
-
|
181
|
-
|
182
|
-
class BaseClientAuth(_AuthLoaded):
|
183
|
-
"""
|
184
|
-
This class is used for creating a data record which authenticates a user.
|
185
|
-
|
186
|
-
The schema verifies the input to :meth:`build`.
|
187
|
-
"""
|
188
|
-
|
189
|
-
schema = null_schema
|
190
|
-
|
191
|
-
def __init__(self, **data):
|
192
|
-
jsonschema.validate(instance=data, schema=type(self).schema)
|
193
|
-
for k, v in data.items():
|
194
|
-
setattr(self, k, v)
|
195
|
-
|
196
|
-
@classmethod
|
197
|
-
def build(cls, user):
|
198
|
-
"""
|
199
|
-
Create a user record from the data conforming to this schema.
|
200
|
-
"""
|
201
|
-
return cls(**user)
|
202
|
-
|
203
|
-
@property
|
204
|
-
def ident(self):
|
205
|
-
"""Some user identifier.
|
206
|
-
Required so that the server can actually find the record.
|
207
|
-
"""
|
208
|
-
return "*"
|
209
|
-
|
210
|
-
async def auth(self, client: Client, chroot=()):
|
211
|
-
"""
|
212
|
-
Authorizes this record with the server.
|
213
|
-
"""
|
214
|
-
try:
|
215
|
-
await client._request(
|
216
|
-
action="auth",
|
217
|
-
typ=self._auth_method,
|
218
|
-
iter=False,
|
219
|
-
chroot=chroot,
|
220
|
-
ident=self.ident,
|
221
|
-
data=self.auth_data(),
|
222
|
-
)
|
223
|
-
except NoData:
|
224
|
-
pass
|
225
|
-
|
226
|
-
def auth_data(self):
|
227
|
-
"""
|
228
|
-
Additional data for the initial auth message.
|
229
|
-
|
230
|
-
Does NOT include 'ident', that gets added explicitly by :meth:`auth`.
|
231
|
-
"""
|
232
|
-
return {}
|
233
|
-
|
234
|
-
|
235
|
-
class BaseClientAuthMaker(_AuthLoaded):
|
236
|
-
"""
|
237
|
-
This class is used for creating a data record which describes a user record.
|
238
|
-
|
239
|
-
While :class:`BaseClientAuth` is used solely for authentication,
|
240
|
-
this class is used to represent the server's user data.
|
241
|
-
|
242
|
-
The schema verifies the input to :meth:`build`.
|
243
|
-
"""
|
244
|
-
|
245
|
-
gen_schema = null_schema
|
246
|
-
mod_schema = null_schema
|
247
|
-
_chain = None
|
248
|
-
|
249
|
-
def __init__(self, _initial=True, **data):
|
250
|
-
if _initial:
|
251
|
-
jsonschema.validate(instance=data, schema=type(self).gen_schema)
|
252
|
-
else:
|
253
|
-
jsonschema.validate(instance=data, schema=type(self).mod_schema)
|
254
|
-
for k, v in data.items():
|
255
|
-
setattr(self, k, v)
|
256
|
-
|
257
|
-
@classmethod
|
258
|
-
def build(cls, user, _initial=True):
|
259
|
-
"""
|
260
|
-
Create a user record from the data conforming to this schema.
|
261
|
-
"""
|
262
|
-
return cls(**user, _initial=_initial)
|
263
|
-
|
264
|
-
def export(self):
|
265
|
-
"""Return the data required to re-create the user via :meth:`build`."""
|
266
|
-
return {} # pragma: no cover
|
267
|
-
|
268
|
-
@property
|
269
|
-
def ident(self):
|
270
|
-
"""The identifier for this user.
|
271
|
-
|
272
|
-
Required so that the server can actually find the record.
|
273
|
-
"""
|
274
|
-
return "*" # pragma: no cover
|
275
|
-
|
276
|
-
@classmethod
|
277
|
-
async def recv(cls, client: Client, ident: str, _kind="user", _initial=True):
|
278
|
-
"""Read this user from the server.
|
279
|
-
|
280
|
-
Sample code …
|
281
|
-
"""
|
282
|
-
# pragma: no cover
|
283
|
-
res = await client._request(
|
284
|
-
"auth_get",
|
285
|
-
typ=cls._auth_method,
|
286
|
-
kind=_kind,
|
287
|
-
ident=ident,
|
288
|
-
nchain=0 if _initial else 2,
|
289
|
-
)
|
290
|
-
self = cls(_initial=_initial)
|
291
|
-
self._chain = res.chain
|
292
|
-
return self
|
293
|
-
|
294
|
-
async def send(self, client: Client, _kind="user"):
|
295
|
-
"""Send this user to the server."""
|
296
|
-
try:
|
297
|
-
await client._request(
|
298
|
-
"auth_set",
|
299
|
-
iter=False,
|
300
|
-
typ=type(self)._auth_method,
|
301
|
-
kind=_kind,
|
302
|
-
ident=self.ident,
|
303
|
-
chain=self._chain,
|
304
|
-
data=self.send_data(),
|
305
|
-
)
|
306
|
-
except NoData:
|
307
|
-
pass
|
308
|
-
|
309
|
-
def send_data(self):
|
310
|
-
return {}
|
311
|
-
|
312
|
-
|
313
|
-
class BaseServerAuth(_AuthLoaded):
|
314
|
-
"""
|
315
|
-
This class is used on the server to represent / verify a user.
|
316
|
-
|
317
|
-
The schema verifies whatever data the associated ``ClientAuth`` initially sends.
|
318
|
-
"""
|
319
|
-
|
320
|
-
schema = null_schema.copy()
|
321
|
-
schema["additionalProperties"] = True
|
322
|
-
|
323
|
-
is_super_root = False
|
324
|
-
can_create_subtree = False
|
325
|
-
can_auth_read = False
|
326
|
-
can_auth_write = False
|
327
|
-
|
328
|
-
def __init__(self, data: dict = {}): # pylint: disable=dangerous-default-value
|
329
|
-
if data:
|
330
|
-
for k, v in data.items():
|
331
|
-
setattr(self, k, v)
|
332
|
-
|
333
|
-
@classmethod
|
334
|
-
def load(cls, data: Entry):
|
335
|
-
"""Create a ServerAuth object from existing stored data"""
|
336
|
-
return cls(data.data)
|
337
|
-
|
338
|
-
async def auth(self, cmd: StreamCommand, data): # pylint: disable=unused-argument
|
339
|
-
"""Verify that @data authenticates this user."""
|
340
|
-
jsonschema.validate(instance=data.get("data", {}), schema=type(self).schema)
|
341
|
-
|
342
|
-
def aux_conv(self, data: Entry, root: Entry):
|
343
|
-
from ..types import ConvNull
|
344
|
-
|
345
|
-
try:
|
346
|
-
data = data["conv"].data["key"]
|
347
|
-
res, _ = root.follow_acl(Path(None, "conv", data), create=False, nulls_ok=True)
|
348
|
-
return res
|
349
|
-
except (KeyError, AttributeError):
|
350
|
-
return ConvNull
|
351
|
-
|
352
|
-
def aux_acl(self, data: Entry, root: Entry):
|
353
|
-
try:
|
354
|
-
data = data["acl"].data["key"]
|
355
|
-
if data == "*":
|
356
|
-
return NullACL
|
357
|
-
acl, _ = root.follow_acl(Path(None, "acl", data), create=False, nulls_ok=True)
|
358
|
-
return ACLFinder(acl)
|
359
|
-
except (KeyError, AttributeError):
|
360
|
-
return NullACL
|
361
|
-
|
362
|
-
def info(self):
|
363
|
-
"""
|
364
|
-
Return whatever public data the user might want to have displayed.
|
365
|
-
|
366
|
-
This includes information to identify the user, but not anything
|
367
|
-
that'd be suitable for verifying or even faking authorization.
|
368
|
-
"""
|
369
|
-
return {}
|
370
|
-
|
371
|
-
async def check_read(self, *path, client: ServerClient, data=None): # pylint: disable=unused-argument
|
372
|
-
"""Check that this user may read the element at this location.
|
373
|
-
This method may modify the data.
|
374
|
-
"""
|
375
|
-
return data
|
376
|
-
|
377
|
-
async def check_write(self, *path, client: ServerClient, data=None): # pylint: disable=unused-argument
|
378
|
-
"""Check that this user may write the element at this location.
|
379
|
-
This method may modify the data.
|
380
|
-
"""
|
381
|
-
return data
|
382
|
-
|
383
|
-
|
384
|
-
class RootServerUser(BaseServerAuth):
|
385
|
-
"""The default user when no auth is required
|
386
|
-
|
387
|
-
Interim record. TODO: create a separate ACL thing.
|
388
|
-
"""
|
389
|
-
|
390
|
-
is_super_root = True
|
391
|
-
can_create_subtree = True
|
392
|
-
can_auth_read = True
|
393
|
-
can_auth_write = True
|
394
|
-
|
395
|
-
|
396
|
-
class BaseServerAuthMaker(_AuthLoaded):
|
397
|
-
"""
|
398
|
-
This class is used on the server to verify the transmitted user record
|
399
|
-
and to store it in MoaT-KV.
|
400
|
-
|
401
|
-
The schema verifies the data from the client.
|
402
|
-
"""
|
403
|
-
|
404
|
-
schema = null_schema
|
405
|
-
aux_schemas = None # set by the loader
|
406
|
-
|
407
|
-
def __init__(self, chain=None, data=None):
|
408
|
-
if data is not None and data is not NotGiven:
|
409
|
-
for k, v in data.items():
|
410
|
-
setattr(self, k, v)
|
411
|
-
self._chain = chain
|
412
|
-
|
413
|
-
@classmethod
|
414
|
-
def load(cls, data: Entry):
|
415
|
-
"""Read the user data from MoaT-KV"""
|
416
|
-
return cls(chain=data.chain, data=data.data)
|
417
|
-
|
418
|
-
@classmethod
|
419
|
-
async def recv(
|
420
|
-
cls,
|
421
|
-
cmd: StreamCommand,
|
422
|
-
data: attrdict, # pylint: disable=unused-argument
|
423
|
-
) -> BaseServerAuthMaker:
|
424
|
-
"""Create/update a new user by reading the record from the client"""
|
425
|
-
dt = data.get("data", None) or {}
|
426
|
-
jsonschema.validate(instance=dt, schema=cls.schema)
|
427
|
-
self = cls(chain=data["chain"], data=dt)
|
428
|
-
return self
|
429
|
-
|
430
|
-
@property
|
431
|
-
def ident(self):
|
432
|
-
"""The record to store this user under."""
|
433
|
-
return "*"
|
434
|
-
|
435
|
-
def save(self):
|
436
|
-
"""Return a record to represent this user, suitable for saving to MoaT-KV"""
|
437
|
-
# does NOT contain "ident" or "chain"!
|
438
|
-
return {}
|
439
|
-
|
440
|
-
async def send(self, cmd: StreamCommand): # pylint: disable=unused-argument
|
441
|
-
"""Send a record to the client, possibly multi-step / secured / whatever"""
|
442
|
-
res = {}
|
443
|
-
res["chain"] = self._chain.serialize() if self._chain else None
|
444
|
-
return res
|