From 31a06a76a72b4097fce993b5131c434580084da0 Mon Sep 17 00:00:00 2001 From: Valentin Rigal <rigal@teklia.com> Date: Wed, 24 May 2023 08:24:32 +0000 Subject: [PATCH] Restrict usage of CreateTranscriptionEntity with a worker run --- arkindex/documents/serializers/entities.py | 15 +- arkindex/documents/tests/test_entities_api.py | 181 ++++++++++++++++-- 2 files changed, 177 insertions(+), 19 deletions(-) diff --git a/arkindex/documents/serializers/entities.py b/arkindex/documents/serializers/entities.py index e7d573ebfc..a77a54823d 100644 --- a/arkindex/documents/serializers/entities.py +++ b/arkindex/documents/serializers/entities.py @@ -349,13 +349,18 @@ class TranscriptionEntityCreateSerializer(serializers.ModelSerializer): offset = serializers.IntegerField(min_value=0) length = serializers.IntegerField(min_value=1) worker_version_id = ForbiddenField() - worker_run_id = serializers.PrimaryKeyRelatedField( - queryset=WorkerRun.objects.all(), - write_only=True, + worker_run_id = WorkerRunIDField( required=False, + write_only=True, allow_null=True, - style={'base_template': 'input.html'}, - source='worker_run' + help_text=dedent(""" + A WorkerRun ID that the new transcription and entity will refer to. + + Regular users may only use the WorkerRuns of their own `Local` process. + + Tasks authenticated via the Ponos task authentication may only use the WorkerRuns of their process. + """).strip(), + ) confidence = serializers.FloatField(min_value=0, max_value=1, default=None) diff --git a/arkindex/documents/tests/test_entities_api.py b/arkindex/documents/tests/test_entities_api.py index 3b9967933a..821a66f72b 100644 --- a/arkindex/documents/tests/test_entities_api.py +++ b/arkindex/documents/tests/test_entities_api.py @@ -440,7 +440,7 @@ class TestEntitiesAPI(FixtureAPITestCase): def test_create_entity_worker_run_non_local(self): """ - A regular user cannot create an entity with a WorkerRun of a non-local process + A regular user cannot create a transcription entity with a WorkerRun of a non-local process """ self.client.force_login(self.superuser) with self.assertNumQueries(5): @@ -783,15 +783,18 @@ class TestEntitiesAPI(FixtureAPITestCase): }) def test_create_transcription_entity_worker_run(self): - self.client.force_login(self.user) - with self.assertNumQueries(9): + """ + A regular user can create classifications with a WorkerRun of their own local process + """ + self.client.force_login(self.superuser) + with self.assertNumQueries(7): response = self.client.post( reverse('api:transcription-entity-create', kwargs={'pk': str(self.transcription.id)}), data={ 'entity': str(self.entity.id), 'offset': 4, 'length': 8, - 'worker_run_id': str(self.worker_run_1.id), + 'worker_run_id': str(self.local_worker_run.id), }, format='json', ) @@ -801,22 +804,21 @@ class TestEntitiesAPI(FixtureAPITestCase): 'offset': 4, 'length': 8, 'worker_run': { - "id": str(self.worker_run_1.id), - "summary": self.worker_run_1.summary + "id": str(self.local_worker_run.id), + "summary": self.local_worker_run.summary }, 'confidence': None, }) - def test_create_transcription_entity_worker_run_or_version(self): + def test_create_transcription_entity_forbidden_version(self): self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(6): response = self.client.post( reverse('api:transcription-entity-create', kwargs={'pk': str(self.transcription.id)}), data={ 'entity': str(self.entity.id), 'offset': 4, 'length': 8, - 'worker_run_id': str(self.worker_run_1.id), 'worker_version_id': str(self.worker_version_1.id), }, format='json', @@ -958,31 +960,182 @@ class TestEntitiesAPI(FixtureAPITestCase): }) def test_create_transcription_entity_duplicate_worker_run(self): - self.client.force_login(self.user) + """ + No duplicate entity can be created on a trancription + """ TranscriptionEntity.objects.create( transcription=self.transcription, entity=self.entity, offset=4, length=8, - worker_run=self.worker_run_1, + worker_run=self.local_worker_run, worker_version=self.worker_version_1 ) - with self.assertNumQueries(8): + + self.client.force_login(self.superuser) + with self.assertNumQueries(6): response = self.client.post( reverse('api:transcription-entity-create', kwargs={'pk': str(self.transcription.id)}), data={ 'entity': str(self.entity.id), 'offset': 4, 'length': 8, - 'worker_run_id': str(self.worker_run_1.id), + 'worker_run_id': str(self.local_worker_run.id), }, format='json' ) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual(response.json(), { '__all__': ['This entity is already linked to this transcription by this worker run at this position.'] }) + def test_create_transcription_entity_worker_run_non_local(self): + """ + A regular user cannot create an entity on a transcription with a WorkerRun of a non-local process + """ + self.client.force_login(self.superuser) + with self.assertNumQueries(5): + response = self.client.post( + reverse('api:transcription-entity-create', kwargs={'pk': str(self.transcription.id)}), + format='json', + data={ + 'entity': str(self.entity.id), + 'offset': 4, + 'length': 8, + 'worker_run_id': str(self.worker_run_1.id), + }, + ) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertDictEqual(response.json(), { + 'worker_run_id': [ + "Ponos task authentication is required to use a WorkerRun " + "of a process other than the user's local process." + ] + }) + + def test_create_transcription_entity_worker_run_other_user(self): + """ + A regular user cannot create an entity on a transcription with a WorkerRun of someone else's local process + """ + private_worker_run = self.user.processes.get(mode=ProcessMode.Local).worker_runs.first() + self.client.force_login(self.superuser) + with self.assertNumQueries(5): + response = self.client.post( + reverse('api:transcription-entity-create', kwargs={'pk': str(self.transcription.id)}), + format='json', + data={ + 'entity': str(self.entity.id), + 'offset': 4, + 'length': 8, + 'worker_run_id': str(private_worker_run.id), + }, + ) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertDictEqual(response.json(), { + 'worker_run_id': [ + "Ponos task authentication is required to use a WorkerRun " + "of a process other than the user's local process." + ] + }) + + def test_create_transcription_entity_worker_run_other_process(self): + """ + A Ponos task cannot create an entity on a transcription with a WorkerRun of another process + """ + process2 = self.worker_run_1.process.creator.processes.create( + mode=ProcessMode.Workers, + corpus=self.corpus, + ) + other_worker_run = process2.worker_runs.create(version=self.worker_version_1, parents=[]) + self.worker_run_1.process.start() + task = self.worker_run_1.process.workflow.tasks.first() + + with self.assertNumQueries(6): + response = self.client.post( + reverse('api:transcription-entity-create', kwargs={'pk': str(self.transcription.id)}), + format='json', + data={ + 'entity': str(self.entity.id), + 'offset': 4, + 'length': 8, + 'worker_run_id': str(other_worker_run.id), + }, + HTTP_AUTHORIZATION=f'Ponos {task.token}', + ) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertDictEqual(response.json(), { + 'worker_run_id': [ + "Only the WorkerRuns of the authenticated task's process may be used." + ] + }) + + def test_create_transcription_entity_worker_run_task_auth(self): + """ + An entity can be created on a transcription with a WorkerRun of a non-local process + when authenticated as a Ponos task of this process + """ + self.worker_run_1.process.start() + task = self.worker_run_1.process.workflow.tasks.first() + + with self.assertNumQueries(8): + response = self.client.post( + reverse('api:transcription-entity-create', kwargs={'pk': str(self.transcription.id)}), + format='json', + data={ + 'entity': str(self.entity.id), + 'offset': 4, + 'length': 8, + 'worker_run_id': str(self.worker_run_1.id), + }, + HTTP_AUTHORIZATION=f'Ponos {task.token}', + ) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertDictEqual(response.json(), { + 'entity': str(self.entity.id), + 'offset': 4, + 'length': 8, + 'worker_run': { + "id": str(self.worker_run_1.id), + "summary": self.worker_run_1.summary + }, + 'confidence': None, + }) + + def test_create_transcription_entity_worker_run_local_task_auth(self): + """ + An entity can be created on a transcription with a WorkerRun of a Local process + even when authenticated as a Ponos task from a different process + """ + local_process = self.user.processes.get(mode=ProcessMode.Local) + local_worker_run = local_process.worker_runs.get() + + self.worker_run_1.process.start() + task = self.worker_run_1.process.workflow.tasks.first() + + with self.assertNumQueries(8): + response = self.client.post( + reverse('api:transcription-entity-create', kwargs={'pk': str(self.transcription.id)}), + format='json', + data={ + 'entity': str(self.entity.id), + 'offset': 4, + 'length': 8, + 'worker_run_id': str(local_worker_run.id), + }, + HTTP_AUTHORIZATION=f'Ponos {task.token}', + ) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertDictEqual(response.json(), { + 'entity': str(self.entity.id), + 'offset': 4, + 'length': 8, + 'worker_run': { + "id": str(local_worker_run.id), + "summary": local_worker_run.summary + }, + 'confidence': None, + }) + def test_create_transcription_entity_manual_existing_worker_run(self): """ A manual TranscriptionEntity can be created even when one exists with the same attributes from a WorkerRun -- GitLab