mirror of https://github.com/zulip/zulip.git
upload: Remove old per-user quota feature.
We'll replace this primarily with per-realm quotas (plus the simple per-file limit of settings.MAX_FILE_UPLOAD_SIZE, 25 MiB by default). We do want per-user quotas too, but they'll need some more management apparatus around them so an admin has a practical way to set them differently for different users. And the error handling in this existing code is rather confused. Just clear this feature out entirely for now; then we'll build the per-realm version more cleanly, and then we can later add back per-realm quotas modelled after that. The migration to actually remove the field is in a subsequent commit. Based in part on work by Vishnu Ks (hackerkid).
This commit is contained in:
parent
0fcf0c5052
commit
55cf54c087
|
@ -59,46 +59,31 @@ zrequire('upload');
|
||||||
$("#compose-error-msg").text('');
|
$("#compose-error-msg").text('');
|
||||||
}
|
}
|
||||||
|
|
||||||
function assert_side_effects(msg, check_html=false) {
|
function assert_side_effects(msg) {
|
||||||
assert($("#compose-send-status").hasClass("alert-error"));
|
assert($("#compose-send-status").hasClass("alert-error"));
|
||||||
assert(!$("#compose-send-status").hasClass("alert-info"));
|
assert(!$("#compose-send-status").hasClass("alert-info"));
|
||||||
assert.equal($("#compose-send-button").prop("disabled"), false);
|
assert.equal($("#compose-send-button").prop("disabled"), false);
|
||||||
if (check_html) {
|
|
||||||
assert.equal($("#compose-error-msg").html(), msg);
|
|
||||||
} else {
|
|
||||||
assert.equal($("#compose-error-msg").text(), msg);
|
assert.equal($("#compose-error-msg").text(), msg);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function test(err, file, msg) {
|
function test(err, file, msg) {
|
||||||
setup_test();
|
setup_test();
|
||||||
upload.uploadError(err, file);
|
upload.uploadError(err, file);
|
||||||
// The text function and html function in zjquery is not in sync
|
|
||||||
// with each other. QuotaExceeded changes html while all other errors
|
|
||||||
// changes body.
|
|
||||||
if (err === 'QuotaExceeded') {
|
|
||||||
assert_side_effects(msg, true);
|
|
||||||
} else {
|
|
||||||
assert_side_effects(msg);
|
assert_side_effects(msg);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
var msg_prefix = 'translated: ';
|
var msg_prefix = 'translated: ';
|
||||||
var msg_1 = 'File upload is not yet available for your browser.';
|
var msg_1 = 'File upload is not yet available for your browser.';
|
||||||
var msg_2 = 'Unable to upload that many files at once.';
|
var msg_2 = 'Unable to upload that many files at once.';
|
||||||
var msg_3 = '"foobar.txt" was too large; the maximum file size is 25MiB.';
|
var msg_3 = '"foobar.txt" was too large; the maximum file size is 25MiB.';
|
||||||
var msg_4 = 'Sorry, the file was too large.';
|
var msg_4 = 'Sorry, the file was too large.';
|
||||||
var msg_5 = 'Upload would exceed your maximum quota. You can delete old attachments to ' +
|
var msg_5 = 'An unknown error occurred.';
|
||||||
'free up space. <a href="#settings/uploaded-files">translated: Click here</a>';
|
|
||||||
|
|
||||||
var msg_6 = 'An unknown error occurred.';
|
|
||||||
|
|
||||||
test('BrowserNotSupported', {}, msg_prefix + msg_1);
|
test('BrowserNotSupported', {}, msg_prefix + msg_1);
|
||||||
test('TooManyFiles', {}, msg_prefix + msg_2);
|
test('TooManyFiles', {}, msg_prefix + msg_2);
|
||||||
test('FileTooLarge', {name: 'foobar.txt'}, msg_prefix + msg_3);
|
test('FileTooLarge', {name: 'foobar.txt'}, msg_prefix + msg_3);
|
||||||
test('REQUEST ENTITY TOO LARGE', {}, msg_prefix + msg_4);
|
test('REQUEST ENTITY TOO LARGE', {}, msg_prefix + msg_4);
|
||||||
test('QuotaExceeded', {}, msg_prefix + msg_5);
|
test('Do-not-match-any-case', {}, msg_prefix + msg_5);
|
||||||
test('Do-not-match-any-case', {}, msg_prefix + msg_6);
|
|
||||||
}());
|
}());
|
||||||
|
|
||||||
(function test_upload_finish() {
|
(function test_upload_finish() {
|
||||||
|
|
|
@ -104,8 +104,6 @@ function _setup_page() {
|
||||||
zuliprc: 'zuliprc',
|
zuliprc: 'zuliprc',
|
||||||
flaskbotrc: 'flaskbotrc',
|
flaskbotrc: 'flaskbotrc',
|
||||||
timezones: moment.tz.names(),
|
timezones: moment.tz.names(),
|
||||||
upload_quota: attachments_ui.bytes_to_size(page_params.upload_quota),
|
|
||||||
total_uploads_size: attachments_ui.bytes_to_size(page_params.total_uploads_size),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$(".settings-box").html(settings_tab);
|
$(".settings-box").html(settings_tab);
|
||||||
|
|
|
@ -57,12 +57,6 @@ exports.uploadError = function (err, file) {
|
||||||
case 'REQUEST ENTITY TOO LARGE':
|
case 'REQUEST ENTITY TOO LARGE':
|
||||||
msg = i18n.t("Sorry, the file was too large.");
|
msg = i18n.t("Sorry, the file was too large.");
|
||||||
break;
|
break;
|
||||||
case 'QuotaExceeded':
|
|
||||||
var translation_part1 = i18n.t('Upload would exceed your maximum quota. You can delete old attachments to free up space.');
|
|
||||||
var translation_part2 = i18n.t('Click here');
|
|
||||||
msg = translation_part1 + ' <a href="#settings/uploaded-files">' + translation_part2 + '</a>';
|
|
||||||
$("#compose-error-msg").html(msg);
|
|
||||||
return;
|
|
||||||
default:
|
default:
|
||||||
msg = i18n.t("An unknown error occurred.");
|
msg = i18n.t("An unknown error occurred.");
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<div id="attachments-settings" class="settings-section" data-name="uploaded-files">
|
<div id="attachments-settings" class="settings-section" data-name="uploaded-files">
|
||||||
<div class='tip'>{{#tr this}}You are currently using __total_uploads_size__ of __upload_quota__ upload space.{{/tr}}</div>
|
|
||||||
<input id="upload_file_search" class="search" type="text" placeholder="{{t 'Search uploads...' }}" aria-label="{{t 'Search uploads...' }}"/>
|
<input id="upload_file_search" class="search" type="text" placeholder="{{t 'Search uploads...' }}" aria-label="{{t 'Search uploads...' }}"/>
|
||||||
<div class="clear-float"></div>
|
<div class="clear-float"></div>
|
||||||
<div class="alert" id="delete-upload-status"></div>
|
<div class="alert" id="delete-upload-status"></div>
|
||||||
|
|
|
@ -362,13 +362,8 @@
|
||||||
|
|
||||||
// Pass any errors to the error option
|
// Pass any errors to the error option
|
||||||
if (xhr.status < 200 || xhr.status > 299) {
|
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);
|
on_error(xhr.statusText, xhr.status);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,6 @@ from zerver.lib.actions import (
|
||||||
get_status_dict, streams_to_dicts_sorted,
|
get_status_dict, streams_to_dicts_sorted,
|
||||||
default_stream_groups_to_dicts_sorted
|
default_stream_groups_to_dicts_sorted
|
||||||
)
|
)
|
||||||
from zerver.lib.upload import get_total_uploads_size_for_user
|
|
||||||
from zerver.lib.user_groups import user_groups_in_realm_serialized
|
from zerver.lib.user_groups import user_groups_in_realm_serialized
|
||||||
from zerver.tornado.event_queue import request_event_queue, get_user_events
|
from zerver.tornado.event_queue import request_event_queue, get_user_events
|
||||||
from zerver.models import Client, Message, Realm, UserPresence, UserProfile, \
|
from zerver.models import Client, Message, Realm, UserPresence, UserProfile, \
|
||||||
|
@ -116,12 +115,6 @@ def fetch_initial_state_data(user_profile: UserProfile,
|
||||||
if want('attachments'):
|
if want('attachments'):
|
||||||
state['attachments'] = user_attachments(user_profile)
|
state['attachments'] = user_attachments(user_profile)
|
||||||
|
|
||||||
if want('upload_quota'):
|
|
||||||
state['upload_quota'] = user_profile.quota
|
|
||||||
|
|
||||||
if want('total_uploads_size'):
|
|
||||||
state['total_uploads_size'] = get_total_uploads_size_for_user(user_profile)
|
|
||||||
|
|
||||||
if want('hotspots'):
|
if want('hotspots'):
|
||||||
state['hotspots'] = get_next_hotspots(user_profile)
|
state['hotspots'] = get_next_hotspots(user_profile)
|
||||||
|
|
||||||
|
|
|
@ -181,22 +181,6 @@ def upload_image_to_s3(
|
||||||
|
|
||||||
key.set_contents_from_string(contents, headers=headers) # type: ignore # https://github.com/python/typeshed/issues/1552
|
key.set_contents_from_string(contents, headers=headers) # type: ignore # https://github.com/python/typeshed/issues/1552
|
||||||
|
|
||||||
def get_total_uploads_size_for_user(user: 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: UserProfile, uploaded_file_size: 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: HttpRequest, user_file: File) -> Tuple[Text, int, Optional[Text]]:
|
def get_file_info(request: HttpRequest, user_file: File) -> Tuple[Text, int, Optional[Text]]:
|
||||||
|
|
||||||
uploaded_file_name = user_file.name
|
uploaded_file_name = user_file.name
|
||||||
|
|
|
@ -2651,7 +2651,7 @@ class FetchQueriesTest(ZulipTestCase):
|
||||||
client_gravatar=False,
|
client_gravatar=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assert_length(queries, 29)
|
self.assert_length(queries, 28)
|
||||||
|
|
||||||
expected_counts = dict(
|
expected_counts = dict(
|
||||||
alert_words=0,
|
alert_words=0,
|
||||||
|
@ -2674,11 +2674,9 @@ class FetchQueriesTest(ZulipTestCase):
|
||||||
realm_user_groups=2,
|
realm_user_groups=2,
|
||||||
stream=2,
|
stream=2,
|
||||||
subscription=5,
|
subscription=5,
|
||||||
total_uploads_size=1,
|
|
||||||
update_display_settings=0,
|
update_display_settings=0,
|
||||||
update_global_notifications=0,
|
update_global_notifications=0,
|
||||||
update_message_flags=5,
|
update_message_flags=5,
|
||||||
upload_quota=0,
|
|
||||||
zulip_version=0,
|
zulip_version=0,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -154,11 +154,9 @@ class HomeTest(ZulipTestCase):
|
||||||
"subscriptions",
|
"subscriptions",
|
||||||
"test_suite",
|
"test_suite",
|
||||||
"timezone",
|
"timezone",
|
||||||
"total_uploads_size",
|
|
||||||
"twenty_four_hour_time",
|
"twenty_four_hour_time",
|
||||||
"unread_msgs",
|
"unread_msgs",
|
||||||
"unsubscribed",
|
"unsubscribed",
|
||||||
"upload_quota",
|
|
||||||
"use_websockets",
|
"use_websockets",
|
||||||
"user_id",
|
"user_id",
|
||||||
"zulip_version",
|
"zulip_version",
|
||||||
|
@ -185,7 +183,7 @@ class HomeTest(ZulipTestCase):
|
||||||
with patch('zerver.lib.cache.cache_set') as cache_mock:
|
with patch('zerver.lib.cache.cache_set') as cache_mock:
|
||||||
result = self._get_home_page(stream='Denmark')
|
result = self._get_home_page(stream='Denmark')
|
||||||
|
|
||||||
self.assert_length(queries, 42)
|
self.assert_length(queries, 41)
|
||||||
self.assert_length(cache_mock.call_args_list, 7)
|
self.assert_length(cache_mock.call_args_list, 7)
|
||||||
|
|
||||||
html = result.content.decode('utf-8')
|
html = result.content.decode('utf-8')
|
||||||
|
@ -250,7 +248,7 @@ class HomeTest(ZulipTestCase):
|
||||||
with queries_captured() as queries2:
|
with queries_captured() as queries2:
|
||||||
result = self._get_home_page()
|
result = self._get_home_page()
|
||||||
|
|
||||||
self.assert_length(queries2, 35)
|
self.assert_length(queries2, 34)
|
||||||
|
|
||||||
# Do a sanity check that our new streams were in the payload.
|
# Do a sanity check that our new streams were in the payload.
|
||||||
html = result.content.decode('utf-8')
|
html = result.content.decode('utf-8')
|
||||||
|
|
|
@ -389,35 +389,6 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
|
||||||
result = self.client_post("/json/user_uploads", {'f1': fp})
|
result = self.client_post("/json/user_uploads", {'f1': fp})
|
||||||
assert sanitize_name(expected) in result.json()['uri']
|
assert sanitize_name(expected) in result.json()['uri']
|
||||||
|
|
||||||
def test_upload_size_quote(self) -> None:
|
|
||||||
"""
|
|
||||||
User quote for uploading should not be exceeded
|
|
||||||
"""
|
|
||||||
self.login(self.example_email("hamlet"))
|
|
||||||
|
|
||||||
d1 = StringIO("zulip!")
|
|
||||||
d1.name = "dummy_1.txt"
|
|
||||||
result = self.client_post("/json/user_uploads", {'file': d1})
|
|
||||||
d1_path_id = re.sub('/user_uploads/', '', result.json()['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/user_uploads", {'file': d2})
|
|
||||||
self.assert_json_success(result)
|
|
||||||
|
|
||||||
d3 = StringIO("zulip!")
|
|
||||||
d3.name = "dummy_3.txt"
|
|
||||||
result = self.client_post("/json/user_uploads", {'file': d3})
|
|
||||||
self.assert_json_error(result, "Upload would exceed your maximum quota.")
|
|
||||||
|
|
||||||
def test_cross_realm_file_access(self) -> None:
|
def test_cross_realm_file_access(self) -> None:
|
||||||
|
|
||||||
def create_user(email: Text, realm_id: Text) -> UserProfile:
|
def create_user(email: Text, realm_id: Text) -> UserProfile:
|
||||||
|
|
|
@ -8,7 +8,7 @@ from django.utils.translation import ugettext as _
|
||||||
from zerver.lib.request import has_request_variables, REQ
|
from zerver.lib.request import has_request_variables, REQ
|
||||||
from zerver.lib.response import json_success, json_error
|
from zerver.lib.response import json_success, json_error
|
||||||
from zerver.lib.upload import upload_message_image_from_request, get_local_file_path, \
|
from zerver.lib.upload import upload_message_image_from_request, get_local_file_path, \
|
||||||
get_signed_upload_url, get_realm_for_filename, within_upload_quota
|
get_signed_upload_url, get_realm_for_filename
|
||||||
from zerver.lib.validator import check_bool
|
from zerver.lib.validator import check_bool
|
||||||
from zerver.models import UserProfile, validate_attachment_request
|
from zerver.models import UserProfile, validate_attachment_request
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
@ -55,8 +55,6 @@ def upload_file_backend(request: HttpRequest, user_profile: UserProfile) -> Http
|
||||||
if settings.MAX_FILE_UPLOAD_SIZE * 1024 * 1024 < file_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") % (
|
return json_error(_("Uploaded file is larger than the allowed limit of %s MB") % (
|
||||||
settings.MAX_FILE_UPLOAD_SIZE))
|
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):
|
if not isinstance(user_file.name, str):
|
||||||
# It seems that in Python 2 unicode strings containing bytes are
|
# It seems that in Python 2 unicode strings containing bytes are
|
||||||
|
|
Loading…
Reference in New Issue