mirror of https://github.com/zulip/zulip.git
realm: Add maximum file size upload restriction.
This commit adds a restriction to the maximum file size that can be uploaded to a realm based on its plan_type.
This commit is contained in:
parent
808acc9e47
commit
3314c89288
|
@ -28,6 +28,11 @@ format used by the Zulip server that they are interacting with.
|
||||||
a standard `realm/update_dict` event to notify clients about changes
|
a standard `realm/update_dict` event to notify clients about changes
|
||||||
in `plan_type` and other fields that atomically change with a given
|
in `plan_type` and other fields that atomically change with a given
|
||||||
change in plan.
|
change in plan.
|
||||||
|
* [`GET /events`](/api/get-events): Added `max_file_upload_size_mib`
|
||||||
|
field to the `data` object in `realm/update_dict` event format;
|
||||||
|
previously, this was a constant. Note that the field does not have a
|
||||||
|
`realm_` prefix in the [`POST /register`](/api/register-queue)
|
||||||
|
response.
|
||||||
|
|
||||||
**Feature level 305**
|
**Feature level 305**
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ DESKTOP_WARNING_VERSION = "5.9.3"
|
||||||
# new level means in api_docs/changelog.md, as well as "**Changes**"
|
# new level means in api_docs/changelog.md, as well as "**Changes**"
|
||||||
# entries in the endpoint's documentation in `zulip.yaml`.
|
# entries in the endpoint's documentation in `zulip.yaml`.
|
||||||
|
|
||||||
API_FEATURE_LEVEL = 305 # Last bumped for adding can_add_members_group to usergroup.
|
API_FEATURE_LEVEL = 306 # Last bumped for adding `max_file_upload_size_mib`.
|
||||||
|
|
||||||
# Bump the minor PROVISION_VERSION to indicate that folks should provision
|
# Bump the minor PROVISION_VERSION to indicate that folks should provision
|
||||||
# only when going from an old version of the code to a newer version. Bump
|
# only when going from an old version of the code to a newer version. Bump
|
||||||
|
|
|
@ -284,7 +284,12 @@ export function dispatch_normal_event(event) {
|
||||||
switch (event.property) {
|
switch (event.property) {
|
||||||
case "default":
|
case "default":
|
||||||
for (const [key, value] of Object.entries(event.data)) {
|
for (const [key, value] of Object.entries(event.data)) {
|
||||||
|
if (key === "max_file_upload_size_mib") {
|
||||||
|
realm[key] = value;
|
||||||
|
} else {
|
||||||
realm["realm_" + key] = value;
|
realm["realm_" + key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
if (Object.hasOwn(realm_settings, key)) {
|
if (Object.hasOwn(realm_settings, key)) {
|
||||||
settings_org.sync_realm_settings(key);
|
settings_org.sync_realm_settings(key);
|
||||||
}
|
}
|
||||||
|
|
|
@ -586,6 +586,7 @@ run_test("realm settings", ({override}) => {
|
||||||
override(realm, "realm_direct_message_permission_group", 1);
|
override(realm, "realm_direct_message_permission_group", 1);
|
||||||
override(realm, "realm_plan_type", 2);
|
override(realm, "realm_plan_type", 2);
|
||||||
override(realm, "realm_upload_quota_mib", 5000);
|
override(realm, "realm_upload_quota_mib", 5000);
|
||||||
|
override(realm, "max_file_upload_size_mib", 10);
|
||||||
override(settings_org, "populate_auth_methods", noop);
|
override(settings_org, "populate_auth_methods", noop);
|
||||||
dispatch(event);
|
dispatch(event);
|
||||||
assert_same(realm.realm_create_multiuse_invite_group, 3);
|
assert_same(realm.realm_create_multiuse_invite_group, 3);
|
||||||
|
@ -599,6 +600,7 @@ run_test("realm settings", ({override}) => {
|
||||||
assert_same(realm.realm_direct_message_permission_group, 3);
|
assert_same(realm.realm_direct_message_permission_group, 3);
|
||||||
assert_same(realm.realm_plan_type, 3);
|
assert_same(realm.realm_plan_type, 3);
|
||||||
assert_same(realm.realm_upload_quota_mib, 50000);
|
assert_same(realm.realm_upload_quota_mib, 50000);
|
||||||
|
assert_same(realm.max_file_upload_size_mib, 1024);
|
||||||
assert_same(update_stream_privacy_choices_called, true);
|
assert_same(update_stream_privacy_choices_called, true);
|
||||||
|
|
||||||
event = event_fixtures.realm__update_dict__icon;
|
event = event_fixtures.realm__update_dict__icon;
|
||||||
|
|
|
@ -372,6 +372,7 @@ exports.fixtures = {
|
||||||
direct_message_permission_group: 3,
|
direct_message_permission_group: 3,
|
||||||
plan_type: 3,
|
plan_type: 3,
|
||||||
upload_quota_mib: 50000,
|
upload_quota_mib: 50000,
|
||||||
|
max_file_upload_size_mib: 1024,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -794,6 +794,7 @@ def do_change_realm_plan_type(
|
||||||
data={
|
data={
|
||||||
"plan_type": plan_type,
|
"plan_type": plan_type,
|
||||||
"upload_quota_mib": optional_bytes_to_mib(realm.upload_quota_bytes()),
|
"upload_quota_mib": optional_bytes_to_mib(realm.upload_quota_bytes()),
|
||||||
|
"max_file_upload_size_mib": realm.get_max_file_upload_size_mebibytes(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
send_event_on_commit(realm, event, active_user_ids(realm.id))
|
send_event_on_commit(realm, event, active_user_ids(realm.id))
|
||||||
|
|
|
@ -1075,6 +1075,7 @@ plan_type_data = DictType(
|
||||||
required_keys=[
|
required_keys=[
|
||||||
("plan_type", int),
|
("plan_type", int),
|
||||||
("upload_quota_mib", OptionalType(int)),
|
("upload_quota_mib", OptionalType(int)),
|
||||||
|
("max_file_upload_size_mib", int),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -350,7 +350,7 @@ def fetch_initial_state_data(
|
||||||
|
|
||||||
# Important: Encode units in the client-facing API name.
|
# Important: Encode units in the client-facing API name.
|
||||||
state["max_avatar_file_size_mib"] = settings.MAX_AVATAR_FILE_SIZE_MIB
|
state["max_avatar_file_size_mib"] = settings.MAX_AVATAR_FILE_SIZE_MIB
|
||||||
state["max_file_upload_size_mib"] = settings.MAX_FILE_UPLOAD_SIZE
|
state["max_file_upload_size_mib"] = realm.get_max_file_upload_size_mebibytes()
|
||||||
state["max_icon_file_size_mib"] = settings.MAX_ICON_FILE_SIZE_MIB
|
state["max_icon_file_size_mib"] = settings.MAX_ICON_FILE_SIZE_MIB
|
||||||
upload_quota_bytes = realm.upload_quota_bytes()
|
upload_quota_bytes = realm.upload_quota_bytes()
|
||||||
state["realm_upload_quota_mib"] = optional_bytes_to_mib(upload_quota_bytes)
|
state["realm_upload_quota_mib"] = optional_bytes_to_mib(upload_quota_bytes)
|
||||||
|
@ -1308,6 +1308,10 @@ def apply_event(
|
||||||
)
|
)
|
||||||
elif event["op"] == "update_dict":
|
elif event["op"] == "update_dict":
|
||||||
for key, value in event["data"].items():
|
for key, value in event["data"].items():
|
||||||
|
if key == "max_file_upload_size_mib":
|
||||||
|
state["max_file_upload_size_mib"] = value
|
||||||
|
continue
|
||||||
|
|
||||||
state["realm_" + key] = value
|
state["realm_" + key] = value
|
||||||
# It's a bit messy, but this is where we need to
|
# It's a bit messy, but this is where we need to
|
||||||
# update the state for whether password authentication
|
# update the state for whether password authentication
|
||||||
|
|
|
@ -1011,6 +1011,21 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub
|
||||||
# it as gibibytes (GiB) to be a bit more generous in case of confusion.
|
# it as gibibytes (GiB) to be a bit more generous in case of confusion.
|
||||||
return self.upload_quota_gb << 30
|
return self.upload_quota_gb << 30
|
||||||
|
|
||||||
|
def get_max_file_upload_size_mebibytes(self) -> int:
|
||||||
|
plan_type = self.plan_type
|
||||||
|
if plan_type == Realm.PLAN_TYPE_SELF_HOSTED:
|
||||||
|
return settings.MAX_FILE_UPLOAD_SIZE
|
||||||
|
elif plan_type == Realm.PLAN_TYPE_LIMITED:
|
||||||
|
return 10
|
||||||
|
elif plan_type in [
|
||||||
|
Realm.PLAN_TYPE_STANDARD,
|
||||||
|
Realm.PLAN_TYPE_STANDARD_FREE,
|
||||||
|
Realm.PLAN_TYPE_PLUS,
|
||||||
|
]:
|
||||||
|
return 1024
|
||||||
|
else:
|
||||||
|
raise AssertionError("Invalid plan type")
|
||||||
|
|
||||||
# `realm` instead of `self` here to make sure the parameters of the cache key
|
# `realm` instead of `self` here to make sure the parameters of the cache key
|
||||||
# function matches the original method.
|
# function matches the original method.
|
||||||
@cache_with_key(
|
@cache_with_key(
|
||||||
|
|
|
@ -4679,6 +4679,14 @@ paths:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: |
|
description: |
|
||||||
Whether [topics are required](/help/require-topics) for messages in this organization.
|
Whether [topics are required](/help/require-topics) for messages in this organization.
|
||||||
|
max_file_upload_size_mib:
|
||||||
|
type: integer
|
||||||
|
description: |
|
||||||
|
The new maximum file size that can be uploaded to this Zulip organization.
|
||||||
|
|
||||||
|
**Changes**: New in Zulip 10.0 (feature level 306). Previously, this field of
|
||||||
|
the core state did not support being updated via the events system, as it was
|
||||||
|
typically hardcoded for a given Zulip installation.
|
||||||
message_content_allowed_in_email_notifications:
|
message_content_allowed_in_email_notifications:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: |
|
description: |
|
||||||
|
@ -17320,7 +17328,7 @@ paths:
|
||||||
description: |
|
description: |
|
||||||
Present if `realm` is present in `fetch_event_types`.
|
Present if `realm` is present in `fetch_event_types`.
|
||||||
|
|
||||||
The maximum file size that can be uploaded to this Zulip server.
|
The maximum file size that can be uploaded to this Zulip organization.
|
||||||
max_avatar_file_size_mib:
|
max_avatar_file_size_mib:
|
||||||
type: integer
|
type: integer
|
||||||
description: |
|
description: |
|
||||||
|
|
|
@ -99,10 +99,11 @@ def handle_upload_pre_create_hook(
|
||||||
if data.size_is_deferred or data.size is None:
|
if data.size_is_deferred or data.size is None:
|
||||||
return reject_upload("SizeIsDeferred is not supported", 411)
|
return reject_upload("SizeIsDeferred is not supported", 411)
|
||||||
|
|
||||||
if data.size > settings.MAX_FILE_UPLOAD_SIZE * 1024 * 1024:
|
max_file_upload_size_mebibytes = user_profile.realm.get_max_file_upload_size_mebibytes()
|
||||||
|
if data.size > max_file_upload_size_mebibytes * 1024 * 1024:
|
||||||
return reject_upload(
|
return reject_upload(
|
||||||
_("Uploaded file is larger than the allowed limit of {max_file_size} MiB").format(
|
_("Uploaded file is larger than the allowed limit of {max_file_size} MiB").format(
|
||||||
max_file_size=settings.MAX_FILE_UPLOAD_SIZE
|
max_file_size=max_file_upload_size_mebibytes
|
||||||
),
|
),
|
||||||
413,
|
413,
|
||||||
)
|
)
|
||||||
|
|
|
@ -448,10 +448,11 @@ def upload_file_backend(request: HttpRequest, user_profile: UserProfile) -> Http
|
||||||
assert isinstance(user_file, UploadedFile)
|
assert isinstance(user_file, UploadedFile)
|
||||||
file_size = user_file.size
|
file_size = user_file.size
|
||||||
assert file_size is not None
|
assert file_size is not None
|
||||||
if file_size > settings.MAX_FILE_UPLOAD_SIZE * 1024 * 1024:
|
max_file_upload_size_mebibytes = user_profile.realm.get_max_file_upload_size_mebibytes()
|
||||||
|
if file_size > max_file_upload_size_mebibytes * 1024 * 1024:
|
||||||
raise JsonableError(
|
raise JsonableError(
|
||||||
_("Uploaded file is larger than the allowed limit of {max_size} MiB").format(
|
_("Uploaded file is larger than the allowed limit of {max_size} MiB").format(
|
||||||
max_size=settings.MAX_FILE_UPLOAD_SIZE,
|
max_size=max_file_upload_size_mebibytes,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
check_upload_within_quota(user_profile.realm, file_size)
|
check_upload_within_quota(user_profile.realm, file_size)
|
||||||
|
|
Loading…
Reference in New Issue