55from mavedb .db .base import Base
66from mavedb .lib .authentication import UserData
77from mavedb .lib .logging .context import logging_context , save_to_logging_context
8+ from mavedb .models .collection import Collection
9+ from mavedb .models .enums .contribution_role import ContributionRole
810from mavedb .models .enums .user_role import UserRole
911from mavedb .models .experiment import Experiment
1012from mavedb .models .experiment_set import ExperimentSet
1517
1618
1719class Action (Enum ):
20+ LOOKUP = "lookup"
1821 READ = "read"
1922 UPDATE = "update"
2023 DELETE = "delete"
@@ -23,6 +26,7 @@ class Action(Enum):
2326 SET_SCORES = "set_scores"
2427 ADD_ROLE = "add_role"
2528 PUBLISH = "publish"
29+ ADD_BADGE = "add_badge"
2630
2731
2832class PermissionResponse :
@@ -33,9 +37,15 @@ def __init__(self, permitted: bool, http_code: int = 403, message: Optional[str]
3337
3438 save_to_logging_context ({"permission_message" : self .message , "access_permitted" : self .permitted })
3539 if self .permitted :
36- logger .debug (msg = "Access to the requested resource is permitted." , extra = logging_context ())
40+ logger .debug (
41+ msg = "Access to the requested resource is permitted." ,
42+ extra = logging_context (),
43+ )
3744 else :
38- logger .debug (msg = "Access to the requested resource is not permitted." , extra = logging_context ())
45+ logger .debug (
46+ msg = "Access to the requested resource is not permitted." ,
47+ extra = logging_context (),
48+ )
3949
4050
4151class PermissionException (Exception ):
@@ -59,6 +69,7 @@ def has_permission(user_data: Optional[UserData], item: Base, action: Action) ->
5969 user_is_owner = False
6070 user_is_self = False
6171 user_may_edit = False
72+ user_may_view_private = False
6273 active_roles = user_data .active_roles if user_data else []
6374
6475 if isinstance (item , ExperimentSet ) or isinstance (item , Experiment ) or isinstance (item , ScoreSet ):
@@ -72,6 +83,27 @@ def has_permission(user_data: Optional[UserData], item: Base, action: Action) ->
7283
7384 save_to_logging_context ({"resource_is_published" : published })
7485
86+ if isinstance (item , Collection ):
87+ assert item .private is not None
88+ private = item .private
89+ published = item .private is False
90+ user_is_owner = item .created_by_id == user_data .user .id if user_data is not None else False
91+ admin_user_ids = set ()
92+ editor_user_ids = set ()
93+ viewer_user_ids = set ()
94+ for user_association in item .user_associations :
95+ if user_association .contribution_role == ContributionRole .admin :
96+ admin_user_ids .add (user_association .user_id )
97+ elif user_association .contribution_role == ContributionRole .editor :
98+ editor_user_ids .add (user_association .user_id )
99+ elif user_association .contribution_role == ContributionRole .viewer :
100+ viewer_user_ids .add (user_association .user_id )
101+ user_is_admin = user_is_owner or (user_data is not None and user_data .user .id in admin_user_ids )
102+ user_may_edit = user_is_admin or (user_data is not None and user_data .user .id in editor_user_ids )
103+ user_may_view_private = user_may_edit or (user_data is not None and (user_data .user .id in viewer_user_ids ))
104+
105+ save_to_logging_context ({"resource_is_published" : published })
106+
75107 if isinstance (item , User ):
76108 user_is_self = item .id == user_data .user .id if user_data is not None else False
77109 user_may_edit = user_is_self
@@ -254,7 +286,109 @@ def has_permission(user_data: Optional[UserData], item: Base, action: Action) ->
254286 else :
255287 raise NotImplementedError (f"has_permission(User, ScoreSet, { action } , Role)" )
256288
289+ elif isinstance (item , Collection ):
290+ if action == Action .READ :
291+ if user_may_view_private or not private :
292+ return PermissionResponse (True )
293+ # Roles which may perform this operation.
294+ elif roles_permitted (active_roles , [UserRole .admin ]):
295+ return PermissionResponse (True )
296+ elif private :
297+ # Do not acknowledge the existence of a private entity.
298+ return PermissionResponse (False , 404 , f"collection with URN '{ item .urn } ' not found" )
299+ elif user_data is None or user_data .user is None :
300+ return PermissionResponse (False , 401 , f"insufficient permissions for URN '{ item .urn } '" )
301+ else :
302+ return PermissionResponse (False , 403 , f"insufficient permissions for URN '{ item .urn } '" )
303+ elif action == Action .UPDATE :
304+ if user_may_edit :
305+ return PermissionResponse (True )
306+ # Roles which may perform this operation.
307+ elif roles_permitted (active_roles , [UserRole .admin ]):
308+ return PermissionResponse (True )
309+ elif private and not user_may_view_private :
310+ # Do not acknowledge the existence of a private entity.
311+ return PermissionResponse (False , 404 , f"score set with URN '{ item .urn } ' not found" )
312+ elif user_data is None or user_data .user is None :
313+ return PermissionResponse (False , 401 , f"insufficient permissions for URN '{ item .urn } '" )
314+ else :
315+ return PermissionResponse (False , 403 , f"insufficient permissions for URN '{ item .urn } '" )
316+ elif action == Action .DELETE :
317+ # A collection may be deleted even if it has been published, as long as it is not an official collection.
318+ if user_is_owner :
319+ return PermissionResponse (
320+ not item .badge_name ,
321+ 403 ,
322+ f"insufficient permissions for URN '{ item .urn } '" ,
323+ )
324+ # MaveDB admins may delete official collections.
325+ elif roles_permitted (active_roles , [UserRole .admin ]):
326+ return PermissionResponse (True )
327+ elif private and not user_may_view_private :
328+ # Do not acknowledge the existence of a private entity.
329+ return PermissionResponse (False , 404 , f"collection with URN '{ item .urn } ' not found" )
330+ else :
331+ return PermissionResponse (False )
332+ elif action == Action .PUBLISH :
333+ if user_is_admin :
334+ return PermissionResponse (True )
335+ elif roles_permitted (active_roles , []):
336+ return PermissionResponse (True )
337+ elif private and not user_may_view_private :
338+ # Do not acknowledge the existence of a private entity.
339+ return PermissionResponse (False , 404 , f"score set with URN '{ item .urn } ' not found" )
340+ else :
341+ return PermissionResponse (False )
342+ elif action == Action .ADD_SCORE_SET :
343+ # Whether the collection is private or public, only permitted users can add a score set to a collection.
344+ if user_may_edit or roles_permitted (active_roles , [UserRole .admin ]):
345+ return PermissionResponse (True )
346+ elif private and not user_may_view_private :
347+ return PermissionResponse (False , 404 , f"collection with URN '{ item .urn } ' not found" )
348+ else :
349+ return PermissionResponse (False , 403 , f"insufficient permissions for URN '{ item .urn } '" )
350+ elif action == Action .ADD_EXPERIMENT :
351+ # Only permitted users can add an experiment to an existing collection.
352+ return PermissionResponse (
353+ user_may_edit or roles_permitted (active_roles , [UserRole .admin ]),
354+ 404 if private and not user_may_view_private else 403 ,
355+ (
356+ f"collection with URN '{ item .urn } ' not found"
357+ if private and not user_may_view_private
358+ else f"insufficient permissions for URN '{ item .urn } '"
359+ ),
360+ )
361+ elif action == Action .ADD_ROLE :
362+ # Both collection admins and MaveDB admins can add a user to a collection role
363+ if user_is_admin or roles_permitted (active_roles , [UserRole .admin ]):
364+ return PermissionResponse (True )
365+ else :
366+ return PermissionResponse (False , 403 , "Insufficient permissions to add user role." )
367+ # only MaveDB admins may add a badge name to a collection, which makes the collection considered "official"
368+ elif action == Action .ADD_BADGE :
369+ # Roles which may perform this operation.
370+ if roles_permitted (active_roles , [UserRole .admin ]):
371+ return PermissionResponse (True )
372+ elif private :
373+ # Do not acknowledge the existence of a private entity.
374+ return PermissionResponse (False , 404 , f"collection with URN '{ item .urn } ' not found" )
375+ elif user_data is None or user_data .user is None :
376+ return PermissionResponse (False , 401 , f"insufficient permissions for URN '{ item .urn } '" )
377+ else :
378+ return PermissionResponse (False , 403 , f"insufficient permissions for URN '{ item .urn } '" )
379+ else :
380+ raise NotImplementedError (f"has_permission(User, ScoreSet, { action } , Role)" )
381+
257382 elif isinstance (item , User ):
383+ if action == Action .LOOKUP :
384+ # any existing user can look up any mavedb user by Orcid ID
385+ # lookup differs from read because lookup means getting the first name, last name, and orcid ID of the user,
386+ # while read means getting an admin view of the user's details
387+ if user_data is not None and user_data .user is not None :
388+ return PermissionResponse (True )
389+ else :
390+ # TODO is this inappropriately acknowledging the existence of the user?
391+ return PermissionResponse (False , 401 , "Insufficient permissions for user lookup." )
258392 if action == Action .READ :
259393 if user_is_self :
260394 return PermissionResponse (True )
0 commit comments