Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Add admin API to remove a local user from a space #11358

Closed
wants to merge 17 commits into from

Conversation

squahtx
Copy link
Contributor

@squahtx squahtx commented Nov 16, 2021

Includes changes from #11356

This PR's a bit on the long side unfortunately. It ought to be reviewable commit by commit.

The commit history's become unhelpful. As a rough guide to the changes:

  1. A new SpaceHierarchyHandler is added in synapse/handlers/space_hierarchy.py. It duplicates space-walking functionality in RoomSummaryHandler, but from the all-knowing perspective of the homeserver.
    I'm not too happy about this. Then again, there's no easy way to refactor the code in RoomSummaryHandler, since it's very much tailored for pagination and the client-facing endpoint.
  2. Tests for the new SpaceHierarchyHandler are added in tests/handlers/test_space_hierarchy.py
  3. The new admin command is added in synapse/rest/admin/space.py.
  4. Documentation for the new command is added in docs/usage/administration/admin_api/spaces.md.
  5. Tests for the new admin command are added in tests/rest/admin/test_space.py.

Sean Quah added 6 commits November 16, 2021 13:37
This change makes mypy complain if the constants are ever reassigned,
and, more usefully, makes mypy type them as `Literal`s instead of `str`s,
allowing code of the following form to pass mypy:
```py
def do_something(membership: Literal["join", "leave"], ...): ...

do_something(Membership.JOIN, ...)
```
@squahtx squahtx requested a review from a team as a code owner November 16, 2021 13:53
This change makes mypy type the constants as `Literal`s instead of
`str`s, allowing code of the following form to pass mypy:
```py
def do_something(
    membership: Literal[Membership.JOIN, Membership.LEAVE], ...
):
    ...

do_something(Membership.JOIN, ...)
```
synapse/handlers/room_hierarchy.py Outdated Show resolved Hide resolved

return descendants, inaccessible_room_ids

async def _get_room_children(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
async def _get_room_children(
async def _get_space_children(

perhaps?

or get_rooms_in_space?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_rooms_in_space doesn't convey the difference between direct children and descendants that I want.

get_space_(children|descendants) sounds plausible. I'm actually confused about the room vs space terminology because in RoomSummaryHandler, we have space summaries and room hierarchies.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_rooms_in_space doesn't convey the difference between direct children and descendants that I want.

Hm, I guess that's fair enough. I don't think I'd imagine anything other than direct children for that, but it's hard to say I can't see where others would be coming from if they did. Best avoid ambiguous terminology after all..

get_space_(children|descendants) sounds plausible. I'm actually confused about the room vs space terminology because in RoomSummaryHandler, we have space summaries and room hierarchies.

This is probably a typical case of naming is hard and everything is awful :/.
However you are checking that the room is a space (bailing if it's not), so personally speaking I would call it get_space_children (especially since m.space.child exists).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Room hierarchies might be because the leaves in the tree are not spaces, they are merely rooms. Of course, the root node and interior nodes, being spaces, are also rooms, so the 'Room' in 'Room hierarchies' is so because it's the most general. I'm just making this up so might have nothing to do with the real reason...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I renamed a bunch of things in this PR to refer to spaces. Calling them spaces rather than rooms is a lot more intuitive for me at least: I had trouble finding room_summary.py because I was looking for something called space_* first.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, happy to conclude that it might be the most intuitive name.

Comment on lines 162 to 165
# Check the room chunks previously returned over federation to see if we
# should really make a request.
# `federation_room_chunks` is intentionally not used earlier since we want
# to trust local data over data from federation.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should be possible to disable the remote federation requests altogether for the kick-out feature; I think this could be a security issue. Thinking about it, a malicious remote HS could cause your user to leave arbitrary rooms.
To do so, the remote homeserver just needs to create a space and get it blessed by the root space, or one of its descendants. It can then place any room IDs it likes in there (or arbitrarily answer your queries).

If my understanding is wrong then ignore me :-).

Copy link
Contributor Author

@squahtx squahtx Nov 16, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a very good point. I'll have to think about how we can have the caller decide whether to recurse into spaces, without coupling this API to a particular use case (eg. no federation or no subspaces that a user cannot view)

It may also have an impact on MSC3216 (matrix-org/matrix-spec-proposals#3216)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My specific point here is that you might want a flag that doesn't do any network requests but perhaps returns some kind of placeholder 'You would need to query a remote HS to find out what rooms are in this space' at that point in the tree? (or omits them entirely; not too fussed at this point in time)

synapse/rest/admin/room_hierarchy.py Outdated Show resolved Hide resolved
@reivilibre reivilibre requested a review from a team November 16, 2021 16:12
@squahtx
Copy link
Contributor Author

squahtx commented Nov 18, 2021

It might be handy to get #11356 resolved first, so that those changes stop cluttering up the diff

@squahtx squahtx added the X-Awaiting-Changes A contributed PR which needs changes and re-review before it can be merged label Nov 18, 2021
@squahtx squahtx removed the X-Awaiting-Changes A contributed PR which needs changes and re-review before it can be merged label Nov 23, 2021
@reivilibre
Copy link
Contributor

It might be handy to get #11356 resolved first, so that those changes stop cluttering up the diff

should develop be perhaps merged into this branch to get that part of the diff to go away?

@squahtx
Copy link
Contributor Author

squahtx commented Nov 30, 2021

It might be handy to get #11356 resolved first, so that those changes stop cluttering up the diff

should develop be perhaps merged into this branch to get that part of the diff to go away?

Yes, I'll try that!

Copy link
Member

@richvdh richvdh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Includes changes from #11356

is this still true? could you update the description if not?

seems to have a conflict?

@squahtx squahtx requested a review from a team December 14, 2021 18:01
@squahtx
Copy link
Contributor Author

squahtx commented Dec 14, 2021

The commit history's become unhelpful. As a rough guide to the changes:

  1. A new SpaceHierarchyHandler is added in synapse/handlers/space_hierarchy.py. It duplicates space-walking functionality in RoomSummaryHandler, but from the all-knowing perspective of the homeserver.
    I'm not too happy about this. Then again, there's no easy way to refactor the code in RoomSummaryHandler, since it's very much tailored for pagination and the client-facing endpoint.
  2. Tests for the new SpaceHierarchyHandler are added in tests/handlers/test_space_hierarchy.py
  3. The new admin command is added in synapse/rest/admin/space.py.
  4. Documentation for the new command is added in docs/usage/administration/admin_api/spaces.md.
  5. Tests for the new admin command are added in tests/rest/admin/test_space.py.

@reivilibre reivilibre self-assigned this Jan 4, 2022
Copy link
Contributor

@reivilibre reivilibre left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few points, but otherwise happy for this one to (finally — sorry) make it :).

Comment on lines +27 to +30
`include_remote_spaces` controls whether to process subspaces that the
local homeserver is not participating in. The listings of such subspaces
have to be retrieved over federation and their accuracy cannot be
guaranteed.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps we should call out the security impact a bit more clearly to help security-paranoid people make a judgement.

Also worth noting the default behaviour: if the body is optional, what happens if you don't specify that?

Comment on lines +45 to +57
`left_rooms`: A list of rooms that the user has been made to leave.

`inaccessible_rooms`: A list of rooms and spaces that the local
homeserver is not in, and may have not been fully processed. Rooms may
appear here if:
* The room is a space that the local homeserver is not in, and so its
full list of child rooms could not be determined.
* The room is inaccessible to the local homeserver, and it is not
known whether the room is a subspace containing further rooms.

`failed_rooms`: A dictionary of errors encountered when leaving rooms.
The keys of the dictionary are room IDs and the values of the dictionary
are error messages.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lovely and clear, thanks :)

self,
space_id: str,
via: Optional[Iterable[str]] = None,
enable_federation: Optional[bool] = True,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would be tempted to make this False by default since it will make it absolutely clear, at the callsites, that there's a danger of trusting remotes.

space_id: str,
via: Optional[Iterable[str]] = None,
enable_federation: Optional[bool] = True,
) -> Tuple[Sequence[Tuple[str, Iterable[str]]], Sequence[str]]:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I would prefer this:

Suggested change
) -> Tuple[Sequence[Tuple[str, Iterable[str]]], Sequence[str]]:
) -> Tuple[Sequence[Tuple[str, Collection[str]]], Sequence[str]]:

I know we don't really have set standards around this but Iterable is potentially only once-iterable and I'd like to avoid conflating it too much.

Comment on lines +162 to +163
except Exception as e:
failures[room_id] = str(e)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wondering if we want to log this exception with its traceback still so we can debug any issues that arise.

I suppose you could check the user's membership at this point to see if they were already kicked like you suggested, but it's unlikely to be worth the effort, so probably no need.

self._room_member_handler = hs.get_room_member_handler()
self._space_hierarchy_handler = hs.get_space_hierarchy_handler()

async def on_DELETE(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we expect this operation to take a long time?

I wonder if we should consider putting a ResponseCache on it (hopefully there are other good examples around) so that an admin API client can just keep retrying the request on timeout and then the final result will be given eventually (much like initial sync and I think a few others).
Mind you, that would be fine as a separate PR since I don't want to bog down this PR any further.
I think this will still work, it's just risking the output taking too long to get back.

@erikjohnston erikjohnston assigned squahtx and unassigned reivilibre Apr 7, 2022
@squahtx squahtx closed this Nov 1, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Create an Admin API to remove a local user from non-public rooms in a space
3 participants