diff --git a/apimail/management/commands/mailgun_get_stored_messages.py b/apimail/management/commands/mailgun_get_stored_messages.py index 51078e22eb83d21f28c9fe0f9b764f09944d4c75..59427f612476707e75bcb2c3ac54d775ae06cc13 100644 --- a/apimail/management/commands/mailgun_get_stored_messages.py +++ b/apimail/management/commands/mailgun_get_stored_messages.py @@ -44,7 +44,6 @@ class Command(BaseCommand): orphan.save() except StoredMessage.DoesNotExist: - # Need to get and create the message try: storage_url = orphan.data['storage']['url'] diff --git a/apimail/migrations/0024_auto_20201017_1658.py b/apimail/migrations/0024_auto_20201017_1658.py new file mode 100644 index 0000000000000000000000000000000000000000..6d3f13b8a85b8091e5dd5ddb44a36cb5653929d6 --- /dev/null +++ b/apimail/migrations/0024_auto_20201017_1658.py @@ -0,0 +1,20 @@ +# Generated by Django 2.2.16 on 2020-10-17 14:58 + +import apimail.storage +import apimail.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('apimail', '0023_domain_status'), + ] + + operations = [ + migrations.AlterField( + model_name='attachmentfile', + name='file', + field=models.FileField(storage=apimail.storage.APIMailSecureFileStorage(), upload_to='uploads/mail/attachments/%Y/%m/%d/', validators=[apimail.validators.validate_max_email_attachment_file_size]), + ), + ] diff --git a/apimail/models/attachment.py b/apimail/models/attachment.py index 891609ac5c509cc2758e2b0e7d76d6f682d39627..75ff1f3fc0af0e849e1754d0a9f399f4a4c9729d 100644 --- a/apimail/models/attachment.py +++ b/apimail/models/attachment.py @@ -8,7 +8,7 @@ from django.contrib.postgres.fields import JSONField from django.db import models from django.urls import reverse -from scipost.storage import SecureFileStorage +from ..storage import APIMailSecureFileStorage from ..validators import validate_max_email_attachment_file_size @@ -27,7 +27,7 @@ class AttachmentFile(models.Model): file = models.FileField( upload_to='uploads/mail/attachments/%Y/%m/%d/', validators=[validate_max_email_attachment_file_size,], - storage=SecureFileStorage()) + storage=APIMailSecureFileStorage()) def __str__(self): return '%s (%s, %s)' % (self.data['name'], self.data['content-type'], self.file.size) diff --git a/apimail/models/composed_message.py b/apimail/models/composed_message.py index 82505f1482c8c31989a96302e83ea3480d61c550..491d6ea7a48b096501040f3221d6845123bc811b 100644 --- a/apimail/models/composed_message.py +++ b/apimail/models/composed_message.py @@ -10,10 +10,7 @@ from django.db import models from django.urls import reverse from django.utils import timezone -from scipost.storage import SecureFileStorage - from ..managers import ComposedMessageQuerySet -from ..validators import validate_max_email_attachment_file_size class ComposedMessage(models.Model): diff --git a/apimail/models/stored_message.py b/apimail/models/stored_message.py index 1f4422e30231fc949439dc98ae638e27d72fba82..63f65740bde1f0b6b80cd055c899b6ea46f738b4 100644 --- a/apimail/models/stored_message.py +++ b/apimail/models/stored_message.py @@ -10,10 +10,7 @@ from django.db import models from django.urls import reverse from django.utils import timezone -from scipost.storage import SecureFileStorage - from ..managers import StoredMessageQuerySet -from ..validators import validate_max_email_attachment_file_size class StoredMessage(models.Model): diff --git a/apimail/storage.py b/apimail/storage.py new file mode 100644 index 0000000000000000000000000000000000000000..01ab996f3fdd2f74cfd38ec18b3844844accb6c0 --- /dev/null +++ b/apimail/storage.py @@ -0,0 +1,29 @@ +__copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)" +__license__ = "AGPL v3" + + +from django.conf import settings +from django.core.files.storage import FileSystemStorage +from django.utils.functional import cached_property + + +class APIMailSecureFileStorage(FileSystemStorage): + """ + Inherit default FileStorage system to prevent files from being publicly accessible + from a server location that is opened without this permission having been explicitly given. + """ + @cached_property + def location(self): + """ + This method determines the storage location for a new file. To secure the file from + public access, it is stored outside the default MEDIA_ROOT folder. + + This also means you need to explicitly handle the file reading/opening! + """ + if hasattr(settings, 'APIMAIL_MEDIA_ROOT_SECURE'): + return self._value_or_setting(self._location, settings.APIMAIL_MEDIA_ROOT_SECURE) + return super().location + + @cached_property + def base_url(self): + return settings.APIMAIL_MEDIA_URL_SECURE