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:
Greg Price 2018-01-26 18:30:26 -08:00 committed by Greg Price
parent 0fcf0c5052
commit 55cf54c087
11 changed files with 10 additions and 97 deletions

View File

@ -59,28 +59,17 @@ 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").text(), msg);
assert.equal($("#compose-error-msg").html(), msg);
} else {
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 assert_side_effects(msg);
// 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);
}
} }
var msg_prefix = 'translated: '; var msg_prefix = 'translated: ';
@ -88,17 +77,13 @@ zrequire('upload');
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() {

View File

@ -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);

View File

@ -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;

View File

@ -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>

View File

@ -362,12 +362,7 @@
// 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.")) { on_error(xhr.statusText, xhr.status);
var errorString = "QuotaExceeded";
on_error(errorString, xhr.status);
} else {
on_error(xhr.statusText, xhr.status);
}
} }
}; };

View File

@ -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)

View File

@ -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

View File

@ -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,
) )

View File

@ -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')

View File

@ -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:

View File

@ -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