3333import attr
3434from typing_extensions import TypedDict
3535
36+ import synapse .events .snapshot
3637from synapse .api .constants import (
3738 EventContentFields ,
3839 EventTypes ,
7778 create_requester ,
7879)
7980from synapse .util import stringutils
80- from synapse .util .async_helpers import Linearizer
8181from synapse .util .caches .response_cache import ResponseCache
8282from synapse .util .stringutils import parse_and_validate_server_name
8383from synapse .visibility import filter_events_for_client
@@ -155,9 +155,6 @@ def __init__(self, hs: "HomeServer"):
155155
156156 self ._replication = hs .get_replication_data_handler ()
157157
158- # linearizer to stop two upgrades happening at once
159- self ._upgrade_linearizer = Linearizer ("room_upgrade_linearizer" )
160-
161158 # If a user tries to update the same room multiple times in quick
162159 # succession, only process the first attempt and return its result to
163160 # subsequent requests
@@ -200,6 +197,39 @@ async def upgrade_room(
200197 400 , "An upgrade for this room is currently in progress"
201198 )
202199
200+ # Check whether the room exists and 404 if it doesn't.
201+ # We could go straight for the auth check, but that will raise a 403 instead.
202+ old_room = await self .store .get_room (old_room_id )
203+ if old_room is None :
204+ raise NotFoundError ("Unknown room id %s" % (old_room_id ,))
205+
206+ new_room_id = self ._generate_room_id ()
207+
208+ # Check whether the user has the power level to carry out the upgrade.
209+ # `check_auth_rules_from_context` will check that they are in the room and have
210+ # the required power level to send the tombstone event.
211+ (
212+ tombstone_event ,
213+ tombstone_context ,
214+ ) = await self .event_creation_handler .create_event (
215+ requester ,
216+ {
217+ "type" : EventTypes .Tombstone ,
218+ "state_key" : "" ,
219+ "room_id" : old_room_id ,
220+ "sender" : user_id ,
221+ "content" : {
222+ "body" : "This room has been replaced" ,
223+ "replacement_room" : new_room_id ,
224+ },
225+ },
226+ )
227+ old_room_version = await self .store .get_room_version (old_room_id )
228+ validate_event_for_room_version (old_room_version , tombstone_event )
229+ await self ._event_auth_handler .check_auth_rules_from_context (
230+ old_room_version , tombstone_event , tombstone_context
231+ )
232+
203233 # Upgrade the room
204234 #
205235 # If this user has sent multiple upgrade requests for the same room
@@ -210,60 +240,51 @@ async def upgrade_room(
210240 self ._upgrade_room ,
211241 requester ,
212242 old_room_id ,
213- new_version , # args for _upgrade_room
243+ old_room , # args for _upgrade_room
244+ new_room_id ,
245+ new_version ,
246+ tombstone_event ,
247+ tombstone_context ,
214248 )
215249
216250 return ret
217251
218252 async def _upgrade_room (
219- self , requester : Requester , old_room_id : str , new_version : RoomVersion
253+ self ,
254+ requester : Requester ,
255+ old_room_id : str ,
256+ old_room : Dict [str , Any ],
257+ new_room_id : str ,
258+ new_version : RoomVersion ,
259+ tombstone_event : EventBase ,
260+ tombstone_context : synapse .events .snapshot .EventContext ,
220261 ) -> str :
221262 """
222263 Args:
223264 requester: the user requesting the upgrade
224265 old_room_id: the id of the room to be replaced
225- new_versions: the version to upgrade the room to
266+ old_room: a dict containing room information for the room to be replaced,
267+ as returned by `RoomWorkerStore.get_room`.
268+ new_room_id: the id of the replacement room
269+ new_version: the version to upgrade the room to
270+ tombstone_event: the tombstone event to send to the old room
271+ tombstone_context: the context for the tombstone event
226272
227273 Raises:
228274 ShadowBanError if the requester is shadow-banned.
229275 """
230276 user_id = requester .user .to_string ()
231277 assert self .hs .is_mine_id (user_id ), "User must be our own: %s" % (user_id ,)
232278
233- # start by allocating a new room id
234- r = await self .store .get_room (old_room_id )
235- if r is None :
236- raise NotFoundError ("Unknown room id %s" % (old_room_id ,))
237- new_room_id = await self ._generate_room_id (
238- creator_id = user_id ,
239- is_public = r ["is_public" ],
240- room_version = new_version ,
241- )
242-
243279 logger .info ("Creating new room %s to replace %s" , new_room_id , old_room_id )
244280
245- # we create and auth the tombstone event before properly creating the new
246- # room, to check our user has perms in the old room.
247- (
248- tombstone_event ,
249- tombstone_context ,
250- ) = await self .event_creation_handler .create_event (
251- requester ,
252- {
253- "type" : EventTypes .Tombstone ,
254- "state_key" : "" ,
255- "room_id" : old_room_id ,
256- "sender" : user_id ,
257- "content" : {
258- "body" : "This room has been replaced" ,
259- "replacement_room" : new_room_id ,
260- },
261- },
262- )
263- old_room_version = await self .store .get_room_version (old_room_id )
264- validate_event_for_room_version (old_room_version , tombstone_event )
265- await self ._event_auth_handler .check_auth_rules_from_context (
266- old_room_version , tombstone_event , tombstone_context
281+ # create the new room. may raise a `StoreError` in the exceedingly unlikely
282+ # event of a room ID collision.
283+ await self .store .store_room (
284+ room_id = new_room_id ,
285+ room_creator_user_id = user_id ,
286+ is_public = old_room ["is_public" ],
287+ room_version = new_version ,
267288 )
268289
269290 await self .clone_existing_room (
@@ -782,7 +803,7 @@ async def create_room(
782803 visibility = config .get ("visibility" , "private" )
783804 is_public = visibility == "public"
784805
785- room_id = await self ._generate_room_id (
806+ room_id = await self ._generate_and_create_room_id (
786807 creator_id = user_id ,
787808 is_public = is_public ,
788809 room_version = room_version ,
@@ -1104,7 +1125,26 @@ async def send(etype: str, content: JsonDict, **kwargs: Any) -> int:
11041125
11051126 return last_sent_stream_id
11061127
1107- async def _generate_room_id (
1128+ def _generate_room_id (self ) -> str :
1129+ """Generates a random room ID.
1130+
1131+ Room IDs look like "!opaque_id:domain" and are case-sensitive as per the spec
1132+ at https://spec.matrix.org/v1.2/appendices/#room-ids-and-event-ids.
1133+
1134+ Does not check for collisions with existing rooms or prevent future calls from
1135+ returning the same room ID. To ensure the uniqueness of a new room ID, use
1136+ `_generate_and_create_room_id` instead.
1137+
1138+ Synapse's room IDs are 18 [a-zA-Z] characters long, which comes out to around
1139+ 102 bits.
1140+
1141+ Returns:
1142+ A random room ID of the form "!opaque_id:domain".
1143+ """
1144+ random_string = stringutils .random_string (18 )
1145+ return RoomID (random_string , self .hs .hostname ).to_string ()
1146+
1147+ async def _generate_and_create_room_id (
11081148 self ,
11091149 creator_id : str ,
11101150 is_public : bool ,
@@ -1115,8 +1155,7 @@ async def _generate_room_id(
11151155 attempts = 0
11161156 while attempts < 5 :
11171157 try :
1118- random_string = stringutils .random_string (18 )
1119- gen_room_id = RoomID (random_string , self .hs .hostname ).to_string ()
1158+ gen_room_id = self ._generate_room_id ()
11201159 await self .store .store_room (
11211160 room_id = gen_room_id ,
11221161 room_creator_user_id = creator_id ,
0 commit comments