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

Update server notices user profile in room if changed #12115

Merged
merged 12 commits into from
Apr 8, 2022
1 change: 1 addition & 0 deletions changelog.d/12115.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix a long-standing bug that updating the server notices user profile (display name/avatar URL) in the configuration would not be applied to pre-existing rooms. Contributed by Jorge Florian.
59 changes: 55 additions & 4 deletions synapse/server_notices/server_notices_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

from synapse.api.constants import EventTypes, Membership, RoomCreationPreset
from synapse.events import EventBase
from synapse.types import UserID, create_requester
from synapse.types import Requester, UserID, create_requester
from synapse.util.caches.descriptors import cached

if TYPE_CHECKING:
Expand All @@ -35,6 +35,7 @@ def __init__(self, hs: "HomeServer"):
self._room_creation_handler = hs.get_room_creation_handler()
self._room_member_handler = hs.get_room_member_handler()
self._event_creation_handler = hs.get_event_creation_handler()
self._message_handler = hs.get_message_handler()
self._is_mine_id = hs.is_mine_id
self._server_name = hs.hostname

Expand Down Expand Up @@ -107,6 +108,10 @@ async def get_or_create_notice_room_for_user(self, user_id: str) -> str:

assert self._is_mine_id(user_id), "Cannot send server notices to remote users"

requester = create_requester(
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]
)
Expand All @@ -125,6 +130,12 @@ async def get_or_create_notice_room_for_user(self, user_id: str) -> str:
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

# apparently no existing notice room: create a new one
Expand All @@ -143,9 +154,6 @@ async def get_or_create_notice_room_for_user(self, user_id: str) -> str:
"avatar_url": self._config.servernotices.server_notices_mxid_avatar_url,
}

requester = create_requester(
self.server_notices_mxid, authenticated_entity=self._server_name
)
info, _ = await self._room_creation_handler.create_room(
requester,
config={
Expand Down Expand Up @@ -194,3 +202,46 @@ async def maybe_invite_user_to_room(self, user_id: str, room_id: str) -> None:
room_id=room_id,
action="invite",
)

async def _update_notice_user_profile_if_changed(
self,
requester: Requester,
room_id: str,
display_name: Optional[str],
avatar_url: Optional[str],
) -> None:
"""
Updates notice user profile if it's different from what it's saved in the room.
clokep marked this conversation as resolved.
Show resolved Hide resolved

Args:
requester: The user who is performing the update.
room_id: The ID of the server notice room
display_name: The displayname of the server notice user
avatar_url: The avatar url of the server notice user
"""
logger.info("Checking whether notice user profile has changed", room_id)
clokep marked this conversation as resolved.
Show resolved Hide resolved

assert self.server_notices_mxid is not None

notice_user_data_in_room = await self._message_handler.get_room_data(
self.server_notices_mxid,
room_id,
EventTypes.Member,
self.server_notices_mxid,
)

assert notice_user_data_in_room is not None

notice_user_profile_changed = (
display_name != notice_user_data_in_room.content.get("displayname")
or avatar_url != notice_user_data_in_room.content.get("avatar_url")
)
if notice_user_profile_changed:
logger.info("Updating notice user profile in room %s", room_id)
await self._room_member_handler.update_membership(
requester=requester,
target=UserID.from_string(self.server_notices_mxid),
room_id=room_id,
action="join",
content={"displayname": display_name, "avatar_url": avatar_url},
)
137 changes: 135 additions & 2 deletions tests/rest/admin/test_server_notice.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,14 @@ def test_user_does_not_exist(self) -> None:
self.assertEqual(HTTPStatus.NOT_FOUND, channel.code, msg=channel.json_body)
self.assertEqual(Codes.NOT_FOUND, channel.json_body["errcode"])

@override_config({"server_notices": {"system_mxid_localpart": "notices"}})
@override_config(
{
"server_notices": {
"system_mxid_localpart": "notices",
"system_mxid_display_name": "new display name",
clokep marked this conversation as resolved.
Show resolved Hide resolved
}
}
)
def test_user_is_not_local(self) -> None:
"""
Tests that a lookup for a user that is not a local returns a HTTPStatus.BAD_REQUEST
Expand Down Expand Up @@ -242,7 +249,14 @@ def test_send_server_notice(self) -> None:
self.assertEqual(messages[1]["content"]["body"], "test msg two")
self.assertEqual(messages[1]["sender"], "@notices:test")

@override_config({"server_notices": {"system_mxid_localpart": "notices"}})
@override_config(
{
"server_notices": {
"system_mxid_localpart": "notices",
"system_mxid_display_name": "display name",
clokep marked this conversation as resolved.
Show resolved Hide resolved
}
}
)
def test_send_server_notice_leave_room(self) -> None:
"""
Tests that sending a server notices is successfully.
Expand Down Expand Up @@ -415,6 +429,110 @@ def test_send_server_notice_delete_room(self) -> None:
# second room has new ID
self.assertNotEqual(first_room_id, second_room_id)

@override_config(
{
"server_notices": {
"system_mxid_localpart": "notices",
"system_mxid_display_name": "display name",
"system_mxid_avatar_url": "test/url",
clokep marked this conversation as resolved.
Show resolved Hide resolved
}
}
)
def test_update_notice_user_name_when_changed(self) -> None:
"""
Tests that existing server notices user name in room is updated after
server notice config changes.
"""
server_notice_request_content = {
"user_id": self.other_user,
"content": {"msgtype": "m.text", "body": "test msg one"},
}

self.make_request(
"POST",
self.url,
access_token=self.admin_user_tok,
content=server_notice_request_content,
)

# simulates a change in server config after a server restart.
clokep marked this conversation as resolved.
Show resolved Hide resolved
new_display_name = "new display name"
self.server_notices_manager._config.servernotices.server_notices_mxid_display_name = (
new_display_name
)
self.server_notices_manager.get_or_create_notice_room_for_user.cache.invalidate_all()

self.make_request(
"POST",
self.url,
access_token=self.admin_user_tok,
content=server_notice_request_content,
)

invited_rooms = self._check_invite_and_join_status(self.other_user, 1, 0)
notice_room_id = invited_rooms[0].room_id
self.helper.join(
room=notice_room_id, user=self.other_user, tok=self.other_user_token
)

member_states = self._sync_and_get_member_state(
notice_room_id, self.other_user_token, "@notices:test"
)

self.assertEqual(member_states[0]["content"]["displayname"], new_display_name)

@override_config(
{
"server_notices": {
"system_mxid_localpart": "notices",
"system_mxid_display_name": "display name",
"system_mxid_avatar_url": "test/url",
}
}
)
def test_update_notice_user_avatar_when_changed(self) -> None:
"""
Tests that existing server notices user avatar in room is updated when is
different from the one in homeserver config.
"""
server_notice_request_content = {
"user_id": self.other_user,
"content": {"msgtype": "m.text", "body": "test msg one"},
}

self.make_request(
"POST",
self.url,
access_token=self.admin_user_tok,
content=server_notice_request_content,
)

# simulates a change in server config after a server restart.
new_avatar_url = "test/new-url"
self.server_notices_manager._config.servernotices.server_notices_mxid_avatar_url = (
new_avatar_url
)
self.server_notices_manager.get_or_create_notice_room_for_user.cache.invalidate_all()

self.make_request(
"POST",
self.url,
access_token=self.admin_user_tok,
content=server_notice_request_content,
)

invited_rooms = self._check_invite_and_join_status(self.other_user, 1, 0)
notice_room_id = invited_rooms[0].room_id
self.helper.join(
room=notice_room_id, user=self.other_user, tok=self.other_user_token
)

member_states = self._sync_and_get_member_state(
notice_room_id, self.other_user_token, "@notices:test"
)

self.assertEqual(member_states[0]["content"]["avatar_url"], new_avatar_url)

def _check_invite_and_join_status(
self, user_id: str, expected_invites: int, expected_memberships: int
) -> List[RoomsForUser]:
Expand Down Expand Up @@ -460,3 +578,18 @@ def _sync_and_get_messages(self, room_id: str, token: str) -> List[JsonDict]:
x for x in room["timeline"]["events"] if x["type"] == "m.room.message"
]
return messages

def _sync_and_get_member_state(
self, room_id: str, token: str, member_id: str
) -> List[JsonDict]:
clokep marked this conversation as resolved.
Show resolved Hide resolved
channel = self.make_request(
"GET", "/_matrix/client/r0/sync", access_token=token
)
self.assertEqual(channel.code, HTTPStatus.OK)

room = channel.json_body["rooms"]["join"][room_id]
return [
x
for x in room["timeline"]["events"]
if x["type"] == "m.room.member" and x["state_key"] == member_id
]