Skip to content
Snippets Groups Projects
Commit a84e53e7 authored by Jörn Künsemöller's avatar Jörn Künsemöller
Browse files

TMSS-2131: Fix illegal blueprint update violation in QA workflow and fix...

TMSS-2131: Fix illegal blueprint update violation in QA workflow and fix object permissions for workflows.
parent 2bf7877e
No related branches found
No related tags found
1 merge request!1002TMSS-2131: Fix illegal blueprint update violation in QA workflow and fix...
......@@ -5,7 +5,7 @@ This file contains permissions and filters that are used in the viewsets
from rest_framework import permissions as drf_permissions, filters as drf_filters
from .. import models
from lofar.sas.tmss.tmss.exceptions import *
from django.core.exceptions import ObjectDoesNotExist
from django.core.exceptions import ObjectDoesNotExist, FieldError
import logging
logger = logging.getLogger(__name__)
logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', level=logging.INFO)
......@@ -40,20 +40,27 @@ def get_project_roles_for_user(user):
# todo: remove these test users when not needed any more (required for local frontend testing where Keycloak is unavailable)
if user == User.objects.get(username='tmss_friend'):
return ({'project': 'high', 'role': 'friend_of_project'},
{'project': 'high', 'role': 'friend_of_project_primary'})
{'project': 'high', 'role': 'friend_of_project_primary'},
{'project': 'high', 'role': 'co_i'})
if user == User.objects.get(username='tmss_friend2'):
return ({'project': 'high', 'role': 'friend_of_project'},)
return ({'project': 'high', 'role': 'friend_of_project'},
{'project': 'high', 'role': 'co_i'})
if user == User.objects.get(username='tmss_contact'):
return ({'project': 'high', 'role': 'contact'},)
return ({'project': 'high', 'role': 'contact'},
{'project': 'high', 'role': 'co_i'})
if user == User.objects.get(username='tmss_PI'):
return ({'project': 'high', 'role': 'pi'},)
return ({'project': 'high', 'role': 'pi'},
{'project': 'high', 'role': 'co_i'})
if user == User.objects.get(username='tmss_shared_support'):
return ({'project': 'high', 'role': 'shared_support'},
{'project': 'high', 'role': 'shared_support_primary'})
{'project': 'high', 'role': 'shared_support_primary'},
{'project': 'high', 'role': 'co_i'})
if user == User.objects.get(username='tmss_shared_support2'):
return ({'project': 'high', 'role': 'shared_support'},)
return ({'project': 'high', 'role': 'shared_support'},
{'project': 'high', 'role': 'co_i'})
if user == User.objects.get(username='scientist'):
return ({'project': 'high', 'role': 'co_i'},)
return ({'project': 'high', 'role': 'co_i'},
{'project': 'high', 'role': 'co_i'})
try:
return tuple(user.project_roles)
......@@ -146,7 +153,12 @@ class IsProjectMember(drf_permissions.DjangoObjectPermissions):
# We need to check the project name before the object is created, but project is an object property.
# Turning the path to the project into a (static) model attribute, allows us to use it both in the object
# property as well as here (where we did not create an object yet).
if view.action == 'create' and request.data:
# Note: the Viewflow actions are currently create actions but they do not actually create anything
# (and do not contain the expected data to create an instance of the underlying model, but instead e.g. the
# data required to perform a workflow step). So we exclude the offending views here.
if view.action == 'create' and request.data \
and not 'SchedulingUnitTaskExecuteViewSet' in str(view) \
and not 'SchedulingUnitTaskAssignViewSet' in str(view):
obj = None
if view.serializer_class.Meta.model == models.Project:
return False # project creation solely depends on system role
......@@ -298,7 +310,17 @@ class IsProjectMemberFilterBackend(drf_filters.BaseFilterBackend):
return permitted_projects
if hasattr(view.serializer_class.Meta.model, 'path_to_project'):
return queryset.filter(**{f"{view.serializer_class.Meta.model.path_to_project}__in": permitted_projects})
path_to_project = view.serializer_class.Meta.model.path_to_project
try:
return queryset.filter(**{f"{path_to_project}__in": permitted_projects})
except FieldError as e:
# this is expected for models.schedulingunitflow.SchedulingUnitTask
logger.warning('Cannot perform in-db filtering for project permissions on model=%s with path_to_project=%s: %s -- applying slow filtering based on project property.' % (view.serializer_class.Meta.model, path_to_project, e))
ids = []
for candidate in queryset.all():
if candidate.project in permitted_projects:
ids.append(candidate.id)
return queryset.filter(id__in=ids)
if hasattr(view.serializer_class.Meta.model, 'project'):
return queryset.filter(project__in=permitted_projects)
......
......@@ -280,10 +280,10 @@ if isViewflowEnabled():
viewflow_router.register('scheduling_unit_flow/qa_decide_acceptance', workflow_viewsets.DecideAcceptanceViewSet, basename='qa_decide_acceptance')
viewflow_router.register('scheduling_unit_flow/qa_scheduling_unit_process', workflow_viewsets.SchedulingUnitProcessViewSet, basename='qa_scheduling_unit_process')
viewflow_router.register('scheduling_unit_flow/qa_scheduling_unit_task', workflow_viewsets.SchedulingUnitTaskViewSet, basename='qa_scheduling_unit_task')
viewflow_router.register(r'scheduling_unit_flow/qa_scheduling_unit_task/(?P<qa_scheduling_unit_task_id>\d+)/assign', workflow_viewsets.SchedulingUnitTaskAssignViewSet)
viewflow_router.register(r'scheduling_unit_flow/qa_scheduling_unit_task/(?P<qa_scheduling_unit_task_id>\d+)/unassign', workflow_viewsets.SchedulingUnitTaskUnassignViewSet)
viewflow_router.register(r'scheduling_unit_flow/qa_scheduling_unit_process/(?P<qa_scheduling_unit_process_id>\d+)/current_task', workflow_viewsets.SchedulingUnitGetActiveTasksViewSet)
viewflow_router.register(r'scheduling_unit_flow/qa_scheduling_unit_process/(?P<qa_scheduling_unit_process_id>\d+)/perform', workflow_viewsets.SchedulingUnitTaskExecuteViewSet)
viewflow_router.register(r'scheduling_unit_flow/qa_scheduling_unit_task/(?P<pk>\d+)/assign', workflow_viewsets.SchedulingUnitTaskAssignViewSet)
viewflow_router.register(r'scheduling_unit_flow/qa_scheduling_unit_task/(?P<pk>\d+)/unassign', workflow_viewsets.SchedulingUnitTaskUnassignViewSet)
viewflow_router.register(r'scheduling_unit_flow/qa_scheduling_unit_process/(?P<pk>\d+)/current_task', workflow_viewsets.SchedulingUnitGetActiveTasksViewSet)
viewflow_router.register(r'scheduling_unit_flow/qa_scheduling_unit_process/(?P<pk>\d+)/perform', workflow_viewsets.SchedulingUnitTaskExecuteViewSet)
viewflow_urlpatterns.extend(viewflow_router.urls)
......
......@@ -323,10 +323,11 @@ class SchedulingUnitFlow(Flow):
def signal_SUB_allow_ingest(self, activation):
logger.info("granting ingest permission for scheduling unit blueprint id=%s", activation.process.su.id)
if activation.process.su.ingest_permission_granted_since is None:
activation.process.su.ingest_permission_granted_since = round_to_second_precision(datetime.utcnow())
activation.process.su.ingest_permission_required = True
else:
logger.warning("ingest permission on scheduling unit blueprint id=%s has already been granted on %s" % (activation.process.su.id, activation.process.su.ingest_permission_granted_since))
activation.process.su.save()
activation.process.save()
def check_SchedulingUnitBlueprint_status_or_beyond(self, activation, scheduling_unit: models.SchedulingUnitBlueprint, expected_status):
......
......@@ -11,29 +11,29 @@ from lofar.sas.tmss.tmss.tmssapp.models.common import ProjectPropertyMixin
class QAReportingTO(Model, ProjectPropertyMixin):
operator_report = TextField()
operator_accept = BooleanField(default=False)
path_to_project = 'flow_process__su__draft__scheduling_set__project' # todo: currently not used, verify in TMSS-982 / TMSS-2054
path_to_project = 'flow_process__su__draft__scheduling_set__project'
class QAReportingSOS(Model, ProjectPropertyMixin):
sos_report = TextField()
quality_within_policy = BooleanField(default=False)
sos_accept_show_pi = BooleanField(default=False)
path_to_project = 'flow_process__su__draft__scheduling_set__project' # todo: currently not used, verify in TMSS-982 / TMSS-2054
path_to_project = 'flow_process__su__draft__scheduling_set__project'
class PIVerification(Model, ProjectPropertyMixin):
pi_report = TextField()
pi_accept = BooleanField(default=False)
path_to_project = 'flow_process__su__draft__scheduling_set__project' # todo: currently not used, verify in TMSS-982 / TMSS-2054
path_to_project = 'flow_process__su__draft__scheduling_set__project'
class DecideAcceptance(Model, ProjectPropertyMixin):
sos_accept_after_pi = BooleanField(default=False)
path_to_project = 'flow_process__su__draft__scheduling_set__project' # todo: currently not used, verify in TMSS-982 / TMSS-2054
path_to_project = 'flow_process__su__draft__scheduling_set__project'
class UnpinData(Model, ProjectPropertyMixin):
unpin_data = BooleanField(default=False)
path_to_project = 'flow_process__su__draft__scheduling_set__project' # todo: currently not used, verify in TMSS-982 / TMSS-2054
path_to_project = 'flow_process__su__draft__scheduling_set__project'
class SchedulingUnitTask(Task, ProjectPropertyMixin):
path_to_project ='flow_process__su__draft__scheduling_set__project' # todo: currently not used, verify in TMSS-982 / TMSS-2054
path_to_project = 'flow_process__su__draft__scheduling_set__project' # Note: flow_process is not a db field but a property here
class SchedulingUnitProcess(Process, ProjectPropertyMixin):
"""
......
......@@ -133,6 +133,7 @@ class SchedulingUnitTaskViewSet(PreventSaveByUnassignedUsersMixin, TMSSFilterAnd
class TMSSPermissionView(generic.CreateView):
# todo: comment in to marshal permissions on the viewflow-internal views, if required.
# Or rather merge with TMSSPermissionGenericViewSet
# -> TMSS-982 / TMSS-2054
# permission_classes = (TMSSPermissions,)
pass
......@@ -279,7 +280,7 @@ class SchedulingUnitTaskAssignViewSet(mixins.CreateModelMixin,
422: 'error'},
operation_description="Assign a Scheduling Unit Task to an user")
def create(self, request, *args, **kwargs):
if 'qa_scheduling_unit_task_id' in kwargs:
if 'pk' in kwargs:
try:
user_email = request.GET.get('user_email')
if user_email: # For some reason this is GET data, even though it's a POST action...
......@@ -288,7 +289,7 @@ class SchedulingUnitTaskAssignViewSet(mixins.CreateModelMixin,
return Response(content, status=status.HTTP_422_UNPROCESSABLE_ENTITY)
user, _ = User.objects.get_or_create(email=user_email, defaults={'username': 'user_%s' % uuid.uuid4()}) # Users that log in via Keycloak are matched with Django users with same email address
elif request.GET.get('project_role'): # For some reason this is GET data, even though it's a POST action...
task = models.SchedulingUnitTask.objects.filter(id=self.kwargs['qa_scheduling_unit_task_id'])[0]
task = models.SchedulingUnitTask.objects.filter(id=self.kwargs['pk'])[0]
scheduling_unit_blueprint = task.flow_process.su
project = scheduling_unit_blueprint.project.name
users = get_users_by_role_in_project(role=request.GET.get('project_role'), project=project)
......@@ -304,7 +305,7 @@ class SchedulingUnitTaskAssignViewSet(mixins.CreateModelMixin,
user, _ = User.objects.get_or_create(email=user_email, defaults={'username': 'user_%s' % uuid.uuid4()})
else:
user = self.request.user
models.SchedulingUnitTask.objects.filter(id=self.kwargs['qa_scheduling_unit_task_id'])[0].activate().assign(user)
models.SchedulingUnitTask.objects.filter(id=self.kwargs['pk'])[0].activate().assign(user)
content = {'Assigned': 'Scheduling Unit Task assigned to user=%s' % user.email}
return Response(content, status=status.HTTP_200_OK)
except AttributeError:
......@@ -329,9 +330,9 @@ class SchedulingUnitTaskUnassignViewSet(mixins.CreateModelMixin,
422: 'error'},
operation_description="Unassign a Scheduling Unit Task")
def create(self, request, *args, **kwargs):
if 'qa_scheduling_unit_task_id' in kwargs:
if 'pk' in kwargs:
try:
models.SchedulingUnitTask.objects.filter(id=self.kwargs['qa_scheduling_unit_task_id'])[0].activate().unassign()
models.SchedulingUnitTask.objects.filter(id=self.kwargs['pk'])[0].activate().unassign()
content = {'Unassign': 'Scheduling Unit Task unassigned'}
return Response(content, status=status.HTTP_200_OK)
except AttributeError:
......@@ -354,10 +355,10 @@ class SchedulingUnitGetActiveTasksViewSet(mixins.CreateModelMixin,
403: 'forbidden',
422: 'error'},
operation_description="Get the list of active tasks.")
def create(self, request, *args, **kwargs):
if 'qa_scheduling_unit_process_id' in kwargs:
def create(self, request, *args, **kwargs): # todo: should be 'get'
if 'pk' in kwargs:
try:
tasks = models.SchedulingUnitProcess.objects.filter(id=self.kwargs['qa_scheduling_unit_process_id'])[0].active_tasks()
tasks = models.SchedulingUnitProcess.objects.filter(id=self.kwargs['pk'])[0].active_tasks()
return JsonResponse(list(tasks.values()), safe=False)
except IndexError:
content = {'Get Active Task(s)': 'No Process with the specified id'}
......@@ -377,14 +378,14 @@ class SchedulingUnitTaskExecuteViewSet(mixins.CreateModelMixin,
@swagger_auto_schema(responses={200: '',
403: 'forbidden',
422: 'error'},
operation_description="Unassign a Scheduling Unit Task")
operation_description="Perform a Scheduling Unit Task")
def create(self, request, *args, **kwargs):
if 'qa_scheduling_unit_process_id' in kwargs:
if 'pk' in kwargs:
try:
process= models.SchedulingUnitProcess.objects.get(pk=self.kwargs['qa_scheduling_unit_process_id'])
task = models.SchedulingUnitProcess.objects.get(pk=self.kwargs['qa_scheduling_unit_process_id']).active_tasks()[0]
process= models.SchedulingUnitProcess.objects.get(pk=self.kwargs['pk'])
task = models.SchedulingUnitProcess.objects.get(pk=self.kwargs['pk']).active_tasks()[0]
view = task.flow_task._view_class.as_view()
act=task.activate()
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment