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

Commit

Permalink
Delete pushers after calling on_logged_out module hook on device dele…
Browse files Browse the repository at this point in the history
…te (#15410)
  • Loading branch information
Mathieu Velten authored Apr 14, 2023
1 parent de4390c commit dabbb94
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 3 deletions.
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.

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="a@example.com",
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

0 comments on commit dabbb94

Please sign in to comment.