1
0

Add flag to control whether remote spaces are processed

This commit is contained in:
Sean Quah
2021-11-19 15:25:43 +00:00
parent 856656a8ee
commit 94586c596f
4 changed files with 127 additions and 54 deletions

View File

@@ -16,31 +16,42 @@ The API is:
DELETE /_synapse/admin/v1/rooms/<room_id>/hierarchy/members/<user_id>
```
with an optional body of:
```json
{
"include_remote_spaces": true,
}
```
`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.
Returning:
```json
{
"left": ["!room1:example.net", "!room2:example.net", ...],
"failed": {
"!room3:example.net": [
"Could not explore space or room fully."
],
"!room4:example.net": [
"Failed to leave room."
],
"left_rooms": ["!room1:example.net", "!room2:example.net", ...],
"inaccessible_rooms": ["!subspace1:example.net", ...],
"failed_rooms": {
"!room4:example.net": "Failed to leave room.",
...
}
}
```
`left`: A list of rooms that the user has been made to leave.
`left_rooms`: A list of rooms that the user has been made to leave.
`failed`: A dictionary with entries for rooms that could not be fully
processed. The values of the dictionary are lists of failure reasons.
Rooms may appear here if:
* The user failed to leave them for any reason.
* The room is a space that the local homeserver is not in, and so
its full list of child rooms could not be determined.
`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.
* Some combination of the above.
`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.

View File

@@ -50,12 +50,18 @@ class SpaceHierarchyHandler:
self._server_name = hs.hostname
async def get_space_descendants(
self, space_id: str, via: Optional[Iterable[str]] = None
self,
space_id: str,
via: Optional[Iterable[str]] = None,
enable_federation: Optional[bool] = True,
) -> Tuple[Sequence[Tuple[str, Iterable[str]]], Sequence[str]]:
"""Gets the children of a space, recursively.
Args:
space_id: The room ID of the space.
via: A list of servers which may know about the space.
enable_federation: A boolean controlling whether children of unknown rooms
should be fetched over federation. Defaults to `True`.
Returns:
A tuple containing:
@@ -65,6 +71,8 @@ class SpaceHierarchyHandler:
Rooms in this list are either spaces not known locally, and thus require
listing over federation, or are unknown rooms or subspaces completely
inaccessible to the local homeserver which may contain further rooms.
Subspaces requiring listing over federation are always included here,
regardless of the value of the `enable_federation` flag.
This list is a subset of the previous list, except it may include
`space_id`.
@@ -91,13 +99,21 @@ class SpaceHierarchyHandler:
children,
federation_room_chunks,
) = await self._get_space_children(
space_id, via, federation_room_chunks
space_id,
via,
federation_room_chunks,
enable_federation=enable_federation,
)
except SynapseError:
# Could not list children over federation
inaccessible_room_ids.append(space_id)
continue
# Children were retrieved over federation, which is not guaranteed to be
# the full list.
if not is_in_room:
inaccessible_room_ids.append(space_id)
for child_room_id, child_via in reversed(children):
if child_room_id in seen:
continue
@@ -109,11 +125,6 @@ class SpaceHierarchyHandler:
# `_get_space_children`.
todo.append((child_room_id, child_via, federation_room_chunks))
# Children were retrieved over federation, which is not guaranteed to be
# the full list.
if not is_in_room:
inaccessible_room_ids.append(space_id)
return descendants, inaccessible_room_ids
async def _get_space_children(
@@ -121,6 +132,7 @@ class SpaceHierarchyHandler:
space_id: str,
via: Optional[Iterable[str]] = None,
federation_room_chunks: Optional[Mapping[str, Optional[JsonDict]]] = None,
enable_federation: Optional[bool] = True,
) -> Tuple[
bool, Sequence[Tuple[str, Iterable[str]]], Mapping[str, Optional[JsonDict]]
]:
@@ -152,7 +164,7 @@ class SpaceHierarchyHandler:
Raises:
SynapseError: if `space_id` is not known locally and its children could not
be retrieved over federation.
be retrieved over federation or `enable_federation` is `False`.
"""
via = via or []
federation_room_chunks = federation_room_chunks or {}
@@ -178,6 +190,11 @@ class SpaceHierarchyHandler:
# `space_id` is not a space according to federation.
return False, [], {}
if not enable_federation:
raise SynapseError(
502, f"{space_id} is not accessible to the local homeserver"
)
children, room_chunks = await self._get_space_children_remote(space_id, via)
return False, children, room_chunks

View File

@@ -12,11 +12,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
from http import HTTPStatus
from typing import TYPE_CHECKING, Dict, List, Tuple
from synapse.api.constants import EventTypes, JoinRules, Membership
from synapse.api.errors import SynapseError
from synapse.http.servlet import ResolveRoomIdMixin, RestServlet
from synapse.http.servlet import (
ResolveRoomIdMixin,
RestServlet,
parse_json_object_from_request,
)
from synapse.http.site import SynapseRequest
from synapse.rest.admin._base import admin_patterns, assert_user_is_admin
from synapse.storage.state import StateFilter
@@ -56,26 +61,38 @@ class RemoveSpaceMemberRestServlet(ResolveRoomIdMixin, RestServlet):
Returns:
A tuple containing the HTTP status code and a JSON dictionary containing:
* `left`: A list of rooms that the user has been made to leave.
* `failed`: A with entries for rooms that could not be fully processed.
The values of the dictionary are lists of failure reasons.
Rooms may appear here if:
* The user failed to leave them for any reason.
* `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.
* Some combination of the above.
* `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.
"""
requester = await self._auth.get_user_by_req(request)
await assert_user_is_admin(self._auth, requester.user)
content = parse_json_object_from_request(request, allow_empty_body=True)
include_remote_spaces = content.get("include_remote_spaces", True)
if not isinstance(include_remote_spaces, bool):
raise SynapseError(
HTTPStatus.BAD_REQUEST,
"'include_remote_spaces' parameter must be a boolean",
)
space_id, _ = await self.resolve_room_id(space_id)
target_user = UserID.from_string(user_id)
if not self._hs.is_mine(target_user):
raise SynapseError(400, "This endpoint can only be used with local users")
raise SynapseError(
HTTPStatus.BAD_REQUEST,
"This endpoint can only be used with local users",
)
# Fetch the list of rooms the target user is currently in
user_rooms = await self._store.get_rooms_for_local_user_where_membership_is(
@@ -87,7 +104,9 @@ class RemoveSpaceMemberRestServlet(ResolveRoomIdMixin, RestServlet):
(
descendants,
inaccessible_room_ids,
) = await self._space_hierarchy_handler.get_space_descendants(space_id)
) = await self._space_hierarchy_handler.get_space_descendants(
space_id, enable_federation=include_remote_spaces
)
# Determine which rooms to leave by checking join rules
rooms_to_leave: List[str] = []
@@ -116,10 +135,7 @@ class RemoveSpaceMemberRestServlet(ResolveRoomIdMixin, RestServlet):
rooms_to_leave.append(room_id)
# Now start leaving rooms
failures: Dict[str, List[str]] = {
room_id: ["Could not fully explore space or room."]
for room_id in inaccessible_room_ids
}
failures: Dict[str, str] = {}
left_rooms: List[str] = []
fake_requester = create_requester(
@@ -144,6 +160,10 @@ class RemoveSpaceMemberRestServlet(ResolveRoomIdMixin, RestServlet):
)
left_rooms.append(room_id)
except Exception as e:
failures.get(room_id, []).append(str(e))
failures[room_id] = str(e)
return 200, {"left": left_rooms, "failed": failures}
return 200, {
"left_rooms": left_rooms,
"inaccessible_rooms": inaccessible_room_ids,
"failed_rooms": failures,
}

View File

@@ -164,9 +164,14 @@ class RemoveSpaceMemberTestCase(unittest.HomeserverTestCase):
)
response = self._remove_from_space(self.target_user)
self.assertCountEqual(response["left"], [self.space_id])
self.assertEqual(response["failed"], {})
self.assertEqual(
response,
{
"left_rooms": [self.space_id],
"inaccessible_rooms": [],
"failed_rooms": {},
},
)
membership, _ = self.get_success(
self.store.get_local_current_membership_for_user_in_room(
@@ -183,9 +188,14 @@ class RemoveSpaceMemberTestCase(unittest.HomeserverTestCase):
self.helper.join(public_room_id, self.target_user, tok=self.target_user_tok)
response = self._remove_from_space(self.target_user)
self.assertCountEqual(response["left"], [self.space_id])
self.assertEqual(response["failed"], {})
self.assertEqual(
response,
{
"left_rooms": [self.space_id],
"inaccessible_rooms": [],
"failed_rooms": {},
},
)
membership, _ = self.get_success(
self.store.get_local_current_membership_for_user_in_room(
@@ -207,9 +217,14 @@ class RemoveSpaceMemberTestCase(unittest.HomeserverTestCase):
)
response = self._remove_from_space(self.target_user)
self.assertCountEqual(response["left"], [self.space_id, invite_only_room_id])
self.assertEqual(response["failed"], {})
self.assertEqual(
response,
{
"left_rooms": [self.space_id, invite_only_room_id],
"inaccessible_rooms": [],
"failed_rooms": {},
},
)
membership, _ = self.get_success(
self.store.get_local_current_membership_for_user_in_room(
@@ -234,9 +249,14 @@ class RemoveSpaceMemberTestCase(unittest.HomeserverTestCase):
)
response = self._remove_from_space(self.target_user)
self.assertCountEqual(response["left"], [self.space_id, invite_only_room_id])
self.assertEqual(response["failed"], {})
self.assertEqual(
response,
{
"left_rooms": [self.space_id, invite_only_room_id],
"inaccessible_rooms": [],
"failed_rooms": {},
},
)
membership, _ = self.get_success(
self.store.get_local_current_membership_for_user_in_room(
@@ -252,9 +272,14 @@ class RemoveSpaceMemberTestCase(unittest.HomeserverTestCase):
self.helper.join(restricted_room_id, self.target_user, tok=self.target_user_tok)
response = self._remove_from_space(self.target_user)
self.assertCountEqual(response["left"], [self.space_id, restricted_room_id])
self.assertEqual(response["failed"], {})
self.assertEqual(
response,
{
"left_rooms": [self.space_id, restricted_room_id],
"inaccessible_rooms": [],
"failed_rooms": {},
},
)
membership, _ = self.get_success(
self.store.get_local_current_membership_for_user_in_room(