Commit Graph

407 Commits

Author SHA1 Message Date
Prakhar Pratyush
738b3e9399 push_notifications: Log roundtrip time since worker decided to send.
Adds a log for the end-to-end latency from when the worker decided
to send push notifications & received a success response from bouncer.

Fixes part of #35368.

(cherry picked from commit c604ecb902)
2025-08-28 17:34:20 -07:00
Alex Vandiver
62bcab1a1a push_notifications: Revert parallel-device sending.
This reverts #26594, due to unexpected ConnectionClosed errors observed in Django.

(cherry picked from commit 331d210dac)
2025-08-28 17:34:20 -07:00
Mateusz Mandera
8c9c39059d message: Use .is_channel_message column instead of is_stream_message().
This avoids a potential unnecessary message.recipient fetch required by
is_stream_message(). is_stream_message() methods precedes the addition
of the denormalized is_channel_message column and is now unnecessary.

In practice, we usually fetch Message objects with `.recipient` already,
so I don't expect any notable performance impact here - but it's still a
useful change to make.

(cherry picked from commit 51cef01c29)
2025-08-28 17:34:20 -07:00
Prakhar Pratyush
51711bd3d9 push_notifications: Parse push/e2ee/notify response using Pydantic.
In `send_push_notifications`, we were manually asserting types of the
fields returned by the `remotes/push/e2ee/notify` endpoint at runtime
and constructed the `response_data` data structure for further use.

Pydantic is the perfect tool for this task.

This commit updates the concerned code to use Pydantic's `TypeAdapter`.

No functional change.

Fixes part of #35368.

(cherry picked from commit 5a37942c61)
2025-08-28 17:34:20 -07:00
Prakhar Pratyush
3cbf0e70a2 push_notification: Add support to send E2EE test push notification.
This commit adds an endpoint `/mobile_push/e2ee/test_notification`
to send an end-to-end encrypted test push notification to the user's
selected mobile device or all of their mobile devices.
2025-08-13 00:13:50 -07:00
Prakhar Pratyush
f034a6c3b4 push_notification: Remove is_removal param from send_push_notifications.
We can determine whether the request is meant to revoke an already
sent push notification using the "type" field of the payload.

Passing `is_removal` parameter explicitly to `send_push_notifications`
is not required.
2025-08-13 00:13:50 -07:00
Prakhar Pratyush
787d73f018 push_notification: Update the payload data that gets encrypted.
This commit updates the data that gets encrypted to be
the same on both android and iOS.

The data and its format is almost the same as what we send
as FCM payload to android clients with no E2EE support,
changes are:

For send push notification payload:
* 'realm_id`, 'server', 'sender_email', and 'realm_uri' fields
  don't exist in the new payload.
* 'event' field renamed to 'type'
* 'stream' and 'stream_id' fields renamed to 'channel_name'
  and 'channel_id' respectively.
* The value of 'recipient_type' will be 'channel' & 'direct'
  instead of 'stream' & 'private' respectively.
* 'zulip_message_id' field renamed to 'message_id'

For remove push notification payload:
* 'realm_id`, 'server', and 'realm_uri' fields don't exist
  in the new payload.
* 'event' field renamed to 'type'
* 'zulip_message_ids' field renamed to 'message_ids' and it's
  value will be a JSON array instead of a string.

In the existing iOS client, we have no code of our own involved
in constructing the notifications in the UI, and instead we
leave it to the iOS SDK to do so.

Since, for clients with E2EE support the data is going to be
interpreted by our own code, not by the iOS SDK - we are free
to keep the same data and format.

Co-authored-by: Tim Abbott <tabbott@zulip.com>
2025-08-05 16:54:22 -07:00
Prakhar Pratyush
dd134ef325 push_notification: Remove 'content_truncated' field from FCM payload.
This commit removes the 'content_truncated' field from the FCM
payload to send push notification as it was unused by clients.
2025-08-05 16:54:22 -07:00
Prakhar Pratyush
c9edb9c9ce push_notification: Remove 'zulip_message_id' from FCM payload.
This commit removes the 'zulip_message_id' field from the FCM
payload for remove push notification.

Clients that use `zulip_message_ids` instead have been out for
years and we no longer support older client versions that don't.
2025-08-05 16:54:22 -07:00
Prakhar Pratyush
1a5cab130d push_notification: Remove obsolete fields from APNs payload.
Mobile push notification payloads for APNs no longer contain
the `server` and `realm_id` fields, which were unused.
2025-08-05 16:54:22 -07:00
Anders Kaseorg
88b9a2cfbd push_notifications: Guard reference to RemotePushDevice.
This doesn’t exist when ZILENCER_ENABLED is False.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2025-08-05 11:03:18 -07:00
Prakhar Pratyush
6c625a0177 push_notification: Remove "time" field from APNs payload.
This reverts 1862c3d9ab.

The "time" field was added in the APNs payload to log
"Remote queuing latency".

The client doesn't use this field. Ideally, we should have
removed the field from the payload before sending it to APNs.

We no longer log "Remote queuing latency", so it's safe to
remove it from the APNs payload.

Only FCM payload will have this field as it is used by android
clients.
2025-07-30 17:24:57 -07:00
Prakhar Pratyush
298a39e457 push_notification: Remove 'modernize_apns_payload'.
The function was used to reformat apns payload sent
by servers older than 613d093d7.

Since we no longer need to worry about servers older
than 1.6.0, it's safe to remove that function.
2025-07-30 17:24:57 -07:00
Prakhar Pratyush
d972bb1ca9 push_notifications: Redact content for older clients if E2EE required.
This commit replaces the `PUSH_NOTIFICATION_REDACT_CONTENT` server
setting with `require_e2ee_push_notifications` realm setting.

If `require_e2ee_push_notifications` set to True:
* Older clients: Content redacted
* Updated clients: Encrypted content

If `require_e2ee_push_notifications` set to False:
* Older clients: Content NOT redacted
* Updated clients: Encrypted content

Note: Older clients refers to clients that don't support E2EE.

Fixes part of #35370.
2025-07-28 17:06:59 -07:00
Prakhar Pratyush
5616be4afa test_e2ee_push_notification: Improve tests to cover more cases.
This commit adds a test and updates a few existing tests to
cover more cases related to send push notifications.

* We no longer mock the 'send_push_notifications_legacy' function
  while testing 'send_push_notifications' codepath and vice-versa.
  This makes the tests more realistic as both functions gets called
  in 'handle_push_notification'.

  This covers the case when only old clients (which don't support
  E2EE) exists for a user. Or only updated clients (which supports
  E2EE) exist.

* Adds a test 'test_both_old_and_new_client_coexists' for the case
  when a user has both type of clients at an instant i.e. they have
  updated a few devices only.
2025-07-28 10:39:48 -07:00
Prakhar Pratyush
d91a6be3f1 push_notification: Return early if no device registered - w/o bouncer.
In 'send_push_notifications_legacy', when a user has no registered
devices:
* `uses_notification_bouncer()`=True: we log "Skipping..." and return.

* `uses_notification_bouncer()`=False: we make some function calls,
  which effectively does nothing.

It's better to have a common check (and log) early in the codepath.
This commit makes that change.
2025-07-28 10:39:48 -07:00
Prakhar Pratyush
36a2093585 push_notification: Remove dev guard from send E2EE notif codepath.
This commit removes the DEVELOPMENT guard from the new
'send_push_notifications' function in 'handle_push_notification'
and 'handle_remove_push_notification'.
2025-07-27 20:47:16 -07:00
Prakhar Pratyush
6ab6df96c8 push_notification: Send a list of push requests.
Earlier, we were passing a map `device_id_to_encrypted_data`
and http headers as separate fields to bouncer.

The downside of that approach is it restricts the bouncer to
process only one type of notice i.e. either notification for
a new message or removal of sent notification, because it
used to receive a fixed priority and push_type for all the
entries in the map.

Also, using map restricts the bouncer to receive only one
request per device_id. Server can't send multiple notices
to a device in a single call to bouncer.

Currently, the server isn't modelled in a way to make a
single call to the bouncer with:
* Both send-notification & remove-notification request data.
* Multiple send-notification request data to the same device.

This commit replaces the old protocol of sending data with
a list of objects where each object has the required data
for bouncer to send it to FCM or APNs.

This makes things a lot flexible and opens possibility for
server to batch requests in a different way if we'd like to.
2025-07-25 12:47:55 -07:00
Prakhar Pratyush
3d3f4d5e62 push_notification: Remove 'get_apns_payload_data_to_encrypt'.
'get_apns_payload_data_to_encrypt' was added in commit
0ae34ddb65, in parallel
to 'get_message_payload_apns' - to use in E2EE codepath.

The intent was to avoid nesting in the payload returned
by 'get_message_payload_apns' function, just like FCM
payload returned by 'get_message_payload_gcm'.

Turned out, the nesting is helpful in APNs case for various
reasons. So, this commit reverts that function and we'll
continue to use the older structure returned by the function
'get_message_payload_apns'.
2025-07-25 12:47:55 -07:00
Prakhar Pratyush
2dbc17b453 push_notification: Revoke push notifications using encrypted payload.
This commit updates 'handle_remove_push_notification' function
to use the new 'send_push_notifications' function.

It leads to encrypt the removal payload before sending it to bouncer.

Fixes part of #35368.
2025-07-24 16:13:00 -07:00
Prakhar Pratyush
84db492dfb push_notification: Reuse 'send_push_notification_legacy'.
This commit refactors 'handle_remove_push_notification'
to reuse 'send_push_notification_legacy' function.
2025-07-24 16:12:59 -07:00
Prakhar Pratyush
29ea8a07c2 push_notification: Add support to configure priority and push_type.
This commit adds support to let server configure:
* fcm_priority
* apns_priority
* apns_push_type

while sending E2EE push notifications.

The values of these fields will vary depending on whether the
send request is to send push notification for a message or
revoke an already sent notification.

Since, the bouncer receives encrypted data so it can't inspect
the payload to determine whether it is a removal request or not,
hence can't configure priority on its own.

The server needs to specify explicitly.

We're not simply sending a single 'is_removal' flag because
allowing the server to configure them separately will help in
future to support other types of notifications with a different
combination of priority and push_type, like whose aim is to notify
user about information other than a new message or removal request.

Fixes part of #35368.
2025-07-24 11:56:42 -07:00
Prakhar Pratyush
7e1afa0e8a push_notification: Send end-to-end encrypted push notifications.
This commit adds support to send encrypted push notifications
to devices registered to receive encrypted notifications.

URL: `POST /api/v1/remotes/push/e2ee/notify`
payload: `realm_uuid` and `device_id_to_encrypted_data`

The POST request needs to be authenticated with the server’s
API key.

Note: For Zulip Cloud, a background fact about the push bouncer is
that it runs on the same server and database as the main application;
it’s not a separate service. So, as an optimization we directly call
'send_e2ee_push_notifications' function and skip the HTTP request.
2025-07-22 17:08:55 -07:00
Alex Vandiver
2f4dd72076 push_notifications: Adjust APNs tokens to be case-insensitive in the database.
APNs apparently treats its tokens case-insensitively; FCM does not.
Adjust the `unique_together` to instead be separate partial
constraints, keyed on the `kind` of the PushDeviceToken.
2025-07-22 14:30:13 -07:00
Alex Vandiver
3e5af466e4 push_notifications: Remove vestiges of base64 storage of tokens.
APNs tokens are provided by the client in hex, and we store them in
hex.  The existing code which attempts to "validate" them by parsing
them as base64 only works because base64 is a superset of hex.

Enforce that APNs tokens are hex, and remove all of the pieces of test
code which were incorrectly passing them in as base64 strings.
2025-07-16 16:59:58 -07:00
Prakhar Pratyush
afe6986991 register-queue: Add push_devices in response.
This commit adds a `push_devices` dictionary to
`POST /register` response, keyed with push account ID,
where each entry describes the user's push device's
registration status and error code (if registration failed).
2025-07-14 14:52:38 -07:00
Prakhar Pratyush
3c6a3b0d77 zilencer: Add endpoint to register push device to bouncer.
This commit adds a zilencer endpoint to let self-hosted
servers register push devices to whom mobile push notifications
will be sent.

POST "/api/v1/remotes/push/e2ee/register"
Payload: realm_uuid, push_account_id, encrypted_push_registration,
bouncer_public_key

The post request needs to be authenticated with the server’s API key.

Note: For Zulip Cloud, a background fact about the push bouncer is
that it runs on the same server and database as the main application;
it’s not a separate service.
So, as an optimization, we plan to directly call the
`do_register_remote_push_device` function and skip the HTTP request.
2025-07-14 14:52:38 -07:00
Greg Price
aaeabeda44 notifications: Dedupe APNs tokens case-insensitively.
Fixes zulip/zulip-flutter#1617.

It turns out that an APNs token (which is a hex string) is equally
valid in lower or upper case.  The old app would send the server
the lower-case form of the token, but the new app sends the
upper-case form.

Because we've been treating tokens case-sensitively, if the user
upgrades from the old app to the new, that results in the server
and bouncer each having two copies of the token (one lower-case and
one upper-case), and therefore sending that device two copies of
each notification: zulip/zulip-flutter#1617.

To fix that immediately, have the bouncer drop duplicate tokens
before sending the notifications to APNs.

Work is also in progress on fixing this in a better-structured way,
by having the database correctly treat tokens as the same when they
differ only in case.
2025-07-10 10:54:14 -07:00
Prakhar Pratyush
e6bdf84363 validate_token: Move validate_token to avoid dependency cycle.
Prep commit to avoid depencency cycle.
2025-07-06 21:11:26 -07:00
Anders Kaseorg
750a08ad17 ruff: Fix LOG014 exc_info= outside exception handlers.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2025-06-25 11:49:02 -07:00
Anders Kaseorg
2747127e6c push_notifications: Convert UserPushIdentityCompat to dataclass.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2025-06-25 11:49:02 -07:00
Mohammad Reza Kianifar
f4b6af5d42 push_notification: Use existing payload for 1:1 DM using DM group.
To maintain API compatibility during migration to DirectMessageGroup
for 1:1 DMs, generate notification payloads for such messages in the
same format as those sent to a Personal recipient.

Fixes: part of issue #25713.
2025-06-18 12:44:56 -07:00
Alex Vandiver
359939ba77 push_notifications: Update for breaking change in aioapns 4.0.
In aioapns 4.0, which we upgraded to in acd7353538, the `key`
parameter is treated as the ASCII-armored string contents of the key,
not the path to the file with that content.

Read the file ourselves, and pass the string it expects.
2025-05-23 11:29:28 -07:00
Tim Abbott
f3a829ce71 notifications: Complete old compatibility TODO.
We no longer support upgrading from old enough versions that bots
should get here, which means we can move to the more compact assert
statement.
2025-03-26 10:54:38 -07:00
Shubham Padia
0def74b097 message: Make is_modifying_message a mandatory kwarg.
This is valuable so that one is forced to explicitly make a decision
on what is correct when adding new callers. Past experience tells us that
not having to explicitly show the decision leads to people introducing
security bugs in PRs that the maintainer has to catch in review, and our
goal for access control code should be that security bugs are hard to write.
Fixes #33688.
2025-02-28 10:10:33 -08:00
Mateusz Mandera
a8fbf6d5d5 docs: Remove HostnameAlreadyInUseBouncerError.docs_url.
Now that we introduced an URL for serving permalinks redirecting to
docs in #33444, the docs_url mechanism is no longer needed, as we can
have a URL that's safe to hard-code in register_server.py.

The HostnameAlreadyInUseBouncerError.docs_url has been merged in main
briefly enough, that this should be safe to remove.
2025-02-13 09:49:28 -08:00
Prakhar Pratyush
23f16885d5 push_notifications: Show EMPTY_TOPIC_FALLBACK_NAME for topic="".
This commit adds support to display `Message.EMPTY_TOPIC_FALLBACK_NAME`
value (translated) in the push notifications for topics having the
actual value of empty string.

Fixes part of #32996.
2025-02-05 05:56:07 -08:00
Mateusz Mandera
27b6181597 register_server: Add docs_url to HostnameAlreadyInUseBouncerError.
This means that the URL is only hard-coded on the bouncer side. That's
useful, because now we'll be able to change the URL and only need a
bouncer deployment for users to get the new URL when they encounter
HostnameAlreadyInUseBouncerError. As opposed to self-hosted servers
being stuck with an outdated docs link hardcoded in their
register_server.py.
2025-01-31 13:20:56 -08:00
Mateusz Mandera
ddcc36c3aa register_server: Improve UX with the "hostname already in use" error.
An even better way than the current json error message recommending the
--registration-transfer option is to return an appropriate error code
and have that get picked up by the register_server command.

The register_server command can then display a more comprehensive,
better formatted error message with proper whitespaces and a pointer to
the documentation.
2025-01-30 14:32:36 -08:00
Anders Kaseorg
f223251ffe requirements: Upgrade Python requirements.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2025-01-14 09:42:16 -08:00
Mateusz Mandera
4a93149435 settings: Rework how push notifications service is configured.
Instead of the PUSH_NOTIFICATIONS_BOUNCER_URL and
SUBMIT_USAGE_STATISTICS settings, we want servers to configure
individual ZULIP_SERVICE_* settings, while maintaining backward
compatibility with the old settings. Thus, if all the new
ZULIP_SERVICE_* are at their default False value, but the legacy
settings are activated, they need to be translated in computed_settings
to the modern way.
2024-07-17 17:14:06 -07:00
Anders Kaseorg
3f29bc42b1 ruff: Fix B905 zip() without an explicit strict= parameter.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2024-07-13 22:28:22 -07:00
Anders Kaseorg
0fa5e7f629 ruff: Fix UP035 Import from collections.abc, typing instead.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2024-07-13 22:28:22 -07:00
Anders Kaseorg
531b34cb4c ruff: Fix UP007 Use X | Y for type annotations.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2024-07-13 22:28:22 -07:00
Anders Kaseorg
e08a24e47f ruff: Fix UP006 Use list instead of List for type annotation.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2024-07-13 22:28:22 -07:00
roanster007
52692a6448 refactor: Rename huddle to direct_message_group in non API.
This commit performs a sweep on the first batch of non API
files to rename "huddle" to "direct_message_group`.

It also renames variables and methods of type -
"huddle_message" to "group_direct_message".

This is a part of #28640
2024-07-04 07:56:31 -07:00
Anders Kaseorg
b115d44b6a requirements: Upgrade Python requirements.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2024-06-27 15:31:43 -07:00
Mateusz Mandera
4917e01ffb push_notifications: Migrate to FCM HTTP v1 API.
The legacy API we use via python-gcm is deprecated and about to be
disabled.

Fixes #29768.
2024-06-17 18:26:26 -07:00
Mateusz Mandera
297141f8c4 push_notifs: Rename parse_gcm_options to parse_fcm_options. 2024-06-17 18:22:59 -07:00
Mateusz Mandera
497be370f5 push_notifs: Rename GCM->FCM in a bunch of comments.
This is called FCM now, even though we're using the legacy API that used
to be called GCM.
2024-06-17 18:22:59 -07:00