diff --git a/apps/jobs/views.py b/apps/jobs/views.py index 9751e98938..a08ce426c9 100644 --- a/apps/jobs/views.py +++ b/apps/jobs/views.py @@ -3,6 +3,7 @@ import logging import os import uuid +from urllib.parse import urlparse import botocore from accounts.permissions import HasVerifiedEmail @@ -95,6 +96,39 @@ logger = logging.getLogger(__name__) +def _get_allowed_submission_file_types(challenge_phase): + allowed_file_types = challenge_phase.allowed_submission_file_types or "" + return [ + ( + file_type.strip().lower() + if file_type.strip().startswith(".") + else ".{}".format(file_type.strip().lower()) + ) + for file_type in allowed_file_types.split(",") + if file_type.strip() + ] + + +def _is_submission_file_type_allowed(file_name, challenge_phase): + allowed_file_types = _get_allowed_submission_file_types(challenge_phase) + if not allowed_file_types: + return True + + file_path = urlparse(file_name).path.lower() + return any( + file_path.endswith(file_type) for file_type in allowed_file_types + ) + + +def _invalid_submission_file_type_response(challenge_phase): + response_data = { + "error": "Invalid submission file type. Allowed file types are: {}.".format( + challenge_phase.allowed_submission_file_types + ) + } + return Response(response_data, status=status.HTTP_400_BAD_REQUEST) + + @extend_schema( methods=["POST"], parameters=[ @@ -334,6 +368,10 @@ def challenge_submission(request, challenge_id, challenge_phase_id): return Response( response_data, status=status.HTTP_400_BAD_REQUEST ) + if not _is_submission_file_type_allowed( + request.data["file_url"], challenge_phase + ): + return _invalid_submission_file_type_response(challenge_phase) download_file_and_publish_submission_message.delay( request.data, request.user.id, @@ -345,6 +383,12 @@ def challenge_submission(request, challenge_id, challenge_phase_id): } return Response(response_data, status=status.HTTP_200_OK) + input_file = request.FILES.get("input_file") + if input_file and not _is_submission_file_type_allowed( + input_file.name, challenge_phase + ): + return _invalid_submission_file_type_response(challenge_phase) + if request.data.get("submission_meta_attributes"): submission_meta_attributes = json.load( request.data.get("submission_meta_attributes") diff --git a/tests/unit/jobs/test_views.py b/tests/unit/jobs/test_views.py index a4e3bbb755..11ad1a3e15 100644 --- a/tests/unit/jobs/test_views.py +++ b/tests/unit/jobs/test_views.py @@ -627,6 +627,73 @@ def test_challenge_submission_for_successful_submission(self): ) self.assertEqual(response.status_code, status.HTTP_201_CREATED) + def test_challenge_submission_rejects_disallowed_upload_file_type(self): + self.url = reverse_lazy( + "jobs:challenge_submission", + kwargs={ + "challenge_id": self.challenge.pk, + "challenge_phase_id": self.challenge_phase.pk, + }, + ) + + self.challenge.participant_teams.add(self.participant_team) + self.challenge.save() + invalid_input_file = SimpleUploadedFile( + "submission.png", b"file_content", content_type="image/png" + ) + + response = self.client.post( + self.url, + {"status": "submitting", "input_file": invalid_input_file}, + format="multipart", + ) + + expected = { + "error": "Invalid submission file type. Allowed file types are: {}.".format( + self.challenge_phase.allowed_submission_file_types + ) + } + self.assertEqual(response.data, expected) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + @mock.patch( + "jobs.views.download_file_and_publish_submission_message.delay" + ) + @mock.patch("jobs.views.is_url_valid", return_value=True) + def test_challenge_submission_rejects_disallowed_file_url_type( + self, mock_is_url_valid, mock_delay + ): + self.url = reverse_lazy( + "jobs:challenge_submission", + kwargs={ + "challenge_id": self.challenge.pk, + "challenge_phase_id": self.challenge_phase.pk, + }, + ) + + self.challenge.participant_teams.add(self.participant_team) + self.challenge.save() + + response = self.client.post( + self.url, + { + "status": "submitting", + "file_url": "http://example.com/submission.png", + }, + ) + + expected = { + "error": "Invalid submission file type. Allowed file types are: {}.".format( + self.challenge_phase.allowed_submission_file_types + ) + } + self.assertEqual(response.data, expected) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + mock_is_url_valid.assert_called_once_with( + "http://example.com/submission.png" + ) + mock_delay.assert_not_called() + @mock.patch("jobs.views.ensure_workers_for_submission") def test_challenge_submission_calls_ensure_workers_for_participant( self, mock_ensure_workers