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

Don't create an empty room when checking for MAU limits #12713

Merged
merged 4 commits into from
May 13, 2022
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/12713.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix a bug introduced in Synapse 1.30.0 where empty rooms could be automatically created if a monthly active users limit is set.
40 changes: 10 additions & 30 deletions synapse/server_notices/resource_limits_server_notices.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
ServerNoticeMsgType,
)
from synapse.api.errors import AuthError, ResourceLimitError, SynapseError
from synapse.server_notices.server_notices_manager import SERVER_NOTICE_ROOM_TAG

if TYPE_CHECKING:
from synapse.server import HomeServer
Expand Down Expand Up @@ -71,18 +70,19 @@ async def maybe_send_server_notice_to_user(self, user_id: str) -> None:
# In practice, not sure we can ever get here
return

room_id = await self._server_notices_manager.get_or_create_notice_room_for_user(
# Check if there's a server notice room for this user.
room_id = await self._server_notices_manager.maybe_get_notice_room_for_user(
user_id
)

if not room_id:
logger.warning("Failed to get server notices room")
return

await self._check_and_set_tags(user_id, room_id)

# Determine current state of room
currently_blocked, ref_events = await self._is_room_currently_blocked(room_id)
if room_id is not None:
# Determine current state of room
currently_blocked, ref_events = await self._is_room_currently_blocked(
room_id
)
else:
currently_blocked = False
ref_events = []

limit_msg = None
limit_type = None
Expand Down Expand Up @@ -161,26 +161,6 @@ async def _apply_limit_block_notification(
user_id, content, EventTypes.Pinned, ""
)

async def _check_and_set_tags(self, user_id: str, room_id: str) -> None:
"""
Since server notices rooms were originally not with tags,
important to check that tags have been set correctly
Args:
user_id(str): the user in question
room_id(str): the server notices room for that user
"""
tags = await self._store.get_tags_for_room(user_id, room_id)
need_to_set_tag = True
if tags:
if SERVER_NOTICE_ROOM_TAG in tags:
# tag already present, nothing to do here
need_to_set_tag = False
if need_to_set_tag:
max_id = await self._account_data_handler.add_tag_to_room(
user_id, room_id, SERVER_NOTICE_ROOM_TAG, {}
)
self._notifier.on_new_event("account_data_key", max_id, users=[user_id])

Comment on lines -164 to -183
Copy link
Contributor Author

@babolivier babolivier May 11, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've taken the liberty to remove this code given it was introduced for backwards compatibility with pre-0.33.4 versions of Synapse (almost 4y ago); I'm pretty sure we can get rid of it now.

async def _is_room_currently_blocked(self, room_id: str) -> Tuple[bool, List[str]]:
"""
Determines if the room is currently blocked
Expand Down
70 changes: 45 additions & 25 deletions synapse/server_notices/server_notices_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,35 @@ async def send_notice(
)
return event

@cached()
async def maybe_get_notice_room_for_user(self, user_id: str) -> Optional[str]:
"""Try to look up the server notice room for this user if it exists.

Does not create one if none can be found.

Args:
user_id: the user we want a server notice room for.

Returns:
The room's ID, or None if no room could be found.
"""
rooms = await self._store.get_rooms_for_local_user_where_membership_is(
user_id, [Membership.INVITE, Membership.JOIN]
)
for room in rooms:
# it's worth noting that there is an asymmetry here in that we
# expect the user to be invited or joined, but the system user must
# be joined. This is kinda deliberate, in that if somebody somehow
# manages to invite the system user to a room, that doesn't make it
# the server notices room.
user_ids = await self._store.get_users_in_room(room.room_id)
if len(user_ids) <= 2 and self.server_notices_mxid in user_ids:
# we found a room which our user shares with the system notice
# user
return room.room_id

return None

@cached()
async def get_or_create_notice_room_for_user(self, user_id: str) -> str:
"""Get the room for notices for a given user
Expand All @@ -112,31 +141,20 @@ async def get_or_create_notice_room_for_user(self, user_id: str) -> str:
self.server_notices_mxid, authenticated_entity=self._server_name
)

rooms = await self._store.get_rooms_for_local_user_where_membership_is(
user_id, [Membership.INVITE, Membership.JOIN]
)
for room in rooms:
# it's worth noting that there is an asymmetry here in that we
# expect the user to be invited or joined, but the system user must
# be joined. This is kinda deliberate, in that if somebody somehow
# manages to invite the system user to a room, that doesn't make it
# the server notices room.
user_ids = await self._store.get_users_in_room(room.room_id)
if len(user_ids) <= 2 and self.server_notices_mxid in user_ids:
# we found a room which our user shares with the system notice
# user
logger.info(
"Using existing server notices room %s for user %s",
room.room_id,
user_id,
)
await self._update_notice_user_profile_if_changed(
requester,
room.room_id,
self._config.servernotices.server_notices_mxid_display_name,
self._config.servernotices.server_notices_mxid_avatar_url,
)
return room.room_id
room_id = await self.maybe_get_notice_room_for_user(user_id)
if room_id is not None:
logger.info(
"Using existing server notices room %s for user %s",
room_id,
user_id,
)
await self._update_notice_user_profile_if_changed(
requester,
room_id,
self._config.servernotices.server_notices_mxid_display_name,
self._config.servernotices.server_notices_mxid_avatar_url,
)
return room_id

# apparently no existing notice room: create a new one
logger.info("Creating server notices room for %s", user_id)
Expand Down Expand Up @@ -166,6 +184,8 @@ async def get_or_create_notice_room_for_user(self, user_id: str) -> str:
)
room_id = info["room_id"]

self.maybe_get_notice_room_for_user.invalidate((user_id,))

max_id = await self._account_data_handler.add_tag_to_room(
user_id, room_id, SERVER_NOTICE_ROOM_TAG, {}
)
Expand Down
11 changes: 10 additions & 1 deletion tests/server_notices/test_resource_limits_server_notices.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ def prepare(self, reactor, clock, hs):
self._rlsn._server_notices_manager.get_or_create_notice_room_for_user = Mock(
return_value=make_awaitable("!something:localhost")
)
self._rlsn._server_notices_manager.maybe_get_notice_room_for_user = Mock(
return_value=make_awaitable("!something:localhost")
)
self._rlsn._store.add_tag_to_room = Mock(return_value=make_awaitable(None))
self._rlsn._store.get_tags_for_room = Mock(return_value=make_awaitable({}))

Expand Down Expand Up @@ -102,6 +105,7 @@ def test_maybe_send_server_notice_to_user_remove_blocked_notice(self):
)
self.get_success(self._rlsn.maybe_send_server_notice_to_user(self.user_id))
# Would be better to check the content, but once == remove blocking event
self._rlsn._server_notices_manager.maybe_get_notice_room_for_user.assert_called_once()
self._send_notice.assert_called_once()

def test_maybe_send_server_notice_to_user_remove_blocked_notice_noop(self):
Expand Down Expand Up @@ -300,7 +304,10 @@ def test_no_invite_without_notice(self):
hasn't been reached (since it's the only user and the limit is 5), so users
shouldn't receive a server notice.
"""
self.register_user("user", "password")
m = Mock(return_value=make_awaitable(None))
self._rlsn._server_notices_manager.maybe_get_notice_room_for_user = m

user_id = self.register_user("user", "password")
tok = self.login("user", "password")

channel = self.make_request("GET", "/sync?timeout=0", access_token=tok)
Expand All @@ -309,6 +316,8 @@ def test_no_invite_without_notice(self):
"rooms", channel.json_body, "Got invites without server notice"
)

m.assert_called_once_with(user_id)

def test_invite_with_notice(self):
"""Tests that, if the MAU limit is hit, the server notices user invites each user
to a room in which it has sent a notice.
Expand Down