moat-kv 0.70.23__py3-none-any.whl → 0.71.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.
Files changed (177) hide show
  1. build/lib/docs/source/conf.py +201 -0
  2. build/lib/examples/pathify.py +45 -0
  3. build/lib/moat/kv/__init__.py +19 -0
  4. build/lib/moat/kv/_cfg.yaml +93 -0
  5. build/lib/moat/kv/_main.py +91 -0
  6. build/lib/moat/kv/actor/__init__.py +98 -0
  7. build/lib/moat/kv/actor/deletor.py +139 -0
  8. build/lib/moat/kv/auth/__init__.py +444 -0
  9. build/lib/moat/kv/auth/_test.py +166 -0
  10. build/lib/moat/kv/auth/password.py +234 -0
  11. build/lib/moat/kv/auth/root.py +58 -0
  12. build/lib/moat/kv/backend/__init__.py +67 -0
  13. build/lib/moat/kv/backend/mqtt.py +71 -0
  14. build/lib/moat/kv/client.py +1025 -0
  15. build/lib/moat/kv/code.py +236 -0
  16. build/lib/moat/kv/codec.py +11 -0
  17. build/lib/moat/kv/command/__init__.py +1 -0
  18. build/lib/moat/kv/command/acl.py +180 -0
  19. build/lib/moat/kv/command/auth.py +261 -0
  20. build/lib/moat/kv/command/code.py +293 -0
  21. build/lib/moat/kv/command/codec.py +186 -0
  22. build/lib/moat/kv/command/data.py +265 -0
  23. build/lib/moat/kv/command/dump/__init__.py +143 -0
  24. build/lib/moat/kv/command/error.py +149 -0
  25. build/lib/moat/kv/command/internal.py +248 -0
  26. build/lib/moat/kv/command/job.py +433 -0
  27. build/lib/moat/kv/command/log.py +53 -0
  28. build/lib/moat/kv/command/server.py +114 -0
  29. build/lib/moat/kv/command/type.py +201 -0
  30. build/lib/moat/kv/config.py +46 -0
  31. build/lib/moat/kv/data.py +216 -0
  32. build/lib/moat/kv/errors.py +561 -0
  33. build/lib/moat/kv/exceptions.py +126 -0
  34. build/lib/moat/kv/mock/__init__.py +101 -0
  35. build/lib/moat/kv/mock/mqtt.py +159 -0
  36. build/lib/moat/kv/mock/tracer.py +63 -0
  37. build/lib/moat/kv/model.py +1069 -0
  38. build/lib/moat/kv/obj/__init__.py +646 -0
  39. build/lib/moat/kv/obj/command.py +241 -0
  40. build/lib/moat/kv/runner.py +1347 -0
  41. build/lib/moat/kv/server.py +2809 -0
  42. build/lib/moat/kv/types.py +513 -0
  43. ci/rtd-requirements.txt +4 -0
  44. ci/test-requirements.txt +7 -0
  45. ci/travis.sh +96 -0
  46. debian/.gitignore +7 -0
  47. debian/changelog +1435 -0
  48. debian/control +43 -0
  49. debian/moat-kv/usr/lib/python3/dist-packages/docs/source/conf.py +201 -0
  50. debian/moat-kv/usr/lib/python3/dist-packages/examples/pathify.py +45 -0
  51. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/__init__.py +19 -0
  52. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/_cfg.yaml +93 -0
  53. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/_main.py +91 -0
  54. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/actor/__init__.py +98 -0
  55. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/actor/deletor.py +139 -0
  56. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/__init__.py +444 -0
  57. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/_test.py +166 -0
  58. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/password.py +234 -0
  59. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/root.py +58 -0
  60. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/backend/__init__.py +67 -0
  61. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/backend/mqtt.py +71 -0
  62. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/client.py +1025 -0
  63. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/code.py +236 -0
  64. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/codec.py +11 -0
  65. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/__init__.py +1 -0
  66. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/acl.py +180 -0
  67. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/auth.py +261 -0
  68. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/code.py +293 -0
  69. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/codec.py +186 -0
  70. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/data.py +265 -0
  71. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/dump/__init__.py +143 -0
  72. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/error.py +149 -0
  73. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/internal.py +248 -0
  74. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/job.py +433 -0
  75. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/log.py +53 -0
  76. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/server.py +114 -0
  77. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/type.py +201 -0
  78. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/config.py +46 -0
  79. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/data.py +216 -0
  80. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/errors.py +561 -0
  81. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/exceptions.py +126 -0
  82. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/__init__.py +101 -0
  83. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/mqtt.py +159 -0
  84. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/tracer.py +63 -0
  85. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/model.py +1069 -0
  86. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/obj/__init__.py +646 -0
  87. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/obj/command.py +241 -0
  88. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/runner.py +1347 -0
  89. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/server.py +2809 -0
  90. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/types.py +513 -0
  91. debian/moat-kv.postinst +3 -0
  92. debian/rules +20 -0
  93. debian/source/format +1 -0
  94. debian/watch +4 -0
  95. docs/Makefile +20 -0
  96. docs/make.bat +36 -0
  97. docs/source/TODO.rst +61 -0
  98. docs/source/_static/.gitkeep +0 -0
  99. docs/source/acls.rst +80 -0
  100. docs/source/auth.rst +84 -0
  101. docs/source/client_protocol.rst +456 -0
  102. docs/source/code.rst +341 -0
  103. docs/source/command_line.rst +1187 -0
  104. docs/source/common_protocol.rst +47 -0
  105. docs/source/conf.py +201 -0
  106. docs/source/debugging.rst +70 -0
  107. docs/source/extend.rst +37 -0
  108. docs/source/history.rst +36 -0
  109. docs/source/index.rst +75 -0
  110. docs/source/model.rst +54 -0
  111. docs/source/overview.rst +83 -0
  112. docs/source/related.rst +89 -0
  113. docs/source/server_protocol.rst +450 -0
  114. docs/source/startup.rst +31 -0
  115. docs/source/translator.rst +244 -0
  116. docs/source/tutorial.rst +711 -0
  117. docs/source/v3.rst +168 -0
  118. examples/code/transform.scale.yml +21 -0
  119. examples/code/transform.switch.yml +82 -0
  120. examples/code/transform.timeslot.yml +63 -0
  121. examples/pathify.py +45 -0
  122. moat/kv/_cfg.yaml +2 -6
  123. moat/kv/actor/__init__.py +98 -0
  124. moat/kv/actor/deletor.py +139 -0
  125. moat/kv/auth/__init__.py +444 -0
  126. moat/kv/auth/_test.py +166 -0
  127. moat/kv/auth/password.py +234 -0
  128. moat/kv/auth/root.py +58 -0
  129. moat/kv/backend/__init__.py +67 -0
  130. moat/kv/backend/mqtt.py +71 -0
  131. moat/kv/command/__init__.py +1 -0
  132. moat/kv/command/acl.py +180 -0
  133. moat/kv/command/auth.py +261 -0
  134. moat/kv/command/code.py +293 -0
  135. moat/kv/command/codec.py +186 -0
  136. moat/kv/command/data.py +265 -0
  137. moat/kv/command/dump/__init__.py +143 -0
  138. moat/kv/command/error.py +149 -0
  139. moat/kv/command/internal.py +248 -0
  140. moat/kv/command/job.py +433 -0
  141. moat/kv/command/log.py +53 -0
  142. moat/kv/command/server.py +114 -0
  143. moat/kv/command/type.py +201 -0
  144. moat/kv/mock/__init__.py +101 -0
  145. moat/kv/mock/mqtt.py +159 -0
  146. moat/kv/mock/tracer.py +63 -0
  147. moat/kv/obj/__init__.py +646 -0
  148. moat/kv/obj/command.py +241 -0
  149. {moat_kv-0.70.23.dist-info → moat_kv-0.71.0.dist-info}/METADATA +2 -5
  150. moat_kv-0.71.0.dist-info/RECORD +188 -0
  151. moat_kv-0.71.0.dist-info/top_level.txt +9 -0
  152. scripts/current +15 -0
  153. scripts/env +8 -0
  154. scripts/init +39 -0
  155. scripts/recover +17 -0
  156. scripts/rotate +33 -0
  157. scripts/run +29 -0
  158. scripts/run-all +10 -0
  159. scripts/run-any +10 -0
  160. scripts/run-single +15 -0
  161. scripts/success +4 -0
  162. systemd/moat-kv-recover.service +21 -0
  163. systemd/moat-kv-rotate.service +20 -0
  164. systemd/moat-kv-rotate.timer +10 -0
  165. systemd/moat-kv-run-all.service +26 -0
  166. systemd/moat-kv-run-all@.service +25 -0
  167. systemd/moat-kv-run-any.service +26 -0
  168. systemd/moat-kv-run-any@.service +25 -0
  169. systemd/moat-kv-run-single.service +26 -0
  170. systemd/moat-kv-run-single@.service +25 -0
  171. systemd/moat-kv.service +27 -0
  172. systemd/postinst +7 -0
  173. systemd/sysusers +3 -0
  174. moat_kv-0.70.23.dist-info/RECORD +0 -19
  175. moat_kv-0.70.23.dist-info/top_level.txt +0 -1
  176. {moat_kv-0.70.23.dist-info → moat_kv-0.71.0.dist-info}/WHEEL +0 -0
  177. {moat_kv-0.70.23.dist-info → moat_kv-0.71.0.dist-info}/licenses/LICENSE.txt +0 -0
@@ -0,0 +1,444 @@
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
@@ -0,0 +1,166 @@
1
+ #
2
+ """
3
+ Test auth method.
4
+
5
+ Does not limit anything, allows everything.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import logging
11
+
12
+ log = logging.getLogger(__name__)
13
+
14
+ from ..client import Client
15
+ from . import (
16
+ BaseClientAuth,
17
+ BaseClientAuthMaker,
18
+ BaseServerAuthMaker,
19
+ RootServerUser,
20
+ null_client_login,
21
+ null_server_login,
22
+ )
23
+
24
+
25
+ def load(typ: str, *, make: bool = False, server: bool):
26
+ if typ == "client":
27
+ if server:
28
+ return null_server_login
29
+ else:
30
+ return null_client_login
31
+ if typ != "user":
32
+ raise NotImplementedError("This module only handles users")
33
+ if server:
34
+ if make:
35
+ return ServerUserMaker
36
+ else:
37
+ return ServerUser
38
+ else:
39
+ if make:
40
+ return ClientUserMaker
41
+ else:
42
+ return ClientUser
43
+
44
+
45
+ class ServerUserMaker(BaseServerAuthMaker):
46
+ name = None
47
+
48
+ @property
49
+ def ident(self):
50
+ return self.name
51
+
52
+ # Overly-complicated methods of exchanging the user name
53
+
54
+ @classmethod
55
+ async def recv(cls, cmd, data):
56
+ await cmd.send(step="GiveName")
57
+ msg = await cmd.recv()
58
+ assert msg.step == "HasName"
59
+ self = cls()
60
+ self.name = msg.name
61
+ return self
62
+
63
+ async def send(self, cmd):
64
+ await cmd.send(step="SendWant")
65
+ msg = await cmd.recv()
66
+ assert msg.step == "WantName"
67
+ await cmd.send(step="SendName", name=self.name, chain=self._chain.serialize(nchain=3))
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(name=dict(type="string", minLength=1, pattern="^[a-zA-Z][a-zA-Z0-9_]*$")),
88
+ required=["name"],
89
+ )
90
+ mod_schema = dict(
91
+ type="object",
92
+ additionalProperties=False,
93
+ properties=dict(name=dict(type="string", minLength=1, pattern="^[a-zA-Z][a-zA-Z0-9_]*$")),
94
+ # required=[],
95
+ )
96
+ name = None
97
+
98
+ @property
99
+ def ident(self):
100
+ return self.name
101
+
102
+ # Overly-complicated methods of exchanging the user name
103
+
104
+ @classmethod
105
+ async def recv(cls, client: Client, ident: str, _kind: str = "user", _initial=True):
106
+ """Read a record representing a user from the server."""
107
+ async with client._stream(
108
+ action="auth_get",
109
+ typ=cls._auth_method,
110
+ kind=_kind,
111
+ ident=ident,
112
+ stream=True,
113
+ nchain=0 if _initial else 2,
114
+ ) as s:
115
+ m = await s.recv()
116
+ assert m.step == "SendWant", m
117
+ await s.send(step="WantName")
118
+ m = await s.recv()
119
+ assert m.step == "SendName", m
120
+ assert m.name == ident
121
+
122
+ self = cls(name=m.name, _initial=_initial)
123
+ self._chain = m.chain
124
+ return self
125
+
126
+ async def send(self, client: Client, _kind="user"):
127
+ """Send a record representing this user to the server."""
128
+ async with client._stream(
129
+ action="auth_set",
130
+ typ=type(self)._auth_method,
131
+ kind=_kind,
132
+ ident=self.ident,
133
+ stream=True,
134
+ ) as s:
135
+ # we could initially send the ident but don't here, for testing
136
+ m = await s.recv()
137
+ assert m.step == "GiveName", m
138
+ await s.send(step="HasName", name=self.name, chain=self._chain)
139
+ m = await s.recv()
140
+ assert m.chain.prev is None
141
+
142
+ def export(self):
143
+ """Return the data required to re-create the user via :meth:`build`."""
144
+ return {"name": self.name}
145
+
146
+
147
+ class ClientUser(BaseClientAuth):
148
+ name = None
149
+
150
+ schema = dict(
151
+ type="object",
152
+ additionalProperties=False,
153
+ properties=dict(name=dict(type="string", minLength=1, pattern="^[a-zA-Z][a-zA-Z0-9_]*$")),
154
+ required=["name"],
155
+ )
156
+ _name = None
157
+
158
+ @property
159
+ def ident(self):
160
+ return self.name
161
+
162
+ @classmethod
163
+ def build(cls, user):
164
+ self = super().build(user)
165
+ self.name = user["name"]
166
+ return self