Skip to content

Commit

Permalink
Add content-length-range to pre-signed URL config (#32)
Browse files Browse the repository at this point in the history
* Add content-length-range to pre-signed URL config
* Add file-size check to test.views.fake_s3_upload
* Add MAX_FILE_SIZE setting to README
* Bump version to 0.1.21
  • Loading branch information
oliverroick authored Jul 28, 2017
1 parent 010ac34 commit 2f83507
Show file tree
Hide file tree
Showing 12 changed files with 81 additions and 10 deletions.
11 changes: 11 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,17 @@ bucket for development.
url(r'', include('buckets.test.urls')),
]
Other settings
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Other optional settings can be added to the :code:`AWS` settings dictionary.

===================== =========== ========================================================================
Name Type Description
===================== =========== ========================================================================
:code:`MAX_FILE_SIZE` :code:`int` The maximum allowed size for file uploads in bytes. If :code:`MAX_FILE_SIZE` is not defined then there will be no limit to the size of file.
===================== =========== ========================================================================

Usage
-------------------------------------------------------------------------------

Expand Down
2 changes: 1 addition & 1 deletion buckets/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '0.1.20'
__version__ = '0.1.21'
18 changes: 16 additions & 2 deletions buckets/static/buckets/js/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
var link_update = document.createEvent('Event');
link_update.initEvent('link:update', true, true);

function toMB(bytes) {
// Convert bytes to MB and round to two decimals.
return Math.round(bytes / (1024*1024) * 100) / 100;
}

function getParentByTagName(el, tagName) {
var p = el.parentElement;

Expand Down Expand Up @@ -103,9 +108,18 @@
})
formData.append('file', file);

request('POST', url, formData, headers, el, function(status, xml) {
request('POST', url, formData, headers, el, function(status, response) {
if (status !== 204) {
error(el, 'Not able to upload file')
var errorMsg = 'Not able to upload file. ';

var xml = new DOMParser().parseFromString(response, "text/xml");
if (xml.getElementsByTagName('Code')[0].innerHTML === 'EntityTooLarge') {
var limit = parseInt(xml.getElementsByTagName('MaxSizeAllowed')[0].innerHTML);

errorMsg += 'The size of the file exceeds the maximum allowed size of ' + toMB(limit) + 'MB.';
}

error(el, errorMsg)
} else {
var fileUrl = data.url + '/' + data.fields.key;
update(el, fileUrl);
Expand Down
8 changes: 7 additions & 1 deletion buckets/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ def __init__(self):
self.secret_key = settings.AWS['SECRET_KEY']
self.region = settings.AWS['REGION']
self.bucket_name = settings.AWS['BUCKET']
self.max_size = settings.AWS.get('MAX_FILE_SIZE')

ensure_dirs('downloads')

Expand Down Expand Up @@ -87,9 +88,14 @@ def get_signed_url(self, key):
if not self.exists(temp_key):
s3_key = temp_key

condtions = []
if self.max_size:
condtions.append(["content-length-range", 0, self.max_size])

params = {
'Bucket': self.bucket_name,
'Key': s3_key
'Key': s3_key,
'Conditions': condtions
}
client = boto3.client(
's3',
Expand Down
9 changes: 9 additions & 0 deletions buckets/test/errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
EXCEED_MAX_SIZE = """
<Error>
<Code>EntityTooLarge</Code>
<Message>Your proposed upload exceeds the maximum
allowed size</Message>
<MaxSizeAllowed>{max_size}</MaxSizeAllowed>
<ProposedSize>{proposed_size}</ProposedSize>
</Error>
"""
2 changes: 1 addition & 1 deletion buckets/test/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,4 @@ def get_signed_url(self, key):
if not self.exists(temp_key):
s3_key = temp_key

return {'url': '/media/s3/uploads', 'fields': {'key': s3_key}}
return {'url': '/media/s3/uploads/', 'fields': {'key': s3_key}}
11 changes: 10 additions & 1 deletion buckets/test/views.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
from django.conf import settings
from django.core.files.storage import default_storage
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
from django.http import HttpResponse

from .errors import EXCEED_MAX_SIZE


@csrf_exempt
@require_POST
def fake_s3_upload(request):
key = request.POST.get('key')

file = request.FILES.get('file')
default_storage.save(key, file.read())

max_file_size = settings.AWS.get('MAX_FILE_SIZE')
if max_file_size and file.size > max_file_size:
msg = EXCEED_MAX_SIZE.format(max_size=max_file_size,
proposed_size=file.size)
return HttpResponse(msg, status=400)

default_storage.save(key, file.read())
return HttpResponse('', status=204)
2 changes: 1 addition & 1 deletion example/exampleapp/models.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from django.db import models
from buckets.fields import S3FileField

TYPES = ['image/jpeg', 'application/gpx+xml']
TYPES = ['image/jpeg', 'application/gpx+xml', 'text/plain']


class FileModel(models.Model):
Expand Down
1 change: 1 addition & 0 deletions example/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@
'ACCESS_KEY': os.environ.get('AWS_ACCESS_KEY'),
'SECRET_KEY': os.environ.get('AWS_SECRET_KEY'),
'REGION': os.environ.get('AWS_REGION'),
'MAX_FILE_SIZE': 1048576,
}

MIME_LOOKUPS = {
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Minimum Django version
Django==1.11.3
Django>=1.10,<1.11
boto3==1.4.4

# Test requirements
Expand Down
1 change: 1 addition & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def pytest_configure():
'ACCESS_KEY': os.environ.get('AWS_ACCESS_KEY'),
'SECRET_KEY': os.environ.get('AWS_SECRET_KEY'),
'REGION': os.environ.get('AWS_REGION'),
'MAX_FILE_SIZE': 1048579,
},
ROOT_URLCONF='buckets.test.urls',
MEDIA_ROOT=os.path.join(os.path.dirname(BASE_DIR), 'files'),
Expand Down
24 changes: 22 additions & 2 deletions tests/test_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from buckets.test.mocks import create_file, make_dirs # noqa
from buckets.test.storage import FakeS3Storage
from buckets.test import views
from buckets.test import views, errors


#############################################################################
Expand Down Expand Up @@ -70,7 +70,7 @@ def test_get_signed_url():
store = FakeS3Storage()

signed = store.get_signed_url(key='file.txt')
assert '/media/s3/uploads' == signed['url']
assert '/media/s3/uploads/' == signed['url']
assert len(signed['fields']['key']) == 28


Expand Down Expand Up @@ -142,3 +142,23 @@ def test_post_upload_file_to_subdir(make_dirs, monkeypatch): # noqa
assert response.status_code == 204
assert os.path.isfile(
os.path.join(settings.MEDIA_ROOT, 's3/uploads/subdir', 'text.txt'))


def test_post_large_file(make_dirs, monkeypatch, settings): # noqa
monkeypatch.setattr(views, 'default_storage', FakeS3Storage())
file = create_file()

upload = SimpleUploadedFile('text.txt', open(file.name, 'rb').read())
upload.size = settings.AWS['MAX_FILE_SIZE'] + 1

request = HttpRequest()
setattr(request, 'method', 'POST')
setattr(request, 'FILES', {'file': upload})
setattr(request, 'POST', {'key': 'subdir/text.txt'})
response = views.fake_s3_upload(request)
assert response.status_code == 400
assert response.content.decode('utf-8') == errors.EXCEED_MAX_SIZE.format(
max_size=settings.AWS['MAX_FILE_SIZE'],
proposed_size=settings.AWS['MAX_FILE_SIZE'] + 1)
assert not os.path.isfile(
os.path.join(settings.MEDIA_ROOT, 's3/uploads/subdir', 'text.txt'))

0 comments on commit 2f83507

Please sign in to comment.