Compare commits

...

4 Commits

Author SHA1 Message Date
Andrew Morgan
151add5e3f wip2 2022-07-14 17:25:00 +01:00
Andrew Morgan
5d80260a08 wip 2022-07-12 13:48:56 +01:00
Andrew Morgan
4a7b02c2b8 Document arguments to _send_events_for_new_room method 2022-07-07 18:26:11 +01:00
Andrew Morgan
67b5b013eb wip 2022-07-07 17:00:14 +01:00
4 changed files with 177 additions and 66 deletions

View File

@@ -90,3 +90,6 @@ class ExperimentalConfig(Config):
# MSC3827: Filtering of /publicRooms by room type
self.msc3827_enabled: bool = experimental.get("msc3827_enabled", False)
# MSCXXXX: Custom room presets.
self.mscxxxx_enabled: bool = experimental.get("mscxxxx_enabled", False)

View File

@@ -73,6 +73,7 @@ from synapse.types import (
RoomAlias,
RoomID,
RoomStreamToken,
StateKey,
StateMap,
StreamKeyType,
StreamToken,
@@ -117,11 +118,13 @@ class RoomCreationHandler:
self.event_creation_handler = hs.get_event_creation_handler()
self.room_member_handler = hs.get_room_member_handler()
self._event_auth_handler = hs.get_event_auth_handler()
self.config = hs.config
self.request_ratelimiter = hs.get_request_ratelimiter()
self.config = hs.config
self._allow_custom_room_presets = hs.config.experimental.mscxxxx_enabled
# Room state based off defined presets
self._presets_dict: Dict[str, Dict[str, Any]] = {
self._default_presets_dict: Dict[str, Dict[str, Any]] = {
RoomCreationPreset.PRIVATE_CHAT: {
"join_rules": JoinRules.INVITE,
"history_visibility": HistoryVisibility.SHARED,
@@ -146,7 +149,7 @@ class RoomCreationHandler:
}
# Modify presets to selectively enable encryption by default per homeserver config
for preset_name, preset_config in self._presets_dict.items():
for preset_name, preset_config in self._default_presets_dict.items():
encrypted = (
preset_name
in self.config.room.encryption_enabled_by_default_for_room_presets
@@ -551,9 +554,6 @@ class RoomCreationHandler:
await self._send_events_for_new_room(
requester,
new_room_id,
# we expect to override all the presets with initial_state, so this is
# somewhat arbitrary.
preset_config=RoomCreationPreset.PRIVATE_CHAT,
invite_list=[],
initial_state=initial_state,
creation_content=creation_content,
@@ -863,13 +863,6 @@ class RoomCreationHandler:
check_membership=False,
)
preset_config = config.get(
"preset",
RoomCreationPreset.PRIVATE_CHAT
if visibility == "private"
else RoomCreationPreset.PUBLIC_CHAT,
)
raw_initial_state = config.get("initial_state", [])
initial_state = OrderedDict()
@@ -881,10 +874,17 @@ class RoomCreationHandler:
# override any attempt to set room versions via the creation_content
creation_content["room_version"] = room_version.identifier
room_preset_identifier = config.get(
"preset",
RoomCreationPreset.PRIVATE_CHAT
if visibility == "private"
else RoomCreationPreset.PUBLIC_CHAT,
)
last_stream_id = await self._send_events_for_new_room(
requester,
room_id,
preset_config=preset_config,
room_preset_identifier=room_preset_identifier,
invite_list=invite_list,
initial_state=initial_state,
creation_content=creation_content,
@@ -894,6 +894,7 @@ class RoomCreationHandler:
ratelimit=ratelimit,
)
# TODO: These could also be moved into `_send_events_for_new_room`
if "name" in config:
name = config["name"]
(
@@ -989,10 +990,10 @@ class RoomCreationHandler:
self,
creator: Requester,
room_id: str,
preset_config: str,
invite_list: List[str],
initial_state: MutableStateMap,
creation_content: JsonDict,
room_preset_identifier: str = RoomCreationPreset.PRIVATE_CHAT,
room_alias: Optional[RoomAlias] = None,
power_level_content_override: Optional[JsonDict] = None,
creator_join_profile: Optional[JsonDict] = None,
@@ -1000,8 +1001,28 @@ class RoomCreationHandler:
) -> int:
"""Sends the initial events into a new room.
`power_level_content_override` doesn't apply when initial state has
power level state event content.
Args:
creator: The requester of the room creation.
room_id: The ID of the room to send events in.
invite_list: A list of Matrix user IDs to invite to the room. This is only
used by the method to set the power levels of the invitees to 100 if
the preset for the room specifies that initial invitees should have ops.
initial_state: A map of state key to an event definition or event ID.
creation_content: A json dict to use as the value of the "content" field
for the room's create event.
room_preset_identifier: The identifier of the room preset to use. This
determines what events are sent into the room. If not provided, a
room of type "private_chat" will be created. If a custom room preset
is provided, modules will be consulted for whether they recognise it
and for what state should be sent.
room_alias: A room alias to link to the room, if provided.
power_level_content_override: A json dictionary that specifies the initial
power levels of the room. If 'initial_state' contains a m.room.power_levels
event then this argument will be ignored.
creator_join_profile: The value of the "content" field for the m.room.membership
join event that will be sent for the creator of the room.
ratelimit: Whether to ratelimit the join event of the room creator.
TODO: Why is this ever True??? Would this leave us with a broken room?
Returns:
The stream_id of the last event persisted.
@@ -1035,18 +1056,40 @@ class RoomCreationHandler:
)
return last_stream_id
try:
config = self._presets_dict[preset_config]
except KeyError:
raise SynapseError(
400, f"'{preset_config}' is not a valid preset", errcode=Codes.BAD_JSON
)
# Determine the options that this preset defines. This will influence which state events will
# be sent below.
if room_preset_identifier not in self._default_presets_dict.keys():
if not self._allow_custom_room_presets:
raise SynapseError(
400,
f"'{room_preset_identifier}' is not a valid preset",
errcode=Codes.BAD_JSON,
)
# TODO: Ask modules about initial_state and base room preset
# Should modules be able to say "empty base room preset"? Aka return None here?
# This would just result in an empty dictionary I think...
initial_state += {}
room_preset_identifier = RoomCreationPreset.PUBLIC_CHAT
# TODO: Should modules also be able to append to room_creation_content? I would think so, just
# that they can't override room version I guess?
# TODO: If no module recognised this preset ID, then raise an exception.
if False:
raise SynapseError(
400,
f"'{room_preset_identifier}' is not a valid preset",
errcode=Codes.BAD_JSON,
)
# Send the create event and creator membership join first. These are required regardless
# of room preset.
creation_content.update({"creator": creator_id})
await send(etype=EventTypes.Create, content=creation_content)
logger.debug("Sending %s in new room", EventTypes.Member)
await self.room_member_handler.update_membership(
last_sent_stream_id = await self.room_member_handler.update_membership(
creator,
creator.user,
room_id,
@@ -1056,16 +1099,54 @@ class RoomCreationHandler:
new_room=True,
)
state_to_send = self._get_state_events_for_new_room(
creator.user,
room_preset_identifier,
initial_state,
invite_list,
power_level_content_override,
room_alias,
)
# Send all state events in succession
for (event_type, state_key), event_content in state_to_send:
last_sent_stream_id = await send(
etype=event_type, state_key=state_key, content=event_content
)
return last_sent_stream_id
def _get_state_events_for_new_room(
self,
room_creator_user: UserID,
room_preset_identifier: str,
initial_state: MutableStateMap,
user_invite_list: List[str],
power_level_content_override: Optional[JsonDict] = None,
room_alias: Optional[RoomAlias] = None,
) -> Dict[StateKey, JsonDict]:
"""
For a given room preset, return a map of state events that should initially be sent into the room.
Args:
room_preset_identifier: The identifier of the room preset.
Returns:
An ordered dict of state events to send into the new room.
"""
# We rely on Python 3.7 mandating that `dict`s are ordered by insertion order.
state_to_send = {}
room_preset_config_options = self._default_presets_dict[room_preset_identifier]
# We treat the power levels override specially as this needs to be one
# of the first events that get sent into a room.
pl_content = initial_state.pop((EventTypes.PowerLevels, ""), None)
if pl_content is not None:
last_sent_stream_id = await send(
etype=EventTypes.PowerLevels, content=pl_content
)
state_to_send[(EventTypes.PowerLevels, "")] = pl_content
else:
power_level_content: JsonDict = {
"users": {creator_id: 100},
"users": {room_creator_user.to_string(): 100},
"users_default": 0,
"events": {
EventTypes.Name: 50,
@@ -1086,18 +1167,22 @@ class RoomCreationHandler:
"historical": 100,
}
if config["original_invitees_have_ops"]:
for invitee in invite_list:
if room_preset_config_options["original_invitees_have_ops"]:
for invitee in user_invite_list:
power_level_content["users"][invitee] = 100
# If the user supplied a preset name e.g. "private_chat",
# we apply that preset
power_level_content.update(config["power_level_content_override"])
power_level_content.update(
room_preset_config_options["power_level_content_override"]
)
# If the server config contains default_power_level_content_override,
# and that contains information for this room preset, apply it.
if self._default_power_level_content_override:
override = self._default_power_level_content_override.get(preset_config)
override = self._default_power_level_content_override.get(
room_preset_identifier
)
if override is not None:
power_level_content.update(override)
@@ -1106,47 +1191,39 @@ class RoomCreationHandler:
if power_level_content_override:
power_level_content.update(power_level_content_override)
last_sent_stream_id = await send(
etype=EventTypes.PowerLevels, content=power_level_content
)
state_to_send[(EventTypes.PowerLevels, "")] = power_level_content
if room_alias and (EventTypes.CanonicalAlias, "") not in initial_state:
last_sent_stream_id = await send(
etype=EventTypes.CanonicalAlias,
content={"alias": room_alias.to_string()},
)
state_to_send[(EventTypes.CanonicalAlias, "")] = {
"alias": room_alias.to_string()
}
if (EventTypes.JoinRules, "") not in initial_state:
last_sent_stream_id = await send(
etype=EventTypes.JoinRules, content={"join_rule": config["join_rules"]}
)
state_to_send[(EventTypes.JoinRules, "")] = {
"join_rule": room_preset_config_options["join_rules"]
}
if (EventTypes.RoomHistoryVisibility, "") not in initial_state:
last_sent_stream_id = await send(
etype=EventTypes.RoomHistoryVisibility,
content={"history_visibility": config["history_visibility"]},
)
state_to_send[(EventTypes.RoomHistoryVisibility, "")] = {
"history_visibility": room_preset_config_options["history_visibility"]
}
if config["guest_can_join"]:
if (EventTypes.GuestAccess, "") not in initial_state:
last_sent_stream_id = await send(
etype=EventTypes.GuestAccess,
content={EventContentFields.GUEST_ACCESS: GuestAccess.CAN_JOIN},
)
if room_preset_config_options["guest_can_join"]:
state_to_send[(EventTypes.GuestAccess, "")] = {
EventContentFields.GUEST_ACCESS: GuestAccess.CAN_JOIN
}
for (etype, state_key), content in initial_state.items():
last_sent_stream_id = await send(
etype=etype, state_key=state_key, content=content
)
#
for (event_type, state_key), content_dict in initial_state.items():
# TODO: Support None meaning delete the state from the dict?
if config["encrypted"]:
last_sent_stream_id = await send(
etype=EventTypes.RoomEncryption,
state_key="",
content={"algorithm": RoomEncryptionAlgorithms.DEFAULT},
)
# If this (state event type, state_key) tuple is already in the list, replace it in-place.
state_to_send[(event_type, state_key)] = content_dict
return last_sent_stream_id
# TODO: Why do we want to send this last? It means that custom room presets can't override it
if room_preset_config_options["encrypted"]:
state_to_send[(EventTypes.RoomEncryption, "")] = {
"algorithm": RoomEncryptionAlgorithms.DEFAULT
}
return state_to_send
def _generate_room_id(self) -> str:
"""Generates a random room ID.

View File

@@ -14,6 +14,7 @@
# limitations under the License.
import email.utils
import logging
import inspect
from typing import (
TYPE_CHECKING,
Any,
@@ -23,6 +24,7 @@ from typing import (
Iterable,
List,
Optional,
Sequence,
Tuple,
TypeVar,
Union,
@@ -206,6 +208,7 @@ class ModuleApi:
self._registration_handler = hs.get_registration_handler()
self._send_email_handler = hs.get_send_email_handler()
self._push_rules_handler = hs.get_push_rules_handler()
self._room_creation_handler = hs.get_room_creation_handler()
self.custom_template_dir = hs.config.server.custom_template_directory
try:
@@ -860,6 +863,33 @@ class ModuleApi:
client_redirect_url,
)
async def define_supported_custom_room_presets(
self,
room_preset_names: Sequence,
) -> None:
"""Define the list of custom room presets that your module supports.
Custom room presets names defined with this method will be available
for use by clients. If not defined, clients will receive an error indicating
the room preset name is not recognised.
Added in Synapse v1.XX.Y.
"""
# We need a unique identifier for each calling module, so that if this
# method is called multiple times, the previous list of room presets can
# be replaced.
# Get the calling module's ...
# OK, instead of this nonsense, just have the module register a single
# custom_room_preset -> initial_state, base_room_preset mapping. The
# module could do this multiple times and override its previous ones.
# If other modules use the same then there's not much Synapse can do.
# We could log if we're overriding to give sysadmins a heads up.
caller_frame = inspect.stack()[1]
module_filepath = inspect.getmodule(caller_frame[0]).__file__
if module_filepath:
self._room_creation_handler.custom_room_presets[module_filepath] = room_preset_names
async def complete_sso_login_async(
self,
registered_user_id: str,

View File

@@ -24,6 +24,7 @@ from typing import (
Mapping,
Match,
MutableMapping,
MutableSequence,
NoReturn,
Optional,
Set,