1818from hexa .datasets .api import get_blob
1919from hexa .files import storage
2020from hexa .metadata .models import MetadataMixin
21- from hexa .user_management .models import OrganizationMembershipRole , User
21+ from hexa .user_management .models import (
22+ Organization ,
23+ ServicePrincipal ,
24+ User ,
25+ UserInterface ,
26+ )
2227
2328logger = logging .getLogger (__name__ )
2429
@@ -37,90 +42,63 @@ def create_dataset_slug(name: str, workspace):
3742
3843
3944class DatasetQuerySet (BaseQuerySet ):
40- @staticmethod
41- def _workspace_query (user ):
42- return Q (links__workspace__members = user )
43-
44- @staticmethod
45- def _org_shared_query (user ):
46- return Q (
47- shared_with_organization = True ,
48- workspace__organization__organizationmembership__user = user ,
49- )
50-
51- @staticmethod
52- def _org_admin_or_owner_query (user ):
53- return Q (
54- workspace__organization__organizationmembership__user = user ,
55- workspace__organization__organizationmembership__role__in = [
56- OrganizationMembershipRole .ADMIN ,
57- OrganizationMembershipRole .OWNER ,
58- ],
59- )
60-
6145 @staticmethod
6246 def optimize_query (qs : models .QuerySet ) -> models .QuerySet :
6347 return qs .select_related (
6448 "workspace" , "workspace__organization" , "created_by"
6549 ).prefetch_related ("versions" , "links" , "links__workspace" )
6650
67- def filter_for_user (self , user : AnonymousUser | User ) :
68- from hexa .pipelines . authentication import PipelineRunUser
51+ def filter_for_user (self , user : AnonymousUser | UserInterface ) -> models . QuerySet :
52+ from hexa .workspaces . models import Workspace
6953
70- if isinstance (user , PipelineRunUser ):
71- return self ._filter_for_user_and_query_object (
72- user , models .Q (links__workspace = user .pipeline_run .pipeline .workspace )
73- )
74- else :
75- return self .optimize_query (
76- self ._filter_for_user_and_query_object (
77- user ,
78- self ._workspace_query (user ) | self ._org_shared_query (user ),
79- return_all_if_superuser = True ,
80- return_all_if_organization_admin_or_owner = True ,
54+ accessible_workspaces = Workspace .objects .filter_for_user (user )
55+ accessible_organizations = Organization .objects .filter_for_user (
56+ user , direct_membership_only = True
57+ )
58+ return self .optimize_query (
59+ self .filter (
60+ Q (links__workspace__in = accessible_workspaces )
61+ | Q (
62+ shared_with_organization = True ,
63+ workspace__organization__in = accessible_organizations ,
8164 )
82- )
65+ ).distinct ()
66+ )
8367
8468 def filter_for_workspace_slugs (
85- self , user : AnonymousUser | User , workspace_slugs : list [str ]
69+ self , user : AnonymousUser | UserInterface , workspace_slugs : list [str ]
8670 ):
87- return self .optimize_query (
88- self ._filter_for_user_and_query_object (
89- user ,
90- self ._workspace_query (user ) & Q (workspace__slug__in = workspace_slugs )
91- | (
92- self ._org_shared_query (user )
93- & Q (links__workspace__slug__in = workspace_slugs )
94- )
95- | (
96- self ._org_admin_or_owner_query (user )
97- & Q (workspace__slug__in = workspace_slugs )
98- ),
99- return_all_if_superuser = False ,
100- )
71+ # Datasets auto-link to their primary workspace, so `links__workspace`
72+ # uniformly covers both primary and shared scopes — no separate
73+ # primary-workspace clause needed.
74+ return (
75+ self .filter_for_user (user )
76+ .filter (links__workspace__slug__in = workspace_slugs )
77+ .distinct ()
10178 )
10279
10380
10481class DatasetManager (models .Manager ):
10582 def create_if_has_perm (
10683 self ,
107- principal : User ,
84+ principal : UserInterface ,
10885 workspace : any ,
10986 * ,
11087 name : str ,
11188 description : str ,
11289 files : list [dict ] | None = None ,
11390 ):
114- from hexa .pipelines .authentication import PipelineRunUser
115-
116- # FIXME: Use a generic permission system instead of differencing between User and PipelineRunUser
117- if isinstance (principal , PipelineRunUser ):
118- if principal .pipeline_run .pipeline .workspace != workspace :
91+ if isinstance (principal , ServicePrincipal ):
92+ if principal .workspace_id != workspace .pk :
11993 raise PermissionDenied
12094 elif not principal .has_perm ("datasets.create_dataset" , workspace ):
12195 raise PermissionDenied
12296
123- created_by = principal if not isinstance (principal , PipelineRunUser ) else None
97+ created_by = (
98+ principal .real_user
99+ if isinstance (principal , ServicePrincipal )
100+ else principal
101+ )
124102
125103 with transaction .atomic ():
126104 dataset = self .create (
@@ -224,7 +202,7 @@ def link(self, principal: User, workspace: any):
224202
225203
226204class DatasetVersionQuerySet (BaseQuerySet ):
227- def filter_for_user (self , user : AnonymousUser | User ):
205+ def filter_for_user (self , user : AnonymousUser | UserInterface ):
228206 # TODO: It should also check workspace where it's added
229207 return self ._filter_for_user_and_query_object (
230208 user ,
@@ -236,25 +214,24 @@ def filter_for_user(self, user: AnonymousUser | User):
236214class DatasetVersionManager (models .Manager ):
237215 def create_if_has_perm (
238216 self ,
239- principal : User ,
217+ principal : UserInterface ,
240218 dataset : Dataset ,
241219 * ,
242220 name : str ,
243221 changelog : str ,
244222 files : list [dict ] | None = None ,
245223 ):
246- # FIXME: Use a generic permission system instead of differencing between User and PipelineRunUser
247- from hexa .pipelines .authentication import PipelineRunUser
248-
249- if isinstance (principal , PipelineRunUser ):
250- if principal .pipeline_run .pipeline .workspace != dataset .workspace :
224+ if isinstance (principal , ServicePrincipal ):
225+ if principal .workspace_id != dataset .workspace_id :
251226 raise PermissionDenied
252227 elif not principal .has_perm ("datasets.create_dataset_version" , dataset ):
253228 raise PermissionDenied
254- created_by = principal if not isinstance (principal , PipelineRunUser ) else None
255- pipeline_run = (
256- principal .pipeline_run if isinstance (principal , PipelineRunUser ) else None
229+ created_by = (
230+ principal .real_user
231+ if isinstance (principal , ServicePrincipal )
232+ else principal
257233 )
234+ pipeline_run = getattr (principal , "pipeline_run" , None )
258235
259236 uploaded_uris = []
260237 with transaction .atomic ():
@@ -357,7 +334,7 @@ def get_file_by_name(self, name: str):
357334
358335
359336class DatasetVersionFileQuerySet (BaseQuerySet ):
360- def filter_for_user (self , user : AnonymousUser | User ):
337+ def filter_for_user (self , user : AnonymousUser | UserInterface ):
361338 return self ._filter_for_user_and_query_object (
362339 user ,
363340 models .Q (
@@ -373,27 +350,25 @@ def filter_by_filename(self, filename: str):
373350class DatasetVersionFileManager (models .Manager ):
374351 def create_if_has_perm (
375352 self ,
376- principal : User ,
353+ principal : UserInterface ,
377354 dataset_version : DatasetVersion ,
378355 * ,
379356 uri : str ,
380357 content_type : str ,
381358 ):
382- # FIXME: Use a generic permission system instead of differencing between User and PipelineRunUser
383- from hexa .pipelines .authentication import PipelineRunUser
384-
385- if isinstance (principal , PipelineRunUser ):
386- if (
387- principal .pipeline_run .pipeline .workspace
388- != dataset_version .dataset .workspace
389- ):
359+ if isinstance (principal , ServicePrincipal ):
360+ if principal .workspace_id != dataset_version .dataset .workspace_id :
390361 raise PermissionDenied
391362 elif not principal .has_perm (
392363 "datasets.create_dataset_version_file" , dataset_version
393364 ):
394365 raise PermissionDenied
395366
396- created_by = principal if not isinstance (principal , PipelineRunUser ) else None
367+ created_by = (
368+ principal .real_user
369+ if isinstance (principal , ServicePrincipal )
370+ else principal
371+ )
397372
398373 return self .create (
399374 dataset_version = dataset_version ,
@@ -591,35 +566,22 @@ def filter_for_workspaces(self, workspaces, pinned=None, query=None):
591566 )
592567 return qs
593568
594- def filter_for_user (self , user : AnonymousUser | User ):
595- # FIXME: Use a generic permission system instead of differencing between User and PipelineRunUser
596- from hexa .pipelines .authentication import PipelineRunUser
597-
598- if isinstance (user , PipelineRunUser ):
599- workspace = user .pipeline_run .pipeline .workspace
600- return self .optimize_query (
601- self ._filter_for_user_and_query_object (
602- user ,
603- models .Q (workspace = workspace )
604- | models .Q (
605- dataset__shared_with_organization = True ,
606- workspace__organization = workspace .organization ,
607- ),
608- )
609- )
610- else :
611- return self .optimize_query (
612- self ._filter_for_user_and_query_object (
613- user ,
614- models .Q (workspace__members = user )
615- | models .Q (
616- dataset__shared_with_organization = True ,
617- workspace__organization__organizationmembership__user = user ,
618- ),
619- return_all_if_superuser = True ,
620- return_all_if_organization_admin_or_owner = True ,
569+ def filter_for_user (self , user : AnonymousUser | UserInterface ) -> models .QuerySet :
570+ from hexa .workspaces .models import Workspace
571+
572+ accessible_workspaces = Workspace .objects .filter_for_user (user )
573+ accessible_organizations = Organization .objects .filter_for_user (
574+ user , direct_membership_only = True
575+ )
576+ return self .optimize_query (
577+ self .filter (
578+ Q (workspace__in = accessible_workspaces )
579+ | Q (
580+ dataset__shared_with_organization = True ,
581+ workspace__organization__in = accessible_organizations ,
621582 )
622- )
583+ ).distinct ()
584+ )
623585
624586
625587class DatasetLink (Base ):
0 commit comments