diff --git a/api_docs/changelog.md b/api_docs/changelog.md index c25407aea0..ff9a80689b 100644 --- a/api_docs/changelog.md +++ b/api_docs/changelog.md @@ -20,6 +20,12 @@ format used by the Zulip server that they are interacting with. ## Changes in Zulip 11.0 +**Feature level 409** + +* `PATCH /realm`, [`POST /register`](/api/register-queue), + [`GET /events`](/api/get-events): Added a new + `require_e2ee_push_notifications` realm setting. + **Feature level 407** * [`GET /users/me/subscriptions`](/api/get-subscriptions), diff --git a/version.py b/version.py index fd0f8c88aa..e3846a3614 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 = 408 +API_FEATURE_LEVEL = 409 # 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/admin.ts b/web/src/admin.ts index 1a51217370..591ea6840d 100644 --- a/web/src/admin.ts +++ b/web/src/admin.ts @@ -41,6 +41,9 @@ const admin_settings_label = { }), realm_inline_url_embed_preview: $t({defaultMessage: "Show previews of linked websites"}), realm_send_welcome_emails: $t({defaultMessage: "Send emails introducing Zulip to new users"}), + realm_require_e2ee_push_notifications: $t({ + defaultMessage: "Require end-to-end encryption for push notification content", + }), realm_message_content_allowed_in_email_notifications: $t({ defaultMessage: "Allow message content in message notification emails", }), @@ -194,6 +197,7 @@ export function build_page(): void { realm_topics_policy_values: settings_config.get_realm_topics_policy_values(), empty_string_topic_display_name: util.get_final_topic_display_name(""), realm_send_welcome_emails: realm.realm_send_welcome_emails, + realm_require_e2ee_push_notifications: realm.realm_require_e2ee_push_notifications, realm_message_content_allowed_in_email_notifications: realm.realm_message_content_allowed_in_email_notifications, realm_enable_spectator_access: realm.realm_enable_spectator_access, diff --git a/web/src/server_events_dispatch.js b/web/src/server_events_dispatch.js index a2fc7f9a82..4fbc762732 100644 --- a/web/src/server_events_dispatch.js +++ b/web/src/server_events_dispatch.js @@ -289,6 +289,7 @@ export function dispatch_normal_event(event) { require_unique_names: noop, send_welcome_emails: noop, topics_policy: noop, + require_e2ee_push_notifications: noop, message_content_allowed_in_email_notifications: noop, enable_spectator_access: noop, signup_announcements_stream_id: noop, diff --git a/web/src/state_data.ts b/web/src/state_data.ts index 4fc0f34d86..5d77fa5431 100644 --- a/web/src/state_data.ts +++ b/web/src/state_data.ts @@ -426,6 +426,7 @@ export const realm_schema = z.object({ realm_presence_disabled: z.boolean(), realm_push_notifications_enabled: z.boolean(), realm_push_notifications_enabled_end_timestamp: z.nullable(z.number()), + realm_require_e2ee_push_notifications: z.boolean(), realm_require_unique_names: z.boolean(), realm_send_welcome_emails: z.boolean(), realm_signup_announcements_stream_id: z.number(), diff --git a/web/templates/settings/organization_settings_admin.hbs b/web/templates/settings/organization_settings_admin.hbs index 4a5d655d18..69e8d720b2 100644 --- a/web/templates/settings/organization_settings_admin.hbs +++ b/web/templates/settings/organization_settings_admin.hbs @@ -70,6 +70,13 @@ {{> settings_save_discard_widget section_name="notifications-security" }}
+ {{> settings_checkbox + setting_name="realm_require_e2ee_push_notifications" + prefix="id_" + is_checked=realm_require_e2ee_push_notifications + label=admin_settings_label.realm_require_e2ee_push_notifications + help_link="/help/mobile-notifications"}} + {{> settings_checkbox setting_name="realm_message_content_allowed_in_email_notifications" prefix="id_" diff --git a/zerver/migrations/0743_realm_require_e2ee_push_notifications.py b/zerver/migrations/0743_realm_require_e2ee_push_notifications.py new file mode 100644 index 0000000000..db0a9e78fb --- /dev/null +++ b/zerver/migrations/0743_realm_require_e2ee_push_notifications.py @@ -0,0 +1,38 @@ +# Generated by Django 5.2.4 on 2025-07-28 18:58 + +from django.conf import settings +from django.db import migrations, models +from django.db.backends.base.schema import BaseDatabaseSchemaEditor +from django.db.migrations.state import StateApps + + +def update_require_e2ee_push_notifications( + apps: StateApps, schema_editor: BaseDatabaseSchemaEditor +) -> None: + Realm = apps.get_model("zerver", "Realm") + + # We use 'getattr' with a default value to allow this migration + # to run in development environment when PUSH_NOTIFICATION_REDACT_CONTENT + # setting is removed in the future. + require_e2ee = getattr(settings, "PUSH_NOTIFICATION_REDACT_CONTENT", False) + if require_e2ee: + Realm.objects.update(require_e2ee_push_notifications=require_e2ee) + + +class Migration(migrations.Migration): + dependencies = [ + ("zerver", "0742_usermessage_zerver_usermessage_is_private_unread_message_id"), + ] + + operations = [ + migrations.AddField( + model_name="realm", + name="require_e2ee_push_notifications", + field=models.BooleanField(db_default=False, default=False), + ), + migrations.RunPython( + update_require_e2ee_push_notifications, + elidable=True, + reverse_code=migrations.RunPython.noop, + ), + ] diff --git a/zerver/models/realms.py b/zerver/models/realms.py index a269a18626..6a4269c500 100644 --- a/zerver/models/realms.py +++ b/zerver/models/realms.py @@ -191,6 +191,7 @@ class Realm(models.Model): # cease to be the case. push_notifications_enabled = models.BooleanField(default=False, db_index=True) push_notifications_enabled_end_timestamp = models.DateTimeField(default=None, null=True) + require_e2ee_push_notifications = models.BooleanField(default=False, db_default=False) date_created = models.DateTimeField(default=timezone_now) scheduled_deletion_date = models.DateTimeField(default=None, db_index=True, null=True) @@ -726,6 +727,7 @@ class Realm(models.Model): name=str, name_changes_disabled=bool, push_notifications_enabled=bool, + require_e2ee_push_notifications=bool, require_unique_names=bool, send_welcome_emails=bool, topics_policy=RealmTopicsPolicyEnum, diff --git a/zerver/openapi/zulip.yaml b/zerver/openapi/zulip.yaml index fef61c9579..ea1bff9dd7 100644 --- a/zerver/openapi/zulip.yaml +++ b/zerver/openapi/zulip.yaml @@ -5402,6 +5402,32 @@ paths: indicated timestamp is near. **Changes**: New in Zulip 8.0 (feature level 231). + require_e2ee_push_notifications: + type: boolean + description: | + Whether this realm is configured to disallow sending mobile + push notifications with message content through the legacy + mobile push notifications APIs. The new API uses end-to-end + encryption to protect message content and metadata from + being accessible to the push bouncer service, APNs, and + FCM. Clients that support the new E2EE API will use it + automatically regardless of this setting. + + If `true`, mobile push notifications sent to clients that + lack support for E2EE push notifications will always have + "New message" as their content. Note that these legacy + mobile notifications will still contain metadata, which may + include the message's ID, the sender's name, email address, + and avatar. + + In a future release, once the official mobile apps have + implemented fully validated their E2EE protocol support, + this setting will become strict, and disable the legacy + protocol entirely. + + **Changes**: New in Zulip 11.0 (feature level 409). Previously, + this behavior was available only via the + `PUSH_NOTIFICATION_REDACT_CONTENT` global server setting. require_unique_names: type: boolean description: | @@ -18953,6 +18979,34 @@ paths: Present if `realm` is present in `fetch_event_types`. The name of the organization, used in login pages etc. + realm_require_e2ee_push_notifications: + type: boolean + description: | + Present if `realm` is present in `fetch_event_types`. + + Whether this realm is configured to disallow sending mobile + push notifications with message content through the legacy + mobile push notifications APIs. The new API uses end-to-end + encryption to protect message content and metadata from + being accessible to the push bouncer service, APNs, and + FCM. Clients that support the new E2EE API will use it + automatically regardless of this setting. + + If `true`, mobile push notifications sent to clients that + lack support for E2EE push notifications will always have + "New message" as their content. Note that these legacy + mobile notifications will still contain metadata, which may + include the message's ID, the sender's name, email address, + and avatar. + + In a future release, once the official mobile apps have + implemented fully validated their E2EE protocol support, + this setting will become strict, and disable the legacy + protocol entirely. + + **Changes**: New in Zulip 11.0 (feature level 409). Previously, + this behavior was available only via the + `PUSH_NOTIFICATION_REDACT_CONTENT` global server setting. realm_require_unique_names: type: boolean description: | diff --git a/zerver/tests/test_home.py b/zerver/tests/test_home.py index 693d330615..1b44bda878 100644 --- a/zerver/tests/test_home.py +++ b/zerver/tests/test_home.py @@ -208,6 +208,7 @@ class HomeTest(ZulipTestCase): "realm_presence_disabled", "realm_push_notifications_enabled", "realm_push_notifications_enabled_end_timestamp", + "realm_require_e2ee_push_notifications", "realm_require_unique_names", "realm_send_welcome_emails", "realm_signup_announcements_stream_id", diff --git a/zerver/views/realm.py b/zerver/views/realm.py index b28a210cca..bc6d19ae59 100644 --- a/zerver/views/realm.py +++ b/zerver/views/realm.py @@ -178,6 +178,7 @@ def update_realm( name_changes_disabled: Json[bool] | None = None, new_stream_announcements_stream_id: Json[int] | None = None, org_type: Json[OrgTypeEnum] | None = None, + require_e2ee_push_notifications: Json[bool] | None = None, require_unique_names: Json[bool] | None = None, send_welcome_emails: Json[bool] | None = None, signup_announcements_stream_id: Json[int] | None = None,