moat-kv 0.70.22__py3-none-any.whl → 0.70.23__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. moat/kv/__init__.py +1 -0
  2. moat/kv/_cfg.yaml +97 -0
  3. moat/kv/_main.py +6 -9
  4. moat/kv/client.py +34 -50
  5. moat/kv/code.py +10 -3
  6. moat/kv/codec.py +1 -0
  7. moat/kv/config.py +2 -0
  8. moat/kv/data.py +8 -7
  9. moat/kv/errors.py +17 -9
  10. moat/kv/exceptions.py +1 -7
  11. moat/kv/model.py +16 -24
  12. moat/kv/runner.py +38 -35
  13. moat/kv/server.py +86 -90
  14. moat/kv/types.py +5 -8
  15. {moat_kv-0.70.22.dist-info → moat_kv-0.70.23.dist-info}/METADATA +15 -18
  16. moat_kv-0.70.23.dist-info/RECORD +19 -0
  17. {moat_kv-0.70.22.dist-info → moat_kv-0.70.23.dist-info}/WHEEL +1 -1
  18. moat_kv-0.70.23.dist-info/licenses/LICENSE.txt +14 -0
  19. moat/kv/_config.yaml +0 -98
  20. moat/kv/actor/__init__.py +0 -97
  21. moat/kv/actor/deletor.py +0 -137
  22. moat/kv/auth/__init__.py +0 -446
  23. moat/kv/auth/_test.py +0 -172
  24. moat/kv/auth/password.py +0 -232
  25. moat/kv/auth/root.py +0 -56
  26. moat/kv/backend/__init__.py +0 -66
  27. moat/kv/backend/mqtt.py +0 -74
  28. moat/kv/backend/serf.py +0 -44
  29. moat/kv/command/__init__.py +0 -1
  30. moat/kv/command/acl.py +0 -174
  31. moat/kv/command/auth.py +0 -258
  32. moat/kv/command/code.py +0 -306
  33. moat/kv/command/codec.py +0 -190
  34. moat/kv/command/data.py +0 -274
  35. moat/kv/command/dump/__init__.py +0 -141
  36. moat/kv/command/error.py +0 -156
  37. moat/kv/command/internal.py +0 -257
  38. moat/kv/command/job.py +0 -438
  39. moat/kv/command/log.py +0 -52
  40. moat/kv/command/server.py +0 -115
  41. moat/kv/command/type.py +0 -203
  42. moat/kv/mock/__init__.py +0 -97
  43. moat/kv/mock/mqtt.py +0 -164
  44. moat/kv/mock/serf.py +0 -253
  45. moat/kv/mock/tracer.py +0 -65
  46. moat/kv/obj/__init__.py +0 -636
  47. moat/kv/obj/command.py +0 -246
  48. moat_kv-0.70.22.dist-info/LICENSE +0 -3
  49. moat_kv-0.70.22.dist-info/LICENSE.APACHE2 +0 -202
  50. moat_kv-0.70.22.dist-info/LICENSE.MIT +0 -20
  51. moat_kv-0.70.22.dist-info/RECORD +0 -49
  52. {moat_kv-0.70.22.dist-info → moat_kv-0.70.23.dist-info}/top_level.txt +0 -0
moat/kv/auth/__init__.py DELETED
@@ -1,446 +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
- import io
63
- from importlib import import_module
64
-
65
- import jsonschema
66
- from moat.util import NotGiven, Path, attrdict, split_arg, yload
67
-
68
- from ..client import Client, NoData
69
- from ..exceptions import NoAuthModuleError
70
- from ..model import Entry
71
- from ..server import ServerClient, StreamCommand
72
- from ..types import ACLFinder, NullACL
73
-
74
- # Empty schema
75
- null_schema = {"type": "object", "additionalProperties": False}
76
-
77
- # Additional schema data for specific types
78
- add_schema = {
79
- "user": {
80
- "acl": {
81
- "type": "object",
82
- "additionalProperties": False,
83
- "properties": {"key": {type: "string", "minLength": 1}},
84
- },
85
- "conv": {
86
- "type": "object",
87
- "additionalProperties": False,
88
- "properties": {"key": {type: "string", "minLength": 1}},
89
- },
90
- }
91
- }
92
-
93
-
94
- def loader(method: str, typ: str, *a, **k):
95
- m = method
96
- if "." not in m:
97
- m = "moat.kv.auth." + m
98
- cls = import_module(m).load(typ, *a, **k)
99
- cls._auth_method = method
100
- cls._auth_typ = typ
101
- cls.aux_schemas = add_schema.get(typ, null_schema)
102
- return cls
103
-
104
-
105
- def gen_auth(s: str):
106
- """
107
- Generate auth data from parameters or YAML file (if first char is '=').
108
- """
109
- if not isinstance(s, str):
110
- return s # called twice. Oh well.
111
-
112
- m, *p = s.split()
113
- if len(p) == 0 and m[0] == "=":
114
- with io.open(m[1:], "r", encoding="utf-8") as f:
115
- kw = yload(f)
116
- m = kw.pop("type")
117
- else:
118
- kw = {}
119
- for pp in p:
120
- split_arg(pp, kw)
121
- try:
122
- m = loader(m, "user", server=False)
123
- except ModuleNotFoundError:
124
- raise NoAuthModuleError(m) from None
125
- return m.build(kw)
126
-
127
-
128
- def load(typ: str, *, make: bool = False, server: bool):
129
- """
130
- This procedure is used to load and return a user management class.
131
-
132
- Arguments:
133
- typ: the type of module to load.
134
- make: flag that the caller wants a record-generating, not a
135
- record-using class.
136
- server: flag that the class is to be used on the server, not on the
137
- client.
138
-
139
- Types:
140
- stream: the filter used for authorizing the user.
141
- user: represents a user record.
142
- """
143
- raise NotImplementedError("You need to implement me") # pragma: no cover
144
-
145
-
146
- async def null_server_login(stream):
147
- return stream
148
-
149
-
150
- async def null_client_login(stream, user: "BaseClientAuth"): # pylint: disable=unused-argument
151
- return stream
152
-
153
-
154
- def _load_example(typ: str, make: bool, server: bool): # pragma: no cover
155
- """example code for :proc:`load`"""
156
- if typ == "client":
157
- if server:
158
- return null_server_login
159
- else:
160
- return null_client_login
161
- if typ != "user":
162
- raise NotImplementedError("This module only handles users")
163
- if server:
164
- if make:
165
- return BaseServerAuthMaker
166
- else:
167
- return BaseServerAuth
168
- else:
169
- if make:
170
- return BaseClientAuthMaker
171
- else:
172
- return BaseClientAuth
173
-
174
-
175
- class _AuthLoaded:
176
- # This class is mainly there to appease pylint
177
- _auth_method = None
178
-
179
-
180
- class BaseClientAuth(_AuthLoaded):
181
- """
182
- This class is used for creating a data record which authenticates a user.
183
-
184
- The schema verifies the input to :meth:`build`.
185
- """
186
-
187
- schema = null_schema
188
-
189
- def __init__(self, **data):
190
- jsonschema.validate(instance=data, schema=type(self).schema)
191
- for k, v in data.items():
192
- setattr(self, k, v)
193
-
194
- @classmethod
195
- def build(cls, user):
196
- """
197
- Create a user record from the data conforming to this schema.
198
- """
199
- return cls(**user)
200
-
201
- @property
202
- def ident(self):
203
- """Some user identifier.
204
- Required so that the server can actually find the record.
205
- """
206
- return "*"
207
-
208
- async def auth(self, client: Client, chroot=()):
209
- """
210
- Authorizes this record with the server.
211
- """
212
- try:
213
- await client._request(
214
- action="auth",
215
- typ=self._auth_method,
216
- iter=False,
217
- chroot=chroot,
218
- ident=self.ident,
219
- data=self.auth_data(),
220
- )
221
- except NoData:
222
- pass
223
-
224
- def auth_data(self):
225
- """
226
- Additional data for the initial auth message.
227
-
228
- Does NOT include 'ident', that gets added explicitly by :meth:`auth`.
229
- """
230
- return {}
231
-
232
-
233
- class BaseClientAuthMaker(_AuthLoaded):
234
- """
235
- This class is used for creating a data record which describes a user record.
236
-
237
- While :class:`BaseClientAuth` is used solely for authentication,
238
- this class is used to represent the server's user data.
239
-
240
- The schema verifies the input to :meth:`build`.
241
- """
242
-
243
- gen_schema = null_schema
244
- mod_schema = null_schema
245
- _chain = None
246
-
247
- def __init__(self, _initial=True, **data):
248
- if _initial:
249
- jsonschema.validate(instance=data, schema=type(self).gen_schema)
250
- else:
251
- jsonschema.validate(instance=data, schema=type(self).mod_schema)
252
- for k, v in data.items():
253
- setattr(self, k, v)
254
-
255
- @classmethod
256
- def build(cls, user, _initial=True):
257
- """
258
- Create a user record from the data conforming to this schema.
259
- """
260
- return cls(**user, _initial=_initial)
261
-
262
- def export(self):
263
- """Return the data required to re-create the user via :meth:`build`."""
264
- return {} # pragma: no cover
265
-
266
- @property
267
- def ident(self):
268
- """The identifier for this user.
269
-
270
- Required so that the server can actually find the record.
271
- """
272
- return "*" # pragma: no cover
273
-
274
- @classmethod
275
- async def recv(cls, client: Client, ident: str, _kind="user", _initial=True):
276
- """Read this user from the server.
277
-
278
- Sample code …
279
- """
280
- # pragma: no cover
281
- res = await client._request(
282
- "auth_get",
283
- typ=cls._auth_method,
284
- kind=_kind,
285
- ident=ident,
286
- nchain=0 if _initial else 2,
287
- )
288
- self = cls(_initial=_initial)
289
- self._chain = res.chain
290
- return self
291
-
292
- async def send(self, client: Client, _kind="user"):
293
- """Send this user to the server."""
294
- try:
295
- await client._request(
296
- "auth_set",
297
- iter=False,
298
- typ=type(self)._auth_method,
299
- kind=_kind,
300
- ident=self.ident,
301
- chain=self._chain,
302
- data=self.send_data(),
303
- )
304
- except NoData:
305
- pass
306
-
307
- def send_data(self):
308
- return {}
309
-
310
-
311
- class BaseServerAuth(_AuthLoaded):
312
- """
313
- This class is used on the server to represent / verify a user.
314
-
315
- The schema verifies whatever data the associated ``ClientAuth`` initially sends.
316
- """
317
-
318
- schema = null_schema.copy()
319
- schema["additionalProperties"] = True
320
-
321
- is_super_root = False
322
- can_create_subtree = False
323
- can_auth_read = False
324
- can_auth_write = False
325
-
326
- def __init__(self, data: dict = {}): # pylint: disable=dangerous-default-value
327
- if data:
328
- for k, v in data.items():
329
- setattr(self, k, v)
330
-
331
- @classmethod
332
- def load(cls, data: Entry):
333
- """Create a ServerAuth object from existing stored data"""
334
- return cls(data.data)
335
-
336
- async def auth(self, cmd: StreamCommand, data): # pylint: disable=unused-argument
337
- """Verify that @data authenticates this user."""
338
- jsonschema.validate(instance=data.get("data", {}), schema=type(self).schema)
339
-
340
- def aux_conv(self, data: Entry, root: Entry):
341
- from ..types import ConvNull
342
-
343
- try:
344
- data = data["conv"].data["key"]
345
- res, _ = root.follow_acl(
346
- Path(None, "conv", data), create=False, nulls_ok=True
347
- )
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(
358
- Path(None, "acl", data), create=False, nulls_ok=True
359
- )
360
- return ACLFinder(acl)
361
- except (KeyError, AttributeError):
362
- return NullACL
363
-
364
- def info(self):
365
- """
366
- Return whatever public data the user might want to have displayed.
367
-
368
- This includes information to identify the user, but not anything
369
- that'd be suitable for verifying or even faking authorization.
370
- """
371
- return {}
372
-
373
- async def check_read(self, *path, client: ServerClient, data=None): # pylint: disable=unused-argument
374
- """Check that this user may read the element at this location.
375
- This method may modify the data.
376
- """
377
- return data
378
-
379
- async def check_write(self, *path, client: ServerClient, data=None): # pylint: disable=unused-argument
380
- """Check that this user may write the element at this location.
381
- This method may modify the data.
382
- """
383
- return data
384
-
385
-
386
- class RootServerUser(BaseServerAuth):
387
- """The default user when no auth is required
388
-
389
- Interim record. TODO: create a separate ACL thing.
390
- """
391
-
392
- is_super_root = True
393
- can_create_subtree = True
394
- can_auth_read = True
395
- can_auth_write = True
396
-
397
-
398
- class BaseServerAuthMaker(_AuthLoaded):
399
- """
400
- This class is used on the server to verify the transmitted user record
401
- and to store it in MoaT-KV.
402
-
403
- The schema verifies the data from the client.
404
- """
405
-
406
- schema = null_schema
407
- aux_schemas = None # set by the loader
408
-
409
- def __init__(self, chain=None, data=None):
410
- if data is not None and data is not NotGiven:
411
- for k, v in data.items():
412
- setattr(self, k, v)
413
- self._chain = chain
414
-
415
- @classmethod
416
- def load(cls, data: Entry):
417
- """Read the user data from MoaT-KV"""
418
- return cls(chain=data.chain, data=data.data)
419
-
420
- @classmethod
421
- async def recv(
422
- cls,
423
- cmd: StreamCommand,
424
- data: attrdict, # pylint: disable=unused-argument
425
- ) -> "BaseServerAuthMaker":
426
- """Create/update a new user by reading the record from the client"""
427
- dt = data.get("data", None) or {}
428
- jsonschema.validate(instance=dt, schema=cls.schema)
429
- self = cls(chain=data["chain"], data=dt)
430
- return self
431
-
432
- @property
433
- def ident(self):
434
- """The record to store this user under."""
435
- return "*"
436
-
437
- def save(self):
438
- """Return a record to represent this user, suitable for saving to MoaT-KV"""
439
- # does NOT contain "ident" or "chain"!
440
- return {}
441
-
442
- async def send(self, cmd: StreamCommand): # pylint: disable=unused-argument
443
- """Send a record to the client, possibly multi-step / secured / whatever"""
444
- res = {}
445
- res["chain"] = self._chain.serialize() if self._chain else None
446
- return res
moat/kv/auth/_test.py DELETED
@@ -1,172 +0,0 @@
1
- #
2
- """
3
- Test auth method.
4
-
5
- Does not limit anything, allows everything.
6
- """
7
-
8
- import logging
9
-
10
- log = logging.getLogger(__name__)
11
-
12
- from ..client import Client
13
- from . import (
14
- BaseClientAuth,
15
- BaseClientAuthMaker,
16
- BaseServerAuthMaker,
17
- RootServerUser,
18
- null_client_login,
19
- null_server_login,
20
- )
21
-
22
-
23
- def load(typ: str, *, make: bool = False, server: bool):
24
- if typ == "client":
25
- if server:
26
- return null_server_login
27
- else:
28
- return null_client_login
29
- if typ != "user":
30
- raise NotImplementedError("This module only handles users")
31
- if server:
32
- if make:
33
- return ServerUserMaker
34
- else:
35
- return ServerUser
36
- else:
37
- if make:
38
- return ClientUserMaker
39
- else:
40
- return ClientUser
41
-
42
-
43
- class ServerUserMaker(BaseServerAuthMaker):
44
- name = None
45
-
46
- @property
47
- def ident(self):
48
- return self.name
49
-
50
- # Overly-complicated methods of exchanging the user name
51
-
52
- @classmethod
53
- async def recv(cls, cmd, data):
54
- await cmd.send(step="GiveName")
55
- msg = await cmd.recv()
56
- assert msg.step == "HasName"
57
- self = cls()
58
- self.name = msg.name
59
- return self
60
-
61
- async def send(self, cmd):
62
- await cmd.send(step="SendWant")
63
- msg = await cmd.recv()
64
- assert msg.step == "WantName"
65
- await cmd.send(
66
- step="SendName", name=self.name, chain=self._chain.serialize(nchain=3)
67
- )
68
- msg = await cmd.recv()
69
-
70
- # Annoying methods to read+save the user name from/to KV
71
-
72
- @classmethod
73
- def load(cls, data):
74
- self = super().load(data)
75
- self.name = data.name
76
- return self
77
-
78
-
79
- class ServerUser(RootServerUser):
80
- pass
81
-
82
-
83
- class ClientUserMaker(BaseClientAuthMaker):
84
- gen_schema = dict(
85
- type="object",
86
- additionalProperties=False,
87
- properties=dict(
88
- name=dict(type="string", minLength=1, pattern="^[a-zA-Z][a-zA-Z0-9_]*$")
89
- ),
90
- required=["name"],
91
- )
92
- mod_schema = dict(
93
- type="object",
94
- additionalProperties=False,
95
- properties=dict(
96
- name=dict(type="string", minLength=1, pattern="^[a-zA-Z][a-zA-Z0-9_]*$")
97
- ),
98
- # required=[],
99
- )
100
- name = None
101
-
102
- @property
103
- def ident(self):
104
- return self.name
105
-
106
- # Overly-complicated methods of exchanging the user name
107
-
108
- @classmethod
109
- async def recv(cls, client: Client, ident: str, _kind: str = "user", _initial=True):
110
- """Read a record representing a user from the server."""
111
- async with client._stream(
112
- action="auth_get",
113
- typ=cls._auth_method,
114
- kind=_kind,
115
- ident=ident,
116
- stream=True,
117
- nchain=0 if _initial else 2,
118
- ) as s:
119
- m = await s.recv()
120
- assert m.step == "SendWant", m
121
- await s.send(step="WantName")
122
- m = await s.recv()
123
- assert m.step == "SendName", m
124
- assert m.name == ident
125
-
126
- self = cls(name=m.name, _initial=_initial)
127
- self._chain = m.chain
128
- return self
129
-
130
- async def send(self, client: Client, _kind="user"):
131
- """Send a record representing this user to the server."""
132
- async with client._stream(
133
- action="auth_set",
134
- typ=type(self)._auth_method,
135
- kind=_kind,
136
- ident=self.ident,
137
- stream=True,
138
- ) as s:
139
- # we could initially send the ident but don't here, for testing
140
- m = await s.recv()
141
- assert m.step == "GiveName", m
142
- await s.send(step="HasName", name=self.name, chain=self._chain)
143
- m = await s.recv()
144
- assert m.chain.prev is None
145
-
146
- def export(self):
147
- """Return the data required to re-create the user via :meth:`build`."""
148
- return {"name": self.name}
149
-
150
-
151
- class ClientUser(BaseClientAuth):
152
- name = None
153
-
154
- schema = dict(
155
- type="object",
156
- additionalProperties=False,
157
- properties=dict(
158
- name=dict(type="string", minLength=1, pattern="^[a-zA-Z][a-zA-Z0-9_]*$")
159
- ),
160
- required=["name"],
161
- )
162
- _name = None
163
-
164
- @property
165
- def ident(self):
166
- return self.name
167
-
168
- @classmethod
169
- def build(cls, user):
170
- self = super().build(user)
171
- self.name = user["name"]
172
- return self