nexo-schemas 0.0.16__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 (69) hide show
  1. nexo/schemas/__init__.py +0 -0
  2. nexo/schemas/application.py +292 -0
  3. nexo/schemas/connection.py +134 -0
  4. nexo/schemas/data.py +27 -0
  5. nexo/schemas/document.py +237 -0
  6. nexo/schemas/error/__init__.py +476 -0
  7. nexo/schemas/error/constants.py +50 -0
  8. nexo/schemas/error/descriptor.py +354 -0
  9. nexo/schemas/error/enums.py +40 -0
  10. nexo/schemas/error/metadata.py +15 -0
  11. nexo/schemas/error/spec.py +312 -0
  12. nexo/schemas/exception/__init__.py +0 -0
  13. nexo/schemas/exception/exc.py +911 -0
  14. nexo/schemas/exception/factory.py +1928 -0
  15. nexo/schemas/exception/handlers.py +110 -0
  16. nexo/schemas/google.py +14 -0
  17. nexo/schemas/key/__init__.py +0 -0
  18. nexo/schemas/key/rsa.py +131 -0
  19. nexo/schemas/metadata.py +21 -0
  20. nexo/schemas/mixins/__init__.py +0 -0
  21. nexo/schemas/mixins/filter.py +140 -0
  22. nexo/schemas/mixins/general.py +65 -0
  23. nexo/schemas/mixins/hierarchy.py +19 -0
  24. nexo/schemas/mixins/identity.py +387 -0
  25. nexo/schemas/mixins/parameter.py +50 -0
  26. nexo/schemas/mixins/service.py +40 -0
  27. nexo/schemas/mixins/sort.py +111 -0
  28. nexo/schemas/mixins/timestamp.py +192 -0
  29. nexo/schemas/model.py +240 -0
  30. nexo/schemas/operation/__init__.py +0 -0
  31. nexo/schemas/operation/action/__init__.py +9 -0
  32. nexo/schemas/operation/action/base.py +14 -0
  33. nexo/schemas/operation/action/resource.py +371 -0
  34. nexo/schemas/operation/action/status.py +8 -0
  35. nexo/schemas/operation/action/system.py +6 -0
  36. nexo/schemas/operation/action/websocket.py +6 -0
  37. nexo/schemas/operation/base.py +289 -0
  38. nexo/schemas/operation/constants.py +18 -0
  39. nexo/schemas/operation/context.py +68 -0
  40. nexo/schemas/operation/dependency.py +26 -0
  41. nexo/schemas/operation/enums.py +168 -0
  42. nexo/schemas/operation/extractor.py +36 -0
  43. nexo/schemas/operation/mixins.py +53 -0
  44. nexo/schemas/operation/request.py +1066 -0
  45. nexo/schemas/operation/resource.py +839 -0
  46. nexo/schemas/operation/system.py +55 -0
  47. nexo/schemas/operation/websocket.py +55 -0
  48. nexo/schemas/pagination.py +67 -0
  49. nexo/schemas/parameter.py +60 -0
  50. nexo/schemas/payload.py +116 -0
  51. nexo/schemas/resource.py +64 -0
  52. nexo/schemas/response.py +1041 -0
  53. nexo/schemas/security/__init__.py +0 -0
  54. nexo/schemas/security/api_key.py +63 -0
  55. nexo/schemas/security/authentication.py +848 -0
  56. nexo/schemas/security/authorization.py +922 -0
  57. nexo/schemas/security/enums.py +32 -0
  58. nexo/schemas/security/impersonation.py +179 -0
  59. nexo/schemas/security/token.py +402 -0
  60. nexo/schemas/security/types.py +17 -0
  61. nexo/schemas/success/__init__.py +0 -0
  62. nexo/schemas/success/descriptor.py +100 -0
  63. nexo/schemas/success/enums.py +23 -0
  64. nexo/schemas/user_agent.py +46 -0
  65. nexo_schemas-0.0.16.dist-info/METADATA +87 -0
  66. nexo_schemas-0.0.16.dist-info/RECORD +69 -0
  67. nexo_schemas-0.0.16.dist-info/WHEEL +5 -0
  68. nexo_schemas-0.0.16.dist-info/licenses/LICENSE +21 -0
  69. nexo_schemas-0.0.16.dist-info/top_level.txt +1 -0
@@ -0,0 +1,32 @@
1
+ from enum import StrEnum
2
+ from pydantic import BaseModel, Field
3
+ from typing import Generic, TypeVar
4
+ from nexo.types.string import ListOfStrs
5
+
6
+
7
+ class Domain(StrEnum):
8
+ TENANT = "tenant"
9
+ SYSTEM = "system"
10
+
11
+ @classmethod
12
+ def choices(cls) -> ListOfStrs:
13
+ return [e.value for e in cls]
14
+
15
+
16
+ DomainT = TypeVar("DomainT", bound=Domain)
17
+ OptDomain = Domain | None
18
+ OptDomainT = TypeVar("OptDomainT", bound=OptDomain)
19
+
20
+
21
+ class DomainMixin(BaseModel, Generic[OptDomainT]):
22
+ domain: OptDomainT = Field(..., description="Domain")
23
+
24
+
25
+ class RolePrefix(StrEnum):
26
+ MEDICAL = "medical"
27
+ TENANT = "tenant"
28
+ SYSTEM = "system"
29
+
30
+ @classmethod
31
+ def choices(cls) -> ListOfStrs:
32
+ return [e.value for e in cls]
@@ -0,0 +1,179 @@
1
+ from enum import StrEnum
2
+ from fastapi import Header, Query
3
+ from fastapi.requests import HTTPConnection, Request
4
+ from fastapi.websockets import WebSocket
5
+ from pydantic import BaseModel, Field
6
+ from typing import (
7
+ Annotated,
8
+ Callable,
9
+ Generic,
10
+ Literal,
11
+ TypeVar,
12
+ overload,
13
+ )
14
+ from uuid import UUID
15
+ from nexo.enums.connection import Header as HeaderEnum, Protocol, OptProtocol
16
+ from nexo.types.string import ListOfStrs
17
+ from nexo.types.uuid import OptUUID
18
+
19
+
20
+ class Source(StrEnum):
21
+ HEADER = "header"
22
+ STATE = "state"
23
+
24
+ @classmethod
25
+ def choices(cls) -> ListOfStrs:
26
+ return [e.value for e in cls]
27
+
28
+
29
+ class Impersonation(BaseModel):
30
+ user_id: Annotated[UUID, Field(..., description="User's ID")]
31
+ organization_id: Annotated[
32
+ OptUUID, Field(None, description="Organization's ID")
33
+ ] = None
34
+
35
+ @classmethod
36
+ def from_header(cls, conn: HTTPConnection) -> "Impersonation | None":
37
+ organization_id = conn.headers.get(HeaderEnum.X_ORGANIZATION_ID, None)
38
+ if organization_id is not None:
39
+ organization_id = UUID(organization_id)
40
+
41
+ user_id = conn.headers.get(HeaderEnum.X_USER_ID, None)
42
+ if user_id is not None:
43
+ user_id = UUID(user_id)
44
+
45
+ return cls(
46
+ user_id=user_id,
47
+ organization_id=organization_id,
48
+ )
49
+
50
+ return None
51
+
52
+ @classmethod
53
+ def from_query(cls, conn: HTTPConnection) -> "Impersonation | None":
54
+ organization_id = conn.query_params.get(
55
+ HeaderEnum.X_ORGANIZATION_ID.value, None
56
+ )
57
+ if organization_id is not None:
58
+ organization_id = UUID(organization_id)
59
+
60
+ user_id = conn.query_params.get(HeaderEnum.X_USER_ID.value, None)
61
+ if user_id is not None:
62
+ user_id = UUID(user_id)
63
+
64
+ return cls(
65
+ user_id=user_id,
66
+ organization_id=organization_id,
67
+ )
68
+
69
+ return None
70
+
71
+ @classmethod
72
+ def extract(cls, conn: HTTPConnection) -> "Impersonation | None":
73
+ impersonation = getattr(conn.state, "impersonation", None)
74
+ if isinstance(impersonation, Impersonation):
75
+ return impersonation
76
+
77
+ impersonation = cls.from_header(conn)
78
+ if impersonation is not None:
79
+ return impersonation
80
+
81
+ impersonation = cls.from_query(conn)
82
+ if impersonation is not None:
83
+ return impersonation
84
+
85
+ return None
86
+
87
+ @overload
88
+ @classmethod
89
+ def as_dependency(
90
+ cls,
91
+ protocol: None = None,
92
+ /,
93
+ ) -> Callable[[HTTPConnection, OptUUID, OptUUID], "Impersonation | None"]: ...
94
+ @overload
95
+ @classmethod
96
+ def as_dependency(
97
+ cls,
98
+ protocol: Literal[Protocol.HTTP],
99
+ /,
100
+ ) -> Callable[[Request, OptUUID, OptUUID], "Impersonation | None"]: ...
101
+ @overload
102
+ @classmethod
103
+ def as_dependency(
104
+ cls,
105
+ protocol: Literal[Protocol.WEBSOCKET],
106
+ /,
107
+ ) -> Callable[[WebSocket, OptUUID, OptUUID], "Impersonation | None"]: ...
108
+ @classmethod
109
+ def as_dependency(
110
+ cls,
111
+ protocol: OptProtocol = None,
112
+ /,
113
+ ) -> (
114
+ Callable[[HTTPConnection, OptUUID, OptUUID], "Impersonation | None"]
115
+ | Callable[[Request, OptUUID, OptUUID], "Impersonation | None"]
116
+ | Callable[[WebSocket, OptUUID, OptUUID], "Impersonation | None"]
117
+ ):
118
+ def _dependency(
119
+ conn: HTTPConnection,
120
+ # These are for documentation purpose only
121
+ _user_id: OptUUID = Header(
122
+ None,
123
+ alias=HeaderEnum.X_USER_ID.value,
124
+ description="User's ID",
125
+ ),
126
+ _organization_id: OptUUID = Header(
127
+ None,
128
+ alias=HeaderEnum.X_ORGANIZATION_ID.value,
129
+ description="Organization's ID",
130
+ ),
131
+ ) -> "Impersonation | None":
132
+ return cls.extract(conn)
133
+
134
+ def _request_dependency(
135
+ request: Request,
136
+ # These are for documentation purpose only
137
+ _user_id: OptUUID = Header(
138
+ None,
139
+ alias=HeaderEnum.X_USER_ID.value,
140
+ description="User's ID",
141
+ ),
142
+ _organization_id: OptUUID = Header(
143
+ None,
144
+ alias=HeaderEnum.X_ORGANIZATION_ID.value,
145
+ description="Organization's ID",
146
+ ),
147
+ ) -> "Impersonation | None":
148
+ return cls.extract(request)
149
+
150
+ def _websocket_dependency(
151
+ websocket: WebSocket,
152
+ # These are for documentation purpose only
153
+ _user_id: OptUUID = Query(
154
+ None,
155
+ alias=HeaderEnum.X_USER_ID.value,
156
+ description="User's ID",
157
+ ),
158
+ _organization_id: OptUUID = Query(
159
+ None,
160
+ alias=HeaderEnum.X_ORGANIZATION_ID.value,
161
+ description="Organization's ID",
162
+ ),
163
+ ) -> "Impersonation | None":
164
+ return cls.extract(websocket)
165
+
166
+ if protocol is None:
167
+ return _dependency
168
+ elif protocol is Protocol.HTTP:
169
+ return _request_dependency
170
+ elif protocol is Protocol.WEBSOCKET:
171
+ return _websocket_dependency
172
+
173
+
174
+ OptImpersonation = Impersonation | None
175
+ OptImpersonationT = TypeVar("OptImpersonationT", bound=OptImpersonation)
176
+
177
+
178
+ class ImpersonationMixin(BaseModel, Generic[OptImpersonationT]):
179
+ impersonation: OptImpersonationT = Field(..., description="Impersonation")
@@ -0,0 +1,402 @@
1
+ from collections.abc import Iterable, Sequence
2
+ from Crypto.PublicKey.RSA import RsaKey
3
+ from datetime import datetime, timedelta, timezone
4
+ from pydantic import BaseModel, Field, ValidationError, model_validator
5
+ from typing import (
6
+ Annotated,
7
+ Generic,
8
+ Literal,
9
+ Self,
10
+ TypeGuard,
11
+ TypeVar,
12
+ overload,
13
+ )
14
+ from uuid import UUID
15
+ from nexo.crypto.token import decode, encode
16
+ from nexo.enums.expiration import Expiration
17
+ from nexo.types.datetime import OptDatetime
18
+ from nexo.types.integer import OptInt, DoubleInts
19
+ from nexo.types.misc import BytesOrStr
20
+ from nexo.types.string import OptListOfStrs, OptStr
21
+ from nexo.types.uuid import OptUUID
22
+ from .enums import Domain, OptDomain, DomainT
23
+
24
+
25
+ class TokenV1(BaseModel):
26
+ iss: Annotated[OptStr, Field(None, description="Issuer")] = None
27
+ sub: Annotated[str, Field(..., description="Subject")]
28
+ sr: Annotated[str, Field(..., description="System role")]
29
+ u_i: Annotated[int, Field(..., description="User's ID")]
30
+ u_uu: Annotated[UUID, Field(..., description="User's UUID")]
31
+ u_u: Annotated[str, Field(..., description="User's Username")]
32
+ u_e: Annotated[str, Field(..., description="User's Email")]
33
+ u_ut: Annotated[str, Field(..., description="User's type")]
34
+ o_i: Annotated[OptInt, Field(None, description="Organization's ID")] = None
35
+ o_uu: Annotated[OptUUID, Field(None, description="Organization's UUID")] = None
36
+ o_k: Annotated[OptStr, Field(None, description="Organization's Key")] = None
37
+ o_ot: Annotated[OptStr, Field(None, description="Organization's type")] = None
38
+ uor: Annotated[
39
+ OptListOfStrs,
40
+ Field(None, description="User's organization role", min_length=1),
41
+ ] = None
42
+ iat_dt: Annotated[datetime, Field(..., description="Issued At Timestamp")]
43
+ iat: Annotated[int, Field(..., description="Issued at")]
44
+ exp_dt: Annotated[datetime, Field(..., description="Expired At Timestamp")]
45
+ exp: Annotated[int, Field(..., description="Expired at")]
46
+
47
+ @classmethod
48
+ def from_string(
49
+ cls,
50
+ token: str,
51
+ *,
52
+ key: BytesOrStr | RsaKey,
53
+ audience: str | Iterable[str] | None = None,
54
+ subject: OptStr = None,
55
+ issuer: str | Sequence[str] | None = None,
56
+ leeway: float | timedelta = 0,
57
+ ) -> "TokenV1":
58
+ obj = decode(
59
+ token,
60
+ key=key,
61
+ audience=audience,
62
+ subject=subject,
63
+ issuer=issuer,
64
+ leeway=leeway,
65
+ )
66
+ return cls.model_validate(obj)
67
+
68
+ @overload
69
+ def to_string(
70
+ self,
71
+ key: RsaKey,
72
+ ) -> str: ...
73
+ @overload
74
+ def to_string(
75
+ self,
76
+ key: BytesOrStr,
77
+ *,
78
+ password: OptStr = None,
79
+ ) -> str: ...
80
+ @overload
81
+ def to_string(
82
+ self,
83
+ key: BytesOrStr | RsaKey,
84
+ *,
85
+ password: OptStr = None,
86
+ ) -> str: ...
87
+ def to_string(
88
+ self,
89
+ key: BytesOrStr | RsaKey,
90
+ *,
91
+ password: OptStr = None,
92
+ ) -> str:
93
+ if isinstance(key, RsaKey):
94
+ return encode(
95
+ payload=self.model_dump(mode="json", exclude_none=True),
96
+ key=key,
97
+ )
98
+ else:
99
+ return encode(
100
+ payload=self.model_dump(mode="json", exclude_none=True),
101
+ key=key,
102
+ password=password,
103
+ )
104
+
105
+
106
+ class Claim(BaseModel):
107
+ iss: Annotated[OptStr, Field(None, description="Issuer")] = None
108
+ sub: Annotated[UUID, Field(..., description="Subject")]
109
+ aud: Annotated[OptStr, Field(None, description="Audience")] = None
110
+ exp: Annotated[int, Field(..., description="Expired at")]
111
+ iat: Annotated[int, Field(..., description="Issued at")]
112
+
113
+ @classmethod
114
+ def new_timestamp(
115
+ cls, iat_dt: OptDatetime = None, exp_in: Expiration = Expiration.EXP_15MN
116
+ ) -> DoubleInts:
117
+ if iat_dt is None:
118
+ iat_dt = datetime.now(tz=timezone.utc)
119
+ exp_dt = iat_dt + timedelta(seconds=exp_in.value)
120
+ return int(iat_dt.timestamp()), int(exp_dt.timestamp())
121
+
122
+
123
+ OrganizationT = TypeVar("OrganizationT", bound=OptUUID)
124
+
125
+
126
+ class Credential(BaseModel, Generic[DomainT, OrganizationT]):
127
+ d: DomainT = Field(..., description="Domain")
128
+ o: OrganizationT = Field(..., description="Organization")
129
+
130
+
131
+ class GenericToken(
132
+ Credential[DomainT, OrganizationT],
133
+ Claim,
134
+ Generic[DomainT, OrganizationT],
135
+ ):
136
+ @classmethod
137
+ def from_string(
138
+ cls,
139
+ token: str,
140
+ *,
141
+ key: BytesOrStr | RsaKey,
142
+ audience: str | Iterable[str] | None = None,
143
+ subject: OptStr = None,
144
+ issuer: str | Sequence[str] | None = None,
145
+ leeway: float | timedelta = 0,
146
+ ) -> Self:
147
+ obj = decode(
148
+ token,
149
+ key=key,
150
+ audience=audience,
151
+ subject=subject,
152
+ issuer=issuer,
153
+ leeway=leeway,
154
+ )
155
+ return cls.model_validate(obj)
156
+
157
+ @model_validator(mode="after")
158
+ def validate_credential(self) -> Self:
159
+ return self
160
+
161
+ @overload
162
+ def to_string(
163
+ self,
164
+ key: RsaKey,
165
+ ) -> str: ...
166
+ @overload
167
+ def to_string(
168
+ self,
169
+ key: BytesOrStr,
170
+ *,
171
+ password: OptStr = None,
172
+ ) -> str: ...
173
+ @overload
174
+ def to_string(
175
+ self,
176
+ key: BytesOrStr | RsaKey,
177
+ *,
178
+ password: OptStr = None,
179
+ ) -> str: ...
180
+ def to_string(
181
+ self,
182
+ key: BytesOrStr | RsaKey,
183
+ *,
184
+ password: OptStr = None,
185
+ ) -> str:
186
+ if isinstance(key, RsaKey):
187
+ return encode(
188
+ payload=self.model_dump(mode="json", exclude_none=True),
189
+ key=key,
190
+ )
191
+ else:
192
+ return encode(
193
+ payload=self.model_dump(mode="json", exclude_none=True),
194
+ key=key,
195
+ password=password,
196
+ )
197
+
198
+
199
+ class TenantToken(GenericToken[Literal[Domain.TENANT], UUID]):
200
+ d: Annotated[Literal[Domain.TENANT], Field(Domain.TENANT, description="Domain")] = (
201
+ Domain.TENANT
202
+ )
203
+
204
+ @model_validator(mode="after")
205
+ def validate_identity(self) -> Self:
206
+ if self.d is not Domain.TENANT:
207
+ raise ValueError(f"Value of 'd' claim must be {Domain.TENANT}")
208
+ if not isinstance(self.o, UUID):
209
+ raise ValueError(f"Value of 'o' claim must be an UUID. Value: {self.o}")
210
+ return self
211
+
212
+ @classmethod
213
+ def new(
214
+ cls,
215
+ *,
216
+ sub: UUID,
217
+ o: UUID,
218
+ iss: OptStr = None,
219
+ aud: OptStr = None,
220
+ iat_dt: OptDatetime = None,
221
+ exp_in: Expiration = Expiration.EXP_15MN,
222
+ ) -> "TenantToken":
223
+ iat, exp = cls.new_timestamp(iat_dt, exp_in)
224
+ return cls(iss=iss, sub=sub, aud=aud, exp=exp, iat=iat, o=o)
225
+
226
+
227
+ class SystemToken(GenericToken[Literal[Domain.SYSTEM], None]):
228
+ d: Annotated[Literal[Domain.SYSTEM], Field(Domain.SYSTEM, description="Domain")] = (
229
+ Domain.SYSTEM
230
+ )
231
+ o: None = None
232
+
233
+ @model_validator(mode="after")
234
+ def validate_identity(self) -> Self:
235
+ if self.d is not Domain.SYSTEM:
236
+ raise ValueError(f"Value of 'd' claim must be {Domain.SYSTEM}")
237
+ if self.o is not None:
238
+ raise ValueError(f"Value of 'o' claim must be None. Value: {self.o}")
239
+ return self
240
+
241
+ @classmethod
242
+ def new(
243
+ cls,
244
+ *,
245
+ sub: UUID,
246
+ iss: OptStr = None,
247
+ aud: OptStr = None,
248
+ iat_dt: OptDatetime = None,
249
+ exp_in: Expiration = Expiration.EXP_15MN,
250
+ ) -> "SystemToken":
251
+ iat, exp = cls.new_timestamp(iat_dt, exp_in)
252
+ return cls(iss=iss, sub=sub, aud=aud, exp=exp, iat=iat)
253
+
254
+
255
+ AnyToken = TenantToken | SystemToken
256
+ AnyTokenT = TypeVar("AnyTokenT", bound=AnyToken)
257
+ OptAnyToken = AnyToken | None
258
+ OptAnyTokenT = TypeVar("OptAnyTokenT", bound=OptAnyToken)
259
+
260
+
261
+ class TokenMixin(BaseModel, Generic[OptAnyTokenT]):
262
+ token: OptAnyTokenT = Field(..., description="Token")
263
+
264
+
265
+ def is_tenant_token(token: AnyToken) -> TypeGuard[TenantToken]:
266
+ return (
267
+ isinstance(token, TenantToken)
268
+ and token.d is Domain.TENANT
269
+ and token.o is not None
270
+ )
271
+
272
+
273
+ def is_system_token(token: AnyToken) -> TypeGuard[SystemToken]:
274
+ return (
275
+ isinstance(token, SystemToken) and token.d is Domain.SYSTEM and token.o is None
276
+ )
277
+
278
+
279
+ class TokenFactory:
280
+ @overload
281
+ @staticmethod
282
+ def from_string(
283
+ token: str,
284
+ domain: Literal[Domain.TENANT],
285
+ *,
286
+ key: BytesOrStr | RsaKey,
287
+ audience: str | Iterable[str] | None = None,
288
+ subject: OptStr = None,
289
+ issuer: str | Sequence[str] | None = None,
290
+ leeway: float | timedelta = 0,
291
+ ) -> TenantToken: ...
292
+ @overload
293
+ @staticmethod
294
+ def from_string(
295
+ token: str,
296
+ domain: Literal[Domain.SYSTEM],
297
+ *,
298
+ key: BytesOrStr | RsaKey,
299
+ audience: str | Iterable[str] | None = None,
300
+ subject: OptStr = None,
301
+ issuer: str | Sequence[str] | None = None,
302
+ leeway: float | timedelta = 0,
303
+ ) -> SystemToken: ...
304
+ @overload
305
+ @staticmethod
306
+ def from_string(
307
+ token: str,
308
+ domain: None = None,
309
+ *,
310
+ key: BytesOrStr | RsaKey,
311
+ audience: str | Iterable[str] | None = None,
312
+ subject: OptStr = None,
313
+ issuer: str | Sequence[str] | None = None,
314
+ leeway: float | timedelta = 0,
315
+ ) -> AnyToken: ...
316
+ @staticmethod
317
+ def from_string(
318
+ token: str,
319
+ domain: OptDomain = None,
320
+ *,
321
+ key: BytesOrStr | RsaKey,
322
+ audience: str | Iterable[str] | None = None,
323
+ subject: OptStr = None,
324
+ issuer: str | Sequence[str] | None = None,
325
+ leeway: float | timedelta = 0,
326
+ ) -> AnyToken:
327
+ validated_token = None
328
+ models = (TokenV1, TenantToken, SystemToken)
329
+ for model in models:
330
+ try:
331
+ validated_token = model.from_string(
332
+ token,
333
+ key=key,
334
+ audience=audience,
335
+ subject=subject,
336
+ issuer=issuer,
337
+ leeway=leeway,
338
+ )
339
+ except ValidationError:
340
+ continue
341
+ if validated_token is None:
342
+ raise ValueError("Unable to validate raw token into known token model")
343
+
344
+ if isinstance(validated_token, (TenantToken, SystemToken)):
345
+ result_token = validated_token
346
+ else:
347
+ if validated_token.sr == "administrator":
348
+ if (
349
+ validated_token.o_i is not None
350
+ or validated_token.o_uu is not None
351
+ or validated_token.o_k is not None
352
+ or validated_token.o_ot is not None
353
+ or validated_token.uor is not None
354
+ ):
355
+ raise ValueError(
356
+ "All organization-related claims must be None for System token"
357
+ )
358
+ result_token = SystemToken(
359
+ iss=validated_token.iss,
360
+ sub=validated_token.u_uu,
361
+ aud=None,
362
+ exp=validated_token.exp,
363
+ iat=validated_token.iat,
364
+ )
365
+ elif validated_token.sr == "user":
366
+ if (
367
+ validated_token.o_i is None
368
+ or validated_token.o_uu is None
369
+ or validated_token.o_k is None
370
+ or validated_token.o_ot is None
371
+ or validated_token.uor is None
372
+ ):
373
+ raise ValueError(
374
+ "All organization-related claims can not be None for Tenant Token"
375
+ )
376
+ result_token = TenantToken(
377
+ iss=validated_token.iss,
378
+ sub=validated_token.u_uu,
379
+ aud=None,
380
+ exp=validated_token.exp,
381
+ iat=validated_token.iat,
382
+ o=validated_token.o_uu,
383
+ )
384
+ else:
385
+ raise ValueError(
386
+ f"Claim 'sr' can only be either 'administrator' or 'user' but received {validated_token.sr}"
387
+ )
388
+
389
+ if domain is None:
390
+ return result_token
391
+ elif domain is Domain.TENANT:
392
+ if not is_tenant_token(result_token):
393
+ raise ValueError(
394
+ "Failed parsing Tenant Token from string, raw token did not qualify as Tenant Token"
395
+ )
396
+ return result_token
397
+ elif domain is Domain.SYSTEM:
398
+ if not is_system_token(result_token):
399
+ raise ValueError(
400
+ "Failed parsing System token from string, raw token did not qualify as System Token"
401
+ )
402
+ return result_token
@@ -0,0 +1,17 @@
1
+ from typing import TypeVar
2
+ from nexo.enums.organization import ListOfOrganizationRoles, SeqOfOrganizationRoles
3
+ from nexo.enums.system import ListOfSystemRoles, SeqOfSystemRoles
4
+
5
+
6
+ ListOfDomainRoles = ListOfOrganizationRoles | ListOfSystemRoles
7
+ ListOfDomainRolesT = TypeVar("ListOfDomainRolesT", bound=ListOfDomainRoles)
8
+
9
+ OptListOfDomainRoles = ListOfDomainRoles | None
10
+ OptListOfDomainRolesT = TypeVar("OptListOfDomainRolesT", bound=OptListOfDomainRoles)
11
+
12
+
13
+ SeqOfDomainRoles = SeqOfOrganizationRoles | SeqOfSystemRoles
14
+ SeqOfDomainRolesT = TypeVar("SeqOfDomainRolesT", bound=SeqOfDomainRoles)
15
+
16
+ OptSeqOfDomainRoles = SeqOfDomainRoles | None
17
+ OptSeqOfDomainRolesT = TypeVar("OptSeqOfDomainRolesT", bound=OptSeqOfDomainRoles)
File without changes