openshell-shared 0.1.2__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.
- api/__init__.py +1 -0
- api/manager/__init__.py +1 -0
- api/manager/v1/__init__.py +78 -0
- api/manager/v1/authentication.py +278 -0
- api/manager/v1/client.py +111 -0
- api/manager/v1/domains.py +64 -0
- api/manager/v1/entities.py +97 -0
- api/manager/v1/exceptions.py +98 -0
- api/manager/v1/identity.py +44 -0
- api/manager/v1/models.py +342 -0
- api/manager/v1/passports.py +131 -0
- api/manager/v1/sessions.py +83 -0
- api/manager/v1/transport.py +253 -0
- api/manager/v1/tunnels.py +120 -0
- cryptography/__init__.py +0 -0
- cryptography/certificate.py +390 -0
- cryptography/encoding.py +0 -0
- cryptography/identity.py +124 -0
- cryptography/keys.py +463 -0
- cryptography/signatures.py +63 -0
- cryptography/utils.py +0 -0
- domain/__init__.py +0 -0
- domain/domain.py +80 -0
- domain/membership.py +21 -0
- domain/permissions.py +14 -0
- domain/policies.py +2 -0
- identity/__init__.py +0 -0
- identity/identification.py +64 -0
- identity/store.py +150 -0
- modules/__init__.py +0 -0
- modules/shell/__init__.py +0 -0
- modules/shell/client.py +361 -0
- modules/shell/models.py +61 -0
- modules/shell/protocol.py +249 -0
- modules/shell/server.py +511 -0
- modules/shell/session.py +339 -0
- modules/utils.py +212 -0
- openshell_shared-0.1.2.dist-info/METADATA +59 -0
- openshell_shared-0.1.2.dist-info/RECORD +62 -0
- openshell_shared-0.1.2.dist-info/WHEEL +5 -0
- openshell_shared-0.1.2.dist-info/top_level.txt +7 -0
- protocols/__init__.py +0 -0
- protocols/negotiation/challenge.py +127 -0
- protocols/negotiation/models.py +28 -0
- standards/__init__.py +0 -0
- standards/certificates/__init__.py +0 -0
- standards/certificates/status.py +12 -0
- standards/certificates/types.py +11 -0
- standards/entities/__init__.py +0 -0
- standards/entities/types.py +14 -0
- standards/events/__init__.py +0 -0
- standards/events/schemas/__init__.py +0 -0
- standards/events/schemas/entity_registered.py +13 -0
- standards/events/types.py +18 -0
- standards/passports/__init__.py +0 -0
- standards/passports/types.py +5 -0
- standards/permissions/__init__.py +0 -0
- standards/permissions/types.py +14 -0
- standards/roles/__init__.py +0 -0
- standards/roles/types.py +8 -0
- standards/transports/__init__.py +0 -0
- standards/transports/types.py +24 -0
cryptography/keys.py
ADDED
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
# shared/cryptography/keyformats.py
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
from typing import Final
|
|
7
|
+
|
|
8
|
+
from cryptography.hazmat.primitives import serialization
|
|
9
|
+
from cryptography.hazmat.primitives.asymmetric.ed25519 import (
|
|
10
|
+
Ed25519PrivateKey,
|
|
11
|
+
Ed25519PublicKey,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Ed25519KeyFormats:
|
|
16
|
+
"""
|
|
17
|
+
Ed25519 key format utilities.
|
|
18
|
+
|
|
19
|
+
Canonical internal format:
|
|
20
|
+
|
|
21
|
+
RAW bytes
|
|
22
|
+
|
|
23
|
+
Public key:
|
|
24
|
+
32 bytes
|
|
25
|
+
|
|
26
|
+
Private key seed:
|
|
27
|
+
32 bytes
|
|
28
|
+
|
|
29
|
+
Formats:
|
|
30
|
+
|
|
31
|
+
STORAGE:
|
|
32
|
+
RAW bytes
|
|
33
|
+
|
|
34
|
+
PROCESS:
|
|
35
|
+
HEX
|
|
36
|
+
|
|
37
|
+
EXPORT:
|
|
38
|
+
PEM / HEX
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
PUBLIC: Final[str] = "PUBLIC"
|
|
42
|
+
PRIVATE: Final[str] = "PRIVATE"
|
|
43
|
+
|
|
44
|
+
PEM: Final[str] = "PEM"
|
|
45
|
+
HEX: Final[str] = "HEX"
|
|
46
|
+
RAW: Final[str] = "RAW"
|
|
47
|
+
|
|
48
|
+
KEY_SIZE: Final[int] = 32
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# =====================================================
|
|
52
|
+
# DETECTION
|
|
53
|
+
# =====================================================
|
|
54
|
+
|
|
55
|
+
@staticmethod
|
|
56
|
+
def is_pem(
|
|
57
|
+
data: str | bytes
|
|
58
|
+
) -> bool:
|
|
59
|
+
|
|
60
|
+
if isinstance(data, bytes):
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
b"-----BEGIN PUBLIC KEY-----" in data
|
|
64
|
+
or
|
|
65
|
+
b"-----BEGIN PRIVATE KEY-----" in data
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
if isinstance(data, str):
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
"-----BEGIN PUBLIC KEY-----" in data
|
|
72
|
+
or
|
|
73
|
+
"-----BEGIN PRIVATE KEY-----" in data
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
return False
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@staticmethod
|
|
80
|
+
def is_hex(
|
|
81
|
+
data: str | bytes
|
|
82
|
+
) -> bool:
|
|
83
|
+
|
|
84
|
+
if isinstance(data, bytes):
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
data = data.decode("ascii")
|
|
88
|
+
|
|
89
|
+
except UnicodeDecodeError:
|
|
90
|
+
return False
|
|
91
|
+
|
|
92
|
+
if not isinstance(data, str):
|
|
93
|
+
return False
|
|
94
|
+
|
|
95
|
+
data = data.strip()
|
|
96
|
+
|
|
97
|
+
if len(data) != 64:
|
|
98
|
+
return False
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
bytes.fromhex(data)
|
|
102
|
+
return True
|
|
103
|
+
|
|
104
|
+
except ValueError:
|
|
105
|
+
return False
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@staticmethod
|
|
109
|
+
def is_raw(
|
|
110
|
+
data
|
|
111
|
+
) -> bool:
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
isinstance(data, bytes)
|
|
115
|
+
and
|
|
116
|
+
len(data) == 32
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@classmethod
|
|
121
|
+
def detect_format(
|
|
122
|
+
cls,
|
|
123
|
+
data
|
|
124
|
+
) -> str | None:
|
|
125
|
+
|
|
126
|
+
if cls.is_pem(data):
|
|
127
|
+
return cls.PEM
|
|
128
|
+
|
|
129
|
+
if cls.is_hex(data):
|
|
130
|
+
return cls.HEX
|
|
131
|
+
|
|
132
|
+
if cls.is_raw(data):
|
|
133
|
+
return cls.RAW
|
|
134
|
+
|
|
135
|
+
return None
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
# =====================================================
|
|
140
|
+
# TYPE DETECTION
|
|
141
|
+
# =====================================================
|
|
142
|
+
|
|
143
|
+
@classmethod
|
|
144
|
+
def detect_key_type(
|
|
145
|
+
cls,
|
|
146
|
+
data
|
|
147
|
+
) -> str | None:
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
if not cls.is_pem(data):
|
|
151
|
+
return None
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
if isinstance(data, bytes):
|
|
155
|
+
|
|
156
|
+
if b"BEGIN PUBLIC KEY" in data:
|
|
157
|
+
return cls.PUBLIC
|
|
158
|
+
|
|
159
|
+
if b"BEGIN PRIVATE KEY" in data:
|
|
160
|
+
return cls.PRIVATE
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
if isinstance(data, str):
|
|
164
|
+
|
|
165
|
+
if "BEGIN PUBLIC KEY" in data:
|
|
166
|
+
return cls.PUBLIC
|
|
167
|
+
|
|
168
|
+
if "BEGIN PRIVATE KEY" in data:
|
|
169
|
+
return cls.PRIVATE
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
return None
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
# =====================================================
|
|
177
|
+
# NORMALIZATION
|
|
178
|
+
# =====================================================
|
|
179
|
+
|
|
180
|
+
@classmethod
|
|
181
|
+
def normalize_public_key(
|
|
182
|
+
cls,
|
|
183
|
+
key
|
|
184
|
+
) -> bytes:
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
fmt = cls.detect_format(key)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
if fmt == cls.RAW:
|
|
191
|
+
|
|
192
|
+
return key
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
if fmt == cls.HEX:
|
|
196
|
+
|
|
197
|
+
if isinstance(key, bytes):
|
|
198
|
+
key = key.decode("ascii")
|
|
199
|
+
|
|
200
|
+
raw = bytes.fromhex(key)
|
|
201
|
+
|
|
202
|
+
cls._validate_size(raw)
|
|
203
|
+
|
|
204
|
+
return raw
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
if fmt == cls.PEM:
|
|
208
|
+
|
|
209
|
+
if isinstance(key, str):
|
|
210
|
+
key = key.encode()
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
public_key = (
|
|
214
|
+
serialization
|
|
215
|
+
.load_pem_public_key(key)
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
if not isinstance(
|
|
220
|
+
public_key,
|
|
221
|
+
Ed25519PublicKey
|
|
222
|
+
):
|
|
223
|
+
raise ValueError(
|
|
224
|
+
"Invalid Ed25519 public key"
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
return public_key.public_bytes(
|
|
229
|
+
serialization.Encoding.Raw,
|
|
230
|
+
serialization.PublicFormat.Raw
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
raise ValueError(
|
|
235
|
+
"Unsupported public key format"
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
@classmethod
|
|
241
|
+
def normalize_private_key(
|
|
242
|
+
cls,
|
|
243
|
+
key
|
|
244
|
+
) -> bytes:
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
fmt = cls.detect_format(key)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
if fmt == cls.RAW:
|
|
251
|
+
|
|
252
|
+
return key
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
if fmt == cls.HEX:
|
|
256
|
+
|
|
257
|
+
if isinstance(key, bytes):
|
|
258
|
+
key = key.decode("ascii")
|
|
259
|
+
|
|
260
|
+
raw = bytes.fromhex(key)
|
|
261
|
+
|
|
262
|
+
cls._validate_size(raw)
|
|
263
|
+
|
|
264
|
+
return raw
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
if fmt == cls.PEM:
|
|
268
|
+
|
|
269
|
+
if isinstance(key, str):
|
|
270
|
+
key = key.encode()
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
private_key = (
|
|
274
|
+
serialization
|
|
275
|
+
.load_pem_private_key(
|
|
276
|
+
key,
|
|
277
|
+
password=None
|
|
278
|
+
)
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
if not isinstance(
|
|
283
|
+
private_key,
|
|
284
|
+
Ed25519PrivateKey
|
|
285
|
+
):
|
|
286
|
+
raise ValueError(
|
|
287
|
+
"Invalid Ed25519 private key"
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
return private_key.private_bytes(
|
|
292
|
+
serialization.Encoding.Raw,
|
|
293
|
+
serialization.PrivateFormat.Raw,
|
|
294
|
+
serialization.NoEncryption()
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
raise ValueError(
|
|
299
|
+
"Unsupported private key format"
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
# =====================================================
|
|
305
|
+
# VALIDATION
|
|
306
|
+
# =====================================================
|
|
307
|
+
|
|
308
|
+
@classmethod
|
|
309
|
+
def _validate_size(
|
|
310
|
+
cls,
|
|
311
|
+
raw: bytes
|
|
312
|
+
):
|
|
313
|
+
|
|
314
|
+
if len(raw) != cls.KEY_SIZE:
|
|
315
|
+
|
|
316
|
+
raise ValueError(
|
|
317
|
+
"Invalid Ed25519 key size"
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
@classmethod
|
|
323
|
+
def validate_public_key(
|
|
324
|
+
cls,
|
|
325
|
+
key
|
|
326
|
+
) -> bool:
|
|
327
|
+
|
|
328
|
+
try:
|
|
329
|
+
|
|
330
|
+
cls.normalize_public_key(key)
|
|
331
|
+
|
|
332
|
+
return True
|
|
333
|
+
|
|
334
|
+
except (ValueError, TypeError):
|
|
335
|
+
|
|
336
|
+
return False
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
@classmethod
|
|
341
|
+
def validate_private_key(
|
|
342
|
+
cls,
|
|
343
|
+
key
|
|
344
|
+
) -> bool:
|
|
345
|
+
|
|
346
|
+
try:
|
|
347
|
+
|
|
348
|
+
cls.normalize_private_key(key)
|
|
349
|
+
|
|
350
|
+
return True
|
|
351
|
+
|
|
352
|
+
except (ValueError, TypeError):
|
|
353
|
+
|
|
354
|
+
return False
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
# =====================================================
|
|
359
|
+
# DOMAIN CONVERSION
|
|
360
|
+
# =====================================================
|
|
361
|
+
|
|
362
|
+
@classmethod
|
|
363
|
+
def to_storage(
|
|
364
|
+
cls,
|
|
365
|
+
key,
|
|
366
|
+
private=False
|
|
367
|
+
) -> bytes:
|
|
368
|
+
"""
|
|
369
|
+
Database / internal storage.
|
|
370
|
+
|
|
371
|
+
Returns RAW bytes.
|
|
372
|
+
"""
|
|
373
|
+
|
|
374
|
+
if private:
|
|
375
|
+
return cls.normalize_private_key(key)
|
|
376
|
+
|
|
377
|
+
return cls.normalize_public_key(key)
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
@classmethod
|
|
382
|
+
def to_process(
|
|
383
|
+
cls,
|
|
384
|
+
key,
|
|
385
|
+
private=False
|
|
386
|
+
) -> str:
|
|
387
|
+
"""
|
|
388
|
+
Internal processing format.
|
|
389
|
+
|
|
390
|
+
Returns HEX.
|
|
391
|
+
"""
|
|
392
|
+
|
|
393
|
+
raw = cls.to_storage(
|
|
394
|
+
key,
|
|
395
|
+
private
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
return raw.hex()
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
@classmethod
|
|
403
|
+
def to_export(
|
|
404
|
+
cls,
|
|
405
|
+
key,
|
|
406
|
+
private=False
|
|
407
|
+
) -> str:
|
|
408
|
+
"""
|
|
409
|
+
External interchange.
|
|
410
|
+
|
|
411
|
+
Returns PEM.
|
|
412
|
+
"""
|
|
413
|
+
|
|
414
|
+
raw = cls.to_storage(
|
|
415
|
+
key,
|
|
416
|
+
private
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
if private:
|
|
421
|
+
|
|
422
|
+
return (
|
|
423
|
+
Ed25519PrivateKey
|
|
424
|
+
.from_private_bytes(raw)
|
|
425
|
+
.private_bytes(
|
|
426
|
+
serialization.Encoding.PEM,
|
|
427
|
+
serialization.PrivateFormat.PKCS8,
|
|
428
|
+
serialization.NoEncryption()
|
|
429
|
+
)
|
|
430
|
+
.decode()
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
return (
|
|
435
|
+
Ed25519PublicKey
|
|
436
|
+
.from_public_bytes(raw)
|
|
437
|
+
.public_bytes(
|
|
438
|
+
serialization.Encoding.PEM,
|
|
439
|
+
serialization.PublicFormat.SubjectPublicKeyInfo
|
|
440
|
+
)
|
|
441
|
+
.decode()
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
# =====================================================
|
|
447
|
+
# FINGERPRINT
|
|
448
|
+
# =====================================================
|
|
449
|
+
|
|
450
|
+
@classmethod
|
|
451
|
+
def fingerprint(
|
|
452
|
+
cls,
|
|
453
|
+
key
|
|
454
|
+
) -> str:
|
|
455
|
+
"""
|
|
456
|
+
Stable SHA256 identity fingerprint.
|
|
457
|
+
"""
|
|
458
|
+
|
|
459
|
+
raw = cls.normalize_public_key(key)
|
|
460
|
+
|
|
461
|
+
return hashlib.sha256(
|
|
462
|
+
raw
|
|
463
|
+
).hexdigest()
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Library import
|
|
2
|
+
import json
|
|
3
|
+
import base64
|
|
4
|
+
|
|
5
|
+
from cryptography.hazmat.primitives.asymmetric.ed25519 import (
|
|
6
|
+
Ed25519PrivateKey,
|
|
7
|
+
Ed25519PublicKey
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
from cryptography.hazmat.primitives import serialization
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# Functions
|
|
14
|
+
def canonicalize(data: dict) -> bytes:
|
|
15
|
+
"""
|
|
16
|
+
Convert dictionary to canonical JSON bytes
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
return json.dumps(
|
|
20
|
+
data,
|
|
21
|
+
sort_keys=True,
|
|
22
|
+
separators=(",", ":")
|
|
23
|
+
).encode()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def sign_data(
|
|
27
|
+
private_key_pem: str,
|
|
28
|
+
data: dict
|
|
29
|
+
) -> str:
|
|
30
|
+
|
|
31
|
+
private_key = serialization.load_pem_private_key(
|
|
32
|
+
private_key_pem.encode(),
|
|
33
|
+
password=None
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
payload = canonicalize(data)
|
|
37
|
+
|
|
38
|
+
signature = private_key.sign(payload)
|
|
39
|
+
|
|
40
|
+
return base64.b64encode(signature).decode()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def verify_signature(
|
|
44
|
+
public_key_pem: str,
|
|
45
|
+
data: dict,
|
|
46
|
+
signature: str
|
|
47
|
+
) -> bool:
|
|
48
|
+
|
|
49
|
+
public_key = serialization.load_pem_public_key(
|
|
50
|
+
public_key_pem.encode()
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
payload = canonicalize(data)
|
|
54
|
+
|
|
55
|
+
signature_bytes = base64.b64decode(signature)
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
public_key.verify(signature_bytes, payload)
|
|
59
|
+
|
|
60
|
+
return True
|
|
61
|
+
|
|
62
|
+
except Exception:
|
|
63
|
+
return False
|
cryptography/utils.py
ADDED
|
File without changes
|
domain/__init__.py
ADDED
|
File without changes
|
domain/domain.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from ..identity.identification import (
|
|
4
|
+
EntityIdentity,
|
|
5
|
+
Identification
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
from ..cryptography.identity import (
|
|
9
|
+
CryptographicIdentity
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True)
|
|
14
|
+
class Domain:
|
|
15
|
+
identity: EntityIdentity
|
|
16
|
+
description: str | None = None
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def uid(self) -> str:
|
|
20
|
+
return self.identity.identification.uid
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def name(self) -> str:
|
|
24
|
+
return self.identity.name
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def cryptographic_identity(self):
|
|
28
|
+
return self.identity.cryptographic_identity
|
|
29
|
+
|
|
30
|
+
@staticmethod
|
|
31
|
+
def generate(
|
|
32
|
+
name: str,
|
|
33
|
+
description: str | None = None
|
|
34
|
+
) -> "Domain":
|
|
35
|
+
|
|
36
|
+
return Domain(
|
|
37
|
+
identity=EntityIdentity.generate(
|
|
38
|
+
name=name
|
|
39
|
+
),
|
|
40
|
+
description=description
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
@staticmethod
|
|
44
|
+
def from_database(
|
|
45
|
+
uid: str,
|
|
46
|
+
name: str,
|
|
47
|
+
pik: str,
|
|
48
|
+
description: str | None = None
|
|
49
|
+
) -> "Domain":
|
|
50
|
+
"""
|
|
51
|
+
Reconstruct domain from database data.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
return Domain(
|
|
55
|
+
identity=EntityIdentity(
|
|
56
|
+
identification=Identification(
|
|
57
|
+
uid=uid
|
|
58
|
+
),
|
|
59
|
+
|
|
60
|
+
cryptographic_identity=CryptographicIdentity(
|
|
61
|
+
public_key=pik
|
|
62
|
+
),
|
|
63
|
+
|
|
64
|
+
name=name
|
|
65
|
+
),
|
|
66
|
+
|
|
67
|
+
description=description
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
def export_public(self) -> dict:
|
|
71
|
+
return {
|
|
72
|
+
"identity": self.identity.export_public(),
|
|
73
|
+
"description": self.description
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
def to_dict(self) -> dict:
|
|
77
|
+
return {
|
|
78
|
+
"identity": self.identity.to_dict(),
|
|
79
|
+
"description": self.description
|
|
80
|
+
}
|
domain/membership.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Library import
|
|
2
|
+
from uuid import UUID
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
from .permissions import Permission
|
|
6
|
+
|
|
7
|
+
# Classes
|
|
8
|
+
class Membership:
|
|
9
|
+
def __init__(
|
|
10
|
+
self,
|
|
11
|
+
entity_uid: UUID,
|
|
12
|
+
domain_uid: UUID,
|
|
13
|
+
):
|
|
14
|
+
self.entity_uid = entity_uid
|
|
15
|
+
self.domain_uid = domain_uid
|
|
16
|
+
|
|
17
|
+
self.permissions: set[Permission] = set()
|
|
18
|
+
|
|
19
|
+
self.created_at = datetime.utcnow()
|
|
20
|
+
|
|
21
|
+
self.revoked = False
|
domain/permissions.py
ADDED
domain/policies.py
ADDED
identity/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from uuid import UUID
|
|
3
|
+
from uuid6 import uuid7
|
|
4
|
+
from ..cryptography.identity import CryptographicIdentity
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass(frozen=True)
|
|
8
|
+
class Identification:
|
|
9
|
+
uid: str
|
|
10
|
+
|
|
11
|
+
def __post_init__(self):
|
|
12
|
+
UUID(self.uid)
|
|
13
|
+
|
|
14
|
+
def to_string(self) -> str:
|
|
15
|
+
return self.uid
|
|
16
|
+
|
|
17
|
+
def to_dict(self) -> dict:
|
|
18
|
+
return {
|
|
19
|
+
"uid": self.uid
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@staticmethod
|
|
23
|
+
def generate() -> "Identification":
|
|
24
|
+
return Identification(uid=str(uuid7()))
|
|
25
|
+
|
|
26
|
+
@dataclass(frozen=True)
|
|
27
|
+
class EntityIdentity:
|
|
28
|
+
identification: Identification
|
|
29
|
+
cryptographic_identity: CryptographicIdentity
|
|
30
|
+
name: str
|
|
31
|
+
|
|
32
|
+
@staticmethod
|
|
33
|
+
def generate(name) -> "EntityIdentity":
|
|
34
|
+
return EntityIdentity(
|
|
35
|
+
identification=Identification.generate(),
|
|
36
|
+
cryptographic_identity=CryptographicIdentity.generate(),
|
|
37
|
+
name=name
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
def export_public(self) -> dict:
|
|
41
|
+
return {
|
|
42
|
+
"identification": (
|
|
43
|
+
self.identification.to_dict()
|
|
44
|
+
),
|
|
45
|
+
|
|
46
|
+
"cryptographic_identity": (
|
|
47
|
+
self.cryptographic_identity.export_public()
|
|
48
|
+
),
|
|
49
|
+
|
|
50
|
+
"name": self.name
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
def to_dict(self) -> dict:
|
|
54
|
+
return {
|
|
55
|
+
"identification": (
|
|
56
|
+
self.identification.to_dict()
|
|
57
|
+
),
|
|
58
|
+
|
|
59
|
+
"cryptographic_identity": (
|
|
60
|
+
self.cryptographic_identity.to_dict()
|
|
61
|
+
),
|
|
62
|
+
|
|
63
|
+
"name": self.name
|
|
64
|
+
}
|