2626)
2727from sanic .log import logger
2828from sqlalchemy .ext .asyncio import AsyncSession
29+ from ulid import ULID
2930
3031from renku_data_services import base_models
3132from renku_data_services .authz .config import AuthzConfig
@@ -132,8 +133,8 @@ class AuthzOperation(StrEnum):
132133
133134class _AuthzConverter :
134135 @staticmethod
135- def project (id : str ) -> ObjectReference :
136- return ObjectReference (object_type = ResourceType .project .value , object_id = id )
136+ def project (id : ULID ) -> ObjectReference :
137+ return ObjectReference (object_type = ResourceType .project .value , object_id = str ( id ) )
137138
138139 @staticmethod
139140 def user (id : str | None ) -> ObjectReference :
@@ -170,9 +171,9 @@ def user_namespace(id: ULID) -> ObjectReference:
170171 return ObjectReference (object_type = ResourceType .user_namespace , object_id = str (id ))
171172
172173 @staticmethod
173- def to_object (resource_type : ResourceType , resource_id : str | int ) -> ObjectReference :
174+ def to_object (resource_type : ResourceType , resource_id : str | ULID | int ) -> ObjectReference :
174175 match (resource_type , resource_id ):
175- case (ResourceType .project , sid ) if isinstance (sid , str ):
176+ case (ResourceType .project , sid ) if isinstance (sid , ULID ):
176177 return _AuthzConverter .project (sid )
177178 case (ResourceType .user , sid ) if isinstance (sid , str ) or sid is None :
178179 return _AuthzConverter .user (sid )
@@ -242,23 +243,26 @@ async def decorated_function(
242243 return decorator
243244
244245
246+ T = TypeVar ("T" , str , ULID )
247+
248+
245249def _is_allowed (
246250 operation : Scope ,
247251) -> Callable [
248- [Callable [Concatenate ["Authz" , base_models .APIUser , ResourceType , str , _P ], Awaitable [_T ]]],
249- Callable [Concatenate ["Authz" , base_models .APIUser , ResourceType , str , _P ], Awaitable [_T ]],
252+ [Callable [Concatenate ["Authz" , base_models .APIUser , ResourceType , T , _P ], Awaitable [_T ]]],
253+ Callable [Concatenate ["Authz" , base_models .APIUser , ResourceType , T , _P ], Awaitable [_T ]],
250254]:
251255 """A decorator that checks if the operation on a resource is allowed or not."""
252256
253257 def decorator (
254- f : Callable [Concatenate ["Authz" , base_models .APIUser , ResourceType , str , _P ], Awaitable [_T ]],
255- ) -> Callable [Concatenate ["Authz" , base_models .APIUser , ResourceType , str , _P ], Awaitable [_T ]]:
258+ f : Callable [Concatenate ["Authz" , base_models .APIUser , ResourceType , T , _P ], Awaitable [_T ]],
259+ ) -> Callable [Concatenate ["Authz" , base_models .APIUser , ResourceType , T , _P ], Awaitable [_T ]]:
256260 @wraps (f )
257261 async def decorated_function (
258262 self : "Authz" ,
259263 user : base_models .APIUser ,
260264 resource_type : ResourceType ,
261- resource_id : str ,
265+ resource_id : T ,
262266 * args : _P .args ,
263267 ** kwargs : _P .kwargs ,
264268 ) -> _T :
@@ -294,7 +298,7 @@ def client(self) -> AsyncClient:
294298 return self ._client
295299
296300 async def _has_permission (
297- self , user : base_models .APIUser , resource_type : ResourceType , resource_id : str | None , scope : Scope
301+ self , user : base_models .APIUser , resource_type : ResourceType , resource_id : str | ULID | None , scope : Scope
298302 ) -> tuple [bool , ZedToken | None ]:
299303 """Checks whether the provided user has a specific permission on the specific resource."""
300304 if not resource_id :
@@ -317,7 +321,7 @@ async def _has_permission(
317321 return response .permissionship == CheckPermissionResponse .PERMISSIONSHIP_HAS_PERMISSION , response .checked_at
318322
319323 async def has_permission (
320- self , user : base_models .APIUser , resource_type : ResourceType , resource_id : str , scope : Scope
324+ self , user : base_models .APIUser , resource_type : ResourceType , resource_id : str | ULID , scope : Scope
321325 ) -> bool :
322326 """Checks whether the provided user has a specific permission on the specific resource."""
323327 res , _ = await self ._has_permission (user , resource_type , resource_id , scope )
@@ -386,12 +390,13 @@ async def members(
386390 self ,
387391 user : base_models .APIUser ,
388392 resource_type : ResourceType ,
389- resource_id : str ,
393+ resource_id : _T ,
390394 role : Role | None = None ,
391395 * ,
392396 zed_token : ZedToken | None = None ,
393397 ) -> list [Member ]:
394398 """Get all users that are members of a resource, if role is None then all roles are retrieved."""
399+ resource_id : str = str (resource_id )
395400 consistency = Consistency (at_least_as_fresh = zed_token ) if zed_token else Consistency (fully_consistent = True )
396401 sub_filter = SubjectFilter (subject_type = ResourceType .user .value )
397402 rel_filter = RelationshipFilter (
@@ -500,7 +505,7 @@ async def _get_authz_change(
500505 )
501506 authz_change .extend (db_repo .authz ._add_user_namespace (res .namespace ))
502507 case _:
503- resource_id : str | None = "unknown"
508+ resource_id : str | ULID | None = "unknown"
504509 if isinstance (result , (Project , Namespace , Group )):
505510 resource_id = result .id
506511 elif isinstance (result , (ProjectUpdate , NamespaceUpdate , GroupUpdate )):
@@ -619,7 +624,7 @@ async def _remove_project(
619624 ) -> _AuthzChange :
620625 """Remove the relationships associated with the project."""
621626 consistency = Consistency (at_least_as_fresh = zed_token ) if zed_token else Consistency (fully_consistent = True )
622- rel_filter = RelationshipFilter (resource_type = ResourceType .project .value , optional_resource_id = project .id )
627+ rel_filter = RelationshipFilter (resource_type = ResourceType .project .value , optional_resource_id = str ( project .id ) )
623628 responses : AsyncIterable [ReadRelationshipsResponse ] = self .client .ReadRelationships (
624629 ReadRelationshipsRequest (consistency = consistency , relationship_filter = rel_filter )
625630 )
@@ -640,6 +645,7 @@ async def _update_project_visibility(
640645 self , user : base_models .APIUser , project : Project , * , zed_token : ZedToken | None = None
641646 ) -> _AuthzChange :
642647 """Update the visibility of the project in the authorization database."""
648+ project_id = str (project .id )
643649 consistency = Consistency (at_least_as_fresh = zed_token ) if zed_token else Consistency (fully_consistent = True )
644650 project_res = _AuthzConverter .project (project .id )
645651 all_users_sub = SubjectReference (object = _AuthzConverter .all_users ())
@@ -668,7 +674,7 @@ async def _update_project_visibility(
668674 )
669675 rel_filter = RelationshipFilter (
670676 resource_type = ResourceType .project .value ,
671- optional_resource_id = project . id ,
677+ optional_resource_id = project_id ,
672678 optional_subject_filter = SubjectFilter (
673679 subject_type = ResourceType .user .value , optional_subject_id = all_users_sub .object .object_id
674680 ),
@@ -678,7 +684,7 @@ async def _update_project_visibility(
678684 )
679685 rel_filter = RelationshipFilter (
680686 resource_type = ResourceType .project .value ,
681- optional_resource_id = project . id ,
687+ optional_resource_id = project_id ,
682688 optional_subject_filter = SubjectFilter (
683689 subject_type = ResourceType .anonymous_user .value ,
684690 optional_subject_id = anon_users_sub .object .object_id ,
@@ -724,11 +730,12 @@ async def _update_project_namespace(
724730 self , user : base_models .APIUser , project : Project , * , zed_token : ZedToken | None = None
725731 ) -> _AuthzChange :
726732 """Update the namespace/group of the project in the authorization database."""
733+ project_id = str (project .id )
727734 consistency = Consistency (at_least_as_fresh = zed_token ) if zed_token else Consistency (fully_consistent = True )
728735 project_res = _AuthzConverter .project (project .id )
729736 project_namespace_filter = RelationshipFilter (
730737 resource_type = ResourceType .project .value ,
731- optional_resource_id = project . id ,
738+ optional_resource_id = str ( project_id ) ,
732739 optional_relation = _Relation .project_namespace .value ,
733740 )
734741 current_namespace : ReadRelationshipsResponse | None = await anext (
@@ -808,7 +815,7 @@ async def upsert_project_members(
808815 self ,
809816 user : base_models .APIUser ,
810817 resource_type : ResourceType ,
811- resource_id : str ,
818+ resource_id : ULID ,
812819 members : list [Member ],
813820 * ,
814821 zed_token : ZedToken | None = None ,
@@ -823,7 +830,8 @@ async def upsert_project_members(
823830 undo : list [RelationshipUpdate ] = []
824831 output : list [MembershipChange ] = []
825832 expected_user_roles = {_Relation .viewer .value , _Relation .owner .value , _Relation .editor .value }
826- existing_owners_rels = await self ._get_resource_owners (resource_type , resource_id , consistency )
833+ resource_id_str = str (resource_id )
834+ existing_owners_rels = await self ._get_resource_owners (resource_type , resource_id_str , consistency )
827835 n_existing_owners = len (existing_owners_rels )
828836 for member in members :
829837 rel = Relationship (
@@ -833,7 +841,7 @@ async def upsert_project_members(
833841 )
834842 existing_rel_filter = RelationshipFilter (
835843 resource_type = resource_type .value ,
836- optional_resource_id = resource_id ,
844+ optional_resource_id = resource_id_str ,
837845 optional_subject_filter = SubjectFilter (
838846 subject_type = ResourceType .user , optional_subject_id = member .user_id
839847 ),
@@ -926,12 +934,13 @@ async def remove_project_members(
926934 self ,
927935 user : base_models .APIUser ,
928936 resource_type : ResourceType ,
929- resource_id : str ,
937+ resource_id : ULID ,
930938 user_ids : list [str ],
931939 * ,
932940 zed_token : ZedToken | None = None ,
933941 ) -> list [MembershipChange ]:
934942 """Remove the specific members from the project, then return the list of members that were removed."""
943+ resource_id : str = str (resource_id )
935944 consistency = Consistency (at_least_as_fresh = zed_token ) if zed_token else Consistency (fully_consistent = True )
936945 add_members : list [RelationshipUpdate ] = []
937946 remove_members : list [RelationshipUpdate ] = []
0 commit comments