diff --git a/api_docs/changelog.md b/api_docs/changelog.md index 69a1098e60..1fc8a63662 100644 --- a/api_docs/changelog.md +++ b/api_docs/changelog.md @@ -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 in `plan_type` and other fields that atomically change with a given 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** diff --git a/version.py b/version.py index 3d114cb7f2..64c9b2499c 100644 --- a/version.py +++ b/version.py @@ -34,7 +34,7 @@ DESKTOP_WARNING_VERSION = "5.9.3" # new level means in api_docs/changelog.md, as well as "**Changes**" # 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 # only when going from an old version of the code to a newer version. Bump diff --git a/web/src/server_events_dispatch.js b/web/src/server_events_dispatch.js index ef9951083c..0315d6d352 100644 --- a/web/src/server_events_dispatch.js +++ b/web/src/server_events_dispatch.js @@ -284,7 +284,12 @@ export function dispatch_normal_event(event) { switch (event.property) { case "default": for (const [key, value] of Object.entries(event.data)) { - realm["realm_" + key] = value; + if (key === "max_file_upload_size_mib") { + realm[key] = value; + } else { + realm["realm_" + key] = value; + } + if (Object.hasOwn(realm_settings, key)) { settings_org.sync_realm_settings(key); } diff --git a/web/tests/dispatch.test.js b/web/tests/dispatch.test.js index c67206504f..3e442ded6c 100644 --- a/web/tests/dispatch.test.js +++ b/web/tests/dispatch.test.js @@ -586,6 +586,7 @@ run_test("realm settings", ({override}) => { override(realm, "realm_direct_message_permission_group", 1); override(realm, "realm_plan_type", 2); override(realm, "realm_upload_quota_mib", 5000); + override(realm, "max_file_upload_size_mib", 10); override(settings_org, "populate_auth_methods", noop); dispatch(event); 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_plan_type, 3); 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); event = event_fixtures.realm__update_dict__icon; diff --git a/web/tests/lib/events.js b/web/tests/lib/events.js index 92d40cefb8..98cc7f8ad7 100644 --- a/web/tests/lib/events.js +++ b/web/tests/lib/events.js @@ -372,6 +372,7 @@ exports.fixtures = { direct_message_permission_group: 3, plan_type: 3, upload_quota_mib: 50000, + max_file_upload_size_mib: 1024, }, }, diff --git a/zerver/actions/realm_settings.py b/zerver/actions/realm_settings.py index 0a10c204d1..babcce7055 100644 --- a/zerver/actions/realm_settings.py +++ b/zerver/actions/realm_settings.py @@ -794,6 +794,7 @@ def do_change_realm_plan_type( data={ "plan_type": plan_type, "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)) diff --git a/zerver/lib/event_schema.py b/zerver/lib/event_schema.py index 11dccafdf2..55e8229592 100644 --- a/zerver/lib/event_schema.py +++ b/zerver/lib/event_schema.py @@ -1075,6 +1075,7 @@ plan_type_data = DictType( required_keys=[ ("plan_type", int), ("upload_quota_mib", OptionalType(int)), + ("max_file_upload_size_mib", int), ], ) diff --git a/zerver/lib/events.py b/zerver/lib/events.py index 68cfd768b2..e2f6dfd81f 100644 --- a/zerver/lib/events.py +++ b/zerver/lib/events.py @@ -350,7 +350,7 @@ def fetch_initial_state_data( # Important: Encode units in the client-facing API name. 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 upload_quota_bytes = realm.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": 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 # It's a bit messy, but this is where we need to # update the state for whether password authentication diff --git a/zerver/models/realms.py b/zerver/models/realms.py index 5ba4501636..2f553566c9 100644 --- a/zerver/models/realms.py +++ b/zerver/models/realms.py @@ -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. 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 # function matches the original method. @cache_with_key( diff --git a/zerver/openapi/zulip.yaml b/zerver/openapi/zulip.yaml index 9cd2cd3615..01de2c5576 100644 --- a/zerver/openapi/zulip.yaml +++ b/zerver/openapi/zulip.yaml @@ -4679,6 +4679,14 @@ paths: type: boolean description: | 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: type: boolean description: | @@ -17320,7 +17328,7 @@ paths: description: | 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: type: integer description: | diff --git a/zerver/views/tusd.py b/zerver/views/tusd.py index e812208da9..cef36a85e0 100644 --- a/zerver/views/tusd.py +++ b/zerver/views/tusd.py @@ -99,10 +99,11 @@ def handle_upload_pre_create_hook( if data.size_is_deferred or data.size is None: 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( _("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, ) diff --git a/zerver/views/upload.py b/zerver/views/upload.py index 50103c69ca..3675c7d966 100644 --- a/zerver/views/upload.py +++ b/zerver/views/upload.py @@ -448,10 +448,11 @@ def upload_file_backend(request: HttpRequest, user_profile: UserProfile) -> Http assert isinstance(user_file, UploadedFile) file_size = user_file.size 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( _("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)