33
33
import attr
34
34
from typing_extensions import TypedDict
35
35
36
+ import synapse .events .snapshot
36
37
from synapse .api .constants import (
37
38
EventContentFields ,
38
39
EventTypes ,
77
78
create_requester ,
78
79
)
79
80
from synapse .util import stringutils
80
- from synapse .util .async_helpers import Linearizer
81
81
from synapse .util .caches .response_cache import ResponseCache
82
82
from synapse .util .stringutils import parse_and_validate_server_name
83
83
from synapse .visibility import filter_events_for_client
@@ -155,9 +155,6 @@ def __init__(self, hs: "HomeServer"):
155
155
156
156
self ._replication = hs .get_replication_data_handler ()
157
157
158
- # linearizer to stop two upgrades happening at once
159
- self ._upgrade_linearizer = Linearizer ("room_upgrade_linearizer" )
160
-
161
158
# If a user tries to update the same room multiple times in quick
162
159
# succession, only process the first attempt and return its result to
163
160
# subsequent requests
@@ -200,6 +197,39 @@ async def upgrade_room(
200
197
400 , "An upgrade for this room is currently in progress"
201
198
)
202
199
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
+
203
233
# Upgrade the room
204
234
#
205
235
# If this user has sent multiple upgrade requests for the same room
@@ -210,60 +240,51 @@ async def upgrade_room(
210
240
self ._upgrade_room ,
211
241
requester ,
212
242
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 ,
214
248
)
215
249
216
250
return ret
217
251
218
252
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 ,
220
261
) -> str :
221
262
"""
222
263
Args:
223
264
requester: the user requesting the upgrade
224
265
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
226
272
227
273
Raises:
228
274
ShadowBanError if the requester is shadow-banned.
229
275
"""
230
276
user_id = requester .user .to_string ()
231
277
assert self .hs .is_mine_id (user_id ), "User must be our own: %s" % (user_id ,)
232
278
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
-
243
279
logger .info ("Creating new room %s to replace %s" , new_room_id , old_room_id )
244
280
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 ,
267
288
)
268
289
269
290
await self .clone_existing_room (
@@ -782,7 +803,7 @@ async def create_room(
782
803
visibility = config .get ("visibility" , "private" )
783
804
is_public = visibility == "public"
784
805
785
- room_id = await self ._generate_room_id (
806
+ room_id = await self ._generate_and_create_room_id (
786
807
creator_id = user_id ,
787
808
is_public = is_public ,
788
809
room_version = room_version ,
@@ -1104,7 +1125,26 @@ async def send(etype: str, content: JsonDict, **kwargs: Any) -> int:
1104
1125
1105
1126
return last_sent_stream_id
1106
1127
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 (
1108
1148
self ,
1109
1149
creator_id : str ,
1110
1150
is_public : bool ,
@@ -1115,8 +1155,7 @@ async def _generate_room_id(
1115
1155
attempts = 0
1116
1156
while attempts < 5 :
1117
1157
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 ()
1120
1159
await self .store .store_room (
1121
1160
room_id = gen_room_id ,
1122
1161
room_creator_user_id = creator_id ,
0 commit comments