Browse Source

Add avatar and topic settings for server notice room (#16679)

develop
Mathieu Velten 5 months ago
committed by GitHub
parent
commit
e108c31fc0
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 235 additions and 14 deletions
  1. +1
    -0
      changelog.d/16679.feature
  2. +4
    -2
      docs/server_notices.md
  3. +7
    -1
      docs/usage/configuration/config_documentation.md
  4. +12
    -0
      synapse/config/server_notices.py
  5. +102
    -11
      synapse/server_notices/server_notices_manager.py
  6. +109
    -0
      tests/rest/admin/test_server_notice.py

+ 1
- 0
changelog.d/16679.feature View File

@@ -0,0 +1 @@
Add config options to set the avatar and the topic of the server notices room.

+ 4
- 2
docs/server_notices.md View File

@@ -44,14 +44,16 @@ section, which should look like this:
server_notices: server_notices:
system_mxid_localpart: server system_mxid_localpart: server
system_mxid_display_name: "Server Notices" system_mxid_display_name: "Server Notices"
system_mxid_avatar_url: "mxc://server.com/oumMVlgDnLYFaPVkExemNVVZ"
system_mxid_avatar_url: "mxc://example.com/oumMVlgDnLYFaPVkExemNVVZ"
room_name: "Server Notices" room_name: "Server Notices"
room_avatar_url: "mxc://example.com/oumMVlgDnLYFaPVkExemNVVZ"
room_topic: "Room used by your server admin to notice you of important information"
auto_join: true auto_join: true
``` ```


The only compulsory setting is `system_mxid_localpart`, which defines the user The only compulsory setting is `system_mxid_localpart`, which defines the user
id of the Server Notices user, as above. `room_name` defines the name of the id of the Server Notices user, as above. `room_name` defines the name of the
room which will be created.
room which will be created, `room_avatar_url` its avatar and `room_topic` its topic.


`system_mxid_display_name` and `system_mxid_avatar_url` can be used to set the `system_mxid_display_name` and `system_mxid_avatar_url` can be used to set the
displayname and avatar of the Server Notices user. displayname and avatar of the Server Notices user.


+ 7
- 1
docs/usage/configuration/config_documentation.md View File

@@ -3837,16 +3837,22 @@ Sub-options for this setting include:
* `system_mxid_display_name`: set the display name of the "notices" user * `system_mxid_display_name`: set the display name of the "notices" user
* `system_mxid_avatar_url`: set the avatar for the "notices" user * `system_mxid_avatar_url`: set the avatar for the "notices" user
* `room_name`: set the room name of the server notices room * `room_name`: set the room name of the server notices room
* `room_avatar_url`: optional string. The room avatar to use for server notice rooms. If set to the empty string `""`, notice rooms will not be given an avatar. Defaults to the empty string. _Added in Synapse 1.99.0._
* `room_topic`: optional string. The topic to use for server notice rooms. If set to the empty string `""`, notice rooms will not be given a topic. Defaults to the empty string. _Added in Synapse 1.99.0._
* `auto_join`: boolean. If true, the user will be automatically joined to the room instead of being invited. * `auto_join`: boolean. If true, the user will be automatically joined to the room instead of being invited.
Defaults to false. _Added in Synapse 1.98.0._ Defaults to false. _Added in Synapse 1.98.0._


Note that the name, topic and avatar of existing server notice rooms will only be updated when a new notice event is sent.

Example configuration: Example configuration:
```yaml ```yaml
server_notices: server_notices:
system_mxid_localpart: notices system_mxid_localpart: notices
system_mxid_display_name: "Server Notices" system_mxid_display_name: "Server Notices"
system_mxid_avatar_url: "mxc://server.com/oumMVlgDnLYFaPVkExemNVVZ"
system_mxid_avatar_url: "mxc://example.com/oumMVlgDnLYFaPVkExemNVVZ"
room_name: "Server Notices" room_name: "Server Notices"
room_avatar_url: "mxc://example.com/oumMVlgDnLYFaPVkExemNVVZ"
room_topic: "Room used by your server admin to notice you of important information"
auto_join: true auto_join: true
``` ```
--- ---


+ 12
- 0
synapse/config/server_notices.py View File

@@ -38,6 +38,14 @@ class ServerNoticesConfig(Config):
server_notices_room_name (str|None): server_notices_room_name (str|None):
The name to use for the server notices room. The name to use for the server notices room.
None if server notices are not enabled. None if server notices are not enabled.

server_notices_room_avatar_url (str|None):
The avatar URL to use for the server notices room.
None if server notices are not enabled.

server_notices_room_topic (str|None):
The topic to use for the server notices room.
None if server notices are not enabled.
""" """


section = "servernotices" section = "servernotices"
@@ -48,6 +56,8 @@ class ServerNoticesConfig(Config):
self.server_notices_mxid_display_name: Optional[str] = None self.server_notices_mxid_display_name: Optional[str] = None
self.server_notices_mxid_avatar_url: Optional[str] = None self.server_notices_mxid_avatar_url: Optional[str] = None
self.server_notices_room_name: Optional[str] = None self.server_notices_room_name: Optional[str] = None
self.server_notices_room_avatar_url: Optional[str] = None
self.server_notices_room_topic: Optional[str] = None
self.server_notices_auto_join: bool = False self.server_notices_auto_join: bool = False


def read_config(self, config: JsonDict, **kwargs: Any) -> None: def read_config(self, config: JsonDict, **kwargs: Any) -> None:
@@ -63,4 +73,6 @@ class ServerNoticesConfig(Config):
self.server_notices_mxid_avatar_url = c.get("system_mxid_avatar_url", None) self.server_notices_mxid_avatar_url = c.get("system_mxid_avatar_url", None)
# todo: i18n # todo: i18n
self.server_notices_room_name = c.get("room_name", "Server Notices") self.server_notices_room_name = c.get("room_name", "Server Notices")
self.server_notices_room_avatar_url = c.get("room_avatar_url", None)
self.server_notices_room_topic = c.get("room_topic", None)
self.server_notices_auto_join = c.get("auto_join", False) self.server_notices_auto_join = c.get("auto_join", False)

+ 102
- 11
synapse/server_notices/server_notices_manager.py View File

@@ -16,7 +16,7 @@ from typing import TYPE_CHECKING, Optional


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


if TYPE_CHECKING: if TYPE_CHECKING:
@@ -36,6 +36,7 @@ class ServerNoticesManager:
self._room_member_handler = hs.get_room_member_handler() self._room_member_handler = hs.get_room_member_handler()
self._event_creation_handler = hs.get_event_creation_handler() self._event_creation_handler = hs.get_event_creation_handler()
self._message_handler = hs.get_message_handler() self._message_handler = hs.get_message_handler()
self._storage_controllers = hs.get_storage_controllers()
self._is_mine_id = hs.is_mine_id self._is_mine_id = hs.is_mine_id
self._server_name = hs.hostname self._server_name = hs.hostname


@@ -160,6 +161,27 @@ class ServerNoticesManager:
self._config.servernotices.server_notices_mxid_display_name, self._config.servernotices.server_notices_mxid_display_name,
self._config.servernotices.server_notices_mxid_avatar_url, self._config.servernotices.server_notices_mxid_avatar_url,
) )
await self._update_room_info(
requester,
room_id,
EventTypes.Name,
"name",
self._config.servernotices.server_notices_room_name,
)
await self._update_room_info(
requester,
room_id,
EventTypes.RoomAvatar,
"url",
self._config.servernotices.server_notices_room_avatar_url,
)
await self._update_room_info(
requester,
room_id,
EventTypes.Topic,
"topic",
self._config.servernotices.server_notices_room_topic,
)
return room_id return room_id


# apparently no existing notice room: create a new one # apparently no existing notice room: create a new one
@@ -178,15 +200,31 @@ class ServerNoticesManager:
"avatar_url": self._config.servernotices.server_notices_mxid_avatar_url, "avatar_url": self._config.servernotices.server_notices_mxid_avatar_url,
} }


room_config: JsonDict = {
"preset": RoomCreationPreset.PRIVATE_CHAT,
"power_level_content_override": {"users_default": -10},
}

if self._config.servernotices.server_notices_room_name:
room_config["name"] = self._config.servernotices.server_notices_room_name
if self._config.servernotices.server_notices_room_topic:
room_config["topic"] = self._config.servernotices.server_notices_room_topic
if self._config.servernotices.server_notices_room_avatar_url:
room_config["initial_state"] = [
{
"type": EventTypes.RoomAvatar,
"state_key": "",
"content": {
"url": self._config.servernotices.server_notices_room_avatar_url,
},
}
]

# `ignore_forced_encryption` is used to bypass `encryption_enabled_by_default_for_room_type` # `ignore_forced_encryption` is used to bypass `encryption_enabled_by_default_for_room_type`
# setting if it set, since the server notices will not be encrypted anyway. # setting if it set, since the server notices will not be encrypted anyway.
room_id, _, _ = await self._room_creation_handler.create_room( room_id, _, _ = await self._room_creation_handler.create_room(
requester, requester,
config={
"preset": RoomCreationPreset.PRIVATE_CHAT,
"name": self._config.servernotices.server_notices_room_name,
"power_level_content_override": {"users_default": -10},
},
config=room_config,
ratelimit=False, ratelimit=False,
creator_join_profile=join_profile, creator_join_profile=join_profile,
ignore_forced_encryption=True, ignore_forced_encryption=True,
@@ -265,11 +303,12 @@ class ServerNoticesManager:


assert self.server_notices_mxid is not None assert self.server_notices_mxid is not None


notice_user_data_in_room = await self._message_handler.get_room_data(
create_requester(self.server_notices_mxid),
room_id,
EventTypes.Member,
self.server_notices_mxid,
notice_user_data_in_room = (
await self._storage_controllers.state.get_current_state_event(
room_id,
EventTypes.Member,
self.server_notices_mxid,
)
) )


assert notice_user_data_in_room is not None assert notice_user_data_in_room is not None
@@ -288,3 +327,55 @@ class ServerNoticesManager:
ratelimit=False, ratelimit=False,
content={"displayname": display_name, "avatar_url": avatar_url}, content={"displayname": display_name, "avatar_url": avatar_url},
) )

async def _update_room_info(
self,
requester: Requester,
room_id: str,
info_event_type: str,
info_content_key: str,
info_value: Optional[str],
) -> None:
"""
Updates a specific notice room's info if it's different from what is set.

Args:
requester: The user who is performing the update.
room_id: The ID of the server notice room
info_event_type: The event type holding the specific info
info_content_key: The key containing the specific info in the event's content
info_value: The expected value for the specific info
"""
room_info_event = await self._storage_controllers.state.get_current_state_event(
room_id,
info_event_type,
"",
)

existing_info_value = None
if room_info_event:
existing_info_value = room_info_event.get(info_content_key)
if existing_info_value == info_value:
return
if not existing_info_value and not info_value:
# A missing `info_value` can either be represented by a None
# or an empty string, so we assume that if they're both falsey
# they're equivalent.
return

if info_value is None:
info_value = ""

room_info_event_dict = {
"type": info_event_type,
"room_id": room_id,
"sender": requester.user.to_string(),
"state_key": "",
"content": {
info_content_key: info_value,
},
}

event, _ = await self._event_creation_handler.create_and_send_nonmember_event(
requester, room_info_event_dict, ratelimit=False
)

+ 109
- 0
tests/rest/admin/test_server_notice.py View File

@@ -596,6 +596,115 @@ class ServerNoticeTestCase(unittest.HomeserverTestCase):
) )
self.assertEqual(notice_user_state["avatar_url"], new_avatar_url) self.assertEqual(notice_user_state["avatar_url"], new_avatar_url)


@override_config(
{
"server_notices": {
"system_mxid_localpart": "notices",
"room_avatar_url": "test/url",
"room_topic": "Test Topic",
}
}
)
def test_notice_room_avatar_and_topic(self) -> None:
"""
Tests that using `room_avatar_url` and `room_topic` config properly sets
those properties for the created notice rooms.
"""
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,
)

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
)

room_avatar_state = self.helper.get_state(
notice_room_id,
"m.room.avatar",
self.other_user_token,
state_key="",
)
self.assertEqual(room_avatar_state["url"], "test/url")

room_topic_state = self.helper.get_state(
notice_room_id,
"m.room.topic",
self.other_user_token,
state_key="",
)
self.assertEqual(room_topic_state["topic"], "Test Topic")

@override_config(
{
"server_notices": {
"system_mxid_localpart": "notices",
"room_avatar_url": "test/url",
}
}
)
def test_update_room_avatar_when_changed(self) -> None:
"""
Tests that existing server notices room avatar is updated when it 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,
)

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
)

room_avatar_state = self.helper.get_state(
notice_room_id,
"m.room.avatar",
self.other_user_token,
state_key="",
)
self.assertEqual(room_avatar_state["url"], "test/url")

# simulate a change in server config after a server restart.
new_avatar_url = "test/new-url"
self.server_notices_manager._config.servernotices.server_notices_room_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,
)

room_avatar_state = self.helper.get_state(
notice_room_id,
"m.room.avatar",
self.other_user_token,
state_key="",
)
self.assertEqual(room_avatar_state["url"], new_avatar_url)

def _check_invite_and_join_status( def _check_invite_and_join_status(
self, user_id: str, expected_invites: int, expected_memberships: int self, user_id: str, expected_invites: int, expected_memberships: int
) -> Sequence[RoomsForUser]: ) -> Sequence[RoomsForUser]:


Loading…
Cancel
Save