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,848 @@
1
+ from abc import ABC, abstractmethod
2
+ from enum import StrEnum
3
+ from fastapi import status, HTTPException
4
+ from fastapi.requests import HTTPConnection
5
+ from functools import cached_property
6
+ from pydantic import BaseModel, Field
7
+ from starlette.authentication import (
8
+ AuthCredentials as StarletteCredentials,
9
+ BaseUser as StarletteUser,
10
+ )
11
+ from typing import (
12
+ Annotated,
13
+ Callable,
14
+ Generic,
15
+ Literal,
16
+ Self,
17
+ TypeGuard,
18
+ TypeVar,
19
+ overload,
20
+ )
21
+ from nexo.enums.organization import (
22
+ OrganizationRole,
23
+ ListOfOrganizationRoles,
24
+ OrganizationType,
25
+ OptOrganizationType,
26
+ )
27
+ from nexo.enums.medical import (
28
+ MedicalRole,
29
+ ListOfMedicalRoles,
30
+ OptSeqOfMedicalRoles,
31
+ OptListOfMedicalRoles,
32
+ OptListOfMedicalRolesT,
33
+ )
34
+ from nexo.enums.system import SystemRole, ListOfSystemRoles
35
+ from nexo.enums.user import UserType, OptUserType
36
+ from nexo.types.integer import OptInt
37
+ from nexo.types.string import (
38
+ OptStr,
39
+ OptStrT,
40
+ ListOfStrs,
41
+ OptListOfStrs,
42
+ OptSeqOfStrs,
43
+ )
44
+ from nexo.types.uuid import OptUUID
45
+ from nexo.utils.exception import extract_details
46
+ from ..mixins.identity import EntityIdentifier
47
+ from .enums import Domain, OptDomain, OptDomainT
48
+ from .types import (
49
+ ListOfDomainRoles,
50
+ OptListOfDomainRoles,
51
+ OptListOfDomainRolesT,
52
+ OptSeqOfDomainRoles,
53
+ )
54
+
55
+
56
+ class ConversionDestination(StrEnum):
57
+ BASE = "base"
58
+ AUTHENTICATED = "authenticated"
59
+ TENANT = "tenant"
60
+ SYSTEM = "system"
61
+
62
+ @classmethod
63
+ def choices(cls) -> ListOfStrs:
64
+ return [e.value for e in cls]
65
+
66
+
67
+ class RequestCredentials(StarletteCredentials):
68
+ def __init__(
69
+ self,
70
+ domain: OptDomain = None,
71
+ user_id: OptInt = None,
72
+ user_uuid: OptUUID = None,
73
+ user_type: OptUserType = None,
74
+ organization_id: OptInt = None,
75
+ organization_uuid: OptUUID = None,
76
+ organization_type: OptOrganizationType = None,
77
+ domain_roles: OptSeqOfDomainRoles = None,
78
+ medical_roles: OptSeqOfMedicalRoles = None,
79
+ scopes: OptSeqOfStrs = None,
80
+ ):
81
+ self.domain = domain
82
+
83
+ if user_id is None and user_uuid is None and user_type is None:
84
+ self.user = None
85
+ elif user_id is not None and user_uuid is not None and user_type is not None:
86
+ self.user = EntityIdentifier[UserType](
87
+ id=user_id, uuid=user_uuid, type=user_type
88
+ )
89
+ else:
90
+ raise ValueError(
91
+ "Both 'user_id', 'user_uuid', and 'user_type' must either be None or not None"
92
+ )
93
+
94
+ if (
95
+ organization_id is None
96
+ and organization_uuid is None
97
+ and organization_type is None
98
+ ):
99
+ self.organization = None
100
+ elif (
101
+ organization_id is not None
102
+ and organization_uuid is not None
103
+ and organization_type is not None
104
+ ):
105
+ self.organization = EntityIdentifier[OrganizationType](
106
+ id=organization_id, uuid=organization_uuid, type=organization_type
107
+ )
108
+ else:
109
+ raise ValueError(
110
+ "Both 'organization_id', 'organization_uuid', and 'organization_type' must either be None or not None"
111
+ )
112
+
113
+ self.domain_roles: OptListOfDomainRoles = list(domain_roles) if domain_roles is not None else None # type: ignore
114
+ self.medical_roles: OptListOfMedicalRoles = (
115
+ list(medical_roles) if medical_roles is not None else None
116
+ )
117
+ self.scopes: OptListOfStrs = list[str](scopes) if scopes is not None else None
118
+
119
+
120
+ class RequestUser(StarletteUser):
121
+ def __init__(
122
+ self,
123
+ authenticated: bool = False,
124
+ organization: OptStr = None,
125
+ username: str = "",
126
+ email: str = "",
127
+ ) -> None:
128
+ self._authenticated = authenticated
129
+ self._organization = organization
130
+ self._username = username
131
+ self._email = email
132
+
133
+ @property
134
+ def is_authenticated(self) -> bool:
135
+ return self._authenticated
136
+
137
+ @property
138
+ def organization(self) -> OptStr:
139
+ return self._organization
140
+
141
+ @property
142
+ def display_name(self) -> str:
143
+ return self._username
144
+
145
+ @property
146
+ def identity(self) -> str:
147
+ return self._email
148
+
149
+
150
+ UserT = TypeVar("UserT", bound=EntityIdentifier[UserType] | None)
151
+ OrganizationT = TypeVar(
152
+ "OrganizationT", bound=EntityIdentifier[OrganizationType] | None
153
+ )
154
+ ScopesT = TypeVar("ScopesT", bound=OptListOfStrs)
155
+
156
+
157
+ class GenericCredentials(
158
+ BaseModel,
159
+ Generic[
160
+ OptDomainT,
161
+ UserT,
162
+ OrganizationT,
163
+ OptListOfDomainRolesT,
164
+ OptListOfMedicalRolesT,
165
+ ScopesT,
166
+ ],
167
+ ):
168
+ domain: Annotated[OptDomainT, Field(..., description="Domain")]
169
+ user: Annotated[UserT, Field(..., description="User")]
170
+ organization: Annotated[OrganizationT, Field(..., description="Organization")]
171
+ domain_roles: Annotated[
172
+ OptListOfDomainRolesT, Field(..., description="Domain Roles")
173
+ ]
174
+ medical_roles: Annotated[
175
+ OptListOfMedicalRolesT, Field(..., description="Medical roles")
176
+ ]
177
+ scopes: Annotated[ScopesT, Field(..., description="Scopes")]
178
+
179
+
180
+ class BaseCredentials(
181
+ GenericCredentials[
182
+ OptDomain,
183
+ EntityIdentifier[UserType] | None,
184
+ EntityIdentifier[OrganizationType] | None,
185
+ OptListOfDomainRoles,
186
+ OptListOfMedicalRoles,
187
+ OptListOfStrs,
188
+ ]
189
+ ):
190
+ domain: Annotated[OptDomain, Field(None, description="Domain")] = None
191
+ user: Annotated[
192
+ EntityIdentifier[UserType] | None, Field(None, description="User")
193
+ ] = None
194
+ organization: Annotated[
195
+ EntityIdentifier[OrganizationType] | None,
196
+ Field(None, description="Organization"),
197
+ ] = None
198
+ domain_roles: Annotated[
199
+ OptListOfDomainRoles, Field(None, description="Domain Roles")
200
+ ] = None
201
+ medical_roles: Annotated[
202
+ OptListOfMedicalRoles, Field(None, description="Medical roles")
203
+ ] = None
204
+ scopes: Annotated[OptListOfStrs, Field(None, description="Scopes")] = None
205
+
206
+
207
+ class AuthenticatedCredentials(
208
+ GenericCredentials[
209
+ Domain,
210
+ EntityIdentifier[UserType],
211
+ EntityIdentifier[OrganizationType] | None,
212
+ ListOfDomainRoles,
213
+ OptListOfMedicalRoles,
214
+ ListOfStrs,
215
+ ]
216
+ ):
217
+ domain: Annotated[Domain, Field(..., description="Domain")]
218
+ user: Annotated[EntityIdentifier[UserType], Field(..., description="User")]
219
+ organization: Annotated[
220
+ EntityIdentifier[OrganizationType] | None,
221
+ Field(..., description="Organization"),
222
+ ]
223
+ domain_roles: Annotated[
224
+ ListOfDomainRoles, Field(..., description="Domain Roles", min_length=1)
225
+ ]
226
+ medical_roles: Annotated[
227
+ OptListOfMedicalRoles, Field(..., description="Medical roles")
228
+ ]
229
+ scopes: Annotated[ListOfStrs, Field(..., description="Scopes", min_length=1)]
230
+
231
+
232
+ class TenantCredentials(
233
+ GenericCredentials[
234
+ Literal[Domain.TENANT],
235
+ EntityIdentifier[UserType],
236
+ EntityIdentifier[OrganizationType],
237
+ ListOfOrganizationRoles,
238
+ OptListOfMedicalRoles,
239
+ ListOfStrs,
240
+ ]
241
+ ):
242
+ domain: Literal[Domain.TENANT] = Domain.TENANT
243
+ user: Annotated[EntityIdentifier[UserType], Field(..., description="User")]
244
+ organization: Annotated[
245
+ EntityIdentifier[OrganizationType], Field(..., description="Organization")
246
+ ]
247
+ domain_roles: Annotated[
248
+ ListOfOrganizationRoles, Field(..., description="Domain Roles", min_length=1)
249
+ ]
250
+ medical_roles: Annotated[
251
+ OptListOfMedicalRoles, Field(..., description="Medical roles")
252
+ ]
253
+ scopes: Annotated[ListOfStrs, Field(..., description="Scopes", min_length=1)]
254
+
255
+
256
+ class SystemCredentials(
257
+ GenericCredentials[
258
+ Literal[Domain.SYSTEM],
259
+ EntityIdentifier[UserType],
260
+ None,
261
+ ListOfSystemRoles,
262
+ None,
263
+ ListOfStrs,
264
+ ]
265
+ ):
266
+ domain: Literal[Domain.SYSTEM] = Domain.SYSTEM
267
+ user: Annotated[EntityIdentifier[UserType], Field(..., description="User")]
268
+ organization: Annotated[None, Field(None, description="Organization")] = None
269
+ domain_roles: Annotated[
270
+ ListOfSystemRoles, Field(..., description="Domain Roles", min_length=1)
271
+ ]
272
+ medical_roles: Annotated[None, Field(None, description="Medical roles")] = None
273
+ scopes: Annotated[ListOfStrs, Field(..., description="Scopes", min_length=1)]
274
+
275
+
276
+ AnyCredentials = (
277
+ BaseCredentials | AuthenticatedCredentials | TenantCredentials | SystemCredentials
278
+ )
279
+ AnyCredentialsT = TypeVar("AnyCredentialsT", bound=AnyCredentials)
280
+
281
+
282
+ class CredentialsMixin(BaseModel, Generic[AnyCredentialsT]):
283
+ credentials: AnyCredentialsT = Field(..., description="Credentials")
284
+
285
+
286
+ IsAuthenticatedT = TypeVar("IsAuthenticatedT", bound=bool)
287
+
288
+
289
+ class GenericUser(BaseModel, Generic[IsAuthenticatedT, OptStrT]):
290
+ is_authenticated: IsAuthenticatedT = Field(..., description="Authenticated")
291
+ organization: Annotated[OptStrT, Field(..., description="Organization")]
292
+ display_name: Annotated[str, Field("", description="Username")] = ""
293
+ identity: Annotated[str, Field("", description="Email")] = ""
294
+
295
+
296
+ class BaseUser(GenericUser[bool, OptStr]):
297
+ is_authenticated: Annotated[bool, Field(False, description="Authenticated")] = False
298
+ organization: Annotated[OptStr, Field(None, description="Organization")] = None
299
+
300
+
301
+ class AuthenticatedUser(GenericUser[Literal[True], OptStr]):
302
+ is_authenticated: Literal[True] = True
303
+ organization: Annotated[OptStr, Field(..., description="Organization")]
304
+
305
+
306
+ class TenantUser(GenericUser[Literal[True], str]):
307
+ is_authenticated: Literal[True] = True
308
+ organization: Annotated[str, Field(..., description="Organization")]
309
+
310
+
311
+ class SystemUser(GenericUser[Literal[True], None]):
312
+ is_authenticated: Literal[True] = True
313
+ organization: Annotated[None, Field(None, description="Organization")] = None
314
+
315
+
316
+ AnyUser = BaseUser | AuthenticatedUser | TenantUser | SystemUser
317
+ AnyUserT = TypeVar("AnyUserT", bound=AnyUser)
318
+
319
+
320
+ class UserMixin(BaseModel, Generic[AnyUserT]):
321
+ user: AnyUserT = Field(..., description="User")
322
+
323
+
324
+ class GenericAuthentication(
325
+ UserMixin[AnyUserT],
326
+ CredentialsMixin[AnyCredentialsT],
327
+ Generic[AnyCredentialsT, AnyUserT],
328
+ ABC,
329
+ ):
330
+ @classmethod
331
+ def _validate_request_credentials(cls, conn: HTTPConnection):
332
+ if not isinstance(conn.auth, RequestCredentials):
333
+ raise HTTPException(
334
+ status_code=status.HTTP_401_UNAUTHORIZED,
335
+ detail=f"Invalid type of request's credentials: '{type(conn.auth)}'",
336
+ )
337
+
338
+ @classmethod
339
+ def _validate_request_user(cls, conn: HTTPConnection):
340
+ if not isinstance(conn.user, RequestUser):
341
+ raise HTTPException(
342
+ status_code=status.HTTP_401_UNAUTHORIZED,
343
+ detail=f"Invalid type of request's user: '{type(conn.user)}'",
344
+ )
345
+
346
+ @classmethod
347
+ @abstractmethod
348
+ def _extract(
349
+ cls,
350
+ conn: HTTPConnection,
351
+ /,
352
+ ) -> Self:
353
+ """Main extractor logic"""
354
+
355
+ @classmethod
356
+ def extract(
357
+ cls,
358
+ conn: HTTPConnection,
359
+ /,
360
+ ) -> Self:
361
+ try:
362
+ return cls._extract(conn)
363
+ except Exception as e:
364
+ raise HTTPException(
365
+ status_code=status.HTTP_401_UNAUTHORIZED, detail=extract_details(e)
366
+ )
367
+
368
+ @classmethod
369
+ def as_dependency(cls) -> Callable[[HTTPConnection], Self]:
370
+ """Create a FastAPI dependency for this authentication"""
371
+
372
+ def dependency(conn: HTTPConnection) -> Self:
373
+ return cls.extract(conn)
374
+
375
+ return dependency
376
+
377
+ @cached_property
378
+ def is_valid_domain(self) -> bool:
379
+ return self.credentials.domain is not None and isinstance(
380
+ self.credentials.domain, Domain
381
+ )
382
+
383
+ @cached_property
384
+ def is_system_domain(self) -> bool:
385
+ return self.is_valid_domain and self.credentials.domain is Domain.SYSTEM
386
+
387
+ @cached_property
388
+ def is_tenant_domain(self) -> bool:
389
+ return self.is_valid_domain and self.credentials.domain is Domain.TENANT
390
+
391
+ @cached_property
392
+ def is_valid_domain_roles(self) -> bool:
393
+ return self.credentials.domain_roles is not None and (
394
+ all(
395
+ isinstance(role, OrganizationRole)
396
+ for role in self.credentials.domain_roles
397
+ )
398
+ or all(
399
+ isinstance(role, SystemRole) for role in self.credentials.domain_roles
400
+ )
401
+ )
402
+
403
+ def has_all_domain_roles(self, roles: ListOfDomainRoles) -> bool:
404
+ return (
405
+ self.is_valid_domain_roles
406
+ and self.credentials.domain_roles is not None
407
+ and all(role in self.credentials.domain_roles for role in roles)
408
+ )
409
+
410
+ def has_any_domain_roles(self, roles: ListOfDomainRoles) -> bool:
411
+ return (
412
+ self.is_valid_domain_roles
413
+ and self.credentials.domain_roles is not None
414
+ and any(role in self.credentials.domain_roles for role in roles)
415
+ )
416
+
417
+ def has_domain_roles(
418
+ self, roles: ListOfDomainRoles, *, logic: Literal["all", "any"]
419
+ ) -> bool:
420
+ if logic == "all":
421
+ return self.has_all_domain_roles(roles)
422
+ elif logic == "any":
423
+ return self.has_any_domain_roles(roles)
424
+
425
+ @cached_property
426
+ def is_valid_system_roles(self) -> bool:
427
+ return (
428
+ self.is_valid_domain_roles
429
+ and self.credentials.domain_roles is not None
430
+ and all(
431
+ isinstance(role, SystemRole) for role in self.credentials.domain_roles
432
+ )
433
+ )
434
+
435
+ def has_all_system_roles(self, roles: ListOfSystemRoles) -> bool:
436
+ return (
437
+ self.is_valid_system_roles
438
+ and self.credentials.domain_roles is not None
439
+ and all(role in self.credentials.domain_roles for role in roles)
440
+ )
441
+
442
+ def has_any_system_roles(self, roles: ListOfSystemRoles) -> bool:
443
+ return (
444
+ self.is_valid_system_roles
445
+ and self.credentials.domain_roles is not None
446
+ and any(role in self.credentials.domain_roles for role in roles)
447
+ )
448
+
449
+ def has_system_roles(
450
+ self, roles: ListOfSystemRoles, *, logic: Literal["all", "any"]
451
+ ) -> bool:
452
+ if logic == "all":
453
+ return self.has_all_system_roles(roles)
454
+ elif logic == "any":
455
+ return self.has_any_system_roles(roles)
456
+
457
+ @cached_property
458
+ def is_valid_tenant_roles(self) -> bool:
459
+ return (
460
+ self.is_valid_domain_roles
461
+ and self.credentials.domain_roles is not None
462
+ and all(
463
+ isinstance(role, OrganizationRole)
464
+ for role in self.credentials.domain_roles
465
+ )
466
+ )
467
+
468
+ def has_all_tenant_roles(self, roles: ListOfOrganizationRoles) -> bool:
469
+ return (
470
+ self.is_valid_tenant_roles
471
+ and self.credentials.domain_roles is not None
472
+ and all(role in self.credentials.domain_roles for role in roles)
473
+ )
474
+
475
+ def has_any_tenant_roles(self, roles: ListOfOrganizationRoles) -> bool:
476
+ return (
477
+ self.is_valid_tenant_roles
478
+ and self.credentials.domain_roles is not None
479
+ and any(role in self.credentials.domain_roles for role in roles)
480
+ )
481
+
482
+ def has_tenant_roles(
483
+ self, roles: ListOfOrganizationRoles, *, logic: Literal["all", "any"]
484
+ ) -> bool:
485
+ if logic == "all":
486
+ return self.has_all_tenant_roles(roles)
487
+ elif logic == "any":
488
+ return self.has_any_tenant_roles(roles)
489
+
490
+ @cached_property
491
+ def is_valid_medical_roles(self) -> bool:
492
+ return self.credentials.medical_roles is not None and all(
493
+ isinstance(role, MedicalRole) for role in self.credentials.medical_roles
494
+ )
495
+
496
+ def has_all_medical_roles(self, roles: ListOfMedicalRoles) -> bool:
497
+ return (
498
+ self.is_valid_medical_roles
499
+ and self.credentials.medical_roles is not None
500
+ and all(role in self.credentials.medical_roles for role in roles)
501
+ )
502
+
503
+ def has_any_medical_roles(self, roles: ListOfMedicalRoles) -> bool:
504
+ return (
505
+ self.is_valid_medical_roles
506
+ and self.credentials.medical_roles is not None
507
+ and any(role in self.credentials.medical_roles for role in roles)
508
+ )
509
+
510
+ def has_medical_roles(
511
+ self, roles: ListOfMedicalRoles, *, logic: Literal["all", "any"]
512
+ ) -> bool:
513
+ if logic == "all":
514
+ return self.has_all_medical_roles(roles)
515
+ elif logic == "any":
516
+ return self.has_any_medical_roles(roles)
517
+
518
+ @cached_property
519
+ def is_authenticated(self) -> bool:
520
+ return (
521
+ self.user.is_authenticated
522
+ and self.is_valid_domain
523
+ and self.credentials.user is not None
524
+ and self.is_valid_domain_roles
525
+ and self.credentials.scopes is not None
526
+ )
527
+
528
+ @cached_property
529
+ def is_system(self) -> bool:
530
+ return (
531
+ self.is_authenticated
532
+ and self.user.organization is None
533
+ and self.is_system_domain
534
+ and self.credentials.organization is None
535
+ and self.credentials.domain_roles is not None
536
+ and self.is_valid_system_roles
537
+ and self.credentials.medical_roles is None
538
+ )
539
+
540
+ @cached_property
541
+ def is_system_administrator(self) -> bool:
542
+ return self.is_system and self.has_all_system_roles([SystemRole.ADMINISTRATOR])
543
+
544
+ @cached_property
545
+ def is_tenant(self) -> bool:
546
+ return (
547
+ self.is_authenticated
548
+ and self.user.organization is not None
549
+ and self.is_tenant_domain
550
+ and self.credentials.organization is not None
551
+ and self.credentials.domain_roles is not None
552
+ and self.is_valid_tenant_roles
553
+ )
554
+
555
+ @cached_property
556
+ def is_tenant_owner(self) -> bool:
557
+ return self.is_tenant and self.has_all_tenant_roles([OrganizationRole.OWNER])
558
+
559
+ @cached_property
560
+ def is_tenant_administrator(self) -> bool:
561
+ return self.is_tenant and self.has_all_tenant_roles(
562
+ [OrganizationRole.ADMINISTRATOR]
563
+ )
564
+
565
+ @cached_property
566
+ def is_tenant_owner_or_administrator(self) -> bool:
567
+ return self.is_tenant and self.has_any_tenant_roles(
568
+ [OrganizationRole.OWNER, OrganizationRole.ADMINISTRATOR]
569
+ )
570
+
571
+
572
+ class BaseAuthentication(GenericAuthentication[BaseCredentials, BaseUser]):
573
+ credentials: Annotated[
574
+ BaseCredentials,
575
+ Field(BaseCredentials(), description="Credentials"),
576
+ ] = BaseCredentials()
577
+
578
+ user: Annotated[BaseUser, Field(BaseUser(), description="User")] = BaseUser()
579
+
580
+ @classmethod
581
+ def _extract(
582
+ cls,
583
+ conn: HTTPConnection,
584
+ /,
585
+ ) -> Self:
586
+ # validate credentials
587
+ cls._validate_request_credentials(conn=conn)
588
+ credentials = BaseCredentials.model_validate(conn.auth, from_attributes=True)
589
+
590
+ # validate user
591
+ cls._validate_request_user(conn=conn)
592
+ user = BaseUser.model_validate(conn.user, from_attributes=True)
593
+ return cls(credentials=credentials, user=user)
594
+
595
+
596
+ class AuthenticatedAuthentication(
597
+ GenericAuthentication[AuthenticatedCredentials, AuthenticatedUser]
598
+ ):
599
+ credentials: Annotated[
600
+ AuthenticatedCredentials, Field(..., description="Credentials")
601
+ ]
602
+
603
+ user: Annotated[AuthenticatedUser, Field(..., description="User")]
604
+
605
+ @classmethod
606
+ def _extract(
607
+ cls,
608
+ conn: HTTPConnection,
609
+ /,
610
+ ) -> Self:
611
+ # validate credentials
612
+ cls._validate_request_credentials(conn=conn)
613
+ credentials = AuthenticatedCredentials.model_validate(
614
+ conn.auth, from_attributes=True
615
+ )
616
+
617
+ # validate user
618
+ cls._validate_request_user(conn=conn)
619
+ user = AuthenticatedUser.model_validate(conn.user, from_attributes=True)
620
+ return cls(credentials=credentials, user=user)
621
+
622
+
623
+ class TenantAuthentication(GenericAuthentication[TenantCredentials, TenantUser]):
624
+ credentials: Annotated[TenantCredentials, Field(..., description="Credentials")]
625
+ user: Annotated[TenantUser, Field(..., description="User")]
626
+
627
+ @classmethod
628
+ def _extract(
629
+ cls,
630
+ conn: HTTPConnection,
631
+ /,
632
+ ) -> Self:
633
+ # validate credentials
634
+ cls._validate_request_credentials(conn=conn)
635
+ credentials = TenantCredentials.model_validate(conn.auth, from_attributes=True)
636
+
637
+ # validate user
638
+ cls._validate_request_user(conn=conn)
639
+ user = TenantUser.model_validate(conn.user, from_attributes=True)
640
+ return cls(credentials=credentials, user=user)
641
+
642
+
643
+ class SystemAuthentication(GenericAuthentication[SystemCredentials, SystemUser]):
644
+ credentials: Annotated[SystemCredentials, Field(..., description="Credentials")]
645
+ user: Annotated[SystemUser, Field(..., description="User")]
646
+
647
+ @classmethod
648
+ def _extract(
649
+ cls,
650
+ conn: HTTPConnection,
651
+ /,
652
+ ) -> Self:
653
+ # validate credentials
654
+ cls._validate_request_credentials(conn=conn)
655
+ credentials = SystemCredentials.model_validate(conn.auth, from_attributes=True)
656
+
657
+ # validate user
658
+ cls._validate_request_user(conn=conn)
659
+ user = SystemUser.model_validate(conn.user, from_attributes=True)
660
+ return cls(credentials=credentials, user=user)
661
+
662
+
663
+ AnyAuthenticatedAuthentication = (
664
+ AuthenticatedAuthentication | TenantAuthentication | SystemAuthentication
665
+ )
666
+ AnyAuthenticatedAuthenticationT = TypeVar(
667
+ "AnyAuthenticatedAuthenticationT", bound=AnyAuthenticatedAuthentication
668
+ )
669
+ OptAnyAuthenticatedAuthentication = AnyAuthenticatedAuthentication | None
670
+ OptAnyAuthenticatedAuthenticationT = TypeVar(
671
+ "OptAnyAuthenticatedAuthenticationT",
672
+ bound=OptAnyAuthenticatedAuthentication,
673
+ )
674
+
675
+
676
+ AnyAuthentication = BaseAuthentication | AnyAuthenticatedAuthentication
677
+ AnyAuthenticationT = TypeVar("AnyAuthenticationT", bound=AnyAuthentication)
678
+ OptAnyAuthentication = AnyAuthentication | None
679
+ OptAnyAuthenticationT = TypeVar("OptAnyAuthenticationT", bound=OptAnyAuthentication)
680
+
681
+
682
+ def is_authenticated(
683
+ authentication: AnyAuthentication,
684
+ ) -> TypeGuard[AnyAuthenticatedAuthentication]:
685
+ return authentication.is_authenticated
686
+
687
+
688
+ def is_system(
689
+ authentication: AnyAuthentication,
690
+ ) -> TypeGuard[SystemAuthentication]:
691
+ return authentication.is_system
692
+
693
+
694
+ def is_tenant(
695
+ authentication: AnyAuthentication,
696
+ ) -> TypeGuard[TenantAuthentication]:
697
+ return authentication.is_tenant
698
+
699
+
700
+ class AuthenticationMixin(BaseModel, Generic[OptAnyAuthenticationT]):
701
+ authentication: OptAnyAuthenticationT = Field(..., description="Authentication")
702
+
703
+
704
+ class AuthenticationFactory:
705
+ @overload
706
+ @classmethod
707
+ def extract(
708
+ cls,
709
+ domain: Literal[Domain.TENANT],
710
+ *,
711
+ conn: HTTPConnection,
712
+ mandatory: Literal[True] = True,
713
+ ) -> TenantAuthentication: ...
714
+ @overload
715
+ @classmethod
716
+ def extract(
717
+ cls,
718
+ domain: Literal[Domain.SYSTEM],
719
+ *,
720
+ conn: HTTPConnection,
721
+ mandatory: Literal[True] = True,
722
+ ) -> SystemAuthentication: ...
723
+ @overload
724
+ @classmethod
725
+ def extract(
726
+ cls,
727
+ domain: None = None,
728
+ *,
729
+ conn: HTTPConnection,
730
+ mandatory: Literal[True] = True,
731
+ ) -> AuthenticatedAuthentication: ...
732
+ @overload
733
+ @classmethod
734
+ def extract(
735
+ cls, domain: None = None, *, conn: HTTPConnection, mandatory: Literal[False]
736
+ ) -> BaseAuthentication: ...
737
+ @overload
738
+ @classmethod
739
+ def extract(
740
+ cls,
741
+ domain: OptDomain = None,
742
+ *,
743
+ conn: HTTPConnection,
744
+ mandatory: bool = False,
745
+ ) -> AnyAuthentication: ...
746
+ @classmethod
747
+ def extract(
748
+ cls,
749
+ domain: OptDomain = None,
750
+ *,
751
+ conn: HTTPConnection,
752
+ mandatory: bool = True,
753
+ ) -> AnyAuthentication:
754
+ if not mandatory:
755
+ return BaseAuthentication.extract(conn)
756
+ if domain is None:
757
+ return AuthenticatedAuthentication.extract(conn)
758
+ elif domain is Domain.TENANT:
759
+ return TenantAuthentication.extract(conn)
760
+ elif domain is Domain.SYSTEM:
761
+ return SystemAuthentication.extract(conn)
762
+
763
+ @overload
764
+ @classmethod
765
+ def as_dependency(
766
+ cls, domain: Literal[Domain.TENANT], *, mandatory: Literal[True] = True
767
+ ) -> Callable[[HTTPConnection], TenantAuthentication]: ...
768
+ @overload
769
+ @classmethod
770
+ def as_dependency(
771
+ cls, domain: Literal[Domain.SYSTEM], *, mandatory: Literal[True] = True
772
+ ) -> Callable[[HTTPConnection], SystemAuthentication]: ...
773
+ @overload
774
+ @classmethod
775
+ def as_dependency(
776
+ cls, domain: None = None, *, mandatory: Literal[True] = True
777
+ ) -> Callable[[HTTPConnection], AuthenticatedAuthentication]: ...
778
+ @overload
779
+ @classmethod
780
+ def as_dependency(
781
+ cls, domain: None = None, *, mandatory: Literal[False]
782
+ ) -> Callable[[HTTPConnection], BaseAuthentication]: ...
783
+ @classmethod
784
+ def as_dependency(
785
+ cls, domain: OptDomain = None, *, mandatory: bool = True
786
+ ) -> Callable[[HTTPConnection], AnyAuthentication]:
787
+
788
+ def dependency(conn: HTTPConnection) -> AnyAuthentication:
789
+ return cls.extract(domain, conn=conn, mandatory=mandatory)
790
+
791
+ return dependency
792
+
793
+ @overload
794
+ @classmethod
795
+ def convert(
796
+ cls,
797
+ destination: Literal[ConversionDestination.BASE],
798
+ *,
799
+ authentication: AnyAuthentication,
800
+ ) -> BaseAuthentication: ...
801
+ @overload
802
+ @classmethod
803
+ def convert(
804
+ cls,
805
+ destination: Literal[ConversionDestination.AUTHENTICATED],
806
+ *,
807
+ authentication: AnyAuthentication,
808
+ ) -> AuthenticatedAuthentication: ...
809
+ @overload
810
+ @classmethod
811
+ def convert(
812
+ cls,
813
+ destination: Literal[ConversionDestination.TENANT],
814
+ *,
815
+ authentication: AnyAuthentication,
816
+ ) -> TenantAuthentication: ...
817
+ @overload
818
+ @classmethod
819
+ def convert(
820
+ cls,
821
+ destination: Literal[ConversionDestination.SYSTEM],
822
+ *,
823
+ authentication: AnyAuthentication,
824
+ ) -> BaseAuthentication: ...
825
+ @classmethod
826
+ def convert(
827
+ cls, destination: ConversionDestination, *, authentication: AnyAuthentication
828
+ ) -> AnyAuthentication:
829
+ if destination is ConversionDestination.BASE:
830
+ return BaseAuthentication.model_validate(authentication.model_dump())
831
+ elif destination is ConversionDestination.AUTHENTICATED:
832
+ return AuthenticatedAuthentication.model_validate(
833
+ authentication.model_dump()
834
+ )
835
+ elif destination is ConversionDestination.TENANT:
836
+ if isinstance(authentication, SystemAuthentication):
837
+ raise TypeError(
838
+ "Failed converting SystemAuthentication to TenantAuthentication",
839
+ "Both authentications can not be converted into one another",
840
+ )
841
+ return TenantAuthentication.model_validate(authentication.model_dump())
842
+ elif destination is ConversionDestination.SYSTEM:
843
+ if isinstance(authentication, TenantAuthentication):
844
+ raise TypeError(
845
+ "Failed converting TenantAuthentication to SystemAuthentication",
846
+ "Both authentications can not be converted into one another",
847
+ )
848
+ return SystemAuthentication.model_validate(authentication.model_dump())