Skip to content

Commit

Permalink
file validation
Browse files Browse the repository at this point in the history
  • Loading branch information
depsiatwal committed Jan 22, 2025
1 parent aab5a26 commit 233ec31
Show file tree
Hide file tree
Showing 8 changed files with 34 additions and 17 deletions.
23 changes: 18 additions & 5 deletions core/file_handler.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import boto3
import logging
import magic

from django.conf import settings
from django.core.files.uploadhandler import UploadFileException
Expand Down Expand Up @@ -46,9 +47,25 @@ def s3_client():

class SafeS3FileUploadHandler(S3FileUploadHandler):
"""
S3FileUploadHandler.
S3FileUploadHandler with mime-type validation.
"""

ACCEPTED_FILE_UPLOAD_MIME_TYPES = settings.ACCEPTED_FILE_UPLOAD_MIME_TYPES

def receive_data_chunk(self, raw_data, start):
"""
Receive a single file chunk from the browser, validate the
file type for the first chunk and leave the rest to super.
"""
# For the first chunk
if start == 0:
mime = magic.from_buffer(raw_data, mime=True)
if mime not in self.ACCEPTED_FILE_UPLOAD_MIME_TYPES:
# Return non this halts the upload and lets the Filefield handle the
# validation for user messages.
return
super().receive_data_chunk(raw_data, start)

def file_complete(self, *args, **kwargs):
"""Override `file_complete` to ensure that all necessary attributes
are set on the file object.
Expand All @@ -66,10 +83,6 @@ class UploadFailed(UploadFileException):
pass


class UnacceptableMimeTypeError(UploadFailed):
pass


def generate_file(result):
for chunk in iter(lambda: result["Body"].read(settings.STREAMING_CHUNK_SIZE), b""):
yield chunk
Expand Down
8 changes: 6 additions & 2 deletions exporter/applications/forms/parties.py
Original file line number Diff line number Diff line change
Expand Up @@ -430,12 +430,12 @@ def get_title(self):

class PartyDocumentUploadForm(forms.Form):
title = "Upload an end-user document"
# from django.core.validators import FileExtensionValidator
party_document = forms.FileField(
label=FileUploadFileTypes.UPLOAD_GUIDANCE_TEXT,
error_messages={
"required": "Select an end-user document",
},
allow_empty_file=True,
validators=[FileExtensionValidator()],
)

Expand Down Expand Up @@ -497,6 +497,7 @@ class PartyEnglishTranslationDocumentUploadForm(forms.Form):
error_messages={
"required": "Select an English translation",
},
allow_empty_file=True,
validators=[FileExtensionValidator()],
)

Expand Down Expand Up @@ -526,6 +527,7 @@ class PartyCompanyLetterheadDocumentUploadForm(forms.Form):
error_messages={
"required": "Select a document on company letterhead",
},
allow_empty_file=True,
validators=[FileExtensionValidator()],
)

Expand All @@ -550,7 +552,9 @@ def get_title(self):

class PartyEC3DocumentUploadForm(forms.Form):
title = "Upload an EC3 form (optional)"
party_ec3_document = forms.FileField(label="", required=False, validators=[FileExtensionValidator()])
party_ec3_document = forms.FileField(
label="", required=False, allow_empty_file=True, validators=[FileExtensionValidator()]
)
ec3_missing_reason = forms.CharField(
widget=forms.Textarea(attrs={"rows": "5"}),
label="",
Expand Down
1 change: 1 addition & 0 deletions exporter/core/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,4 @@ class FileExtensionValidator(FileExtensionValidator):
def __init__(self, allowed_extensions=[], message=None):
self.message = message or "The file type is not supported. Upload a supported file type"
self.allowed_extensions = allowed_extensions or settings.ACCEPTED_FILE_UPLOAD_EXTENSIONS
super().__init__(self.allowed_extensions, self.message)
1 change: 1 addition & 0 deletions exporter/goods/forms/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,7 @@ class Layout:
error_messages={
"required": "Select a document that shows what your product is designed to do",
},
allow_empty_file=True,
validators=[FileExtensionValidator()],
)
description = forms.CharField(
Expand Down
2 changes: 2 additions & 0 deletions exporter/goods/forms/firearms.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ class Layout:
error_messages={
"required": "Select a registered firearms dealer certificate",
},
allow_empty_file=True,
widget=PotentiallyUnsafeClearableFileInput,
validators=[FileExtensionValidator()],
)
Expand Down Expand Up @@ -335,6 +336,7 @@ class Layout:
widget=PotentiallyUnsafeClearableFileInput(
force_required=True,
),
allow_empty_file=True,
validators=[FileExtensionValidator()],
)

Expand Down
1 change: 1 addition & 0 deletions exporter/goods/forms/goods.py
Original file line number Diff line number Diff line change
Expand Up @@ -1113,6 +1113,7 @@ class AttachFirearmsDealerCertificateForm(forms.Form):
error_messages={
"required": "Select certificate file to upload",
},
allow_empty_file=True,
validators=[FileExtensionValidator()],
)

Expand Down
3 changes: 3 additions & 0 deletions exporter/organisation/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ class Layout:
label="",
help_text="The file must be smaller than 50MB",
error_messages={"required": "Select certificate file to upload"},
allow_empty_file=True,
validators=[FileExtensionValidator()],
)
reference_code = forms.CharField(
label="Certificate number",
Expand Down Expand Up @@ -64,6 +66,7 @@ class Layout:
label=FileUploadFileTypes.UPLOAD_GUIDANCE_TEXT,
help_text="The file must be smaller than 50MB",
error_messages={"required": "Select certificate file to upload"},
allow_empty_file=True,
validators=[FileExtensionValidator()],
)
reference_code = forms.CharField(
Expand Down
12 changes: 2 additions & 10 deletions unit_tests/core/test_file_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,11 @@
from unittest import mock
from os import path

from django.core.files.uploadhandler import UploadFileException

from core.file_handler import (
s3_client,
SafeS3FileUploadHandler,
S3Wrapper,
UnacceptableMimeTypeError,
)


Expand Down Expand Up @@ -45,20 +43,14 @@ def test_invalid_file_type_upload(mock_handler):
with open(f"{TEST_FILES_PATH}/invalid_type.zip", "rb") as f:
content = f.read()
mock_handler.abort = mock.Mock()
with pytest.raises(UnacceptableMimeTypeError, match="Unsupported file type: application/zip") as e:
mock_handler.receive_data_chunk(content, 0)
assert isinstance(e.value, UploadFileException)
mock_handler.abort.assert_called_once()
assert mock_handler.receive_data_chunk(content, 0) is None


def test_invalid_file_mime_type_upload(mock_handler):
with open(f"{TEST_FILES_PATH}/invalid_mime.txt", "rb") as f:
content = f.read()
mock_handler.abort = mock.Mock()
with pytest.raises(UnacceptableMimeTypeError, match="Unsupported file type: application/zip") as e:
mock_handler.receive_data_chunk(content, 0)
assert isinstance(e.value, UploadFileException)
mock_handler.abort.assert_called_once()
assert mock_handler.receive_data_chunk(content, 0) is None


def test_s3_wrapper_get_client(settings, mocker):
Expand Down

0 comments on commit 233ec31

Please sign in to comment.