diff --git a/changelog.d/5045.bugfix b/changelog.d/5045.bugfix new file mode 100644 index 000000000000..483942aad70b --- /dev/null +++ b/changelog.d/5045.bugfix @@ -0,0 +1 @@ +Prevent the abiltiy to upgrade the same room more than once. diff --git a/synapse/handlers/room.py b/synapse/handlers/room.py index 17628e268438..0d1670167f55 100644 --- a/synapse/handlers/room.py +++ b/synapse/handlers/room.py @@ -76,7 +76,9 @@ def __init__(self, hs): @defer.inlineCallbacks def upgrade_room(self, requester, old_room_id, new_version): - """Replace a room with a new room with a different version + """Replace a room with a new room with a different version. Will + raise an exception if the room has already been upgraded, or in + currently in the process of being upgraded. Args: requester (synapse.types.Requester): the user requesting the upgrade @@ -85,12 +87,22 @@ def upgrade_room(self, requester, old_room_id, new_version): Returns: Deferred[unicode]: the new room id + + Raises: + NotFoundError if the room is unknown + SynapseError if this room has already been upgraded """ yield self.ratelimit(requester) user_id = requester.user.to_string() with (yield self._upgrade_linearizer.queue(old_room_id)): + # Check that this room has not already been upgraded + tombstone = yield self.store.get_room_tombstone(old_room_id) + + if tombstone is not None: + raise SynapseError(400, "This room has already been upgraded") + # start by allocating a new room id r = yield self.store.get_room(old_room_id) if r is None: diff --git a/synapse/storage/state.py b/synapse/storage/state.py index 0bfe1b4550c6..c183422aa958 100644 --- a/synapse/storage/state.py +++ b/synapse/storage/state.py @@ -433,7 +433,8 @@ def get_room_predecessor(self, room_id): room_id (str) Returns: - Deferred[unicode|None]: predecessor room id + Deferred[unicode|None]: predecessor room id, or None if a + predecessor is not found Raises: NotFoundError if the room is unknown @@ -444,6 +445,33 @@ def get_room_predecessor(self, room_id): # Return predecessor if present defer.returnValue(create_event.content.get("predecessor", None)) + @defer.inlineCallbacks + def get_room_tombstone(self, room_id): + """Get the tombstone event of an upgraded room if one exists. + Otherwise return None. + + Args: + room_id (str) + + Returns: + Deferred[FrozenEvent|None]: the tombstone event, or None if a + tombstone event was not found + + Raises: + NotFoundError if the room is unknown + """ + # Retrieve the room's tombstone event if it exists + state_ids = yield self.get_current_state_ids(room_id) + tombstone_id = state_ids.get((EventTypes.Tombstone, "")) + + # If we can't find the tombstone, assume we've hit a dead end + if not tombstone_id: + defer.returnValue(None) + + # Retrieve the room's tombstone event and return + tombstone_event = yield self.get_event(tombstone_id) + defer.returnValue(tombstone_event) + @defer.inlineCallbacks def get_create_event_for_room(self, room_id): """Get the create state event for a room.