forked from MirrorHub/synapse
Remove deprecated Shutdown Room and Purge Room Admin API (#8830)
This commit is contained in:
parent
1a9f531c79
commit
430241a1e9
9 changed files with 14 additions and 384 deletions
1
changelog.d/8830.removal
Normal file
1
changelog.d/8830.removal
Normal file
|
@ -0,0 +1 @@
|
|||
Remove deprecated Shutdown Room and Purge Room Admin API.
|
|
@ -51,12 +51,10 @@
|
|||
- [Event Reports](admin_api/event_reports.md)
|
||||
- [Media](admin_api/media_admin_api.md)
|
||||
- [Purge History](admin_api/purge_history_api.md)
|
||||
- [Purge Rooms](admin_api/purge_room.md)
|
||||
- [Register Users](admin_api/register_api.md)
|
||||
- [Manipulate Room Membership](admin_api/room_membership.md)
|
||||
- [Rooms](admin_api/rooms.md)
|
||||
- [Server Notices](admin_api/server_notices.md)
|
||||
- [Shutdown Room](admin_api/shutdown_room.md)
|
||||
- [Statistics](admin_api/statistics.md)
|
||||
- [Users](admin_api/user_admin_api.md)
|
||||
- [Server Version](admin_api/version_api.md)
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
Deprecated: Purge room API
|
||||
==========================
|
||||
|
||||
**The old Purge room API is deprecated and will be removed in a future release.
|
||||
See the new [Delete Room API](rooms.md#delete-room-api) for more details.**
|
||||
|
||||
This API will remove all trace of a room from your database.
|
||||
|
||||
All local users must have left the room before it can be removed.
|
||||
|
||||
The API is:
|
||||
|
||||
```
|
||||
POST /_synapse/admin/v1/purge_room
|
||||
|
||||
{
|
||||
"room_id": "!room:id"
|
||||
}
|
||||
```
|
||||
|
||||
You must authenticate using the access token of an admin user.
|
|
@ -1,102 +0,0 @@
|
|||
# Deprecated: Shutdown room API
|
||||
|
||||
**The old Shutdown room API is deprecated and will be removed in a future release.
|
||||
See the new [Delete Room API](rooms.md#delete-room-api) for more details.**
|
||||
|
||||
Shuts down a room, preventing new joins and moves local users and room aliases automatically
|
||||
to a new room. The new room will be created with the user specified by the
|
||||
`new_room_user_id` parameter as room administrator and will contain a message
|
||||
explaining what happened. Users invited to the new room will have power level
|
||||
-10 by default, and thus be unable to speak. The old room's power levels will be changed to
|
||||
disallow any further invites or joins.
|
||||
|
||||
The local server will only have the power to move local user and room aliases to
|
||||
the new room. Users on other servers will be unaffected.
|
||||
|
||||
## API
|
||||
|
||||
You will need to authenticate with an access token for an admin user.
|
||||
|
||||
### URL
|
||||
|
||||
`POST /_synapse/admin/v1/shutdown_room/{room_id}`
|
||||
|
||||
### URL Parameters
|
||||
|
||||
* `room_id` - The ID of the room (e.g `!someroom:example.com`)
|
||||
|
||||
### JSON Body Parameters
|
||||
|
||||
* `new_room_user_id` - Required. A string representing the user ID of the user that will admin
|
||||
the new room that all users in the old room will be moved to.
|
||||
* `room_name` - Optional. A string representing the name of the room that new users will be
|
||||
invited to.
|
||||
* `message` - Optional. A string containing the first message that will be sent as
|
||||
`new_room_user_id` in the new room. Ideally this will clearly convey why the
|
||||
original room was shut down.
|
||||
|
||||
If not specified, the default value of `room_name` is "Content Violation
|
||||
Notification". The default value of `message` is "Sharing illegal content on
|
||||
othis server is not permitted and rooms in violation will be blocked."
|
||||
|
||||
### Response Parameters
|
||||
|
||||
* `kicked_users` - An integer number representing the number of users that
|
||||
were kicked.
|
||||
* `failed_to_kick_users` - An integer number representing the number of users
|
||||
that were not kicked.
|
||||
* `local_aliases` - An array of strings representing the local aliases that were migrated from
|
||||
the old room to the new.
|
||||
* `new_room_id` - A string representing the room ID of the new room.
|
||||
|
||||
## Example
|
||||
|
||||
Request:
|
||||
|
||||
```
|
||||
POST /_synapse/admin/v1/shutdown_room/!somebadroom%3Aexample.com
|
||||
|
||||
{
|
||||
"new_room_user_id": "@someuser:example.com",
|
||||
"room_name": "Content Violation Notification",
|
||||
"message": "Bad Room has been shutdown due to content violations on this server. Please review our Terms of Service."
|
||||
}
|
||||
```
|
||||
|
||||
Response:
|
||||
|
||||
```
|
||||
{
|
||||
"kicked_users": 5,
|
||||
"failed_to_kick_users": 0,
|
||||
"local_aliases": ["#badroom:example.com", "#evilsaloon:example.com],
|
||||
"new_room_id": "!newroomid:example.com",
|
||||
},
|
||||
```
|
||||
|
||||
## Undoing room shutdowns
|
||||
|
||||
*Note*: This guide may be outdated by the time you read it. By nature of room shutdowns being performed at the database level,
|
||||
the structure can and does change without notice.
|
||||
|
||||
First, it's important to understand that a room shutdown is very destructive. Undoing a shutdown is not as simple as pretending it
|
||||
never happened - work has to be done to move forward instead of resetting the past. In fact, in some cases it might not be possible
|
||||
to recover at all:
|
||||
|
||||
* If the room was invite-only, your users will need to be re-invited.
|
||||
* If the room no longer has any members at all, it'll be impossible to rejoin.
|
||||
* The first user to rejoin will have to do so via an alias on a different server.
|
||||
|
||||
With all that being said, if you still want to try and recover the room:
|
||||
|
||||
1. For safety reasons, shut down Synapse.
|
||||
2. In the database, run `DELETE FROM blocked_rooms WHERE room_id = '!example:example.org';`
|
||||
* For caution: it's recommended to run this in a transaction: `BEGIN; DELETE ...;`, verify you got 1 result, then `COMMIT;`.
|
||||
* The room ID is the same one supplied to the shutdown room API, not the Content Violation room.
|
||||
3. Restart Synapse.
|
||||
|
||||
You will have to manually handle, if you so choose, the following:
|
||||
|
||||
* Aliases that would have been redirected to the Content Violation room.
|
||||
* Users that would have been booted from the room (and will have been force-joined to the Content Violation room).
|
||||
* Removal of the Content Violation room if desired.
|
|
@ -85,6 +85,19 @@ process, for example:
|
|||
dpkg -i matrix-synapse-py3_1.3.0+stretch1_amd64.deb
|
||||
```
|
||||
|
||||
# Upgrading to v1.xx.0
|
||||
|
||||
## Removal of old Room Admin API
|
||||
|
||||
The following admin APIs were deprecated in [Synapse 1.25](https://github.com/matrix-org/synapse/blob/v1.25.0/CHANGES.md#removal-warning)
|
||||
(released on 2021-01-13) and have now been removed:
|
||||
|
||||
- `POST /_synapse/admin/v1/purge_room`
|
||||
- `POST /_synapse/admin/v1/shutdown_room/<room_id>`
|
||||
|
||||
Any scripts still using the above APIs should be converted to use the
|
||||
[Delete Room API](https://matrix-org.github.io/synapse/latest/admin_api/rooms.html#delete-room-api).
|
||||
|
||||
|
||||
# Upgrading to v1.xx.0
|
||||
|
||||
|
|
|
@ -36,7 +36,6 @@ from synapse.rest.admin.event_reports import (
|
|||
)
|
||||
from synapse.rest.admin.groups import DeleteGroupAdminRestServlet
|
||||
from synapse.rest.admin.media import ListMediaInRoom, register_servlets_for_media_repo
|
||||
from synapse.rest.admin.purge_room_servlet import PurgeRoomServlet
|
||||
from synapse.rest.admin.rooms import (
|
||||
DeleteRoomRestServlet,
|
||||
ForwardExtremitiesRestServlet,
|
||||
|
@ -47,7 +46,6 @@ from synapse.rest.admin.rooms import (
|
|||
RoomMembersRestServlet,
|
||||
RoomRestServlet,
|
||||
RoomStateRestServlet,
|
||||
ShutdownRoomRestServlet,
|
||||
)
|
||||
from synapse.rest.admin.server_notice_servlet import SendServerNoticeServlet
|
||||
from synapse.rest.admin.statistics import UserMediaStatisticsRestServlet
|
||||
|
@ -221,7 +219,6 @@ def register_servlets(hs: "HomeServer", http_server: HttpServer) -> None:
|
|||
RoomMembersRestServlet(hs).register(http_server)
|
||||
DeleteRoomRestServlet(hs).register(http_server)
|
||||
JoinRoomAliasServlet(hs).register(http_server)
|
||||
PurgeRoomServlet(hs).register(http_server)
|
||||
SendServerNoticeServlet(hs).register(http_server)
|
||||
VersionServlet(hs).register(http_server)
|
||||
UserAdminServlet(hs).register(http_server)
|
||||
|
@ -255,7 +252,6 @@ def register_servlets_for_client_rest_resource(
|
|||
PurgeHistoryRestServlet(hs).register(http_server)
|
||||
ResetPasswordRestServlet(hs).register(http_server)
|
||||
SearchUsersRestServlet(hs).register(http_server)
|
||||
ShutdownRoomRestServlet(hs).register(http_server)
|
||||
UserRegisterServlet(hs).register(http_server)
|
||||
DeleteGroupAdminRestServlet(hs).register(http_server)
|
||||
AccountValidityRenewServlet(hs).register(http_server)
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
# Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
from typing import TYPE_CHECKING, Tuple
|
||||
|
||||
from synapse.http.servlet import (
|
||||
RestServlet,
|
||||
assert_params_in_dict,
|
||||
parse_json_object_from_request,
|
||||
)
|
||||
from synapse.http.site import SynapseRequest
|
||||
from synapse.rest.admin import assert_requester_is_admin
|
||||
from synapse.rest.admin._base import admin_patterns
|
||||
from synapse.types import JsonDict
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from synapse.server import HomeServer
|
||||
|
||||
|
||||
class PurgeRoomServlet(RestServlet):
|
||||
"""Servlet which will remove all trace of a room from the database
|
||||
|
||||
POST /_synapse/admin/v1/purge_room
|
||||
{
|
||||
"room_id": "!room:id"
|
||||
}
|
||||
|
||||
returns:
|
||||
|
||||
{}
|
||||
"""
|
||||
|
||||
PATTERNS = admin_patterns("/purge_room$")
|
||||
|
||||
def __init__(self, hs: "HomeServer"):
|
||||
self.hs = hs
|
||||
self.auth = hs.get_auth()
|
||||
self.pagination_handler = hs.get_pagination_handler()
|
||||
|
||||
async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
|
||||
await assert_requester_is_admin(self.auth, request)
|
||||
|
||||
body = parse_json_object_from_request(request)
|
||||
assert_params_in_dict(body, ("room_id",))
|
||||
|
||||
await self.pagination_handler.purge_room(body["room_id"])
|
||||
|
||||
return 200, {}
|
|
@ -46,41 +46,6 @@ if TYPE_CHECKING:
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ShutdownRoomRestServlet(RestServlet):
|
||||
"""Shuts down a room by removing all local users from the room and blocking
|
||||
all future invites and joins to the room. Any local aliases will be repointed
|
||||
to a new room created by `new_room_user_id` and kicked users will be auto
|
||||
joined to the new room.
|
||||
"""
|
||||
|
||||
PATTERNS = admin_patterns("/shutdown_room/(?P<room_id>[^/]+)")
|
||||
|
||||
def __init__(self, hs: "HomeServer"):
|
||||
self.hs = hs
|
||||
self.auth = hs.get_auth()
|
||||
self.room_shutdown_handler = hs.get_room_shutdown_handler()
|
||||
|
||||
async def on_POST(
|
||||
self, request: SynapseRequest, room_id: str
|
||||
) -> Tuple[int, JsonDict]:
|
||||
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)
|
||||
assert_params_in_dict(content, ["new_room_user_id"])
|
||||
|
||||
ret = await self.room_shutdown_handler.shutdown_room(
|
||||
room_id=room_id,
|
||||
new_room_user_id=content["new_room_user_id"],
|
||||
new_room_name=content.get("room_name"),
|
||||
message=content.get("message"),
|
||||
requester_user_id=requester.user.to_string(),
|
||||
block=True,
|
||||
)
|
||||
|
||||
return (200, ret)
|
||||
|
||||
|
||||
class DeleteRoomRestServlet(RestServlet):
|
||||
"""Delete a room from server.
|
||||
|
||||
|
|
|
@ -29,123 +29,6 @@ from tests import unittest
|
|||
"""Tests admin REST events for /rooms paths."""
|
||||
|
||||
|
||||
class ShutdownRoomTestCase(unittest.HomeserverTestCase):
|
||||
servlets = [
|
||||
synapse.rest.admin.register_servlets_for_client_rest_resource,
|
||||
login.register_servlets,
|
||||
events.register_servlets,
|
||||
room.register_servlets,
|
||||
room.register_deprecated_servlets,
|
||||
]
|
||||
|
||||
def prepare(self, reactor, clock, hs):
|
||||
self.event_creation_handler = hs.get_event_creation_handler()
|
||||
hs.config.user_consent_version = "1"
|
||||
|
||||
consent_uri_builder = Mock()
|
||||
consent_uri_builder.build_user_consent_uri.return_value = "http://example.com"
|
||||
self.event_creation_handler._consent_uri_builder = consent_uri_builder
|
||||
|
||||
self.store = hs.get_datastore()
|
||||
|
||||
self.admin_user = self.register_user("admin", "pass", admin=True)
|
||||
self.admin_user_tok = self.login("admin", "pass")
|
||||
|
||||
self.other_user = self.register_user("user", "pass")
|
||||
self.other_user_token = self.login("user", "pass")
|
||||
|
||||
# Mark the admin user as having consented
|
||||
self.get_success(self.store.user_set_consent_version(self.admin_user, "1"))
|
||||
|
||||
def test_shutdown_room_consent(self):
|
||||
"""Test that we can shutdown rooms with local users who have not
|
||||
yet accepted the privacy policy. This used to fail when we tried to
|
||||
force part the user from the old room.
|
||||
"""
|
||||
self.event_creation_handler._block_events_without_consent_error = None
|
||||
|
||||
room_id = self.helper.create_room_as(self.other_user, tok=self.other_user_token)
|
||||
|
||||
# Assert one user in room
|
||||
users_in_room = self.get_success(self.store.get_users_in_room(room_id))
|
||||
self.assertEqual([self.other_user], users_in_room)
|
||||
|
||||
# Enable require consent to send events
|
||||
self.event_creation_handler._block_events_without_consent_error = "Error"
|
||||
|
||||
# Assert that the user is getting consent error
|
||||
self.helper.send(
|
||||
room_id, body="foo", tok=self.other_user_token, expect_code=403
|
||||
)
|
||||
|
||||
# Test that the admin can still send shutdown
|
||||
url = "/_synapse/admin/v1/shutdown_room/" + room_id
|
||||
channel = self.make_request(
|
||||
"POST",
|
||||
url.encode("ascii"),
|
||||
json.dumps({"new_room_user_id": self.admin_user}),
|
||||
access_token=self.admin_user_tok,
|
||||
)
|
||||
|
||||
self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
|
||||
|
||||
# Assert there is now no longer anyone in the room
|
||||
users_in_room = self.get_success(self.store.get_users_in_room(room_id))
|
||||
self.assertEqual([], users_in_room)
|
||||
|
||||
def test_shutdown_room_block_peek(self):
|
||||
"""Test that a world_readable room can no longer be peeked into after
|
||||
it has been shut down.
|
||||
"""
|
||||
|
||||
self.event_creation_handler._block_events_without_consent_error = None
|
||||
|
||||
room_id = self.helper.create_room_as(self.other_user, tok=self.other_user_token)
|
||||
|
||||
# Enable world readable
|
||||
url = "rooms/%s/state/m.room.history_visibility" % (room_id,)
|
||||
channel = self.make_request(
|
||||
"PUT",
|
||||
url.encode("ascii"),
|
||||
json.dumps({"history_visibility": "world_readable"}),
|
||||
access_token=self.other_user_token,
|
||||
)
|
||||
self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
|
||||
|
||||
# Test that the admin can still send shutdown
|
||||
url = "/_synapse/admin/v1/shutdown_room/" + room_id
|
||||
channel = self.make_request(
|
||||
"POST",
|
||||
url.encode("ascii"),
|
||||
json.dumps({"new_room_user_id": self.admin_user}),
|
||||
access_token=self.admin_user_tok,
|
||||
)
|
||||
|
||||
self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
|
||||
|
||||
# Assert we can no longer peek into the room
|
||||
self._assert_peek(room_id, expect_code=403)
|
||||
|
||||
def _assert_peek(self, room_id, expect_code):
|
||||
"""Assert that the admin user can (or cannot) peek into the room."""
|
||||
|
||||
url = "rooms/%s/initialSync" % (room_id,)
|
||||
channel = self.make_request(
|
||||
"GET", url.encode("ascii"), access_token=self.admin_user_tok
|
||||
)
|
||||
self.assertEqual(
|
||||
expect_code, int(channel.result["code"]), msg=channel.result["body"]
|
||||
)
|
||||
|
||||
url = "events?timeout=0&room_id=" + room_id
|
||||
channel = self.make_request(
|
||||
"GET", url.encode("ascii"), access_token=self.admin_user_tok
|
||||
)
|
||||
self.assertEqual(
|
||||
expect_code, int(channel.result["code"]), msg=channel.result["body"]
|
||||
)
|
||||
|
||||
|
||||
@parameterized_class(
|
||||
("method", "url_template"),
|
||||
[
|
||||
|
@ -557,51 +440,6 @@ class DeleteRoomTestCase(unittest.HomeserverTestCase):
|
|||
)
|
||||
|
||||
|
||||
class PurgeRoomTestCase(unittest.HomeserverTestCase):
|
||||
"""Test /purge_room admin API."""
|
||||
|
||||
servlets = [
|
||||
synapse.rest.admin.register_servlets,
|
||||
login.register_servlets,
|
||||
room.register_servlets,
|
||||
]
|
||||
|
||||
def prepare(self, reactor, clock, hs):
|
||||
self.store = hs.get_datastore()
|
||||
|
||||
self.admin_user = self.register_user("admin", "pass", admin=True)
|
||||
self.admin_user_tok = self.login("admin", "pass")
|
||||
|
||||
def test_purge_room(self):
|
||||
room_id = self.helper.create_room_as(self.admin_user, tok=self.admin_user_tok)
|
||||
|
||||
# All users have to have left the room.
|
||||
self.helper.leave(room_id, user=self.admin_user, tok=self.admin_user_tok)
|
||||
|
||||
url = "/_synapse/admin/v1/purge_room"
|
||||
channel = self.make_request(
|
||||
"POST",
|
||||
url.encode("ascii"),
|
||||
{"room_id": room_id},
|
||||
access_token=self.admin_user_tok,
|
||||
)
|
||||
|
||||
self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
|
||||
|
||||
# Test that the following tables have been purged of all rows related to the room.
|
||||
for table in PURGE_TABLES:
|
||||
count = self.get_success(
|
||||
self.store.db_pool.simple_select_one_onecol(
|
||||
table=table,
|
||||
keyvalues={"room_id": room_id},
|
||||
retcol="COUNT(*)",
|
||||
desc="test_purge_room",
|
||||
)
|
||||
)
|
||||
|
||||
self.assertEqual(count, 0, msg=f"Rows not purged in {table}")
|
||||
|
||||
|
||||
class RoomTestCase(unittest.HomeserverTestCase):
|
||||
"""Test /room admin API."""
|
||||
|
||||
|
|
Loading…
Reference in a new issue