Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Delete pushers after calling on_logged_out module hook on device delete #15410

Merged
merged 8 commits into from
Apr 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/15410.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix and document untold assumption that `on_logged_out` module hooks will be called before pushers deletion.
3 changes: 3 additions & 0 deletions docs/modules/password_auth_provider_callbacks.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ Called during a logout request for a user. It is passed the qualified user ID, t
deactivated device (if any: access tokens are occasionally created without an associated
device ID), and the (now deactivated) access token.

Deleting the related pushers is done after calling `on_logged_out`, so you can rely on them
to still be present.
MatMaul marked this conversation as resolved.
Show resolved Hide resolved

If multiple modules implement this callback, Synapse runs them all in order.

### `get_username_for_registration`
Expand Down
6 changes: 4 additions & 2 deletions synapse/handlers/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -513,8 +513,6 @@ async def delete_devices(self, user_id: str, device_ids: List[str]) -> None:
else:
raise

await self.hs.get_pusherpool().remove_pushers_by_devices(user_id, device_ids)

# Delete data specific to each device. Not optimised as it is not
# considered as part of a critical path.
for device_id in device_ids:
Expand All @@ -533,6 +531,10 @@ async def delete_devices(self, user_id: str, device_ids: List[str]) -> None:
f"org.matrix.msc3890.local_notification_settings.{device_id}",
)

# Pushers are deleted after `delete_access_tokens_for_user` is called so that
# modules using `on_logged_out` hook can use them if needed.
await self.hs.get_pusherpool().remove_pushers_by_devices(user_id, device_ids)

await self.notify_device_update(user_id, device_ids)

async def update_device(self, user_id: str, device_id: str, content: dict) -> None:
Expand Down
51 changes: 50 additions & 1 deletion tests/module_api/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from typing import Any, Dict
from typing import Any, Dict, Optional
from unittest.mock import Mock

from twisted.internet import defer
Expand All @@ -21,6 +21,7 @@
from synapse.api.errors import NotFoundError
from synapse.events import EventBase
from synapse.federation.units import Transaction
from synapse.handlers.device import DeviceHandler
from synapse.handlers.presence import UserPresenceState
from synapse.handlers.push_rules import InvalidRuleException
from synapse.module_api import ModuleApi
Expand Down Expand Up @@ -773,6 +774,54 @@ def test_create_room(self) -> None:
# Check room alias.
self.assertIsNone(room_alias)

def test_on_logged_out(self) -> None:
"""Test that on_logged_out module hook is properly called when logging out
a device, and that related pushers are still available at this time.
"""
device_id = "AAAAAAA"
user_id = self.register_user("test_on_logged_out", "secret")
self.login("test_on_logged_out", "secret", device_id)

self.get_success(
self.hs.get_pusherpool().add_or_update_pusher(
user_id=user_id,
device_id=device_id,
kind="http",
app_id="m.http",
app_display_name="HTTP Push Notifications",
device_display_name="pushy push",
pushkey="[email protected]",
lang=None,
data={"url": "http://example.com/_matrix/push/v1/notify"},
)
)

# Setup a callback counting the number of pushers.
number_of_pushers_in_callback: Optional[int] = None

async def _on_logged_out_mock(
user_id: str, device_id: Optional[str], access_token: str
) -> None:
nonlocal number_of_pushers_in_callback
number_of_pushers_in_callback = len(
self.hs.get_pusherpool().pushers[user_id].values()
)

self.module_api.register_password_auth_provider_callbacks(
on_logged_out=_on_logged_out_mock
)

# Delete the device.
device_handler = self.hs.get_device_handler()
assert isinstance(device_handler, DeviceHandler)
self.get_success(device_handler.delete_devices(user_id, [device_id]))

# Check that the callback was called and the pushers still existed.
self.assertEqual(number_of_pushers_in_callback, 1)

# Ensure the pushers were deleted after the callback.
self.assertEqual(len(self.hs.get_pusherpool().pushers[user_id].values()), 0)


class ModuleApiWorkerTestCase(BaseModuleApiTestCase, BaseMultiWorkerStreamTestCase):
"""For testing ModuleApi functionality in a multi-worker setup"""
Expand Down