fastapi-rtk 1.0.8__py3-none-any.whl → 1.0.10__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.
- fastapi_rtk/__init__.py +0 -1
- fastapi_rtk/_version.py +1 -1
- fastapi_rtk/auth/auth.py +0 -9
- fastapi_rtk/bases/file_manager.py +12 -0
- fastapi_rtk/cli/commands/security.py +6 -6
- fastapi_rtk/db.py +1 -0
- fastapi_rtk/dependencies.py +110 -64
- fastapi_rtk/fastapi_react_toolkit.py +109 -161
- fastapi_rtk/file_managers/s3_file_manager.py +63 -32
- fastapi_rtk/security/sqla/apis.py +20 -5
- fastapi_rtk/security/sqla/models.py +8 -23
- fastapi_rtk/security/sqla/security_manager.py +367 -10
- fastapi_rtk/utils/hooks.py +7 -4
- {fastapi_rtk-1.0.8.dist-info → fastapi_rtk-1.0.10.dist-info}/METADATA +1 -1
- {fastapi_rtk-1.0.8.dist-info → fastapi_rtk-1.0.10.dist-info}/RECORD +18 -18
- {fastapi_rtk-1.0.8.dist-info → fastapi_rtk-1.0.10.dist-info}/WHEEL +0 -0
- {fastapi_rtk-1.0.8.dist-info → fastapi_rtk-1.0.10.dist-info}/entry_points.txt +0 -0
- {fastapi_rtk-1.0.8.dist-info → fastapi_rtk-1.0.10.dist-info}/licenses/LICENSE +0 -0
fastapi_rtk/__init__.py
CHANGED
fastapi_rtk/_version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "1.0.
|
|
1
|
+
__version__ = "1.0.10"
|
fastapi_rtk/auth/auth.py
CHANGED
|
@@ -142,15 +142,6 @@ class Authenticator(BaseAuthenticator):
|
|
|
142
142
|
except HTTPException as e:
|
|
143
143
|
if not default_to_none:
|
|
144
144
|
raise e
|
|
145
|
-
|
|
146
|
-
# Retrieve list of apis, that user has access to
|
|
147
|
-
if user:
|
|
148
|
-
user.permissions = []
|
|
149
|
-
for role in user.roles:
|
|
150
|
-
for permission_api in role.permissions:
|
|
151
|
-
user.permissions.append(permission_api.api.name)
|
|
152
|
-
user.permissions = list(set(user.permissions))
|
|
153
|
-
|
|
154
145
|
return user, token
|
|
155
146
|
|
|
156
147
|
|
|
@@ -35,6 +35,18 @@ class AbstractFileManager(abc.ABC):
|
|
|
35
35
|
namegen: typing.Callable[[str], str] | None = None,
|
|
36
36
|
permission: int | None = None,
|
|
37
37
|
):
|
|
38
|
+
"""
|
|
39
|
+
Initializes the AbstractFileManager.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
base_path (str | None, optional): Base path for file storage. Defaults to None.
|
|
43
|
+
allowed_extensions (list[str] | None, optional): Allowed file extensions. Defaults to None.
|
|
44
|
+
namegen (typing.Callable[[str], str] | None, optional): Callable for generating file names. Defaults to None.
|
|
45
|
+
permission (int | None, optional): File permission settings. Defaults to None.
|
|
46
|
+
|
|
47
|
+
Raises:
|
|
48
|
+
ValueError: If `base_path` is not set.
|
|
49
|
+
"""
|
|
38
50
|
if base_path is not None:
|
|
39
51
|
self.base_path = base_path
|
|
40
52
|
if allowed_extensions is not None:
|
|
@@ -238,7 +238,7 @@ async def _check_roles(name: str, create: bool = False):
|
|
|
238
238
|
if not role:
|
|
239
239
|
if not create:
|
|
240
240
|
raise Exception(f"Role {name} does not exist")
|
|
241
|
-
await g.current_app.
|
|
241
|
+
await g.current_app.sm.create_role(name=name, session=session)
|
|
242
242
|
|
|
243
243
|
|
|
244
244
|
async def _create_user(
|
|
@@ -255,7 +255,7 @@ async def _create_user(
|
|
|
255
255
|
"""
|
|
256
256
|
if role:
|
|
257
257
|
await _check_roles(role, create=create_role)
|
|
258
|
-
return await g.current_app.
|
|
258
|
+
return await g.current_app.sm.create_user(
|
|
259
259
|
email=email,
|
|
260
260
|
username=username,
|
|
261
261
|
password=password,
|
|
@@ -269,8 +269,8 @@ async def _reset_password(email_or_username: str, password: str):
|
|
|
269
269
|
"""
|
|
270
270
|
Reset user password.
|
|
271
271
|
"""
|
|
272
|
-
user = await g.current_app.
|
|
273
|
-
return await g.current_app.
|
|
272
|
+
user = await g.current_app.sm.get_user(email_or_username)
|
|
273
|
+
return await g.current_app.sm.reset_password(user, password)
|
|
274
274
|
|
|
275
275
|
|
|
276
276
|
async def export_data(
|
|
@@ -281,7 +281,7 @@ async def export_data(
|
|
|
281
281
|
"""
|
|
282
282
|
Export data.
|
|
283
283
|
"""
|
|
284
|
-
data = await g.current_app.
|
|
284
|
+
data = await g.current_app.sm.export_data(data, type)
|
|
285
285
|
with open(file_path, "w") as f:
|
|
286
286
|
f.write(data)
|
|
287
287
|
|
|
@@ -290,4 +290,4 @@ async def _cleanup():
|
|
|
290
290
|
"""
|
|
291
291
|
Cleanup unused permissions from apis and roles.
|
|
292
292
|
"""
|
|
293
|
-
await g.current_app.
|
|
293
|
+
await g.current_app.sm.cleanup()
|
fastapi_rtk/db.py
CHANGED
|
@@ -121,6 +121,7 @@ class UserDatabase(SQLAlchemyUserDatabase[UP, ID]):
|
|
|
121
121
|
raise NotImplementedError()
|
|
122
122
|
|
|
123
123
|
await safe_call(self.session.refresh(user))
|
|
124
|
+
await user.load("oauth_accounts")
|
|
124
125
|
oauth_account = self.oauth_account_table(**create_dict)
|
|
125
126
|
self.session.add(oauth_account)
|
|
126
127
|
user.oauth_accounts.append(oauth_account)
|
fastapi_rtk/dependencies.py
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import fastapi
|
|
2
|
+
import sqlalchemy
|
|
2
3
|
from fastapi import Depends, HTTPException
|
|
3
4
|
|
|
4
5
|
from .api.base_api import BaseApi
|
|
5
|
-
from .const import PERMISSION_PREFIX, ErrorCode
|
|
6
|
+
from .const import PERMISSION_PREFIX, ErrorCode, logger
|
|
7
|
+
from .db import db
|
|
6
8
|
from .globals import g
|
|
7
|
-
from .security.sqla.models import PermissionApi, User
|
|
9
|
+
from .security.sqla.models import Api, Permission, PermissionApi, Role, User
|
|
10
|
+
from .utils import smart_run
|
|
8
11
|
|
|
9
12
|
__all__ = [
|
|
10
13
|
"set_global_user",
|
|
11
|
-
"permissions",
|
|
12
14
|
"current_permissions",
|
|
13
15
|
"has_access_dependency",
|
|
14
16
|
]
|
|
@@ -62,82 +64,65 @@ def check_g_user():
|
|
|
62
64
|
return check_g_user_dependency
|
|
63
65
|
|
|
64
66
|
|
|
65
|
-
def
|
|
67
|
+
def current_permissions(api: BaseApi):
|
|
66
68
|
"""
|
|
67
|
-
A dependency for FastAPI that will return all permissions of the current user.
|
|
69
|
+
A dependency for FastAPI that will return all permissions of the current user for the specified API.
|
|
68
70
|
|
|
69
|
-
|
|
71
|
+
Because it will implicitly check whether the user is authenticated, it can return `401 Unauthorized` or `403 Forbidden`.
|
|
70
72
|
|
|
71
73
|
Args:
|
|
72
|
-
|
|
74
|
+
api (BaseApi): The API to be checked.
|
|
73
75
|
|
|
74
76
|
Usage:
|
|
75
77
|
```python
|
|
76
78
|
async def get_info(
|
|
77
79
|
*,
|
|
78
|
-
permissions: List[str] = Depends(
|
|
80
|
+
permissions: List[str] = Depends(current_permissions(self)),
|
|
79
81
|
session: AsyncSession | Session = Depends(get_async_session),
|
|
80
82
|
):
|
|
81
83
|
...more code
|
|
82
84
|
```
|
|
83
85
|
"""
|
|
84
86
|
|
|
85
|
-
async def
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
if not g.user.roles:
|
|
93
|
-
raise HTTPException(
|
|
94
|
-
fastapi.status.HTTP_403_FORBIDDEN, ErrorCode.GET_USER_NO_ROLES
|
|
95
|
-
)
|
|
87
|
+
async def current_permissions_depedency(_=Depends(_ensure_roles)):
|
|
88
|
+
sm = g.current_app.sm
|
|
89
|
+
api_name = api.__class__.__name__
|
|
90
|
+
permissions = set[str]()
|
|
91
|
+
db_role_ids = list[int]()
|
|
96
92
|
|
|
97
|
-
permissions
|
|
93
|
+
# Retrieve permissions from built-in roles
|
|
98
94
|
for role in g.user.roles:
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
else:
|
|
103
|
-
permissions.append(permission_api.api.name)
|
|
104
|
-
permissions = list(set(permissions))
|
|
105
|
-
|
|
106
|
-
return permissions
|
|
107
|
-
|
|
108
|
-
return permissions_depedency
|
|
95
|
+
if role.name not in sm.builtin_roles:
|
|
96
|
+
db_role_ids.append(role.id)
|
|
97
|
+
continue
|
|
109
98
|
|
|
99
|
+
api_permission_tuples = sm.get_api_permission_tuples_from_builtin_roles(
|
|
100
|
+
role.name
|
|
101
|
+
)
|
|
102
|
+
for multi_apis_str, multi_perms_str in api_permission_tuples:
|
|
103
|
+
api_names = multi_apis_str.split("|")
|
|
104
|
+
perm_names = multi_perms_str.split("|")
|
|
105
|
+
if api_name in api_names:
|
|
106
|
+
permissions.update(perm_names)
|
|
107
|
+
|
|
108
|
+
if db_role_ids:
|
|
109
|
+
query = (
|
|
110
|
+
sqlalchemy.select(Permission)
|
|
111
|
+
.join(PermissionApi)
|
|
112
|
+
.join(PermissionApi.roles)
|
|
113
|
+
.join(Api)
|
|
114
|
+
.where(Api.name == api_name, Role.id.in_(db_role_ids))
|
|
115
|
+
)
|
|
116
|
+
if api.base_permissions:
|
|
117
|
+
query = query.where(Permission.name.in_(api.base_permissions))
|
|
110
118
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
A dependency for FastAPI that will return all permissions of the current user for the specified API.
|
|
114
|
-
|
|
115
|
-
Because it will implicitly call the `permissions` dependency, it can return `403 Forbidden` if the user is not authenticated.
|
|
116
|
-
|
|
117
|
-
Args:
|
|
118
|
-
api (BaseApi): The API to be checked.
|
|
119
|
-
|
|
120
|
-
Usage:
|
|
121
|
-
```python
|
|
122
|
-
async def get_info(
|
|
123
|
-
*,
|
|
124
|
-
permissions: List[str] = Depends(current_permissions(self)),
|
|
125
|
-
session: AsyncSession | Session = Depends(get_async_session),
|
|
126
|
-
):
|
|
127
|
-
...more code
|
|
128
|
-
```
|
|
129
|
-
"""
|
|
119
|
+
permissions_in_db = await smart_run(db.current_session.scalars, query)
|
|
120
|
+
permissions.update(perm.name for perm in permissions_in_db.all())
|
|
130
121
|
|
|
131
|
-
async def current_permissions_depedency(
|
|
132
|
-
permissions_apis: list[PermissionApi] = Depends(permissions(as_object=True)),
|
|
133
|
-
):
|
|
134
|
-
permissions = []
|
|
135
|
-
for permission_api in permissions_apis:
|
|
136
|
-
if api.__class__.__name__ in permission_api.api.name.split("|"):
|
|
137
|
-
permissions = permissions + permission_api.permission.name.split("|")
|
|
138
122
|
if api.base_permissions:
|
|
139
|
-
permissions =
|
|
140
|
-
|
|
123
|
+
permissions = permissions.intersection(set(api.base_permissions))
|
|
124
|
+
|
|
125
|
+
return list(permissions)
|
|
141
126
|
|
|
142
127
|
return current_permissions_depedency
|
|
143
128
|
|
|
@@ -149,7 +134,7 @@ def has_access_dependency(
|
|
|
149
134
|
"""
|
|
150
135
|
A dependency for FastAPI to check whether current user has access to the specified API and permission.
|
|
151
136
|
|
|
152
|
-
Because it will implicitly
|
|
137
|
+
Because it will implicitly check whether the user is authenticated, it can return `401 Unauthorized` or `403 Forbidden`.
|
|
153
138
|
|
|
154
139
|
Usage:
|
|
155
140
|
```python
|
|
@@ -165,15 +150,55 @@ def has_access_dependency(
|
|
|
165
150
|
api (BaseApi): The API to be checked.
|
|
166
151
|
permission (str): The permission to check.
|
|
167
152
|
"""
|
|
153
|
+
permission = f"{PERMISSION_PREFIX}{permission}"
|
|
168
154
|
|
|
169
|
-
async def check_permission(
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
155
|
+
async def check_permission():
|
|
156
|
+
_ensure_roles(ErrorCode.PERMISSION_DENIED)
|
|
157
|
+
sm = g.current_app.sm
|
|
158
|
+
|
|
159
|
+
# First, check built-in roles (avoiding unnecessary DB queries)
|
|
160
|
+
# This also covers the case for API and permission name with pipes
|
|
161
|
+
if any(
|
|
162
|
+
sm.has_access_in_builtin_roles(
|
|
163
|
+
role.name, api.__class__.__name__, permission
|
|
164
|
+
)
|
|
165
|
+
for role in g.user.roles
|
|
166
|
+
):
|
|
167
|
+
logger.debug(
|
|
168
|
+
f"User {g.user} has access to {api.__class__.__name__} with permission {permission} via built-in roles."
|
|
169
|
+
)
|
|
170
|
+
return
|
|
171
|
+
|
|
172
|
+
db_role_ids = [
|
|
173
|
+
role.id for role in g.user.roles if role.name not in sm.builtin_roles
|
|
174
|
+
]
|
|
175
|
+
if not db_role_ids:
|
|
173
176
|
raise HTTPException(
|
|
174
177
|
fastapi.status.HTTP_403_FORBIDDEN, ErrorCode.PERMISSION_DENIED
|
|
175
178
|
)
|
|
176
|
-
|
|
179
|
+
|
|
180
|
+
api_name = api.__class__.__name__
|
|
181
|
+
stmt = (
|
|
182
|
+
sqlalchemy.select(sqlalchemy.literal(True))
|
|
183
|
+
.select_from(Permission)
|
|
184
|
+
.join(PermissionApi)
|
|
185
|
+
.join(PermissionApi.roles)
|
|
186
|
+
.join(Api)
|
|
187
|
+
.where(
|
|
188
|
+
Api.name == api_name,
|
|
189
|
+
Permission.name == permission,
|
|
190
|
+
Role.id.in_(db_role_ids),
|
|
191
|
+
)
|
|
192
|
+
.limit(1)
|
|
193
|
+
)
|
|
194
|
+
result = await smart_run(db.current_session.scalar, stmt)
|
|
195
|
+
result = bool(result)
|
|
196
|
+
if result:
|
|
197
|
+
return
|
|
198
|
+
|
|
199
|
+
raise HTTPException(
|
|
200
|
+
fastapi.status.HTTP_403_FORBIDDEN, ErrorCode.PERMISSION_DENIED
|
|
201
|
+
)
|
|
177
202
|
|
|
178
203
|
return check_permission
|
|
179
204
|
|
|
@@ -208,3 +233,24 @@ async def set_global_request(request: fastapi.Request):
|
|
|
208
233
|
...more code
|
|
209
234
|
"""
|
|
210
235
|
g.request = request
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _ensure_roles(err_forbidden_message=ErrorCode.GET_USER_NO_ROLES):
|
|
239
|
+
"""
|
|
240
|
+
A dependency for FastAPI that will ensure the current user has roles assigned.
|
|
241
|
+
|
|
242
|
+
Args:
|
|
243
|
+
err_forbidden_message (str): The error message to be used when raising `403 Forbidden`. Defaults to `ErrorCode.GET_USER_NO_ROLES`.
|
|
244
|
+
|
|
245
|
+
Raises:
|
|
246
|
+
HTTPException: Raised when the user is not authenticated.
|
|
247
|
+
HTTPException: Raised when the user has no roles assigned.
|
|
248
|
+
"""
|
|
249
|
+
if not g.user:
|
|
250
|
+
raise HTTPException(
|
|
251
|
+
fastapi.status.HTTP_401_UNAUTHORIZED,
|
|
252
|
+
ErrorCode.GET_USER_MISSING_TOKEN_OR_INACTIVE_USER,
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
if not g.user.roles:
|
|
256
|
+
raise HTTPException(fastapi.status.HTTP_403_FORBIDDEN, err_forbidden_message)
|
|
@@ -16,13 +16,11 @@ from fastapi.templating import Jinja2Templates
|
|
|
16
16
|
from fastapi_babel import BabelMiddleware
|
|
17
17
|
from jinja2 import Environment, TemplateNotFound, select_autoescape
|
|
18
18
|
from prometheus_fastapi_instrumentator import Instrumentator
|
|
19
|
-
from sqlalchemy import and_, select
|
|
20
|
-
from sqlalchemy.ext.asyncio import AsyncSession
|
|
21
|
-
from sqlalchemy.orm import Session
|
|
22
19
|
from starlette.routing import _DefaultLifespan
|
|
23
20
|
|
|
24
21
|
from .api.model_rest_api import ModelRestApi
|
|
25
22
|
from .auth import Auth
|
|
23
|
+
from .backends.sqla.session import SQLASession
|
|
26
24
|
from .cli.commands.db import upgrade
|
|
27
25
|
from .cli.commands.translate import init_babel_cli
|
|
28
26
|
from .const import (
|
|
@@ -56,7 +54,7 @@ from .security.sqla.apis import (
|
|
|
56
54
|
from .security.sqla.models import Api, Permission, PermissionApi, Role
|
|
57
55
|
from .security.sqla.security_manager import SecurityManager
|
|
58
56
|
from .setting import Setting
|
|
59
|
-
from .utils import deep_merge, multiple_async_contexts, safe_call, smart_run
|
|
57
|
+
from .utils import deep_merge, lazy, multiple_async_contexts, safe_call, smart_run
|
|
60
58
|
from .version import __version__
|
|
61
59
|
|
|
62
60
|
__all__ = ["FastAPIReactToolkit"]
|
|
@@ -163,7 +161,11 @@ class FastAPIReactToolkit:
|
|
|
163
161
|
# Public attributes
|
|
164
162
|
apis: list[ModelRestApi] = None
|
|
165
163
|
initialized = False
|
|
166
|
-
|
|
164
|
+
sm: SecurityManager = None
|
|
165
|
+
security: SecurityManager = lazy(lambda self: self.sm)
|
|
166
|
+
"""
|
|
167
|
+
Old attribute for `sm`, kept for backward compatibility.
|
|
168
|
+
"""
|
|
167
169
|
started = False
|
|
168
170
|
"""
|
|
169
171
|
Indicates whether the application has been started.
|
|
@@ -219,7 +221,7 @@ class FastAPIReactToolkit:
|
|
|
219
221
|
"""
|
|
220
222
|
g.current_app = self
|
|
221
223
|
self.apis = []
|
|
222
|
-
self.
|
|
224
|
+
self.sm = SecurityManager(self)
|
|
223
225
|
|
|
224
226
|
# Database configuration
|
|
225
227
|
self.create_tables = create_tables
|
|
@@ -418,175 +420,121 @@ class FastAPIReactToolkit:
|
|
|
418
420
|
|
|
419
421
|
async with db.session() as session:
|
|
420
422
|
logger.info("INITIALIZING DATABASE")
|
|
421
|
-
await self._insert_permissions(session)
|
|
422
|
-
await self._insert_apis(session)
|
|
423
|
-
await self._insert_roles(session)
|
|
424
|
-
|
|
425
|
-
|
|
423
|
+
permissions = await self._insert_permissions(session)
|
|
424
|
+
apis = await self._insert_apis(session)
|
|
425
|
+
roles = await self._insert_roles(session)
|
|
426
|
+
assert permissions is not None, "Permissions should not be None"
|
|
427
|
+
assert apis is not None, "APIs should not be None"
|
|
428
|
+
permission_apis = await self._associate_permission_with_api(
|
|
429
|
+
session, permissions, apis
|
|
430
|
+
)
|
|
431
|
+
assert roles is not None, "Roles should not be None"
|
|
432
|
+
assert permission_apis is not None, "PermissionApis should not be None"
|
|
433
|
+
await self._associate_role_with_permission_api(
|
|
434
|
+
session, roles, permission_apis
|
|
435
|
+
)
|
|
426
436
|
if self.cleanup:
|
|
427
|
-
await self.
|
|
437
|
+
await self.sm.cleanup()
|
|
428
438
|
logger.info("DATABASE INITIALIZED")
|
|
429
439
|
|
|
430
|
-
async def _insert_permissions(self, session:
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
result = await safe_call(session.scalars(stmt))
|
|
434
|
-
existing_permissions = [permission.name for permission in result.all()]
|
|
435
|
-
if len(new_permissions) == len(existing_permissions):
|
|
436
|
-
return
|
|
437
|
-
|
|
438
|
-
permission_objs = [
|
|
439
|
-
Permission(name=permission)
|
|
440
|
-
for permission in new_permissions
|
|
441
|
-
if permission not in existing_permissions
|
|
440
|
+
async def _insert_permissions(self, session: SQLASession):
|
|
441
|
+
permissions = self.total_permissions() + [
|
|
442
|
+
x[1] for x in self.sm.get_api_permission_tuples_from_builtin_roles()
|
|
442
443
|
]
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
session.add(permission)
|
|
446
|
-
await safe_call(session.commit())
|
|
447
|
-
|
|
448
|
-
async def _insert_apis(self, session: AsyncSession | Session):
|
|
449
|
-
new_apis = [api.__class__.__name__ for api in self.apis]
|
|
450
|
-
stmt = select(Api).where(Api.name.in_(new_apis))
|
|
451
|
-
result = await safe_call(session.scalars(stmt))
|
|
452
|
-
existing_apis = [api.name for api in result.all()]
|
|
453
|
-
if len(new_apis) == len(existing_apis):
|
|
454
|
-
return
|
|
444
|
+
permissions = list(dict.fromkeys(permissions))
|
|
445
|
+
return await self.sm.create_permissions(permissions, session=session)
|
|
455
446
|
|
|
456
|
-
|
|
457
|
-
for api in
|
|
458
|
-
|
|
459
|
-
session.add(api)
|
|
460
|
-
await safe_call(session.commit())
|
|
461
|
-
|
|
462
|
-
async def _insert_roles(self, session: AsyncSession | Session):
|
|
463
|
-
new_roles = [x for x in [g.admin_role, g.public_role] if x is not None]
|
|
464
|
-
stmt = select(Role).where(Role.name.in_(new_roles))
|
|
465
|
-
result = await safe_call(session.scalars(stmt))
|
|
466
|
-
existing_roles = [role.name for role in result.all()]
|
|
467
|
-
if len(new_roles) == len(existing_roles):
|
|
468
|
-
return
|
|
469
|
-
|
|
470
|
-
role_objs = [
|
|
471
|
-
Role(name=role) for role in new_roles if role not in existing_roles
|
|
447
|
+
async def _insert_apis(self, session: SQLASession):
|
|
448
|
+
apis = [api.__class__.__name__ for api in self.apis] + [
|
|
449
|
+
x[0] for x in self.sm.get_api_permission_tuples_from_builtin_roles()
|
|
472
450
|
]
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
451
|
+
apis = list(dict.fromkeys(apis))
|
|
452
|
+
return await self.sm.create_apis(apis, session=session)
|
|
453
|
+
|
|
454
|
+
async def _insert_roles(self, session: SQLASession):
|
|
455
|
+
roles = self.sm.get_roles_from_builtin_roles()
|
|
456
|
+
if g.admin_role and g.admin_role not in roles:
|
|
457
|
+
roles.append(g.admin_role)
|
|
458
|
+
if g.public_role and g.public_role not in roles:
|
|
459
|
+
roles.append(g.public_role)
|
|
460
|
+
return await self.sm.create_roles(roles, session=session)
|
|
461
|
+
|
|
462
|
+
async def _associate_permission_with_api(
|
|
463
|
+
self,
|
|
464
|
+
session: SQLASession,
|
|
465
|
+
permissions: list[Permission],
|
|
466
|
+
apis: list[Api],
|
|
467
|
+
):
|
|
468
|
+
permission_map = {permission.name: permission for permission in permissions}
|
|
469
|
+
api_map = {api.name: api for api in apis}
|
|
470
|
+
permission_api_tuples = list[tuple[Permission, Api]]()
|
|
471
|
+
added_permission_api = set[tuple[str, str]]()
|
|
477
472
|
|
|
478
|
-
async def _associate_permission_with_api(self, session: AsyncSession | Session):
|
|
479
473
|
for api in self.apis:
|
|
480
|
-
|
|
481
|
-
if not
|
|
474
|
+
api_permissions = api.permissions
|
|
475
|
+
if not api_permissions:
|
|
482
476
|
continue
|
|
483
477
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
)
|
|
497
|
-
permission_result = await safe_call(session.scalars(permission_stmt))
|
|
498
|
-
new_permissions = permission_result.all()
|
|
499
|
-
|
|
500
|
-
if not new_permissions:
|
|
478
|
+
for permission_name in api_permissions:
|
|
479
|
+
if (permission_name, api.__class__.__name__) in added_permission_api:
|
|
480
|
+
continue
|
|
481
|
+
permission = permission_map[permission_name]
|
|
482
|
+
api_obj = api_map[api.__class__.__name__]
|
|
483
|
+
permission_api_tuples.append((permission, api_obj))
|
|
484
|
+
added_permission_api.add((permission.name, api_obj.name))
|
|
485
|
+
|
|
486
|
+
for (
|
|
487
|
+
api_name,
|
|
488
|
+
perm_name,
|
|
489
|
+
) in self.sm.get_api_permission_tuples_from_builtin_roles():
|
|
490
|
+
if (perm_name, api_name) in added_permission_api:
|
|
501
491
|
continue
|
|
492
|
+
permission = permission_map[perm_name]
|
|
493
|
+
api_obj = api_map[api_name]
|
|
494
|
+
permission_api_tuples.append((permission, api_obj))
|
|
495
|
+
added_permission_api.add((permission.name, api_obj.name))
|
|
502
496
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
)
|
|
507
|
-
logger.info(f"ASSOCIATING PERMISSION {permission} WITH API {api_obj}")
|
|
508
|
-
await safe_call(session.commit())
|
|
497
|
+
return await self.sm.associate_list_of_permission_with_api(
|
|
498
|
+
permission_api_tuples, session=session
|
|
499
|
+
)
|
|
509
500
|
|
|
510
|
-
async def
|
|
511
|
-
self,
|
|
501
|
+
async def _associate_role_with_permission_api(
|
|
502
|
+
self,
|
|
503
|
+
session: SQLASession,
|
|
504
|
+
roles: list[Role],
|
|
505
|
+
permission_apis: list[PermissionApi],
|
|
512
506
|
):
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
)
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
stmt = select(Api).where(Api.name == api_name)
|
|
546
|
-
api = await safe_call(session.scalar(stmt))
|
|
547
|
-
if not api:
|
|
548
|
-
api = Api(name=api_name)
|
|
549
|
-
logger.info(f"ADDING API {api}")
|
|
550
|
-
session.add(api)
|
|
551
|
-
|
|
552
|
-
permission_api = PermissionApi(permission=permission, api=api)
|
|
553
|
-
logger.info(f"ADDING PERMISSION-API {permission_api}")
|
|
554
|
-
session.add(permission_api)
|
|
555
|
-
|
|
556
|
-
# Associate role with permission-api
|
|
557
|
-
if role not in permission_api.roles:
|
|
558
|
-
permission_api.roles.append(role)
|
|
559
|
-
logger.info(
|
|
560
|
-
f"ASSOCIATING {role} WITH PERMISSION-API {permission_api}"
|
|
561
|
-
)
|
|
562
|
-
|
|
563
|
-
await safe_call(session.commit())
|
|
564
|
-
|
|
565
|
-
# Get admin role
|
|
566
|
-
if g.admin_role is None:
|
|
567
|
-
logger.warning("Admin role is not set, skipping admin role association")
|
|
568
|
-
return
|
|
569
|
-
|
|
570
|
-
admin_role_stmt = select(Role).where(Role.name == g.admin_role)
|
|
571
|
-
admin_role = await safe_call(session.scalar(admin_role_stmt))
|
|
572
|
-
|
|
573
|
-
if admin_role:
|
|
574
|
-
# Get list of permission-api.assoc_permission_api_id of the admin role
|
|
575
|
-
stmt = (
|
|
576
|
-
select(PermissionApi)
|
|
577
|
-
.where(~PermissionApi.roles.contains(admin_role))
|
|
578
|
-
.join(Api)
|
|
579
|
-
)
|
|
580
|
-
result = await safe_call(session.scalars(stmt))
|
|
581
|
-
existing_assoc_permission_api_roles = result.all()
|
|
582
|
-
|
|
583
|
-
# Add admin role to all permission-api objects
|
|
584
|
-
for permission_api in existing_assoc_permission_api_roles:
|
|
585
|
-
permission_api.roles.append(admin_role)
|
|
586
|
-
logger.info(
|
|
587
|
-
f"ASSOCIATING {admin_role} WITH PERMISSION-API {permission_api}"
|
|
588
|
-
)
|
|
589
|
-
await safe_call(session.commit())
|
|
507
|
+
role_map = {role.name: role for role in roles}
|
|
508
|
+
permission_api_map = {
|
|
509
|
+
(pa.permission.name, pa.api.name): pa for pa in permission_apis
|
|
510
|
+
}
|
|
511
|
+
permission_apis_to_exclude_from_admin = list[PermissionApi]()
|
|
512
|
+
role_permission_api_tuples = list[tuple[Role, PermissionApi]]()
|
|
513
|
+
|
|
514
|
+
for (
|
|
515
|
+
role_name,
|
|
516
|
+
role_api_permissions,
|
|
517
|
+
) in self.sm.get_role_and_api_permission_tuples_from_builtin_roles():
|
|
518
|
+
for api_name, permission_name in role_api_permissions:
|
|
519
|
+
role = role_map[role_name]
|
|
520
|
+
permission_api = permission_api_map[(permission_name, api_name)]
|
|
521
|
+
role_permission_api_tuples.append((role, permission_api))
|
|
522
|
+
if "|" in permission_name and role_name != g.public_role:
|
|
523
|
+
# Exclude multi-tenant permissions from admin role if not explicitly set
|
|
524
|
+
permission_apis_to_exclude_from_admin.append(permission_api)
|
|
525
|
+
|
|
526
|
+
if g.admin_role:
|
|
527
|
+
admin_role = role_map[g.admin_role]
|
|
528
|
+
for permission_api in [
|
|
529
|
+
pa
|
|
530
|
+
for pa in permission_apis
|
|
531
|
+
if pa not in permission_apis_to_exclude_from_admin
|
|
532
|
+
]:
|
|
533
|
+
role_permission_api_tuples.append((admin_role, permission_api))
|
|
534
|
+
|
|
535
|
+
await self.sm.associate_list_of_role_with_permission_api(
|
|
536
|
+
role_permission_api_tuples, session=session
|
|
537
|
+
)
|
|
590
538
|
|
|
591
539
|
def _mount_static_folder(self):
|
|
592
540
|
"""
|