fractal-server 2.17.2__py3-none-any.whl → 2.18.0a0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. fractal_server/__init__.py +1 -1
  2. fractal_server/app/models/linkuserproject.py +40 -0
  3. fractal_server/app/routes/admin/v2/__init__.py +2 -0
  4. fractal_server/app/routes/admin/v2/job.py +17 -6
  5. fractal_server/app/routes/admin/v2/sharing.py +103 -0
  6. fractal_server/app/routes/admin/v2/task.py +1 -0
  7. fractal_server/app/routes/api/v2/__init__.py +2 -0
  8. fractal_server/app/routes/api/v2/_aux_functions.py +43 -17
  9. fractal_server/app/routes/api/v2/_aux_functions_history.py +8 -3
  10. fractal_server/app/routes/api/v2/_aux_functions_sharing.py +97 -0
  11. fractal_server/app/routes/api/v2/dataset.py +23 -17
  12. fractal_server/app/routes/api/v2/history.py +21 -11
  13. fractal_server/app/routes/api/v2/images.py +22 -8
  14. fractal_server/app/routes/api/v2/job.py +28 -12
  15. fractal_server/app/routes/api/v2/pre_submission_checks.py +13 -6
  16. fractal_server/app/routes/api/v2/project.py +37 -14
  17. fractal_server/app/routes/api/v2/sharing.py +312 -0
  18. fractal_server/app/routes/api/v2/status_legacy.py +7 -4
  19. fractal_server/app/routes/api/v2/submit.py +11 -5
  20. fractal_server/app/routes/api/v2/task_version_update.py +7 -4
  21. fractal_server/app/routes/api/v2/workflow.py +23 -11
  22. fractal_server/app/routes/api/v2/workflow_import.py +14 -12
  23. fractal_server/app/routes/api/v2/workflowtask.py +41 -7
  24. fractal_server/app/schemas/v2/__init__.py +7 -0
  25. fractal_server/app/schemas/v2/sharing.py +99 -0
  26. fractal_server/migrations/versions/bc0e8b3327a7_project_sharing.py +72 -0
  27. fractal_server/runner/executors/slurm_common/_batching.py +4 -10
  28. fractal_server/runner/executors/slurm_ssh/runner.py +1 -1
  29. fractal_server/runner/executors/slurm_sudo/runner.py +1 -1
  30. {fractal_server-2.17.2.dist-info → fractal_server-2.18.0a0.dist-info}/METADATA +3 -2
  31. {fractal_server-2.17.2.dist-info → fractal_server-2.18.0a0.dist-info}/RECORD +34 -29
  32. {fractal_server-2.17.2.dist-info → fractal_server-2.18.0a0.dist-info}/WHEEL +0 -0
  33. {fractal_server-2.17.2.dist-info → fractal_server-2.18.0a0.dist-info}/entry_points.txt +0 -0
  34. {fractal_server-2.17.2.dist-info → fractal_server-2.18.0a0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,312 @@
1
+ from fastapi import APIRouter
2
+ from fastapi import Depends
3
+ from fastapi import HTTPException
4
+ from fastapi import Response
5
+ from fastapi import status
6
+ from pydantic import EmailStr
7
+ from sqlmodel import select
8
+
9
+ from fractal_server.app.db import AsyncSession
10
+ from fractal_server.app.db import get_async_db
11
+ from fractal_server.app.models import UserOAuth
12
+ from fractal_server.app.models.v2 import LinkUserProjectV2
13
+ from fractal_server.app.models.v2 import ProjectV2
14
+ from fractal_server.app.routes.auth import current_user_act_ver_prof
15
+ from fractal_server.app.schemas.v2 import ProjectAccessRead
16
+ from fractal_server.app.schemas.v2 import ProjectGuestCreate
17
+ from fractal_server.app.schemas.v2 import ProjectGuestRead
18
+ from fractal_server.app.schemas.v2 import ProjectGuestUpdate
19
+ from fractal_server.app.schemas.v2 import ProjectInvitationRead
20
+
21
+ from ._aux_functions_sharing import get_link_or_404
22
+ from ._aux_functions_sharing import get_pending_invitation_or_404
23
+ from ._aux_functions_sharing import get_user_id_from_email_or_404
24
+ from ._aux_functions_sharing import raise_403_if_not_owner
25
+ from ._aux_functions_sharing import raise_422_if_link_exists
26
+
27
+ router = APIRouter()
28
+
29
+
30
+ @router.get(
31
+ "/project/{project_id}/guest/",
32
+ response_model=list[ProjectGuestRead],
33
+ )
34
+ async def get_project_guests(
35
+ project_id: int,
36
+ owner: UserOAuth = Depends(current_user_act_ver_prof),
37
+ db: AsyncSession = Depends(get_async_db),
38
+ ) -> list[ProjectGuestRead]:
39
+ """
40
+ Get the list of all the guests of your project (verified or not).
41
+ """
42
+ await raise_403_if_not_owner(user_id=owner.id, project_id=project_id, db=db)
43
+ # Get (email, is_verified, permissions) for all guests
44
+ res = await db.execute(
45
+ select(
46
+ UserOAuth.email,
47
+ LinkUserProjectV2.is_verified,
48
+ LinkUserProjectV2.permissions,
49
+ )
50
+ .join(LinkUserProjectV2, LinkUserProjectV2.user_id == UserOAuth.id)
51
+ .where(LinkUserProjectV2.project_id == project_id)
52
+ .where(LinkUserProjectV2.is_owner.is_(False))
53
+ .order_by(UserOAuth.email)
54
+ )
55
+ guest_tuples = res.all()
56
+ return [
57
+ dict(
58
+ email=guest_email,
59
+ is_verified=is_verified,
60
+ permissions=permissions,
61
+ )
62
+ for guest_email, is_verified, permissions in guest_tuples
63
+ ]
64
+
65
+
66
+ @router.post("/project/{project_id}/guest/", status_code=201)
67
+ async def invite_guest(
68
+ project_id: int,
69
+ email: EmailStr,
70
+ project_invitation: ProjectGuestCreate,
71
+ owner: UserOAuth = Depends(current_user_act_ver_prof),
72
+ db: AsyncSession = Depends(get_async_db),
73
+ ) -> Response:
74
+ """
75
+ Add a guest to your project.
76
+ """
77
+ await raise_403_if_not_owner(user_id=owner.id, project_id=project_id, db=db)
78
+
79
+ guest_id = await get_user_id_from_email_or_404(user_email=email, db=db)
80
+
81
+ await raise_422_if_link_exists(
82
+ user_id=guest_id,
83
+ project_id=project_id,
84
+ db=db,
85
+ )
86
+
87
+ db.add(
88
+ LinkUserProjectV2(
89
+ project_id=project_id,
90
+ user_id=guest_id,
91
+ is_owner=False,
92
+ is_verified=False,
93
+ permissions=project_invitation.permissions,
94
+ )
95
+ )
96
+ await db.commit()
97
+
98
+ return Response(status_code=status.HTTP_201_CREATED)
99
+
100
+
101
+ @router.patch("/project/{project_id}/guest/", status_code=200)
102
+ async def patch_guest(
103
+ project_id: int,
104
+ email: EmailStr,
105
+ update: ProjectGuestUpdate,
106
+ owner: UserOAuth = Depends(current_user_act_ver_prof),
107
+ db: AsyncSession = Depends(get_async_db),
108
+ ) -> Response:
109
+ """
110
+ Change guest's permissions on your project.
111
+ """
112
+ await raise_403_if_not_owner(user_id=owner.id, project_id=project_id, db=db)
113
+
114
+ guest_id = await get_user_id_from_email_or_404(user_email=email, db=db)
115
+
116
+ if guest_id == owner.id:
117
+ raise HTTPException(
118
+ status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
119
+ detail="Cannot perform this operation on project owner.",
120
+ )
121
+
122
+ link = await get_link_or_404(
123
+ user_id=guest_id,
124
+ project_id=project_id,
125
+ db=db,
126
+ )
127
+
128
+ # Update link and commit
129
+ for key, value in update.model_dump(exclude_unset=True).items():
130
+ setattr(link, key, value)
131
+ await db.commit()
132
+
133
+ return Response(status_code=status.HTTP_200_OK)
134
+
135
+
136
+ @router.delete("/project/{project_id}/guest/", status_code=204)
137
+ async def revoke_guest_access(
138
+ project_id: int,
139
+ email: EmailStr,
140
+ owner: UserOAuth = Depends(current_user_act_ver_prof),
141
+ db: AsyncSession = Depends(get_async_db),
142
+ ) -> Response:
143
+ """
144
+ Remove a guest from your project.
145
+ """
146
+ await raise_403_if_not_owner(user_id=owner.id, project_id=project_id, db=db)
147
+
148
+ guest_id = await get_user_id_from_email_or_404(user_email=email, db=db)
149
+
150
+ if guest_id == owner.id:
151
+ raise HTTPException(
152
+ status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
153
+ detail="Cannot perform this operation on project owner.",
154
+ )
155
+
156
+ link = await get_link_or_404(
157
+ user_id=guest_id,
158
+ project_id=project_id,
159
+ db=db,
160
+ )
161
+
162
+ # Delete link and commit
163
+ await db.delete(link)
164
+ await db.commit()
165
+
166
+ return Response(status_code=status.HTTP_204_NO_CONTENT)
167
+
168
+
169
+ @router.get(
170
+ "/project/invitation/",
171
+ response_model=list[ProjectInvitationRead],
172
+ )
173
+ async def get_pending_invitations(
174
+ user: UserOAuth = Depends(current_user_act_ver_prof),
175
+ db: AsyncSession = Depends(get_async_db),
176
+ ) -> list[ProjectInvitationRead]:
177
+ """
178
+ See your current invitations.
179
+ """
180
+
181
+ res = await db.execute(
182
+ select(
183
+ ProjectV2.id,
184
+ ProjectV2.name,
185
+ LinkUserProjectV2.permissions,
186
+ (
187
+ select(UserOAuth.email)
188
+ .join(
189
+ LinkUserProjectV2,
190
+ UserOAuth.id == LinkUserProjectV2.user_id,
191
+ )
192
+ .where(LinkUserProjectV2.is_owner.is_(True))
193
+ .where(LinkUserProjectV2.project_id == ProjectV2.id)
194
+ .scalar_subquery()
195
+ .correlate(ProjectV2)
196
+ ),
197
+ )
198
+ .join(LinkUserProjectV2, LinkUserProjectV2.project_id == ProjectV2.id)
199
+ .where(LinkUserProjectV2.user_id == user.id)
200
+ .where(LinkUserProjectV2.is_verified.is_(False))
201
+ .order_by(ProjectV2.name)
202
+ )
203
+
204
+ guest_project_info = res.all()
205
+
206
+ return [
207
+ dict(
208
+ project_id=project_id,
209
+ project_name=project_name,
210
+ guest_permissions=guest_permissions,
211
+ owner_email=owner_email,
212
+ )
213
+ for (
214
+ project_id,
215
+ project_name,
216
+ guest_permissions,
217
+ owner_email,
218
+ ) in guest_project_info
219
+ ]
220
+
221
+
222
+ @router.get(
223
+ "/project/{project_id}/access/",
224
+ response_model=ProjectAccessRead,
225
+ )
226
+ async def get_access_info(
227
+ project_id: int,
228
+ user: UserOAuth = Depends(current_user_act_ver_prof),
229
+ db: AsyncSession = Depends(get_async_db),
230
+ ) -> ProjectAccessRead:
231
+ """
232
+ Returns information on your relationship with Project[`project_id`].
233
+ """
234
+
235
+ res = await db.execute(
236
+ select(
237
+ LinkUserProjectV2.is_owner,
238
+ LinkUserProjectV2.permissions,
239
+ (
240
+ select(UserOAuth.email)
241
+ .join(
242
+ LinkUserProjectV2,
243
+ UserOAuth.id == LinkUserProjectV2.user_id,
244
+ )
245
+ .where(LinkUserProjectV2.is_owner.is_(True))
246
+ .where(LinkUserProjectV2.project_id == ProjectV2.id)
247
+ .scalar_subquery()
248
+ .correlate(LinkUserProjectV2)
249
+ ),
250
+ )
251
+ .where(LinkUserProjectV2.project_id == project_id)
252
+ .where(LinkUserProjectV2.user_id == user.id)
253
+ .where(LinkUserProjectV2.is_verified.is_(True))
254
+ )
255
+
256
+ guest_project_info = res.one_or_none()
257
+
258
+ if guest_project_info is None:
259
+ raise HTTPException(
260
+ status_code=status.HTTP_404_NOT_FOUND,
261
+ detail=f"User has no access to project {project_id}.",
262
+ )
263
+
264
+ is_owner, permissions, owner_email = guest_project_info
265
+
266
+ return dict(
267
+ is_owner=is_owner,
268
+ permissions=permissions,
269
+ owner_email=owner_email,
270
+ )
271
+
272
+
273
+ @router.post("/project/{project_id}/access/accept/", status_code=200)
274
+ async def accept_project_invitation(
275
+ project_id: int,
276
+ user: UserOAuth = Depends(current_user_act_ver_prof),
277
+ db: AsyncSession = Depends(get_async_db),
278
+ ) -> Response:
279
+ """
280
+ Accept invitation to project `project_id`.
281
+ """
282
+ link = await get_pending_invitation_or_404(
283
+ user_id=user.id, project_id=project_id, db=db
284
+ )
285
+ link.is_verified = True
286
+ await db.commit()
287
+
288
+ return Response(status_code=status.HTTP_200_OK)
289
+
290
+
291
+ @router.delete("/project/{project_id}/access/", status_code=204)
292
+ async def leave_project(
293
+ project_id: int,
294
+ user: UserOAuth = Depends(current_user_act_ver_prof),
295
+ db: AsyncSession = Depends(get_async_db),
296
+ ) -> Response:
297
+ """
298
+ Decline invitation to project `project_id` or stop being a guest of that
299
+ project.
300
+ """
301
+ link = await get_link_or_404(user_id=user.id, project_id=project_id, db=db)
302
+
303
+ if link.is_owner:
304
+ raise HTTPException(
305
+ status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
306
+ detail=f"You are the owner of project {project_id}.",
307
+ )
308
+
309
+ await db.delete(link)
310
+ await db.commit()
311
+
312
+ return Response(status_code=status.HTTP_204_NO_CONTENT)
@@ -8,13 +8,14 @@ from fractal_server.app.db import get_async_db
8
8
  from fractal_server.app.models import UserOAuth
9
9
  from fractal_server.app.models.v2 import JobV2
10
10
  from fractal_server.app.routes.auth import current_user_act_ver_prof
11
+ from fractal_server.app.schemas.v2.sharing import ProjectPermissions
11
12
  from fractal_server.app.schemas.v2.status_legacy import LegacyStatusReadV2
12
13
  from fractal_server.app.schemas.v2.status_legacy import WorkflowTaskStatusTypeV2
13
14
  from fractal_server.logger import set_logger
14
15
 
15
- from ._aux_functions import _get_dataset_check_owner
16
+ from ._aux_functions import _get_dataset_check_access
16
17
  from ._aux_functions import _get_submitted_jobs_statement
17
- from ._aux_functions import _get_workflow_check_owner
18
+ from ._aux_functions import _get_workflow_check_access
18
19
 
19
20
  router = APIRouter()
20
21
 
@@ -42,19 +43,21 @@ async def get_workflowtask_status(
42
43
  order). See fractal-server GitHub issues: 793, 1083.
43
44
  """
44
45
  # Get the dataset DB entry
45
- output = await _get_dataset_check_owner(
46
+ output = await _get_dataset_check_access(
46
47
  project_id=project_id,
47
48
  dataset_id=dataset_id,
48
49
  user_id=user.id,
50
+ required_permissions=ProjectPermissions.READ,
49
51
  db=db,
50
52
  )
51
53
  dataset = output["dataset"]
52
54
 
53
55
  # Get the workflow DB entry
54
- workflow = await _get_workflow_check_owner(
56
+ workflow = await _get_workflow_check_access(
55
57
  project_id=project_id,
56
58
  workflow_id=workflow_id,
57
59
  user_id=user.id,
60
+ required_permissions=ProjectPermissions.READ,
58
61
  db=db,
59
62
  )
60
63
 
@@ -27,6 +27,7 @@ from fractal_server.app.schemas.v2 import JobCreateV2
27
27
  from fractal_server.app.schemas.v2 import JobReadV2
28
28
  from fractal_server.app.schemas.v2 import JobStatusTypeV2
29
29
  from fractal_server.app.schemas.v2 import ResourceType
30
+ from fractal_server.app.schemas.v2.sharing import ProjectPermissions
30
31
  from fractal_server.config import get_settings
31
32
  from fractal_server.logger import set_logger
32
33
  from fractal_server.runner.set_start_and_last_task_index import (
@@ -35,8 +36,8 @@ from fractal_server.runner.set_start_and_last_task_index import (
35
36
  from fractal_server.runner.v2.submit_workflow import submit_workflow
36
37
  from fractal_server.syringe import Inject
37
38
 
38
- from ._aux_functions import _get_dataset_check_owner
39
- from ._aux_functions import _get_workflow_check_owner
39
+ from ._aux_functions import _get_dataset_check_access
40
+ from ._aux_functions import _get_workflow_check_access
40
41
  from ._aux_functions import clean_app_job_list_v2
41
42
  from ._aux_functions_tasks import _check_type_filters_compatibility
42
43
 
@@ -72,10 +73,11 @@ async def apply_workflow(
72
73
  )
73
74
  request.app.state.jobsV2 = new_jobs_list
74
75
 
75
- output = await _get_dataset_check_owner(
76
+ output = await _get_dataset_check_access(
76
77
  project_id=project_id,
77
78
  dataset_id=dataset_id,
78
79
  user_id=user.id,
80
+ required_permissions=ProjectPermissions.EXECUTE,
79
81
  db=db,
80
82
  )
81
83
  project = output["project"]
@@ -92,8 +94,12 @@ async def apply_workflow(
92
94
  detail="Project resource does not match with user's resource",
93
95
  )
94
96
 
95
- workflow = await _get_workflow_check_owner(
96
- project_id=project_id, workflow_id=workflow_id, user_id=user.id, db=db
97
+ workflow = await _get_workflow_check_access(
98
+ project_id=project_id,
99
+ workflow_id=workflow_id,
100
+ user_id=user.id,
101
+ required_permissions=ProjectPermissions.EXECUTE,
102
+ db=db,
97
103
  )
98
104
  num_tasks = len(workflow.task_list)
99
105
  if num_tasks == 0:
@@ -21,9 +21,10 @@ from fractal_server.app.routes.auth import current_user_act_ver_prof
21
21
  from fractal_server.app.schemas.v2 import TaskType
22
22
  from fractal_server.app.schemas.v2 import WorkflowTaskReadV2
23
23
  from fractal_server.app.schemas.v2 import WorkflowTaskReplaceV2
24
+ from fractal_server.app.schemas.v2.sharing import ProjectPermissions
24
25
 
25
- from ._aux_functions import _get_workflow_check_owner
26
- from ._aux_functions import _get_workflow_task_check_owner
26
+ from ._aux_functions import _get_workflow_check_access
27
+ from ._aux_functions import _get_workflow_task_check_access
27
28
  from ._aux_functions_task_version_update import get_new_workflow_task_meta
28
29
  from ._aux_functions_tasks import _check_type_filters_compatibility
29
30
  from ._aux_functions_tasks import _get_task_group_or_404
@@ -79,10 +80,11 @@ async def get_workflow_version_update_candidates(
79
80
  user: UserOAuth = Depends(current_user_act_ver_prof),
80
81
  db: AsyncSession = Depends(get_async_db),
81
82
  ) -> list[list[TaskVersionRead]]:
82
- workflow = await _get_workflow_check_owner(
83
+ workflow = await _get_workflow_check_access(
83
84
  project_id=project_id,
84
85
  workflow_id=workflow_id,
85
86
  user_id=user.id,
87
+ required_permissions=ProjectPermissions.READ,
86
88
  db=db,
87
89
  )
88
90
 
@@ -182,11 +184,12 @@ async def replace_workflowtask(
182
184
  db: AsyncSession = Depends(get_async_db),
183
185
  ) -> WorkflowTaskReadV2:
184
186
  # Get objects from database
185
- workflow_task, workflow = await _get_workflow_task_check_owner(
187
+ workflow_task, workflow = await _get_workflow_task_check_access(
186
188
  project_id=project_id,
187
189
  workflow_id=workflow_id,
188
190
  workflow_task_id=workflow_task_id,
189
191
  user_id=user.id,
192
+ required_permissions=ProjectPermissions.WRITE,
190
193
  db=db,
191
194
  )
192
195
  new_task = await _get_task_read_access(
@@ -20,12 +20,13 @@ from fractal_server.app.schemas.v2 import WorkflowExportV2
20
20
  from fractal_server.app.schemas.v2 import WorkflowReadV2
21
21
  from fractal_server.app.schemas.v2 import WorkflowReadV2WithWarnings
22
22
  from fractal_server.app.schemas.v2 import WorkflowUpdateV2
23
+ from fractal_server.app.schemas.v2.sharing import ProjectPermissions
23
24
  from fractal_server.images.tools import merge_type_filters
24
25
 
25
26
  from ._aux_functions import _check_workflow_exists
26
- from ._aux_functions import _get_project_check_owner
27
+ from ._aux_functions import _get_project_check_access
27
28
  from ._aux_functions import _get_submitted_jobs_statement
28
- from ._aux_functions import _get_workflow_check_owner
29
+ from ._aux_functions import _get_workflow_check_access
29
30
  from ._aux_functions import _workflow_has_submitted_job
30
31
  from ._aux_functions_tasks import _add_warnings_to_workflow_tasks
31
32
 
@@ -45,8 +46,11 @@ async def get_workflow_list(
45
46
  Get workflow list for given project
46
47
  """
47
48
  # Access control
48
- project = await _get_project_check_owner(
49
- project_id=project_id, user_id=user.id, db=db
49
+ project = await _get_project_check_access(
50
+ project_id=project_id,
51
+ user_id=user.id,
52
+ required_permissions=ProjectPermissions.READ,
53
+ db=db,
50
54
  )
51
55
  # Find workflows of the current project. Note: this select/where approach
52
56
  # has much better scaling than refreshing all elements of
@@ -71,8 +75,11 @@ async def create_workflow(
71
75
  """
72
76
  Create a workflow, associate to a project
73
77
  """
74
- await _get_project_check_owner(
75
- project_id=project_id, user_id=user.id, db=db
78
+ await _get_project_check_access(
79
+ project_id=project_id,
80
+ user_id=user.id,
81
+ required_permissions=ProjectPermissions.WRITE,
82
+ db=db,
76
83
  )
77
84
  await _check_workflow_exists(
78
85
  name=workflow.name, project_id=project_id, db=db
@@ -100,10 +107,11 @@ async def read_workflow(
100
107
  Get info on an existing workflow
101
108
  """
102
109
 
103
- workflow = await _get_workflow_check_owner(
110
+ workflow = await _get_workflow_check_access(
104
111
  project_id=project_id,
105
112
  workflow_id=workflow_id,
106
113
  user_id=user.id,
114
+ required_permissions=ProjectPermissions.READ,
107
115
  db=db,
108
116
  )
109
117
 
@@ -133,10 +141,11 @@ async def update_workflow(
133
141
  """
134
142
  Edit a workflow
135
143
  """
136
- workflow = await _get_workflow_check_owner(
144
+ workflow = await _get_workflow_check_access(
137
145
  project_id=project_id,
138
146
  workflow_id=workflow_id,
139
147
  user_id=user.id,
148
+ required_permissions=ProjectPermissions.WRITE,
140
149
  db=db,
141
150
  )
142
151
 
@@ -208,10 +217,11 @@ async def delete_workflow(
208
217
  Delete a workflow
209
218
  """
210
219
 
211
- workflow = await _get_workflow_check_owner(
220
+ workflow = await _get_workflow_check_access(
212
221
  project_id=project_id,
213
222
  workflow_id=workflow_id,
214
223
  user_id=user.id,
224
+ required_permissions=ProjectPermissions.WRITE,
215
225
  db=db,
216
226
  )
217
227
 
@@ -252,10 +262,11 @@ async def export_workflow(
252
262
  """
253
263
  Export an existing workflow, after stripping all IDs
254
264
  """
255
- workflow = await _get_workflow_check_owner(
265
+ workflow = await _get_workflow_check_access(
256
266
  project_id=project_id,
257
267
  workflow_id=workflow_id,
258
268
  user_id=user.id,
269
+ required_permissions=ProjectPermissions.READ,
259
270
  db=db,
260
271
  )
261
272
  wf_task_list = []
@@ -293,10 +304,11 @@ async def get_workflow_type_filters(
293
304
  Get info on type/type-filters flow for a workflow.
294
305
  """
295
306
 
296
- workflow = await _get_workflow_check_owner(
307
+ workflow = await _get_workflow_check_access(
297
308
  project_id=project_id,
298
309
  workflow_id=workflow_id,
299
310
  user_id=user.id,
311
+ required_permissions=ProjectPermissions.READ,
300
312
  db=db,
301
313
  )
302
314
 
@@ -24,10 +24,11 @@ from fractal_server.app.schemas.v2 import TaskImportV2Legacy
24
24
  from fractal_server.app.schemas.v2 import WorkflowImportV2
25
25
  from fractal_server.app.schemas.v2 import WorkflowReadV2WithWarnings
26
26
  from fractal_server.app.schemas.v2 import WorkflowTaskCreateV2
27
+ from fractal_server.app.schemas.v2.sharing import ProjectPermissions
27
28
  from fractal_server.logger import set_logger
28
29
 
29
30
  from ._aux_functions import _check_workflow_exists
30
- from ._aux_functions import _get_project_check_owner
31
+ from ._aux_functions import _get_project_check_access
31
32
  from ._aux_functions import _get_user_resource_id
32
33
  from ._aux_functions import _workflow_insert_task
33
34
  from ._aux_functions_tasks import _add_warnings_to_workflow_tasks
@@ -65,7 +66,7 @@ async def _get_user_accessible_taskgroups(
65
66
  )
66
67
  res = await db.execute(stm)
67
68
  accessible_task_groups = res.scalars().all()
68
- logger.info(
69
+ logger.debug(
69
70
  f"Found {len(accessible_task_groups)} accessible "
70
71
  f"task groups for {user_id=}."
71
72
  )
@@ -120,7 +121,7 @@ async def _get_task_by_taskimport(
120
121
  `id` of the matching task, or `None`.
121
122
  """
122
123
 
123
- logger.info(f"[_get_task_by_taskimport] START, {task_import=}")
124
+ logger.debug(f"[_get_task_by_taskimport] START, {task_import=}")
124
125
 
125
126
  # Filter by `pkg_name` and by presence of a task with given `name`.
126
127
  matching_task_groups = [
@@ -132,7 +133,7 @@ async def _get_task_by_taskimport(
132
133
  )
133
134
  ]
134
135
  if len(matching_task_groups) < 1:
135
- logger.info(
136
+ logger.debug(
136
137
  "[_get_task_by_taskimport] "
137
138
  f"No task group with {task_import.pkg_name=} "
138
139
  f"and a task with {task_import.name=}."
@@ -142,13 +143,13 @@ async def _get_task_by_taskimport(
142
143
  # Determine target `version`
143
144
  # Note that task_import.version cannot be "", due to a validator
144
145
  if task_import.version is None:
145
- logger.info(
146
+ logger.debug(
146
147
  "[_get_task_by_taskimport] "
147
148
  "No version requested, looking for latest."
148
149
  )
149
150
  latest_task = max(matching_task_groups, key=lambda tg: tg.version or "")
150
151
  version = latest_task.version
151
- logger.info(
152
+ logger.debug(
152
153
  f"[_get_task_by_taskimport] Latest version set to {version}."
153
154
  )
154
155
  else:
@@ -160,19 +161,19 @@ async def _get_task_by_taskimport(
160
161
  )
161
162
 
162
163
  if len(final_matching_task_groups) < 1:
163
- logger.info(
164
+ logger.debug(
164
165
  "[_get_task_by_taskimport] "
165
166
  "No task group left after filtering by version."
166
167
  )
167
168
  return None
168
169
  elif len(final_matching_task_groups) == 1:
169
170
  final_task_group = final_matching_task_groups[0]
170
- logger.info(
171
+ logger.debug(
171
172
  "[_get_task_by_taskimport] "
172
173
  "Found a single task group, after filtering by version."
173
174
  )
174
175
  else:
175
- logger.info(
176
+ logger.debug(
176
177
  "[_get_task_by_taskimport] "
177
178
  f"Found {len(final_matching_task_groups)} task groups, "
178
179
  "after filtering by version."
@@ -184,7 +185,7 @@ async def _get_task_by_taskimport(
184
185
  default_group_id=default_group_id,
185
186
  )
186
187
  if final_task_group is None:
187
- logger.info(
188
+ logger.debug(
188
189
  "[_get_task_by_taskimport] Disambiguation returned None."
189
190
  )
190
191
  return None
@@ -199,7 +200,7 @@ async def _get_task_by_taskimport(
199
200
  None,
200
201
  )
201
202
 
202
- logger.info(f"[_get_task_by_taskimport] END, {task_import=}, {task_id=}.")
203
+ logger.debug(f"[_get_task_by_taskimport] END, {task_import=}, {task_id=}.")
203
204
 
204
205
  return task_id
205
206
 
@@ -222,9 +223,10 @@ async def import_workflow(
222
223
  user_resource_id = await _get_user_resource_id(user_id=user.id, db=db)
223
224
 
224
225
  # Preliminary checks
225
- await _get_project_check_owner(
226
+ await _get_project_check_access(
226
227
  project_id=project_id,
227
228
  user_id=user.id,
229
+ required_permissions=ProjectPermissions.WRITE,
228
230
  db=db,
229
231
  )
230
232
  await _check_workflow_exists(