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.
- nexo/schemas/__init__.py +0 -0
- nexo/schemas/application.py +292 -0
- nexo/schemas/connection.py +134 -0
- nexo/schemas/data.py +27 -0
- nexo/schemas/document.py +237 -0
- nexo/schemas/error/__init__.py +476 -0
- nexo/schemas/error/constants.py +50 -0
- nexo/schemas/error/descriptor.py +354 -0
- nexo/schemas/error/enums.py +40 -0
- nexo/schemas/error/metadata.py +15 -0
- nexo/schemas/error/spec.py +312 -0
- nexo/schemas/exception/__init__.py +0 -0
- nexo/schemas/exception/exc.py +911 -0
- nexo/schemas/exception/factory.py +1928 -0
- nexo/schemas/exception/handlers.py +110 -0
- nexo/schemas/google.py +14 -0
- nexo/schemas/key/__init__.py +0 -0
- nexo/schemas/key/rsa.py +131 -0
- nexo/schemas/metadata.py +21 -0
- nexo/schemas/mixins/__init__.py +0 -0
- nexo/schemas/mixins/filter.py +140 -0
- nexo/schemas/mixins/general.py +65 -0
- nexo/schemas/mixins/hierarchy.py +19 -0
- nexo/schemas/mixins/identity.py +387 -0
- nexo/schemas/mixins/parameter.py +50 -0
- nexo/schemas/mixins/service.py +40 -0
- nexo/schemas/mixins/sort.py +111 -0
- nexo/schemas/mixins/timestamp.py +192 -0
- nexo/schemas/model.py +240 -0
- nexo/schemas/operation/__init__.py +0 -0
- nexo/schemas/operation/action/__init__.py +9 -0
- nexo/schemas/operation/action/base.py +14 -0
- nexo/schemas/operation/action/resource.py +371 -0
- nexo/schemas/operation/action/status.py +8 -0
- nexo/schemas/operation/action/system.py +6 -0
- nexo/schemas/operation/action/websocket.py +6 -0
- nexo/schemas/operation/base.py +289 -0
- nexo/schemas/operation/constants.py +18 -0
- nexo/schemas/operation/context.py +68 -0
- nexo/schemas/operation/dependency.py +26 -0
- nexo/schemas/operation/enums.py +168 -0
- nexo/schemas/operation/extractor.py +36 -0
- nexo/schemas/operation/mixins.py +53 -0
- nexo/schemas/operation/request.py +1066 -0
- nexo/schemas/operation/resource.py +839 -0
- nexo/schemas/operation/system.py +55 -0
- nexo/schemas/operation/websocket.py +55 -0
- nexo/schemas/pagination.py +67 -0
- nexo/schemas/parameter.py +60 -0
- nexo/schemas/payload.py +116 -0
- nexo/schemas/resource.py +64 -0
- nexo/schemas/response.py +1041 -0
- nexo/schemas/security/__init__.py +0 -0
- nexo/schemas/security/api_key.py +63 -0
- nexo/schemas/security/authentication.py +848 -0
- nexo/schemas/security/authorization.py +922 -0
- nexo/schemas/security/enums.py +32 -0
- nexo/schemas/security/impersonation.py +179 -0
- nexo/schemas/security/token.py +402 -0
- nexo/schemas/security/types.py +17 -0
- nexo/schemas/success/__init__.py +0 -0
- nexo/schemas/success/descriptor.py +100 -0
- nexo/schemas/success/enums.py +23 -0
- nexo/schemas/user_agent.py +46 -0
- nexo_schemas-0.0.16.dist-info/METADATA +87 -0
- nexo_schemas-0.0.16.dist-info/RECORD +69 -0
- nexo_schemas-0.0.16.dist-info/WHEEL +5 -0
- nexo_schemas-0.0.16.dist-info/licenses/LICENSE +21 -0
- 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())
|