From 884f39ce3f4b540d287133f46d1bd6b627abdb6c Mon Sep 17 00:00:00 2001
From: "J.-S. Caux" <J.S.Caux@uva.nl>
Date: Sat, 17 Oct 2020 17:01:20 +0200
Subject: [PATCH] Put secure storage directly in apimail, for reusability's
 sake

---
 .../commands/mailgun_get_stored_messages.py   |  1 -
 apimail/migrations/0024_auto_20201017_1658.py | 20 +++++++++++++
 apimail/models/attachment.py                  |  4 +--
 apimail/models/composed_message.py            |  3 --
 apimail/models/stored_message.py              |  3 --
 apimail/storage.py                            | 29 +++++++++++++++++++
 6 files changed, 51 insertions(+), 9 deletions(-)
 create mode 100644 apimail/migrations/0024_auto_20201017_1658.py
 create mode 100644 apimail/storage.py

diff --git a/apimail/management/commands/mailgun_get_stored_messages.py b/apimail/management/commands/mailgun_get_stored_messages.py
index 51078e22e..59427f612 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 000000000..6d3f13b8a
--- /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 891609ac5..75ff1f3fc 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 82505f148..491d6ea7a 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 1f4422e30..63f65740b 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 000000000..01ab996f3
--- /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
-- 
GitLab