diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/models/specification.py b/SAS/TMSS/backend/src/tmss/tmssapp/models/specification.py index b48d8f43cf34f46c863f328d502470141225e116..825785792a643ab25bf61be66a3e589b1651b725 100644 --- a/SAS/TMSS/backend/src/tmss/tmssapp/models/specification.py +++ b/SAS/TMSS/backend/src/tmss/tmssapp/models/specification.py @@ -19,6 +19,24 @@ from collections import Counter from django.utils.functional import cached_property +# +# Mixins +# + +class ProjectPropertyMixin: + @cached_property + def project(self): # -> Project: + '''return the related project of this task + ''' + if not hasattr(self, 'path_to_project'): + return TMSSException("Please define a 'path_to_project' attribute on the object for the ProjectPropertyMixin to function.") + obj = self + for attr in self.path_to_project.split('__'): + obj = getattr(obj, attr) + if attr == 'project': + return obj + + # # I/O # @@ -593,19 +611,6 @@ class SchedulingUnitBlueprint(NamedCommon): ''' return self.draft.scheduling_set.project -class ProjectPropertyMixin: - @cached_property - def project(self) -> Project: - '''return the related project of this task - ''' - if not hasattr(self, 'path_to_project'): - return TMSSException("Please define a 'path_to_project' attribute on the object for the ProjectPropertyMixin to function.") - obj = self - for attr in self.path_to_project.split('__'): - obj = getattr(obj, attr) - if attr == 'project': - return obj - class TaskDraft(NamedCommon, ProjectPropertyMixin): specifications_doc = JSONField(help_text='Specifications for this task.') diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/permissions.py b/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/permissions.py index 419f98d0e4f6ac59627b6a845eaf1a29f0c8ce2a..66124b5c3ba77f70eecd7533369037b9d1f5d88e 100644 --- a/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/permissions.py +++ b/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/permissions.py @@ -104,7 +104,14 @@ class IsProjectMember(drf_permissions.DjangoObjectPermissions): return True else: related_project = None - logger.error("No project property on object %s, so cannot check project permission.") + logger.error("No project property on object %s, so cannot check project permission." % obj) + # todo: how to deal with objects that do not have a unique project associated to them? + # Do need users need the required role in all of them? Or just one? + # Also, we need to support lists in 'path_to_project' if we want to handle this. + if issubclass(type(obj), models.Template) and view.action == 'default': + # todo: review this. Why is the default action called in a GET of an object that has a template reference? + logger.warning("'%s' is a Template and action is '%s' so granting object access nonetheless." % (obj, view.action)) + return True logger.info('User=%s is not permitted to access object=%s with related project=%s since it requires one of project_roles=%s' % (request.user, obj, related_project, permitted_project_roles)) logger.info('### IsProjectMember.has_object_permission %s False' % (request._request)) @@ -257,7 +264,8 @@ class IsProjectMemberFilterBackend(drf_filters.BaseFilterBackend): permitted_fetched_objects = [] not_permitted = [o for o in queryset if o not in permitted_fetched_objects] - logger.info('User=%s is not permitted to access objects=%s with related projects=%s' % (request.user, not_permitted, [o.project for o in not_permitted if hasattr(o, 'project')])) + logger.info('### User=%s is not permitted to access objects=%s with related projects=%s' % (request.user, not_permitted, [o.project for o in not_permitted if hasattr(o, 'project')])) + logger.info('### User=%s is permitted to access objects=%s with related projects=%s' % (request.user, permitted_fetched_objects, [o.project for o in permitted_fetched_objects if hasattr(o, 'project')])) # we could return the list of objects, which seems to work if you don't touch the get_queryset. # But are supposed to return a queryset instead, so we make a new one, even though we fetched already. diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/specification.py b/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/specification.py index 721aef8d1b5a812cb670963bab1b59d0d24a8c19..9ddd07ce5f278d2d9ec00a9270c83516f7397182 100644 --- a/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/specification.py +++ b/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/specification.py @@ -778,10 +778,6 @@ class TaskDraftViewSet(LOFARViewSet): @action(methods=['get'], detail=True, url_name="create_task_blueprint", name="Create TaskBlueprint") # todo: I think these actions should be 'post'-only, since they alter the DB ?! def create_task_blueprint(self, request, pk=None): task_draft = get_object_or_404(models.TaskDraft, pk=pk) - # todo: it seems there is no way to pass IsProjectMember as a permission class to actions without explicitly - # registering them in the router for some reason. but explicitly doing a check_object_permission works as well. - # We may want to extend get_object_or_404 to also perform a permission check before returning an object...? - self.check_object_permissions(self.request, task_draft) # or request.user.has_perm('create_task_blueprint') task_blueprint = create_task_blueprint_from_task_draft(task_draft) # url path magic to construct the new task_blueprint_path url