diff --git a/zerver/lib/push_notifications.py b/zerver/lib/push_notifications.py index 7ccf41f232..b9f334f0bf 100644 --- a/zerver/lib/push_notifications.py +++ b/zerver/lib/push_notifications.py @@ -744,6 +744,7 @@ def remove_push_device_token(user_profile: UserProfile, token_str: str, kind: in # TODO: Make this a remove item post_data = { "server_uuid": settings.ZULIP_ORG_ID, + "realm_uuid": str(user_profile.realm.uuid), # We don't know here if the token was registered with uuid # or using the legacy id format, so we need to send both. "user_uuid": str(user_profile.uuid), @@ -758,9 +759,11 @@ def remove_push_device_token(user_profile: UserProfile, token_str: str, kind: in def clear_push_device_tokens(user_profile_id: int) -> None: # Deletes all of a user's PushDeviceTokens. if uses_notification_bouncer(): - user_uuid = str(get_user_profile_by_id(user_profile_id).uuid) + user_profile = get_user_profile_by_id(user_profile_id) + user_uuid = str(user_profile.uuid) post_data = { "server_uuid": settings.ZULIP_ORG_ID, + "realm_uuid": str(user_profile.realm.uuid), # We want to clear all registered token, and they may have # been registered with either uuid or id. "user_uuid": user_uuid, @@ -1457,6 +1460,7 @@ def send_test_push_notification(user_profile: UserProfile, devices: List[PushDev if uses_notification_bouncer(): for device in devices: post_data = { + "realm_uuid": str(user_profile.realm.uuid), "user_uuid": str(user_profile.uuid), "user_id": user_profile.id, "token": device.token, diff --git a/zerver/tests/test_push_notifications.py b/zerver/tests/test_push_notifications.py index caef440fc5..41e4fbe75e 100644 --- a/zerver/tests/test_push_notifications.py +++ b/zerver/tests/test_push_notifications.py @@ -125,6 +125,7 @@ class SendTestPushNotificationEndpointTest(BouncerTestCase): # What response the server receives when it makes a request to the bouncer # to the /test_notification endpoint: payload = { + "realm_uuid": str(user.realm.uuid), "user_uuid": str(user.uuid), "user_id": user.id, "token": "invalid", @@ -290,6 +291,7 @@ class SendTestPushNotificationEndpointTest(BouncerTestCase): user = self.example_user("cordelia") server = self.server + remote_realm = RemoteRealm.objects.get(server=server, uuid=user.realm.uuid) token = "111222" token_kind = PushDeviceToken.GCM @@ -324,6 +326,9 @@ class SendTestPushNotificationEndpointTest(BouncerTestCase): ) self.assert_json_success(result) + remote_realm.refresh_from_db() + self.assertEqual(remote_realm.last_request_datetime, time_now) + class PushBouncerNotificationTest(BouncerTestCase): DEFAULT_SUBDOMAIN = "" @@ -1265,8 +1270,10 @@ class PushBouncerNotificationTest(BouncerTestCase): self.assert_length(tokens, 2) # Remove tokens + time_sent = time_sent + timedelta(minutes=1) for endpoint, token, kind, appid in endpoints: - result = self.client_delete(endpoint, {"token": token}, subdomain="zulip") + with time_machine.travel(time_sent, tick=False): + result = self.client_delete(endpoint, {"token": token}, subdomain="zulip") self.assert_json_success(result) tokens = list( RemotePushDeviceToken.objects.filter( @@ -1275,6 +1282,9 @@ class PushBouncerNotificationTest(BouncerTestCase): ) self.assert_length(tokens, 0) + remote_realm.refresh_from_db() + self.assertEqual(remote_realm.last_request_datetime, time_sent) + # Re-add copies of those tokens for endpoint, token, kind, appid in endpoints: result = self.client_post(endpoint, {"token": token, **appid}, subdomain="zulip") @@ -1296,10 +1306,15 @@ class PushBouncerNotificationTest(BouncerTestCase): self.assert_length(tokens, 2) # Now we successfully remove them: - do_regenerate_api_key(user, user) + time_sent = time_sent + timedelta(minutes=1) + with time_machine.travel(time_sent, tick=False): + do_regenerate_api_key(user, user) tokens = list(RemotePushDeviceToken.objects.filter(user_uuid=user.uuid, server=server)) self.assert_length(tokens, 0) + remote_realm.refresh_from_db() + self.assertEqual(remote_realm.last_request_datetime, time_sent) + class AnalyticsBouncerTest(BouncerTestCase): TIME_ZERO = datetime(1988, 3, 14, tzinfo=timezone.utc) diff --git a/zilencer/views.py b/zilencer/views.py index 88fdb5f910..59f9370679 100644 --- a/zilencer/views.py +++ b/zilencer/views.py @@ -238,10 +238,13 @@ def unregister_remote_push_device( token_kind: int = REQ(json_validator=check_int), user_id: Optional[int] = REQ(json_validator=check_int, default=None), user_uuid: Optional[str] = REQ(default=None), + realm_uuid: Optional[str] = REQ(default=None), ) -> HttpResponse: validate_bouncer_token_request(token, token_kind) user_identity = UserPushIdentityCompat(user_id=user_id, user_uuid=user_uuid) + update_remote_realm_last_request_datetime_helper(request, server, realm_uuid, user_uuid) + (num_deleted, ignored) = RemotePushDeviceToken.objects.filter( user_identity.filter_q(), token=token, kind=token_kind, server=server ).delete() @@ -257,13 +260,30 @@ def unregister_all_remote_push_devices( server: RemoteZulipServer, user_id: Optional[int] = REQ(json_validator=check_int, default=None), user_uuid: Optional[str] = REQ(default=None), + realm_uuid: Optional[str] = REQ(default=None), ) -> HttpResponse: user_identity = UserPushIdentityCompat(user_id=user_id, user_uuid=user_uuid) + update_remote_realm_last_request_datetime_helper(request, server, realm_uuid, user_uuid) + RemotePushDeviceToken.objects.filter(user_identity.filter_q(), server=server).delete() return json_success(request) +def update_remote_realm_last_request_datetime_helper( + request: HttpRequest, + server: RemoteZulipServer, + realm_uuid: Optional[str], + user_uuid: Optional[str], +) -> None: + if realm_uuid is not None: + assert user_uuid is not None + remote_realm = get_remote_realm_helper(request, server, realm_uuid, user_uuid) + if remote_realm is not None: + remote_realm.last_request_datetime = timezone_now() + remote_realm.save(update_fields=["last_request_datetime"]) + + def delete_duplicate_registrations( registrations: List[RemotePushDeviceToken], server_id: int, user_id: int, user_uuid: str ) -> List[RemotePushDeviceToken]: @@ -327,6 +347,7 @@ class TestNotificationPayload(BaseModel): token_kind: int user_id: int user_uuid: str + realm_uuid: Optional[str] = None base_payload: Dict[str, Any] model_config = ConfigDict(extra="forbid") @@ -344,6 +365,7 @@ def remote_server_send_test_notification( user_id = payload.user_id user_uuid = payload.user_uuid + realm_uuid = payload.realm_uuid # The remote server only sends the base payload with basic user and server info, # and the actual format of the test notification is defined on the bouncer, as that @@ -355,6 +377,8 @@ def remote_server_send_test_notification( # servers that will send user both UUID and ID. user_identity = UserPushIdentityCompat(user_id=user_id, user_uuid=user_uuid) + update_remote_realm_last_request_datetime_helper(request, server, realm_uuid, user_uuid) + try: device = RemotePushDeviceToken.objects.get( user_identity.filter_q(), token=token, kind=token_kind, server=server