fractal-server 2.16.6__py3-none-any.whl → 2.17.0__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.
- fractal_server/__init__.py +1 -1
- fractal_server/__main__.py +178 -52
- fractal_server/app/db/__init__.py +9 -11
- fractal_server/app/models/security.py +30 -22
- fractal_server/app/models/user_settings.py +5 -4
- fractal_server/app/models/v2/__init__.py +4 -0
- fractal_server/app/models/v2/profile.py +16 -0
- fractal_server/app/models/v2/project.py +5 -0
- fractal_server/app/models/v2/resource.py +130 -0
- fractal_server/app/models/v2/task_group.py +4 -0
- fractal_server/app/routes/admin/v2/__init__.py +4 -0
- fractal_server/app/routes/admin/v2/_aux_functions.py +55 -0
- fractal_server/app/routes/admin/v2/accounting.py +3 -3
- fractal_server/app/routes/admin/v2/impersonate.py +2 -2
- fractal_server/app/routes/admin/v2/job.py +51 -15
- fractal_server/app/routes/admin/v2/profile.py +100 -0
- fractal_server/app/routes/admin/v2/project.py +2 -2
- fractal_server/app/routes/admin/v2/resource.py +222 -0
- fractal_server/app/routes/admin/v2/task.py +59 -32
- fractal_server/app/routes/admin/v2/task_group.py +17 -12
- fractal_server/app/routes/admin/v2/task_group_lifecycle.py +52 -86
- fractal_server/app/routes/api/__init__.py +45 -8
- fractal_server/app/routes/api/v2/_aux_functions.py +17 -1
- fractal_server/app/routes/api/v2/_aux_functions_history.py +2 -2
- fractal_server/app/routes/api/v2/_aux_functions_task_lifecycle.py +3 -3
- fractal_server/app/routes/api/v2/_aux_functions_tasks.py +55 -19
- fractal_server/app/routes/api/v2/_aux_task_group_disambiguation.py +21 -17
- fractal_server/app/routes/api/v2/dataset.py +10 -19
- fractal_server/app/routes/api/v2/history.py +8 -8
- fractal_server/app/routes/api/v2/images.py +5 -5
- fractal_server/app/routes/api/v2/job.py +8 -8
- fractal_server/app/routes/api/v2/pre_submission_checks.py +3 -3
- fractal_server/app/routes/api/v2/project.py +15 -7
- fractal_server/app/routes/api/v2/status_legacy.py +2 -2
- fractal_server/app/routes/api/v2/submit.py +49 -42
- fractal_server/app/routes/api/v2/task.py +26 -8
- fractal_server/app/routes/api/v2/task_collection.py +39 -50
- fractal_server/app/routes/api/v2/task_collection_custom.py +10 -6
- fractal_server/app/routes/api/v2/task_collection_pixi.py +34 -42
- fractal_server/app/routes/api/v2/task_group.py +19 -9
- fractal_server/app/routes/api/v2/task_group_lifecycle.py +43 -86
- fractal_server/app/routes/api/v2/task_version_update.py +3 -3
- fractal_server/app/routes/api/v2/workflow.py +9 -9
- fractal_server/app/routes/api/v2/workflow_import.py +25 -13
- fractal_server/app/routes/api/v2/workflowtask.py +5 -5
- fractal_server/app/routes/auth/__init__.py +34 -5
- fractal_server/app/routes/auth/_aux_auth.py +39 -20
- fractal_server/app/routes/auth/current_user.py +56 -67
- fractal_server/app/routes/auth/group.py +29 -46
- fractal_server/app/routes/auth/oauth.py +55 -38
- fractal_server/app/routes/auth/register.py +2 -2
- fractal_server/app/routes/auth/router.py +4 -2
- fractal_server/app/routes/auth/users.py +29 -53
- fractal_server/app/routes/aux/_runner.py +2 -1
- fractal_server/app/routes/aux/validate_user_profile.py +62 -0
- fractal_server/app/schemas/__init__.py +0 -1
- fractal_server/app/schemas/user.py +43 -13
- fractal_server/app/schemas/user_group.py +2 -1
- fractal_server/app/schemas/v2/__init__.py +12 -0
- fractal_server/app/schemas/v2/profile.py +78 -0
- fractal_server/app/schemas/v2/resource.py +137 -0
- fractal_server/app/schemas/v2/task_collection.py +11 -3
- fractal_server/app/schemas/v2/task_group.py +5 -0
- fractal_server/app/security/__init__.py +174 -75
- fractal_server/app/security/signup_email.py +52 -34
- fractal_server/config/__init__.py +27 -0
- fractal_server/config/_data.py +68 -0
- fractal_server/config/_database.py +59 -0
- fractal_server/config/_email.py +133 -0
- fractal_server/config/_main.py +78 -0
- fractal_server/config/_oauth.py +69 -0
- fractal_server/config/_settings_config.py +7 -0
- fractal_server/data_migrations/2_17_0.py +339 -0
- fractal_server/images/tools.py +3 -3
- fractal_server/logger.py +3 -3
- fractal_server/main.py +17 -23
- fractal_server/migrations/naming_convention.py +1 -1
- fractal_server/migrations/versions/83bc2ad3ffcc_2_17_0.py +195 -0
- fractal_server/runner/config/__init__.py +2 -0
- fractal_server/runner/config/_local.py +21 -0
- fractal_server/runner/config/_slurm.py +129 -0
- fractal_server/runner/config/slurm_mem_to_MB.py +63 -0
- fractal_server/runner/exceptions.py +4 -0
- fractal_server/runner/executors/base_runner.py +17 -7
- fractal_server/runner/executors/local/get_local_config.py +21 -86
- fractal_server/runner/executors/local/runner.py +48 -5
- fractal_server/runner/executors/slurm_common/_batching.py +2 -2
- fractal_server/runner/executors/slurm_common/base_slurm_runner.py +60 -26
- fractal_server/runner/executors/slurm_common/get_slurm_config.py +39 -55
- fractal_server/runner/executors/slurm_common/remote.py +1 -1
- fractal_server/runner/executors/slurm_common/slurm_config.py +214 -0
- fractal_server/runner/executors/slurm_common/slurm_job_task_models.py +1 -1
- fractal_server/runner/executors/slurm_ssh/runner.py +12 -14
- fractal_server/runner/executors/slurm_sudo/_subprocess_run_as_user.py +2 -2
- fractal_server/runner/executors/slurm_sudo/runner.py +12 -12
- fractal_server/runner/v2/_local.py +36 -21
- fractal_server/runner/v2/_slurm_ssh.py +41 -4
- fractal_server/runner/v2/_slurm_sudo.py +42 -12
- fractal_server/runner/v2/db_tools.py +1 -1
- fractal_server/runner/v2/runner.py +3 -11
- fractal_server/runner/v2/runner_functions.py +42 -28
- fractal_server/runner/v2/submit_workflow.py +88 -109
- fractal_server/runner/versions.py +8 -3
- fractal_server/ssh/_fabric.py +6 -6
- fractal_server/tasks/config/__init__.py +3 -0
- fractal_server/tasks/config/_pixi.py +127 -0
- fractal_server/tasks/config/_python.py +51 -0
- fractal_server/tasks/v2/local/_utils.py +7 -7
- fractal_server/tasks/v2/local/collect.py +13 -5
- fractal_server/tasks/v2/local/collect_pixi.py +26 -10
- fractal_server/tasks/v2/local/deactivate.py +7 -1
- fractal_server/tasks/v2/local/deactivate_pixi.py +5 -1
- fractal_server/tasks/v2/local/delete.py +5 -1
- fractal_server/tasks/v2/local/reactivate.py +13 -5
- fractal_server/tasks/v2/local/reactivate_pixi.py +27 -9
- fractal_server/tasks/v2/ssh/_pixi_slurm_ssh.py +11 -10
- fractal_server/tasks/v2/ssh/_utils.py +6 -7
- fractal_server/tasks/v2/ssh/collect.py +19 -12
- fractal_server/tasks/v2/ssh/collect_pixi.py +34 -16
- fractal_server/tasks/v2/ssh/deactivate.py +12 -8
- fractal_server/tasks/v2/ssh/deactivate_pixi.py +14 -10
- fractal_server/tasks/v2/ssh/delete.py +12 -9
- fractal_server/tasks/v2/ssh/reactivate.py +18 -12
- fractal_server/tasks/v2/ssh/reactivate_pixi.py +36 -17
- fractal_server/tasks/v2/templates/4_pip_show.sh +4 -6
- fractal_server/tasks/v2/utils_database.py +2 -2
- fractal_server/tasks/v2/utils_pixi.py +3 -0
- fractal_server/tasks/v2/utils_python_interpreter.py +8 -16
- fractal_server/tasks/v2/utils_templates.py +7 -10
- fractal_server/utils.py +1 -1
- {fractal_server-2.16.6.dist-info → fractal_server-2.17.0.dist-info}/METADATA +4 -6
- {fractal_server-2.16.6.dist-info → fractal_server-2.17.0.dist-info}/RECORD +136 -117
- fractal_server/app/routes/aux/validate_user_settings.py +0 -73
- fractal_server/app/schemas/user_settings.py +0 -67
- fractal_server/app/user_settings.py +0 -42
- fractal_server/config.py +0 -906
- fractal_server/data_migrations/2_14_10.py +0 -48
- fractal_server/runner/executors/slurm_common/_slurm_config.py +0 -471
- /fractal_server/{runner → app}/shutdown.py +0 -0
- {fractal_server-2.16.6.dist-info → fractal_server-2.17.0.dist-info}/WHEEL +0 -0
- {fractal_server-2.16.6.dist-info → fractal_server-2.17.0.dist-info}/entry_points.txt +0 -0
- {fractal_server-2.16.6.dist-info → fractal_server-2.17.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -15,7 +15,7 @@ from ._aux_functions import _workflow_insert_task
|
|
|
15
15
|
from ._aux_functions_tasks import _check_type_filters_compatibility
|
|
16
16
|
from ._aux_functions_tasks import _get_task_read_access
|
|
17
17
|
from fractal_server.app.models import UserOAuth
|
|
18
|
-
from fractal_server.app.routes.auth import
|
|
18
|
+
from fractal_server.app.routes.auth import current_user_act_ver_prof
|
|
19
19
|
from fractal_server.app.schemas.v2 import TaskType
|
|
20
20
|
from fractal_server.app.schemas.v2 import WorkflowTaskCreateV2
|
|
21
21
|
from fractal_server.app.schemas.v2 import WorkflowTaskReadV2
|
|
@@ -34,7 +34,7 @@ async def create_workflowtask(
|
|
|
34
34
|
workflow_id: int,
|
|
35
35
|
task_id: int,
|
|
36
36
|
wftask: WorkflowTaskCreateV2,
|
|
37
|
-
user: UserOAuth = Depends(
|
|
37
|
+
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
38
38
|
db: AsyncSession = Depends(get_async_db),
|
|
39
39
|
) -> WorkflowTaskReadV2 | None:
|
|
40
40
|
"""
|
|
@@ -103,7 +103,7 @@ async def read_workflowtask(
|
|
|
103
103
|
project_id: int,
|
|
104
104
|
workflow_id: int,
|
|
105
105
|
workflow_task_id: int,
|
|
106
|
-
user: UserOAuth = Depends(
|
|
106
|
+
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
107
107
|
db: AsyncSession = Depends(get_async_db),
|
|
108
108
|
):
|
|
109
109
|
workflow_task, _ = await _get_workflow_task_check_owner(
|
|
@@ -125,7 +125,7 @@ async def update_workflowtask(
|
|
|
125
125
|
workflow_id: int,
|
|
126
126
|
workflow_task_id: int,
|
|
127
127
|
workflow_task_update: WorkflowTaskUpdateV2,
|
|
128
|
-
user: UserOAuth = Depends(
|
|
128
|
+
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
129
129
|
db: AsyncSession = Depends(get_async_db),
|
|
130
130
|
) -> WorkflowTaskReadV2 | None:
|
|
131
131
|
"""
|
|
@@ -210,7 +210,7 @@ async def delete_workflowtask(
|
|
|
210
210
|
project_id: int,
|
|
211
211
|
workflow_id: int,
|
|
212
212
|
workflow_task_id: int,
|
|
213
|
-
user: UserOAuth = Depends(
|
|
213
|
+
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
214
214
|
db: AsyncSession = Depends(get_async_db),
|
|
215
215
|
) -> Response:
|
|
216
216
|
"""
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
from fastapi import Depends
|
|
2
|
+
from fastapi import HTTPException
|
|
3
|
+
from fastapi import status
|
|
1
4
|
from fastapi_users import FastAPIUsers
|
|
2
5
|
from fastapi_users.authentication import AuthenticationBackend
|
|
3
6
|
from fastapi_users.authentication import BearerTransport
|
|
@@ -46,10 +49,36 @@ fastapi_users = FastAPIUsers[UserOAuth, int](
|
|
|
46
49
|
get_user_manager,
|
|
47
50
|
[token_backend, cookie_backend],
|
|
48
51
|
)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
+
|
|
53
|
+
# Current-user dependencies
|
|
54
|
+
current_user_act = fastapi_users.current_user(active=True)
|
|
55
|
+
current_user_act_ver = fastapi_users.current_user(
|
|
56
|
+
active=True,
|
|
57
|
+
verified=True,
|
|
52
58
|
)
|
|
53
|
-
|
|
54
|
-
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
async def current_user_act_ver_prof(
|
|
62
|
+
user: UserOAuth = Depends(current_user_act_ver),
|
|
63
|
+
) -> UserOAuth:
|
|
64
|
+
"""
|
|
65
|
+
Require a active&verified user, with a non-null `profile_id`.
|
|
66
|
+
|
|
67
|
+
Raises 401 if user does not exist or is not active.
|
|
68
|
+
Raises 403 if user is not verified or has null `profile_id`.
|
|
69
|
+
"""
|
|
70
|
+
if user.profile_id is None:
|
|
71
|
+
raise HTTPException(
|
|
72
|
+
status_code=status.HTTP_403_FORBIDDEN,
|
|
73
|
+
detail=(
|
|
74
|
+
f"Forbidden access "
|
|
75
|
+
f"({user.is_verified=} {user.profile_id=})."
|
|
76
|
+
),
|
|
77
|
+
)
|
|
78
|
+
return user
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
current_superuser_act = fastapi_users.current_user(
|
|
82
|
+
active=True,
|
|
83
|
+
superuser=True,
|
|
55
84
|
)
|
|
@@ -9,8 +9,10 @@ from fractal_server.app.models.security import UserGroup
|
|
|
9
9
|
from fractal_server.app.models.security import UserOAuth
|
|
10
10
|
from fractal_server.app.schemas.user import UserRead
|
|
11
11
|
from fractal_server.app.schemas.user_group import UserGroupRead
|
|
12
|
-
from fractal_server.
|
|
12
|
+
from fractal_server.config import get_settings
|
|
13
13
|
from fractal_server.logger import set_logger
|
|
14
|
+
from fractal_server.syringe import Inject
|
|
15
|
+
|
|
14
16
|
|
|
15
17
|
logger = set_logger(__name__)
|
|
16
18
|
|
|
@@ -22,13 +24,16 @@ async def _get_single_user_with_groups(
|
|
|
22
24
|
"""
|
|
23
25
|
Enrich a user object by filling its `group_ids_names` attribute.
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
Args:
|
|
26
28
|
user: The current `UserOAuth` object
|
|
27
29
|
db: Async db session
|
|
28
30
|
|
|
29
31
|
Returns:
|
|
30
32
|
A `UserRead` object with `group_ids_names` dict
|
|
31
33
|
"""
|
|
34
|
+
|
|
35
|
+
settings = Inject(get_settings)
|
|
36
|
+
|
|
32
37
|
stm_groups = (
|
|
33
38
|
select(UserGroup)
|
|
34
39
|
.join(LinkUserGroup)
|
|
@@ -39,25 +44,25 @@ async def _get_single_user_with_groups(
|
|
|
39
44
|
groups = res.scalars().unique().all()
|
|
40
45
|
group_ids_names = [(group.id, group.name) for group in groups]
|
|
41
46
|
|
|
42
|
-
#
|
|
47
|
+
# Identify the default-group position in the list of groups
|
|
43
48
|
index = next(
|
|
44
49
|
(
|
|
45
|
-
|
|
46
|
-
for
|
|
47
|
-
if group_tuple[1] == FRACTAL_DEFAULT_GROUP_NAME
|
|
50
|
+
ind
|
|
51
|
+
for ind, group_tuple in enumerate(group_ids_names)
|
|
52
|
+
if group_tuple[1] == settings.FRACTAL_DEFAULT_GROUP_NAME
|
|
48
53
|
),
|
|
49
54
|
None,
|
|
50
55
|
)
|
|
51
|
-
if index is None:
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
if (index is None) or (index == 0):
|
|
57
|
+
# Either the default group does not exist, or it is already the first
|
|
58
|
+
# one. No action needed.
|
|
59
|
+
pass
|
|
60
|
+
else:
|
|
61
|
+
# Move the default group to the first position
|
|
57
62
|
default_group = group_ids_names.pop(index)
|
|
58
63
|
group_ids_names.insert(0, default_group)
|
|
59
|
-
|
|
60
|
-
|
|
64
|
+
|
|
65
|
+
# Create dump of `user.oauth_accounts` relationship
|
|
61
66
|
oauth_accounts = [
|
|
62
67
|
oauth_account.model_dump() for oauth_account in user.oauth_accounts
|
|
63
68
|
]
|
|
@@ -75,7 +80,7 @@ async def _get_single_usergroup_with_user_ids(
|
|
|
75
80
|
"""
|
|
76
81
|
Get a group, and construct its `user_ids` list.
|
|
77
82
|
|
|
78
|
-
|
|
83
|
+
Args:
|
|
79
84
|
group_id:
|
|
80
85
|
db:
|
|
81
86
|
|
|
@@ -98,7 +103,7 @@ async def _user_or_404(user_id: int, db: AsyncSession) -> UserOAuth:
|
|
|
98
103
|
"""
|
|
99
104
|
Get a user from db, or raise a 404 HTTP exception if missing.
|
|
100
105
|
|
|
101
|
-
|
|
106
|
+
Args:
|
|
102
107
|
user_id: ID of the user
|
|
103
108
|
db: Async db session
|
|
104
109
|
"""
|
|
@@ -121,17 +126,31 @@ async def _usergroup_or_404(usergroup_id: int, db: AsyncSession) -> UserGroup:
|
|
|
121
126
|
return user
|
|
122
127
|
|
|
123
128
|
|
|
124
|
-
async def
|
|
129
|
+
async def _get_default_usergroup_id_or_none(db: AsyncSession) -> int | None:
|
|
130
|
+
"""
|
|
131
|
+
Return the ID of the group named `"All"`, if `FRACTAL_DEFAULT_GROUP_NAME`
|
|
132
|
+
is set and such group exists. Return `None`, if
|
|
133
|
+
`FRACTAL_DEFAULT_GROUP_NAME=None` or if the `"All"` group does not exist.
|
|
134
|
+
"""
|
|
135
|
+
settings = Inject(get_settings)
|
|
125
136
|
stm = select(UserGroup.id).where(
|
|
126
|
-
UserGroup.name == FRACTAL_DEFAULT_GROUP_NAME
|
|
137
|
+
UserGroup.name == settings.FRACTAL_DEFAULT_GROUP_NAME
|
|
127
138
|
)
|
|
128
139
|
res = await db.execute(stm)
|
|
129
140
|
user_group_id = res.scalars().one_or_none()
|
|
130
|
-
|
|
141
|
+
|
|
142
|
+
if (
|
|
143
|
+
settings.FRACTAL_DEFAULT_GROUP_NAME is not None
|
|
144
|
+
and user_group_id is None
|
|
145
|
+
):
|
|
131
146
|
raise HTTPException(
|
|
132
147
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
133
|
-
detail=
|
|
148
|
+
detail=(
|
|
149
|
+
f"User group '{settings.FRACTAL_DEFAULT_GROUP_NAME}'"
|
|
150
|
+
" not found.",
|
|
151
|
+
),
|
|
134
152
|
)
|
|
153
|
+
|
|
135
154
|
return user_group_id
|
|
136
155
|
|
|
137
156
|
|
|
@@ -5,26 +5,28 @@ import os
|
|
|
5
5
|
|
|
6
6
|
from fastapi import APIRouter
|
|
7
7
|
from fastapi import Depends
|
|
8
|
-
from fastapi_users import schemas
|
|
9
8
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
10
9
|
from sqlmodel import select
|
|
11
10
|
|
|
12
|
-
from . import
|
|
13
|
-
from ...db import get_async_db
|
|
14
|
-
from ...schemas.user import UserRead
|
|
15
|
-
from ...schemas.user import UserUpdate
|
|
16
|
-
from ...schemas.user import UserUpdateStrict
|
|
17
|
-
from ..aux.validate_user_settings import verify_user_has_settings
|
|
18
|
-
from ._aux_auth import _get_single_user_with_groups
|
|
11
|
+
from fractal_server.app.db import get_async_db
|
|
19
12
|
from fractal_server.app.models import LinkUserGroup
|
|
13
|
+
from fractal_server.app.models import Profile
|
|
14
|
+
from fractal_server.app.models import Resource
|
|
20
15
|
from fractal_server.app.models import UserGroup
|
|
21
16
|
from fractal_server.app.models import UserOAuth
|
|
22
|
-
from fractal_server.app.
|
|
23
|
-
from fractal_server.app.
|
|
24
|
-
from fractal_server.app.
|
|
17
|
+
from fractal_server.app.routes.auth import current_user_act
|
|
18
|
+
from fractal_server.app.routes.auth import current_user_act_ver
|
|
19
|
+
from fractal_server.app.routes.auth._aux_auth import (
|
|
20
|
+
_get_single_user_with_groups,
|
|
21
|
+
)
|
|
22
|
+
from fractal_server.app.schemas import UserProfileInfo
|
|
23
|
+
from fractal_server.app.schemas.user import UserRead
|
|
24
|
+
from fractal_server.app.schemas.user import UserUpdate
|
|
25
|
+
from fractal_server.app.schemas.user import UserUpdateStrict
|
|
25
26
|
from fractal_server.app.security import get_user_manager
|
|
26
27
|
from fractal_server.app.security import UserManager
|
|
27
|
-
from fractal_server.config import
|
|
28
|
+
from fractal_server.config import DataAuthScheme
|
|
29
|
+
from fractal_server.config import get_data_settings
|
|
28
30
|
from fractal_server.syringe import Inject
|
|
29
31
|
|
|
30
32
|
router_current_user = APIRouter()
|
|
@@ -33,7 +35,7 @@ router_current_user = APIRouter()
|
|
|
33
35
|
@router_current_user.get("/current-user/", response_model=UserRead)
|
|
34
36
|
async def get_current_user(
|
|
35
37
|
group_ids_names: bool = False,
|
|
36
|
-
user: UserOAuth = Depends(
|
|
38
|
+
user: UserOAuth = Depends(current_user_act),
|
|
37
39
|
db: AsyncSession = Depends(get_async_db),
|
|
38
40
|
):
|
|
39
41
|
"""
|
|
@@ -49,7 +51,7 @@ async def get_current_user(
|
|
|
49
51
|
@router_current_user.patch("/current-user/", response_model=UserRead)
|
|
50
52
|
async def patch_current_user(
|
|
51
53
|
user_update: UserUpdateStrict,
|
|
52
|
-
current_user: UserOAuth = Depends(
|
|
54
|
+
current_user: UserOAuth = Depends(current_user_act),
|
|
53
55
|
user_manager: UserManager = Depends(get_user_manager),
|
|
54
56
|
db: AsyncSession = Depends(get_async_db),
|
|
55
57
|
):
|
|
@@ -64,7 +66,7 @@ async def patch_current_user(
|
|
|
64
66
|
# their own password
|
|
65
67
|
|
|
66
68
|
user = await user_manager.update(update, current_user, safe=True)
|
|
67
|
-
validated_user =
|
|
69
|
+
validated_user = UserOAuth.model_validate(user.model_dump())
|
|
68
70
|
|
|
69
71
|
patched_user = await db.get(
|
|
70
72
|
UserOAuth, validated_user.id, populate_existing=True
|
|
@@ -76,83 +78,71 @@ async def patch_current_user(
|
|
|
76
78
|
|
|
77
79
|
|
|
78
80
|
@router_current_user.get(
|
|
79
|
-
"/current-user/
|
|
81
|
+
"/current-user/profile-info/",
|
|
82
|
+
response_model=UserProfileInfo,
|
|
80
83
|
)
|
|
81
|
-
async def
|
|
82
|
-
current_user: UserOAuth = Depends(
|
|
84
|
+
async def get_current_user_profile_info(
|
|
85
|
+
current_user: UserOAuth = Depends(current_user_act),
|
|
83
86
|
db: AsyncSession = Depends(get_async_db),
|
|
84
|
-
) ->
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
"/current-user/settings/", response_model=UserSettingsReadStrict
|
|
92
|
-
)
|
|
93
|
-
async def patch_current_user_settings(
|
|
94
|
-
settings_update: UserSettingsUpdateStrict,
|
|
95
|
-
current_user: UserOAuth = Depends(current_active_user),
|
|
96
|
-
db: AsyncSession = Depends(get_async_db),
|
|
97
|
-
) -> UserSettingsReadStrict:
|
|
98
|
-
verify_user_has_settings(current_user)
|
|
99
|
-
current_user_settings = await db.get(
|
|
100
|
-
UserSettings, current_user.user_settings_id
|
|
87
|
+
) -> UserProfileInfo:
|
|
88
|
+
stm = (
|
|
89
|
+
select(Resource, Profile)
|
|
90
|
+
.join(UserOAuth)
|
|
91
|
+
.where(Resource.id == Profile.resource_id)
|
|
92
|
+
.where(Profile.id == UserOAuth.profile_id)
|
|
93
|
+
.where(UserOAuth.id == current_user.id)
|
|
101
94
|
)
|
|
95
|
+
res = await db.execute(stm)
|
|
96
|
+
db_data = res.one_or_none()
|
|
97
|
+
if db_data is None:
|
|
98
|
+
response_data = dict(has_profile=False)
|
|
99
|
+
else:
|
|
100
|
+
resource, profile = db_data
|
|
101
|
+
response_data = dict(
|
|
102
|
+
has_profile=True,
|
|
103
|
+
resource_name=resource.name,
|
|
104
|
+
profile_name=profile.name,
|
|
105
|
+
username=profile.username,
|
|
106
|
+
)
|
|
102
107
|
|
|
103
|
-
|
|
104
|
-
setattr(current_user_settings, k, v)
|
|
105
|
-
|
|
106
|
-
db.add(current_user_settings)
|
|
107
|
-
await db.commit()
|
|
108
|
-
await db.refresh(current_user_settings)
|
|
109
|
-
|
|
110
|
-
return current_user_settings
|
|
108
|
+
return response_data
|
|
111
109
|
|
|
112
110
|
|
|
113
111
|
@router_current_user.get(
|
|
114
112
|
"/current-user/allowed-viewer-paths/", response_model=list[str]
|
|
115
113
|
)
|
|
116
114
|
async def get_current_user_allowed_viewer_paths(
|
|
117
|
-
current_user: UserOAuth = Depends(
|
|
115
|
+
current_user: UserOAuth = Depends(current_user_act_ver),
|
|
118
116
|
db: AsyncSession = Depends(get_async_db),
|
|
119
117
|
) -> list[str]:
|
|
120
118
|
"""
|
|
121
119
|
Returns the allowed viewer paths for current user, according to the
|
|
122
|
-
selected
|
|
120
|
+
selected FRACTAL_DATA_AUTH_SCHEME
|
|
123
121
|
"""
|
|
124
122
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
if settings.FRACTAL_VIEWER_AUTHORIZATION_SCHEME == "none":
|
|
128
|
-
return []
|
|
123
|
+
data_settings = Inject(get_data_settings)
|
|
129
124
|
|
|
130
125
|
authorized_paths = []
|
|
131
126
|
|
|
132
|
-
|
|
133
|
-
|
|
127
|
+
if data_settings.FRACTAL_DATA_AUTH_SCHEME == DataAuthScheme.NONE:
|
|
128
|
+
return authorized_paths
|
|
134
129
|
|
|
135
|
-
#
|
|
136
|
-
|
|
137
|
-
UserSettings, current_user.user_settings_id
|
|
138
|
-
)
|
|
139
|
-
# If project_dir is set, append it to the list of authorized paths
|
|
140
|
-
if current_user_settings.project_dir is not None:
|
|
141
|
-
authorized_paths.append(current_user_settings.project_dir)
|
|
130
|
+
# Append `project_dir` to the list of authorized paths
|
|
131
|
+
authorized_paths.append(current_user.project_dir)
|
|
142
132
|
|
|
143
133
|
# If auth scheme is "users-folders" and `slurm_user` is set,
|
|
144
134
|
# build and append the user folder
|
|
145
135
|
if (
|
|
146
|
-
|
|
147
|
-
and
|
|
136
|
+
data_settings.FRACTAL_DATA_AUTH_SCHEME == DataAuthScheme.USERS_FOLDERS
|
|
137
|
+
and current_user.profile_id is not None
|
|
148
138
|
):
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
base_folder
|
|
152
|
-
|
|
153
|
-
|
|
139
|
+
profile = await db.get(Profile, current_user.profile_id)
|
|
140
|
+
if profile is not None and profile.username is not None:
|
|
141
|
+
base_folder = data_settings.FRACTAL_DATA_BASE_FOLDER
|
|
142
|
+
user_folder = os.path.join(base_folder, profile.username)
|
|
143
|
+
authorized_paths.append(user_folder)
|
|
154
144
|
|
|
155
|
-
if
|
|
145
|
+
if data_settings.FRACTAL_DATA_AUTH_SCHEME == DataAuthScheme.VIEWER_PATHS:
|
|
156
146
|
# Returns the union of `viewer_paths` for all user's groups
|
|
157
147
|
cmd = (
|
|
158
148
|
select(UserGroup.viewer_paths)
|
|
@@ -169,7 +159,6 @@ async def get_current_user_allowed_viewer_paths(
|
|
|
169
159
|
for _viewer_paths in viewer_paths_nested
|
|
170
160
|
for path in _viewer_paths
|
|
171
161
|
}
|
|
172
|
-
|
|
173
162
|
authorized_paths.extend(all_viewer_paths_set)
|
|
174
163
|
|
|
175
164
|
return authorized_paths
|
|
@@ -9,8 +9,8 @@ from fastapi import status
|
|
|
9
9
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
10
10
|
from sqlmodel import select
|
|
11
11
|
|
|
12
|
-
from . import
|
|
13
|
-
from ._aux_auth import
|
|
12
|
+
from . import current_superuser_act
|
|
13
|
+
from ._aux_auth import _get_default_usergroup_id_or_none
|
|
14
14
|
from ._aux_auth import _get_single_usergroup_with_user_ids
|
|
15
15
|
from ._aux_auth import _user_or_404
|
|
16
16
|
from ._aux_auth import _usergroup_or_404
|
|
@@ -18,16 +18,16 @@ from fractal_server.app.db import get_async_db
|
|
|
18
18
|
from fractal_server.app.models import LinkUserGroup
|
|
19
19
|
from fractal_server.app.models import UserGroup
|
|
20
20
|
from fractal_server.app.models import UserOAuth
|
|
21
|
-
from fractal_server.app.models import UserSettings
|
|
22
21
|
from fractal_server.app.schemas.user_group import UserGroupCreate
|
|
23
22
|
from fractal_server.app.schemas.user_group import UserGroupRead
|
|
24
23
|
from fractal_server.app.schemas.user_group import UserGroupUpdate
|
|
25
|
-
from fractal_server.
|
|
26
|
-
from fractal_server.app.security import FRACTAL_DEFAULT_GROUP_NAME
|
|
24
|
+
from fractal_server.config import get_settings
|
|
27
25
|
from fractal_server.logger import set_logger
|
|
26
|
+
from fractal_server.syringe import Inject
|
|
28
27
|
|
|
29
28
|
logger = set_logger(__name__)
|
|
30
29
|
|
|
30
|
+
|
|
31
31
|
router_group = APIRouter()
|
|
32
32
|
|
|
33
33
|
|
|
@@ -36,11 +36,11 @@ router_group = APIRouter()
|
|
|
36
36
|
)
|
|
37
37
|
async def get_list_user_groups(
|
|
38
38
|
user_ids: bool = False,
|
|
39
|
-
user: UserOAuth = Depends(
|
|
39
|
+
user: UserOAuth = Depends(current_superuser_act),
|
|
40
40
|
db: AsyncSession = Depends(get_async_db),
|
|
41
41
|
) -> list[UserGroupRead]:
|
|
42
42
|
# Get all groups
|
|
43
|
-
stm_all_groups = select(UserGroup)
|
|
43
|
+
stm_all_groups = select(UserGroup).order_by(UserGroup.id)
|
|
44
44
|
res = await db.execute(stm_all_groups)
|
|
45
45
|
groups = res.scalars().all()
|
|
46
46
|
|
|
@@ -70,7 +70,7 @@ async def get_list_user_groups(
|
|
|
70
70
|
)
|
|
71
71
|
async def get_single_user_group(
|
|
72
72
|
group_id: int,
|
|
73
|
-
user: UserOAuth = Depends(
|
|
73
|
+
user: UserOAuth = Depends(current_superuser_act),
|
|
74
74
|
db: AsyncSession = Depends(get_async_db),
|
|
75
75
|
) -> UserGroupRead:
|
|
76
76
|
group = await _get_single_usergroup_with_user_ids(group_id=group_id, db=db)
|
|
@@ -84,7 +84,7 @@ async def get_single_user_group(
|
|
|
84
84
|
)
|
|
85
85
|
async def create_single_group(
|
|
86
86
|
group_create: UserGroupCreate,
|
|
87
|
-
user: UserOAuth = Depends(
|
|
87
|
+
user: UserOAuth = Depends(current_superuser_act),
|
|
88
88
|
db: AsyncSession = Depends(get_async_db),
|
|
89
89
|
) -> UserGroupRead:
|
|
90
90
|
# Check that name is not already in use
|
|
@@ -116,7 +116,7 @@ async def create_single_group(
|
|
|
116
116
|
async def update_single_group(
|
|
117
117
|
group_id: int,
|
|
118
118
|
group_update: UserGroupUpdate,
|
|
119
|
-
user: UserOAuth = Depends(
|
|
119
|
+
user: UserOAuth = Depends(current_superuser_act),
|
|
120
120
|
db: AsyncSession = Depends(get_async_db),
|
|
121
121
|
) -> UserGroupRead:
|
|
122
122
|
group = await _usergroup_or_404(group_id, db)
|
|
@@ -137,58 +137,38 @@ async def update_single_group(
|
|
|
137
137
|
@router_group.delete("/group/{group_id}/", status_code=204)
|
|
138
138
|
async def delete_single_group(
|
|
139
139
|
group_id: int,
|
|
140
|
-
user: UserOAuth = Depends(
|
|
140
|
+
user: UserOAuth = Depends(current_superuser_act),
|
|
141
141
|
db: AsyncSession = Depends(get_async_db),
|
|
142
142
|
) -> Response:
|
|
143
|
+
"""
|
|
144
|
+
Delete a user group.
|
|
145
|
+
|
|
146
|
+
If `FRACTAL_DEFAULT_GROUP_NAME="All"`, a group named `"All"` cannot be
|
|
147
|
+
deleted. If `FRACTAL_DEFAULT_GROUP_NAME=None`, any group can be deleted.
|
|
148
|
+
"""
|
|
149
|
+
settings = Inject(get_settings)
|
|
143
150
|
group = await _usergroup_or_404(group_id, db)
|
|
144
151
|
|
|
145
|
-
if group.name == FRACTAL_DEFAULT_GROUP_NAME:
|
|
152
|
+
if group.name == settings.FRACTAL_DEFAULT_GROUP_NAME:
|
|
146
153
|
raise HTTPException(
|
|
147
154
|
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
|
|
148
155
|
detail=(
|
|
149
156
|
"Cannot delete default UserGroup "
|
|
150
|
-
f"'{FRACTAL_DEFAULT_GROUP_NAME}'."
|
|
157
|
+
f"'{settings.FRACTAL_DEFAULT_GROUP_NAME}'."
|
|
151
158
|
),
|
|
152
159
|
)
|
|
153
160
|
|
|
154
|
-
# Delete
|
|
155
|
-
|
|
156
161
|
await db.delete(group)
|
|
157
162
|
await db.commit()
|
|
158
163
|
|
|
159
164
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
|
160
165
|
|
|
161
166
|
|
|
162
|
-
@router_group.patch("/group/{group_id}/user-settings/", status_code=200)
|
|
163
|
-
async def patch_user_settings_bulk(
|
|
164
|
-
group_id: int,
|
|
165
|
-
settings_update: UserSettingsUpdate,
|
|
166
|
-
superuser: UserOAuth = Depends(current_active_superuser),
|
|
167
|
-
db: AsyncSession = Depends(get_async_db),
|
|
168
|
-
):
|
|
169
|
-
await _usergroup_or_404(group_id, db)
|
|
170
|
-
res = await db.execute(
|
|
171
|
-
select(UserSettings)
|
|
172
|
-
.join(UserOAuth)
|
|
173
|
-
.where(LinkUserGroup.user_id == UserOAuth.id)
|
|
174
|
-
.where(LinkUserGroup.group_id == group_id)
|
|
175
|
-
)
|
|
176
|
-
settings_list = res.scalars().all()
|
|
177
|
-
update = settings_update.model_dump(exclude_unset=True)
|
|
178
|
-
for settings in settings_list:
|
|
179
|
-
for k, v in update.items():
|
|
180
|
-
setattr(settings, k, v)
|
|
181
|
-
db.add(settings)
|
|
182
|
-
await db.commit()
|
|
183
|
-
|
|
184
|
-
return Response(status_code=status.HTTP_200_OK)
|
|
185
|
-
|
|
186
|
-
|
|
187
167
|
@router_group.post("/group/{group_id}/add-user/{user_id}/", status_code=200)
|
|
188
168
|
async def add_user_to_group(
|
|
189
169
|
group_id: int,
|
|
190
170
|
user_id: int,
|
|
191
|
-
superuser: UserOAuth = Depends(
|
|
171
|
+
superuser: UserOAuth = Depends(current_superuser_act),
|
|
192
172
|
db: AsyncSession = Depends(get_async_db),
|
|
193
173
|
) -> UserGroupRead:
|
|
194
174
|
await _usergroup_or_404(group_id, db)
|
|
@@ -212,21 +192,24 @@ async def add_user_to_group(
|
|
|
212
192
|
async def remove_user_from_group(
|
|
213
193
|
group_id: int,
|
|
214
194
|
user_id: int,
|
|
215
|
-
superuser: UserOAuth = Depends(
|
|
195
|
+
superuser: UserOAuth = Depends(current_superuser_act),
|
|
216
196
|
db: AsyncSession = Depends(get_async_db),
|
|
217
197
|
) -> UserGroupRead:
|
|
198
|
+
settings = Inject(get_settings)
|
|
218
199
|
# Check that user and group exist
|
|
219
200
|
await _usergroup_or_404(group_id, db)
|
|
220
201
|
user = await _user_or_404(user_id, db)
|
|
221
202
|
|
|
222
203
|
# Check that group is not the default one
|
|
223
|
-
|
|
224
|
-
|
|
204
|
+
default_user_group_id_or_none = await _get_default_usergroup_id_or_none(
|
|
205
|
+
db=db
|
|
206
|
+
)
|
|
207
|
+
if default_user_group_id_or_none == group_id:
|
|
225
208
|
raise HTTPException(
|
|
226
209
|
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
|
|
227
210
|
detail=(
|
|
228
|
-
f"Cannot remove user from
|
|
229
|
-
"group.",
|
|
211
|
+
f"Cannot remove user from "
|
|
212
|
+
f"'{settings.FRACTAL_DEFAULT_GROUP_NAME}' group.",
|
|
230
213
|
),
|
|
231
214
|
)
|
|
232
215
|
|