|
|
|
|
@@ -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.
|
|
|
|
|
|