fastapi-rtk 1.0.8__tar.gz → 1.0.10__tar.gz
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-1.0.8 → fastapi_rtk-1.0.10}/PKG-INFO +1 -1
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/__init__.py +0 -1
- fastapi_rtk-1.0.10/fastapi_rtk/_version.py +1 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/auth/auth.py +0 -9
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/bases/file_manager.py +12 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/cli/commands/security.py +6 -6
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/db.py +1 -0
- fastapi_rtk-1.0.10/fastapi_rtk/dependencies.py +256 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/fastapi_react_toolkit.py +109 -161
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/file_managers/s3_file_manager.py +63 -32
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/security/sqla/apis.py +20 -5
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/security/sqla/models.py +8 -23
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/security/sqla/security_manager.py +367 -10
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/utils/hooks.py +7 -4
- fastapi_rtk-1.0.8/fastapi_rtk/_version.py +0 -1
- fastapi_rtk-1.0.8/fastapi_rtk/dependencies.py +0 -210
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/.gitignore +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/LICENSE +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/README.md +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/api/__init__.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/api/base_api.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/api/model_rest_api.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/apis.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/auth/__init__.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/auth/hashers/__init__.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/auth/hashers/pbkdf2.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/auth/hashers/scrypt.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/auth/hashers/utils.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/auth/password_helpers/__init__.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/auth/password_helpers/fab.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/auth/strategies/__init__.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/auth/strategies/config.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/auth/strategies/db.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/auth/strategies/jwt.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/backends/__init__.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/backends/generic/__init__.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/backends/generic/column.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/backends/generic/db.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/backends/generic/exceptions.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/backends/generic/filters.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/backends/generic/interface.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/backends/generic/model.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/backends/generic/session.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/backends/sqla/__init__.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/backends/sqla/column.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/backends/sqla/db.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/backends/sqla/exceptions.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/backends/sqla/extensions/__init__.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/backends/sqla/extensions/audit/__init__.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/backends/sqla/extensions/audit/audit.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/backends/sqla/extensions/audit/types.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/__init__.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/filters.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/geometry_converter.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/backends/sqla/filters.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/backends/sqla/interface.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/backends/sqla/model.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/backends/sqla/session.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/bases/__init__.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/bases/db.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/bases/filter.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/bases/interface.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/bases/model.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/bases/session.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/cli/__init__.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/cli/cli.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/cli/commands/__init__.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/cli/commands/db/__init__.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/cli/commands/db/templates/fastapi/README +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/cli/commands/db/templates/fastapi/alembic.ini.mako +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/cli/commands/db/templates/fastapi/env.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/cli/commands/db/templates/fastapi/script.py.mako +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/README +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/alembic.ini.mako +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/env.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/script.py.mako +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/cli/commands/export.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/cli/commands/translate.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/cli/const.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/cli/decorators.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/cli/types.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/cli/utils.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/config.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/const.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/decorators.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/exceptions.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/file_managers/__init__.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/file_managers/file_manager.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/file_managers/image_manager.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/file_managers/s3_image_manager.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/filters.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/globals.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/lang/__init__.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/lang/babel/__init__.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/lang/babel/cli.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/lang/babel/config.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/lang/babel.cfg +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/lang/lazy_text.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/lang/messages.pot +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.mo +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.po +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.mo +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.po +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/manager.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/middlewares.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/mixins.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/models.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/routers.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/schemas.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/security/__init__.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/security/sqla/__init__.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/setting.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/types.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/utils/__init__.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/utils/async_task_runner.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/utils/class_factory.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/utils/csv_json_converter.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/utils/deep_merge.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/utils/extender_mixin.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/utils/flask_appbuilder_utils.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/utils/lazy.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/utils/merge_schema.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/utils/multiple_async_contexts.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/utils/prettify_dict.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/utils/pydantic.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/utils/run_utils.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/utils/self_dependencies.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/utils/smartdefaultdict.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/utils/sqla.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/utils/timezone.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/utils/update_signature.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/utils/use_default_when_none.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/utils/werkzeug.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/fastapi_rtk/version.py +0 -0
- {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.10}/pyproject.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastapi-rtk
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.10
|
|
4
4
|
Summary: A package that provides a set of tools to build a FastAPI application with a Class-Based CRUD API.
|
|
5
5
|
Project-URL: Homepage, https://codeberg.org/datatactics/fastapi-rtk
|
|
6
6
|
Project-URL: Issues, https://codeberg.org/datatactics/fastapi-rtk/issues
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.0.10"
|
|
@@ -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()
|
|
@@ -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)
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import fastapi
|
|
2
|
+
import sqlalchemy
|
|
3
|
+
from fastapi import Depends, HTTPException
|
|
4
|
+
|
|
5
|
+
from .api.base_api import BaseApi
|
|
6
|
+
from .const import PERMISSION_PREFIX, ErrorCode, logger
|
|
7
|
+
from .db import db
|
|
8
|
+
from .globals import g
|
|
9
|
+
from .security.sqla.models import Api, Permission, PermissionApi, Role, User
|
|
10
|
+
from .utils import smart_run
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"set_global_user",
|
|
14
|
+
"current_permissions",
|
|
15
|
+
"has_access_dependency",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def set_global_user():
|
|
20
|
+
"""
|
|
21
|
+
A dependency for FastAPI that will set the current user to the global variable `g.user`.
|
|
22
|
+
|
|
23
|
+
Usage:
|
|
24
|
+
```python
|
|
25
|
+
async def get_info(
|
|
26
|
+
*,
|
|
27
|
+
session: AsyncSession | Session = Depends(get_async_session),
|
|
28
|
+
none: None = Depends(set_global_user()),
|
|
29
|
+
):
|
|
30
|
+
...more code
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
async def set_global_user_dependency(
|
|
34
|
+
user: User | None = Depends(
|
|
35
|
+
g.auth.fastapi_users.current_user(active=True, default_to_none=True)
|
|
36
|
+
),
|
|
37
|
+
):
|
|
38
|
+
g.user = user
|
|
39
|
+
|
|
40
|
+
return set_global_user_dependency
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def check_g_user():
|
|
44
|
+
"""
|
|
45
|
+
A dependency for FastAPI that will check if the current user is set to the global variable `g.user`.
|
|
46
|
+
|
|
47
|
+
Usage:
|
|
48
|
+
```python
|
|
49
|
+
async def get_info(
|
|
50
|
+
*,
|
|
51
|
+
session: AsyncSession | Session = Depends(get_async_session),
|
|
52
|
+
none: None = Depends(check_g_user()),
|
|
53
|
+
):
|
|
54
|
+
...more code
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
async def check_g_user_dependency():
|
|
58
|
+
if not g.user:
|
|
59
|
+
raise HTTPException(
|
|
60
|
+
fastapi.status.HTTP_401_UNAUTHORIZED,
|
|
61
|
+
ErrorCode.GET_USER_MISSING_TOKEN_OR_INACTIVE_USER,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
return check_g_user_dependency
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def current_permissions(api: BaseApi):
|
|
68
|
+
"""
|
|
69
|
+
A dependency for FastAPI that will return all permissions of the current user for the specified API.
|
|
70
|
+
|
|
71
|
+
Because it will implicitly check whether the user is authenticated, it can return `401 Unauthorized` or `403 Forbidden`.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
api (BaseApi): The API to be checked.
|
|
75
|
+
|
|
76
|
+
Usage:
|
|
77
|
+
```python
|
|
78
|
+
async def get_info(
|
|
79
|
+
*,
|
|
80
|
+
permissions: List[str] = Depends(current_permissions(self)),
|
|
81
|
+
session: AsyncSession | Session = Depends(get_async_session),
|
|
82
|
+
):
|
|
83
|
+
...more code
|
|
84
|
+
```
|
|
85
|
+
"""
|
|
86
|
+
|
|
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]()
|
|
92
|
+
|
|
93
|
+
# Retrieve permissions from built-in roles
|
|
94
|
+
for role in g.user.roles:
|
|
95
|
+
if role.name not in sm.builtin_roles:
|
|
96
|
+
db_role_ids.append(role.id)
|
|
97
|
+
continue
|
|
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))
|
|
118
|
+
|
|
119
|
+
permissions_in_db = await smart_run(db.current_session.scalars, query)
|
|
120
|
+
permissions.update(perm.name for perm in permissions_in_db.all())
|
|
121
|
+
|
|
122
|
+
if api.base_permissions:
|
|
123
|
+
permissions = permissions.intersection(set(api.base_permissions))
|
|
124
|
+
|
|
125
|
+
return list(permissions)
|
|
126
|
+
|
|
127
|
+
return current_permissions_depedency
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def has_access_dependency(
|
|
131
|
+
api: BaseApi,
|
|
132
|
+
permission: str,
|
|
133
|
+
):
|
|
134
|
+
"""
|
|
135
|
+
A dependency for FastAPI to check whether current user has access to the specified API and permission.
|
|
136
|
+
|
|
137
|
+
Because it will implicitly check whether the user is authenticated, it can return `401 Unauthorized` or `403 Forbidden`.
|
|
138
|
+
|
|
139
|
+
Usage:
|
|
140
|
+
```python
|
|
141
|
+
@self.router.get(
|
|
142
|
+
"/_info",
|
|
143
|
+
response_model=self.info_return_schema,
|
|
144
|
+
dependencies=[Depends(has_access(self, "info"))],
|
|
145
|
+
)
|
|
146
|
+
...more code
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
api (BaseApi): The API to be checked.
|
|
151
|
+
permission (str): The permission to check.
|
|
152
|
+
"""
|
|
153
|
+
permission = f"{PERMISSION_PREFIX}{permission}"
|
|
154
|
+
|
|
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:
|
|
176
|
+
raise HTTPException(
|
|
177
|
+
fastapi.status.HTTP_403_FORBIDDEN, ErrorCode.PERMISSION_DENIED
|
|
178
|
+
)
|
|
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
|
+
)
|
|
202
|
+
|
|
203
|
+
return check_permission
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
async def set_global_background_tasks(background_tasks: fastapi.BackgroundTasks):
|
|
207
|
+
"""
|
|
208
|
+
A dependency for FastAPI that will set the `background_tasks` to the global variable `g.background_tasks`.
|
|
209
|
+
|
|
210
|
+
Usage:
|
|
211
|
+
```python
|
|
212
|
+
async def get_info(
|
|
213
|
+
*,
|
|
214
|
+
session: AsyncSession | Session = Depends(get_async_session),
|
|
215
|
+
none: None = Depends(set_global_background_tasks),
|
|
216
|
+
):
|
|
217
|
+
...more code
|
|
218
|
+
"""
|
|
219
|
+
g.background_tasks = background_tasks
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
async def set_global_request(request: fastapi.Request):
|
|
223
|
+
"""
|
|
224
|
+
A dependency for FastAPI that will set the `request` to the global variable `g.request`.
|
|
225
|
+
|
|
226
|
+
Usage:
|
|
227
|
+
```python
|
|
228
|
+
async def get_info(
|
|
229
|
+
*,
|
|
230
|
+
session: AsyncSession | Session = Depends(get_async_session),
|
|
231
|
+
none: None = Depends(set_global_request),
|
|
232
|
+
):
|
|
233
|
+
...more code
|
|
234
|
+
"""
|
|
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)
|