mirror of
https://github.com/zulip/zulip.git
synced 2025-11-15 11:22:04 +00:00
upload: Limit total size of files uploaded by a user to 1GB.
Fixes #3884.
This commit is contained in:
committed by
Tim Abbott
parent
777b7d4cb7
commit
866a7b06b2
@@ -1109,6 +1109,10 @@ $(function () {
|
||||
case 'REQUEST ENTITY TOO LARGE':
|
||||
msg = i18n.t("Sorry, the file was too large.");
|
||||
break;
|
||||
case 'QuotaExceeded':
|
||||
msg = i18n.t("Upload would exceed your maximum quota."
|
||||
+ " Consider deleting some previously uploaded files.");
|
||||
break;
|
||||
default:
|
||||
msg = i18n.t("An unknown error occured.");
|
||||
break;
|
||||
|
||||
@@ -360,8 +360,13 @@
|
||||
|
||||
// Pass any errors to the error option
|
||||
if (xhr.status < 200 || xhr.status > 299) {
|
||||
if (this.responseText.includes("Upload would exceed your maximum quota.")) {
|
||||
var errorString = "QuotaExceeded";
|
||||
on_error(errorString, xhr.status);
|
||||
} else {
|
||||
on_error(xhr.statusText, xhr.status);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ from django.conf import settings
|
||||
from django.template.defaultfilters import slugify
|
||||
from django.core.files import File
|
||||
from django.http import HttpRequest
|
||||
from django.db.models import Sum
|
||||
from jinja2 import Markup as mark_safe
|
||||
import unicodedata
|
||||
|
||||
@@ -84,6 +85,9 @@ def random_name(bytes=60):
|
||||
class BadImageError(JsonableError):
|
||||
pass
|
||||
|
||||
class ExceededQuotaError(JsonableError):
|
||||
pass
|
||||
|
||||
def resize_avatar(image_data, size=DEFAULT_AVATAR_SIZE):
|
||||
# type: (binary_type, int) -> binary_type
|
||||
try:
|
||||
@@ -165,6 +169,24 @@ def upload_image_to_s3(
|
||||
|
||||
key.set_contents_from_string(force_str(contents), headers=headers)
|
||||
|
||||
def get_total_uploads_size_for_user(user):
|
||||
# type: (UserProfile) -> int
|
||||
uploads = Attachment.objects.filter(owner=user)
|
||||
total_quota = uploads.aggregate(Sum('size'))['size__sum']
|
||||
|
||||
# In case user has no uploads
|
||||
if (total_quota is None):
|
||||
total_quota = 0
|
||||
return total_quota
|
||||
|
||||
def within_upload_quota(user, uploaded_file_size):
|
||||
# type: (UserProfile, int) -> bool
|
||||
total_quota = get_total_uploads_size_for_user(user)
|
||||
if (total_quota + uploaded_file_size > user.quota):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def get_file_info(request, user_file):
|
||||
# type: (HttpRequest, File) -> Tuple[Text, int, Optional[Text]]
|
||||
|
||||
|
||||
20
zerver/migrations/0057_userprofile_quota.py
Normal file
20
zerver/migrations/0057_userprofile_quota.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.5 on 2017-03-04 07:40
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('zerver', '0056_userprofile_emoji_alt_code'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='userprofile',
|
||||
name='quota',
|
||||
field=models.IntegerField(default=1073741824),
|
||||
),
|
||||
]
|
||||
@@ -613,6 +613,10 @@ class UserProfile(ModelReprMixin, AbstractBaseUser, PermissionsMixin):
|
||||
|
||||
objects = UserManager() # type: UserManager
|
||||
|
||||
DEFAULT_UPLOADS_QUOTA = 1024*1024*1024
|
||||
|
||||
quota = models.IntegerField(default=DEFAULT_UPLOADS_QUOTA) # type: int
|
||||
|
||||
def can_admin_user(self, target_user):
|
||||
# type: (UserProfile) -> bool
|
||||
"""Returns whether this user has permission to modify target_user"""
|
||||
|
||||
@@ -318,6 +318,38 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
|
||||
content = ujson.loads(result.content)
|
||||
assert sanitize_name(expected) in content['uri']
|
||||
|
||||
def test_upload_size_quote(self):
|
||||
# type: () -> None
|
||||
"""
|
||||
User quote for uploading should not be exceeded
|
||||
"""
|
||||
self.login("hamlet@zulip.com")
|
||||
|
||||
d1 = StringIO("zulip!")
|
||||
d1.name = "dummy_1.txt"
|
||||
result = self.client_post("/json/upload_file", {'file': d1})
|
||||
json = ujson.loads(result.content)
|
||||
uri = json["uri"]
|
||||
d1_path_id = re.sub('/user_uploads/', '', uri)
|
||||
d1_attachment = Attachment.objects.get(path_id = d1_path_id)
|
||||
self.assert_json_success(result)
|
||||
|
||||
"""
|
||||
Below we set size quota to the limit without 1 upload(1GB - 11 bytes).
|
||||
"""
|
||||
d1_attachment.size = UserProfile.DEFAULT_UPLOADS_QUOTA - 11
|
||||
d1_attachment.save()
|
||||
|
||||
d2 = StringIO("zulip!")
|
||||
d2.name = "dummy_2.txt"
|
||||
result = self.client_post("/json/upload_file", {'file': d2})
|
||||
self.assert_json_success(result)
|
||||
|
||||
d3 = StringIO("zulip!")
|
||||
d3.name = "dummy_3.txt"
|
||||
result = self.client_post("/json/upload_file", {'file': d3})
|
||||
self.assert_json_error(result, "Upload would exceed your maximum quota.")
|
||||
|
||||
def tearDown(self):
|
||||
# type: () -> None
|
||||
destroy_uploads()
|
||||
|
||||
@@ -10,7 +10,7 @@ from zerver.decorator import authenticated_json_post_view
|
||||
from zerver.lib.request import has_request_variables, REQ
|
||||
from zerver.lib.response import json_success, json_error
|
||||
from zerver.lib.upload import upload_message_image_from_request, get_local_file_path, \
|
||||
get_signed_upload_url, get_realm_for_filename
|
||||
get_signed_upload_url, get_realm_for_filename, within_upload_quota
|
||||
from zerver.lib.validator import check_bool
|
||||
from zerver.models import UserProfile
|
||||
from django.conf import settings
|
||||
@@ -69,9 +69,12 @@ def upload_file_backend(request, user_profile):
|
||||
return json_error(_("You may only upload one file at a time"))
|
||||
|
||||
user_file = list(request.FILES.values())[0]
|
||||
if ((settings.MAX_FILE_UPLOAD_SIZE * 1024 * 1024) < user_file._get_size()):
|
||||
file_size = user_file._get_size()
|
||||
if settings.MAX_FILE_UPLOAD_SIZE * 1024 * 1024 < file_size:
|
||||
return json_error(_("Uploaded file is larger than the allowed limit of %s MB") % (
|
||||
settings.MAX_FILE_UPLOAD_SIZE))
|
||||
if not within_upload_quota(user_profile, file_size):
|
||||
return json_error(_("Upload would exceed your maximum quota."))
|
||||
|
||||
if not isinstance(user_file.name, str):
|
||||
# It seems that in Python 2 unicode strings containing bytes are
|
||||
|
||||
Reference in New Issue
Block a user