From 0cd2f3d9d4a7a3686ef036653a3e62da28cb42a7 Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Tue, 22 Oct 2019 18:00:03 +0100 Subject: [PATCH 1/9] Transfer power level state events on room upgrade --- synapse/handlers/room.py | 1 + 1 file changed, 1 insertion(+) diff --git a/synapse/handlers/room.py b/synapse/handlers/room.py index 2816bd8f8731..681aa5a5d02d 100644 --- a/synapse/handlers/room.py +++ b/synapse/handlers/room.py @@ -326,6 +326,7 @@ def clone_existing_room( (EventTypes.Encryption, ""), (EventTypes.ServerACL, ""), (EventTypes.RelatedGroups, ""), + (EventTypes.PowerLevels, ""), ) old_room_state_ids = yield self.store.get_filtered_current_state_ids( From ecaf1d70389eae6faffcba6811ef70bad4b72156 Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Tue, 22 Oct 2019 18:04:32 +0100 Subject: [PATCH 2/9] Add changelog --- changelog.d/6237.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/6237.bugfix diff --git a/changelog.d/6237.bugfix b/changelog.d/6237.bugfix new file mode 100644 index 000000000000..b09789c941d9 --- /dev/null +++ b/changelog.d/6237.bugfix @@ -0,0 +1 @@ +Transfer power levels on room upgrade. \ No newline at end of file From 64a9b8fd18397025e8566a28d17bd0c731b21989 Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Mon, 18 Nov 2019 14:11:38 +0000 Subject: [PATCH 3/9] Transfer power levels on room create, instead of afterwards --- changelog.d/6237.bugfix | 2 +- synapse/handlers/message.py | 4 ++++ synapse/handlers/room.py | 42 +++++++++++-------------------------- 3 files changed, 17 insertions(+), 31 deletions(-) diff --git a/changelog.d/6237.bugfix b/changelog.d/6237.bugfix index b09789c941d9..9285600b00a2 100644 --- a/changelog.d/6237.bugfix +++ b/changelog.d/6237.bugfix @@ -1 +1 @@ -Transfer power levels on room upgrade. \ No newline at end of file +Transfer non-standard power levels on room upgrade. \ No newline at end of file diff --git a/synapse/handlers/message.py b/synapse/handlers/message.py index d682dc2b7a80..8592bb1f379d 100644 --- a/synapse/handlers/message.py +++ b/synapse/handlers/message.py @@ -533,6 +533,8 @@ def send_nonmember_event(self, requester, event, context, ratelimit=True): ) return prev_state + logger.info("Really sending new event: %s", event) + yield self.handle_new_client_event( requester=requester, event=event, context=context, ratelimit=ratelimit ) @@ -580,6 +582,8 @@ def create_and_send_nonmember_event( requester, event_dict, token_id=requester.access_token_id, txn_id=txn_id ) + logger.info("Created new event: %s", event) + spam_error = self.spam_checker.check_event_for_spam(event) if spam_error: if not isinstance(spam_error, string_types): diff --git a/synapse/handlers/room.py b/synapse/handlers/room.py index 6b99567f2736..5f80c26d5d96 100644 --- a/synapse/handlers/room.py +++ b/synapse/handlers/room.py @@ -195,39 +195,34 @@ def _upgrade_room(self, requester, old_room_id, new_version): old_room_id, new_room_id ) - # finally, shut down the PLs in the old room, and update them in the new - # room. - yield self._update_upgraded_room_pls( - requester, old_room_id, new_room_id, old_room_state - ) + # finally, shut down the PLs in the old room + # PLs are transferred in clone_existing_room + yield self._update_upgraded_room_pls(requester, old_room_id, old_room_state) return new_room_id @defer.inlineCallbacks - def _update_upgraded_room_pls( - self, requester, old_room_id, new_room_id, old_room_state - ): - """Send updated power levels in both rooms after an upgrade + def _update_upgraded_room_pls(self, requester, room_id, room_state): + """Send updated power levels in a room after an upgrade Args: requester (synapse.types.Requester): the user requesting the upgrade - old_room_id (unicode): the id of the room to be replaced - new_room_id (unicode): the id of the replacement room - old_room_state (dict[tuple[str, str], str]): the state map for the old room + room_id (unicode): the id of the room to be replaced + room_state (dict[tuple[str, str], str]): the state map for the old room Returns: Deferred """ - old_room_pl_event_id = old_room_state.get((EventTypes.PowerLevels, "")) + room_pl_event_id = room_state.get((EventTypes.PowerLevels, "")) - if old_room_pl_event_id is None: + if room_pl_event_id is None: logger.warning( "Not supported: upgrading a room with no PL event. Not setting PLs " "in old room." ) return - old_room_pl_state = yield self.store.get_event(old_room_pl_event_id) + old_room_pl_state = yield self.store.get_event(room_pl_event_id) # we try to stop regular users from speaking by setting the PL required # to send regular events and invites to 'Moderator' level. That's normally @@ -245,7 +240,7 @@ def _update_upgraded_room_pls( logger.info( "Setting level for %s in %s to %i (was %i)", v, - old_room_id, + room_id, restricted_level, current, ) @@ -261,7 +256,7 @@ def _update_upgraded_room_pls( { "type": EventTypes.PowerLevels, "state_key": "", - "room_id": old_room_id, + "room_id": room_id, "sender": requester.user.to_string(), "content": pl_content, }, @@ -270,19 +265,6 @@ def _update_upgraded_room_pls( except AuthError as e: logger.warning("Unable to update PLs in old room: %s", e) - logger.info("Setting correct PLs in new room") - yield self.event_creation_handler.create_and_send_nonmember_event( - requester, - { - "type": EventTypes.PowerLevels, - "state_key": "", - "room_id": new_room_id, - "sender": requester.user.to_string(), - "content": old_room_pl_state.content, - }, - ratelimit=False, - ) - @defer.inlineCallbacks def clone_existing_room( self, requester, old_room_id, new_room_id, new_room_version, tombstone_event_id From aa8b9039679e817d6fb422b768c51a0117faa81d Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Mon, 18 Nov 2019 14:23:14 +0000 Subject: [PATCH 4/9] remove log lines --- synapse/handlers/message.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/synapse/handlers/message.py b/synapse/handlers/message.py index 8592bb1f379d..d682dc2b7a80 100644 --- a/synapse/handlers/message.py +++ b/synapse/handlers/message.py @@ -533,8 +533,6 @@ def send_nonmember_event(self, requester, event, context, ratelimit=True): ) return prev_state - logger.info("Really sending new event: %s", event) - yield self.handle_new_client_event( requester=requester, event=event, context=context, ratelimit=ratelimit ) @@ -582,8 +580,6 @@ def create_and_send_nonmember_event( requester, event_dict, token_id=requester.access_token_id, txn_id=txn_id ) - logger.info("Created new event: %s", event) - spam_error = self.spam_checker.check_event_for_spam(event) if spam_error: if not isinstance(spam_error, string_types): From a18e6b9be2a0569d46e3f135ee9ef3a6f05a5419 Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Mon, 18 Nov 2019 17:03:43 +0000 Subject: [PATCH 5/9] Split power level transfer into users first, state events after --- synapse/handlers/room.py | 64 +++++++++++++++++++++++++++++++++------- 1 file changed, 54 insertions(+), 10 deletions(-) diff --git a/synapse/handlers/room.py b/synapse/handlers/room.py index 5f80c26d5d96..79da7d906384 100644 --- a/synapse/handlers/room.py +++ b/synapse/handlers/room.py @@ -197,32 +197,37 @@ def _upgrade_room(self, requester, old_room_id, new_version): # finally, shut down the PLs in the old room # PLs are transferred in clone_existing_room - yield self._update_upgraded_room_pls(requester, old_room_id, old_room_state) + yield self._update_upgraded_room_pls( + requester, old_room_id, new_room_id, old_room_state + ) return new_room_id @defer.inlineCallbacks - def _update_upgraded_room_pls(self, requester, room_id, room_state): + def _update_upgraded_room_pls( + self, requester, old_room_id, new_room_id, old_room_state + ): """Send updated power levels in a room after an upgrade Args: requester (synapse.types.Requester): the user requesting the upgrade - room_id (unicode): the id of the room to be replaced - room_state (dict[tuple[str, str], str]): the state map for the old room + old_room_id (str): the id of the room to be replaced + new_room_id (str): the id of the replacement room + old_room_state (dict[tuple[str, str], str]): the state map for the old room Returns: Deferred """ - room_pl_event_id = room_state.get((EventTypes.PowerLevels, "")) + old_room_pl_event_id = old_room_state.get((EventTypes.PowerLevels, "")) - if room_pl_event_id is None: + if old_room_pl_event_id is None: logger.warning( "Not supported: upgrading a room with no PL event. Not setting PLs " "in old room." ) return - old_room_pl_state = yield self.store.get_event(room_pl_event_id) + old_room_pl_state = yield self.store.get_event(old_room_pl_event_id) # we try to stop regular users from speaking by setting the PL required # to send regular events and invites to 'Moderator' level. That's normally @@ -240,7 +245,7 @@ def _update_upgraded_room_pls(self, requester, room_id, room_state): logger.info( "Setting level for %s in %s to %i (was %i)", v, - room_id, + old_room_id, restricted_level, current, ) @@ -256,7 +261,7 @@ def _update_upgraded_room_pls(self, requester, room_id, room_state): { "type": EventTypes.PowerLevels, "state_key": "", - "room_id": room_id, + "room_id": old_room_id, "sender": requester.user.to_string(), "content": pl_content, }, @@ -265,6 +270,19 @@ def _update_upgraded_room_pls(self, requester, room_id, room_state): except AuthError as e: logger.warning("Unable to update PLs in old room: %s", e) + logger.info("Setting correct PLs in new room") + yield self.event_creation_handler.create_and_send_nonmember_event( + requester, + { + "type": EventTypes.PowerLevels, + "state_key": "", + "room_id": new_room_id, + "sender": requester.user.to_string(), + "content": old_room_pl_state.content, + }, + ratelimit=False, + ) + @defer.inlineCallbacks def clone_existing_room( self, requester, old_room_id, new_room_id, new_room_version, tombstone_event_id @@ -315,7 +333,7 @@ def clone_existing_room( (EventTypes.Encryption, ""), (EventTypes.ServerACL, ""), (EventTypes.RelatedGroups, ""), - (EventTypes.PowerLevels, ""), + (EventTypes.PowerLevels, ""), # Removed before sending in the new room ) old_room_state_ids = yield self.store.get_filtered_current_state_ids( @@ -329,6 +347,31 @@ def clone_existing_room( if old_event: initial_state[k] = old_event.content + # Copy over user power levels now as this will not be possible with >100PL users once + # the room has been created + old_user_power_levels = initial_state[(EventTypes.PowerLevels, "")].get("users") + new_user_power_levels = None + if old_user_power_levels: + user_level_dict = {} + + # Track the maximum power level in the old room, and temporarily make the + # upgrading user this power level so that they can replicate all state events over + # They will be returned to their original power level later + max_power_level = 100 + for user, power_level in old_user_power_levels.items(): + power_level = int(power_level) + user_level_dict[user] = power_level + max_power_level = max(max_power_level, power_level) + + # Upgrade the requester to the max known room power level so that + # they're allowed to send any state event in the new room + user_level_dict[requester.user.to_string()] = max_power_level + new_user_power_levels = {"users": user_level_dict} + + # Prevent the rest of the power level events from being transferred now. They will + # be transferred later after the room state has been established + del initial_state[(EventTypes.PowerLevels, "")] + yield self._send_events_for_new_room( requester, new_room_id, @@ -338,6 +381,7 @@ def clone_existing_room( invite_list=[], initial_state=initial_state, creation_content=creation_content, + power_level_content_override=new_user_power_levels, ) # Transfer membership events From 576dae9093fc9adc0f057b59893f80686e8d025b Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Mon, 18 Nov 2019 17:19:26 +0000 Subject: [PATCH 6/9] Fix comments --- synapse/handlers/room.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/synapse/handlers/room.py b/synapse/handlers/room.py index 79da7d906384..8f0d29c59349 100644 --- a/synapse/handlers/room.py +++ b/synapse/handlers/room.py @@ -195,8 +195,8 @@ def _upgrade_room(self, requester, old_room_id, new_version): old_room_id, new_room_id ) - # finally, shut down the PLs in the old room - # PLs are transferred in clone_existing_room + # finally, shut down the PLs in the old room, and update them in the new + # room. yield self._update_upgraded_room_pls( requester, old_room_id, new_room_id, old_room_state ) @@ -207,7 +207,7 @@ def _upgrade_room(self, requester, old_room_id, new_version): def _update_upgraded_room_pls( self, requester, old_room_id, new_room_id, old_room_state ): - """Send updated power levels in a room after an upgrade + """Send updated power levels in both rooms after an upgrade Args: requester (synapse.types.Requester): the user requesting the upgrade From 1d7cce76dcad087ce549e1c3bfe4390356477fc1 Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Tue, 26 Nov 2019 17:08:31 +0000 Subject: [PATCH 7/9] Only use a single power_levels event when necessary --- synapse/handlers/room.py | 82 +++++++++++++++++++++++++++------------- 1 file changed, 55 insertions(+), 27 deletions(-) diff --git a/synapse/handlers/room.py b/synapse/handlers/room.py index 8f0d29c59349..f7a73a4525d7 100644 --- a/synapse/handlers/room.py +++ b/synapse/handlers/room.py @@ -170,7 +170,7 @@ def _upgrade_room(self, requester, old_room_id, new_version): old_room_version, tombstone_event, tombstone_context ) - yield self.clone_existing_room( + requester_original_power_level = yield self.clone_existing_room( requester, old_room_id=old_room_id, new_room_id=new_room_id, @@ -198,14 +198,23 @@ def _upgrade_room(self, requester, old_room_id, new_version): # finally, shut down the PLs in the old room, and update them in the new # room. yield self._update_upgraded_room_pls( - requester, old_room_id, new_room_id, old_room_state + requester, + old_room_id, + new_room_id, + old_room_state, + requester_original_power_level=requester_original_power_level, ) return new_room_id @defer.inlineCallbacks def _update_upgraded_room_pls( - self, requester, old_room_id, new_room_id, old_room_state + self, + requester, + old_room_id, + new_room_id, + old_room_state, + requester_original_power_level=None, ): """Send updated power levels in both rooms after an upgrade @@ -214,6 +223,8 @@ def _update_upgraded_room_pls( old_room_id (str): the id of the room to be replaced new_room_id (str): the id of the replacement room old_room_state (dict[tuple[str, str], str]): the state map for the old room + requester_original_power_level (int|None): If not None, reduce the requester's + power level to this value in the new room Returns: Deferred @@ -271,6 +282,13 @@ def _update_upgraded_room_pls( logger.warning("Unable to update PLs in old room: %s", e) logger.info("Setting correct PLs in new room") + if requester_original_power_level: + # The requester's power level was temporarily raised earlier in the upgrade + # process in order for them to be allowed to send the room's initial state. + # Return them to their original power level in the old room now + old_room_pl_state.content["users"][ + requester.user.to_string() + ] = requester_original_power_level yield self.event_creation_handler.create_and_send_nonmember_event( requester, { @@ -298,7 +316,11 @@ def clone_existing_room( tombstone_event_id (unicode|str): the ID of the tombstone event in the old room. Returns: - Deferred[None] + Deferred[int|None]: Returns an int if the requester's power level + needed to be raised in the new room to send initial state events. + The returned int is the user's original power level. The calling function + should return the user's powerlevel to this value for consistency + once all other initial state events have been sent. None otherwise. """ user_id = requester.user.to_string() @@ -333,7 +355,7 @@ def clone_existing_room( (EventTypes.Encryption, ""), (EventTypes.ServerACL, ""), (EventTypes.RelatedGroups, ""), - (EventTypes.PowerLevels, ""), # Removed before sending in the new room + (EventTypes.PowerLevels, ""), ) old_room_state_ids = yield self.store.get_filtered_current_state_ids( @@ -347,29 +369,33 @@ def clone_existing_room( if old_event: initial_state[k] = old_event.content + # Resolve the minimum power level required to send any state event + # We will give the upgrading user this power level temporarily (if necessary) such that + # they are able to copy all of the state events over, then revert them back to their + # original power level afterwards + # Copy over user power levels now as this will not be possible with >100PL users once # the room has been created - old_user_power_levels = initial_state[(EventTypes.PowerLevels, "")].get("users") - new_user_power_levels = None - if old_user_power_levels: - user_level_dict = {} - - # Track the maximum power level in the old room, and temporarily make the - # upgrading user this power level so that they can replicate all state events over - # They will be returned to their original power level later - max_power_level = 100 - for user, power_level in old_user_power_levels.items(): - power_level = int(power_level) - user_level_dict[user] = power_level - max_power_level = max(max_power_level, power_level) - - # Upgrade the requester to the max known room power level so that - # they're allowed to send any state event in the new room - user_level_dict[requester.user.to_string()] = max_power_level - new_user_power_levels = {"users": user_level_dict} - - # Prevent the rest of the power level events from being transferred now. They will - # be transferred later after the room state has been established + + power_levels = initial_state[(EventTypes.PowerLevels, "")] + + # Calculate the minimum power level needed to clone the room + event_power_levels = power_levels.get("events", {}) + state_default = power_levels.get("state_default", 0) + ban = power_levels.get("ban") + needed_power_level = max(state_default, ban, max(event_power_levels.values())) + + # Raise the requester's power level in the new room if necessary + current_power_level = power_levels["users"][requester.user.to_string()] + raised_power_level = None + if current_power_level < needed_power_level: + # Save and return this power level + raised_power_level = current_power_level + + # Assign this power level to the requester + power_levels["users"][requester.user.to_string()] = needed_power_level + + # Transfer the power levels via power_level_content_override instead of initial_state del initial_state[(EventTypes.PowerLevels, "")] yield self._send_events_for_new_room( @@ -381,7 +407,7 @@ def clone_existing_room( invite_list=[], initial_state=initial_state, creation_content=creation_content, - power_level_content_override=new_user_power_levels, + power_level_content_override=power_levels, ) # Transfer membership events @@ -408,6 +434,8 @@ def clone_existing_room( content=old_event.content, ) + return raised_power_level + # XXX invites/joins # XXX 3pid invites From b26a14d0ff3428983e2e9aa744c3c4aaea50b529 Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Fri, 29 Nov 2019 13:45:33 +0000 Subject: [PATCH 8/9] Address review comments --- synapse/handlers/room.py | 41 ++++++---------------------------------- 1 file changed, 6 insertions(+), 35 deletions(-) diff --git a/synapse/handlers/room.py b/synapse/handlers/room.py index f7a73a4525d7..dc9b01cded64 100644 --- a/synapse/handlers/room.py +++ b/synapse/handlers/room.py @@ -170,7 +170,7 @@ def _upgrade_room(self, requester, old_room_id, new_version): old_room_version, tombstone_event, tombstone_context ) - requester_original_power_level = yield self.clone_existing_room( + yield self.clone_existing_room( requester, old_room_id=old_room_id, new_room_id=new_room_id, @@ -198,23 +198,14 @@ def _upgrade_room(self, requester, old_room_id, new_version): # finally, shut down the PLs in the old room, and update them in the new # room. yield self._update_upgraded_room_pls( - requester, - old_room_id, - new_room_id, - old_room_state, - requester_original_power_level=requester_original_power_level, + requester, old_room_id, new_room_id, old_room_state, ) return new_room_id @defer.inlineCallbacks def _update_upgraded_room_pls( - self, - requester, - old_room_id, - new_room_id, - old_room_state, - requester_original_power_level=None, + self, requester, old_room_id, new_room_id, old_room_state, ): """Send updated power levels in both rooms after an upgrade @@ -223,8 +214,6 @@ def _update_upgraded_room_pls( old_room_id (str): the id of the room to be replaced new_room_id (str): the id of the replacement room old_room_state (dict[tuple[str, str], str]): the state map for the old room - requester_original_power_level (int|None): If not None, reduce the requester's - power level to this value in the new room Returns: Deferred @@ -282,13 +271,6 @@ def _update_upgraded_room_pls( logger.warning("Unable to update PLs in old room: %s", e) logger.info("Setting correct PLs in new room") - if requester_original_power_level: - # The requester's power level was temporarily raised earlier in the upgrade - # process in order for them to be allowed to send the room's initial state. - # Return them to their original power level in the old room now - old_room_pl_state.content["users"][ - requester.user.to_string() - ] = requester_original_power_level yield self.event_creation_handler.create_and_send_nonmember_event( requester, { @@ -316,11 +298,7 @@ def clone_existing_room( tombstone_event_id (unicode|str): the ID of the tombstone event in the old room. Returns: - Deferred[int|None]: Returns an int if the requester's power level - needed to be raised in the new room to send initial state events. - The returned int is the user's original power level. The calling function - should return the user's powerlevel to this value for consistency - once all other initial state events have been sent. None otherwise. + Deferred """ user_id = requester.user.to_string() @@ -387,16 +365,12 @@ def clone_existing_room( # Raise the requester's power level in the new room if necessary current_power_level = power_levels["users"][requester.user.to_string()] - raised_power_level = None if current_power_level < needed_power_level: - # Save and return this power level - raised_power_level = current_power_level - # Assign this power level to the requester power_levels["users"][requester.user.to_string()] = needed_power_level - # Transfer the power levels via power_level_content_override instead of initial_state - del initial_state[(EventTypes.PowerLevels, "")] + # Set the power levels to the modified state + initial_state[(EventTypes.PowerLevels, "")] = power_levels yield self._send_events_for_new_room( requester, @@ -407,7 +381,6 @@ def clone_existing_room( invite_list=[], initial_state=initial_state, creation_content=creation_content, - power_level_content_override=power_levels, ) # Transfer membership events @@ -434,8 +407,6 @@ def clone_existing_room( content=old_event.content, ) - return raised_power_level - # XXX invites/joins # XXX 3pid invites From 92e3a278832fe28cf687965477a8185df464cdb1 Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Mon, 2 Dec 2019 14:49:31 +0000 Subject: [PATCH 9/9] State where power levels are restored --- synapse/handlers/room.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/synapse/handlers/room.py b/synapse/handlers/room.py index dc9b01cded64..35a759f2fe86 100644 --- a/synapse/handlers/room.py +++ b/synapse/handlers/room.py @@ -350,7 +350,7 @@ def clone_existing_room( # Resolve the minimum power level required to send any state event # We will give the upgrading user this power level temporarily (if necessary) such that # they are able to copy all of the state events over, then revert them back to their - # original power level afterwards + # original power level afterwards in _update_upgraded_room_pls # Copy over user power levels now as this will not be possible with >100PL users once # the room has been created