Skip to content
Snippets Groups Projects
Commit 4b83f578 authored by Bastien Abadie's avatar Bastien Abadie
Browse files

Merge branch 'tasks-artifact' into 'master'

Add specific permissions for tasks PATCH and artifacts download

Closes #613

See merge request !1175
parents 2fb0c840 847688b4
No related branches found
No related tags found
1 merge request!1175Add specific permissions for tasks PATCH and artifacts download
import tempfile
from unittest.mock import patch
from django.test import TestCase, override_settings
from django.test import override_settings
from django.urls import reverse
from rest_framework import status
from arkindex.dataimport.models import DataImport, DataImportMode
from arkindex.users.models import User
from ponos.models import Agent, Farm, Secret, Workflow, encrypt
from arkindex.documents.models import Corpus
from arkindex.project.tests import FixtureAPITestCase
from arkindex.users.models import Role, User
from ponos.models import Agent, Artifact, Farm, Secret, encrypt
@override_settings(PONOS_PRIVATE_KEY='staging')
class TestPonosView(TestCase):
class TestPonosView(FixtureAPITestCase):
@classmethod
def setUpTestData(cls):
super().setUpTestData()
cls.dataimport_corpus = Corpus.objects.create(name='Another public corpus', public=True)
cls.corpus_admin = User.objects.create(email='corpusadmin@test.me')
cls.corpus_admin.rights.create(content_object=cls.dataimport_corpus, level=Role.Admin.value)
cls.creator = User.objects.create(email="creator@user.me")
cls.artifact = Artifact.objects.get(path='/path/to/docker_build')
cls.task = cls.artifact.task
cls.workflow = cls.task.workflow
cls.task = cls.workflow.tasks.get()
cls.dataimport = DataImport.objects.create(
mode=DataImportMode.Repository,
creator=cls.creator,
workflow=cls.workflow,
corpus=cls.dataimport_corpus
)
@patch("ponos.models.settings")
def test_internal_user_retrieve_secret(self, settings_mock):
......@@ -82,27 +103,43 @@ class TestPonosView(TestCase):
def test_update_task(self):
"""
Only admin users or the task creator should have the ability to update a task
Only users with an admin privilege and creators have the ability to update a dataimport task
"""
creator = User.objects.create(email="creator@user.fr")
workflow = Workflow.objects.create(
recipe="tasks: {test: {image: alpine}}"
test_cases = (
(None, status.HTTP_403_FORBIDDEN, 1),
(self.creator, status.HTTP_200_OK, 9),
(self.user, status.HTTP_403_FORBIDDEN, 9),
(self.superuser, status.HTTP_200_OK, 7),
(self.corpus_admin, status.HTTP_200_OK, 11),
)
workflow.start()
task = workflow.tasks.get()
DataImport.objects.create(mode=DataImportMode.Repository, creator=creator, workflow=workflow)
response = self.client.patch(reverse('ponos-task-update', kwargs={'pk': str(task.id)}), json={"state": "stopping"})
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
self.client.force_login(User.objects.create(email="lambda@user.fr"))
response = self.client.patch(reverse('ponos-task-update', kwargs={'pk': str(task.id)}), json={"state": "stopping"})
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
self.client.force_login(User.objects.create(email='admin@admin.fr', is_admin=True))
response = self.client.patch(reverse('ponos-task-update', kwargs={'pk': str(task.id)}), json={"state": "stopping"})
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.client.force_login(creator)
response = self.client.patch(reverse('ponos-task-update', kwargs={'pk': str(task.id)}), json={"state": "stopping"})
self.assertEqual(response.status_code, status.HTTP_200_OK)
for user, status_code, requests_count in test_cases:
if user:
self.client.force_login(user)
with self.assertNumQueries(requests_count):
response = self.client.patch(
reverse('ponos-task-update', kwargs={'pk': str(self.task.id)}),
json={'state': 'stopping'}
)
self.assertEqual(response.status_code, status_code)
def test_download_artifacts(self):
"""
Only users with an admin privilege and creators have the ability to
download an artifact of a dataimport task
"""
test_cases = (
(None, status.HTTP_403_FORBIDDEN, 2),
(self.creator, status.HTTP_302_FOUND, 6),
(self.user, status.HTTP_403_FORBIDDEN, 9),
(self.superuser, status.HTTP_302_FOUND, 4),
(self.corpus_admin, status.HTTP_302_FOUND, 8),
)
for user, status_code, requests_count in test_cases:
if user:
self.client.force_login(user)
with self.assertNumQueries(requests_count):
response = self.client.get(
reverse('ponos-artifact-dl', kwargs={'pk': str(self.task.id), 'path': self.artifact.path}),
follow=False
)
self.assertEqual(response.status_code, status_code)
......@@ -10,6 +10,7 @@ from arkindex.project.views import (
OpenAPIDocsView,
PonosAgentDetails,
PonosAgentsState,
PonosArtifactDownload,
PonosSecretDetails,
PonosTaskUpdate,
)
......@@ -25,6 +26,11 @@ urlpatterns = [
path('ponos/v1/agents/', PonosAgentsState.as_view(), name='ponos-agents'),
path('ponos/v1/agent/<uuid:pk>/', PonosAgentDetails.as_view(), name='ponos-agent-details'),
path('ponos/v1/task/<uuid:pk>/', PonosTaskUpdate.as_view(), name='ponos-task-update'),
path(
'ponos/v1/task/<uuid:pk>/artifact/<path:path>',
PonosArtifactDownload.as_view(),
name='ponos-artifact-dl'
),
path('ponos/', include('ponos.urls')),
path('admin/', admin.site.urls),
path('rq/', include('django_rq.urls')),
......
......@@ -2,9 +2,38 @@ from django.conf import settings
from django.views.generic import TemplateView, View
from rest_framework import permissions
from arkindex.project.mixins import CachedViewMixin
from arkindex.project.mixins import CachedViewMixin, CorpusACLMixin
from arkindex.project.permissions import IsVerified
from ponos.api import AgentDetails, AgentsState, SecretDetails, TaskUpdate
from ponos.api import AgentDetails, AgentsState, SecretDetails, TaskArtifactDownload, TaskUpdate
class IsTaskAdminOrCreator(CorpusACLMixin, permissions.BasePermission):
"""
Permission to access a task with high privilege
Allowed for superuser, dataimport creators and users with an admin right on its associated corpus
"""
def has_object_permission(self, request, view, task):
# Add request to attributes for the ACL mixin to work with self.user
self.request = request
if not self.user.is_authenticated:
return False
return (
self.user.is_admin
or self.user.is_internal
or self.user.id == task.workflow.dataimport.creator_id
or (
task.workflow.dataimport.corpus_id
and self.has_admin_access(task.workflow.dataimport.corpus)
)
)
class IsArtifactAdminOrCreator(IsTaskAdminOrCreator):
def has_object_permission(self, request, view, artifact):
return super().has_object_permission(request, view, artifact.task)
class FrontendView(View):
......@@ -70,11 +99,13 @@ class PonosAgentDetails(AgentDetails):
class PonosTaskUpdate(TaskUpdate):
"""
Allow any super admin or the task creator to update a task.
Allow users with an admin privilege and creators to update a dataimport task
"""
class IsAdminOrCreator(permissions.BasePermission):
permission_classes = (IsTaskAdminOrCreator, )
def has_object_permission(self, request, view, obj):
return request.user.is_authenticated and (request.user.is_admin or request.user == obj.workflow.dataimport.creator)
permission_classes = (IsAdminOrCreator, )
class PonosArtifactDownload(TaskArtifactDownload):
"""
Allow users with an admin privilege and creators to download an artifact of a dataimport task
"""
permission_classes = (IsArtifactAdminOrCreator, )
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment