Add third_party module callbacks to check if a user can delete a room and deactivate a user (#12028)
* Add check_can_deactivate_user * Add check_can_shutdown_rooms * Documentation * callbacks, not functions * Various suggested tweaks * Add tests for test_check_can_shutdown_room and test_check_can_deactivate_user * Update check_can_deactivate_user to not take a Requester * Fix check_can_shutdown_room docs * Renegade and use `by_admin` instead of `admin_user_id` * fix lint * Update docs/modules/third_party_rules_callbacks.md Co-authored-by: Brendan Abolivier <babolivier@matrix.org> * Update docs/modules/third_party_rules_callbacks.md Co-authored-by: Brendan Abolivier <babolivier@matrix.org> * Update docs/modules/third_party_rules_callbacks.md Co-authored-by: Brendan Abolivier <babolivier@matrix.org> * Update docs/modules/third_party_rules_callbacks.md Co-authored-by: Brendan Abolivier <babolivier@matrix.org> Co-authored-by: Brendan Abolivier <babolivier@matrix.org>
This commit is contained in:
parent
690cb4f3b3
commit
15382b1afa
|
@ -0,0 +1 @@
|
||||||
|
Add third-party rules rules callbacks `check_can_shutdown_room` and `check_can_deactivate_user`.
|
|
@ -148,6 +148,49 @@ deny an incoming event, see [`check_event_for_spam`](spam_checker_callbacks.md#c
|
||||||
|
|
||||||
If multiple modules implement this callback, Synapse runs them all in order.
|
If multiple modules implement this callback, Synapse runs them all in order.
|
||||||
|
|
||||||
|
### `check_can_shutdown_room`
|
||||||
|
|
||||||
|
_First introduced in Synapse v1.55.0_
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def check_can_shutdown_room(
|
||||||
|
user_id: str, room_id: str,
|
||||||
|
) -> bool:
|
||||||
|
```
|
||||||
|
|
||||||
|
Called when an admin user requests the shutdown of a room. The module must return a
|
||||||
|
boolean indicating whether the shutdown can go through. If the callback returns `False`,
|
||||||
|
the shutdown will not proceed and the caller will see a `M_FORBIDDEN` error.
|
||||||
|
|
||||||
|
If multiple modules implement this callback, they will be considered in order. If a
|
||||||
|
callback returns `True`, Synapse falls through to the next one. The value of the first
|
||||||
|
callback that does not return `True` will be used. If this happens, Synapse will not call
|
||||||
|
any of the subsequent implementations of this callback.
|
||||||
|
|
||||||
|
### `check_can_deactivate_user`
|
||||||
|
|
||||||
|
_First introduced in Synapse v1.55.0_
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def check_can_deactivate_user(
|
||||||
|
user_id: str, by_admin: bool,
|
||||||
|
) -> bool:
|
||||||
|
```
|
||||||
|
|
||||||
|
Called when the deactivation of a user is requested. User deactivation can be
|
||||||
|
performed by an admin or the user themselves, so developers are encouraged to check the
|
||||||
|
requester when implementing this callback. The module must return a
|
||||||
|
boolean indicating whether the deactivation can go through. If the callback returns `False`,
|
||||||
|
the deactivation will not proceed and the caller will see a `M_FORBIDDEN` error.
|
||||||
|
|
||||||
|
The module is passed two parameters, `user_id` which is the ID of the user being deactivated, and `by_admin` which is `True` if the request is made by a serve admin, and `False` otherwise.
|
||||||
|
|
||||||
|
If multiple modules implement this callback, they will be considered in order. If a
|
||||||
|
callback returns `True`, Synapse falls through to the next one. The value of the first
|
||||||
|
callback that does not return `True` will be used. If this happens, Synapse will not call
|
||||||
|
any of the subsequent implementations of this callback.
|
||||||
|
|
||||||
|
|
||||||
### `on_profile_update`
|
### `on_profile_update`
|
||||||
|
|
||||||
_First introduced in Synapse v1.54.0_
|
_First introduced in Synapse v1.54.0_
|
||||||
|
|
|
@ -38,6 +38,8 @@ CHECK_VISIBILITY_CAN_BE_MODIFIED_CALLBACK = Callable[
|
||||||
[str, StateMap[EventBase], str], Awaitable[bool]
|
[str, StateMap[EventBase], str], Awaitable[bool]
|
||||||
]
|
]
|
||||||
ON_NEW_EVENT_CALLBACK = Callable[[EventBase, StateMap[EventBase]], Awaitable]
|
ON_NEW_EVENT_CALLBACK = Callable[[EventBase, StateMap[EventBase]], Awaitable]
|
||||||
|
CHECK_CAN_SHUTDOWN_ROOM_CALLBACK = Callable[[str, str], Awaitable[bool]]
|
||||||
|
CHECK_CAN_DEACTIVATE_USER_CALLBACK = Callable[[str, bool], Awaitable[bool]]
|
||||||
ON_PROFILE_UPDATE_CALLBACK = Callable[[str, ProfileInfo, bool, bool], Awaitable]
|
ON_PROFILE_UPDATE_CALLBACK = Callable[[str, ProfileInfo, bool, bool], Awaitable]
|
||||||
ON_USER_DEACTIVATION_STATUS_CHANGED_CALLBACK = Callable[[str, bool, bool], Awaitable]
|
ON_USER_DEACTIVATION_STATUS_CHANGED_CALLBACK = Callable[[str, bool, bool], Awaitable]
|
||||||
|
|
||||||
|
@ -157,6 +159,12 @@ class ThirdPartyEventRules:
|
||||||
CHECK_VISIBILITY_CAN_BE_MODIFIED_CALLBACK
|
CHECK_VISIBILITY_CAN_BE_MODIFIED_CALLBACK
|
||||||
] = []
|
] = []
|
||||||
self._on_new_event_callbacks: List[ON_NEW_EVENT_CALLBACK] = []
|
self._on_new_event_callbacks: List[ON_NEW_EVENT_CALLBACK] = []
|
||||||
|
self._check_can_shutdown_room_callbacks: List[
|
||||||
|
CHECK_CAN_SHUTDOWN_ROOM_CALLBACK
|
||||||
|
] = []
|
||||||
|
self._check_can_deactivate_user_callbacks: List[
|
||||||
|
CHECK_CAN_DEACTIVATE_USER_CALLBACK
|
||||||
|
] = []
|
||||||
self._on_profile_update_callbacks: List[ON_PROFILE_UPDATE_CALLBACK] = []
|
self._on_profile_update_callbacks: List[ON_PROFILE_UPDATE_CALLBACK] = []
|
||||||
self._on_user_deactivation_status_changed_callbacks: List[
|
self._on_user_deactivation_status_changed_callbacks: List[
|
||||||
ON_USER_DEACTIVATION_STATUS_CHANGED_CALLBACK
|
ON_USER_DEACTIVATION_STATUS_CHANGED_CALLBACK
|
||||||
|
@ -173,6 +181,8 @@ class ThirdPartyEventRules:
|
||||||
CHECK_VISIBILITY_CAN_BE_MODIFIED_CALLBACK
|
CHECK_VISIBILITY_CAN_BE_MODIFIED_CALLBACK
|
||||||
] = None,
|
] = None,
|
||||||
on_new_event: Optional[ON_NEW_EVENT_CALLBACK] = None,
|
on_new_event: Optional[ON_NEW_EVENT_CALLBACK] = None,
|
||||||
|
check_can_shutdown_room: Optional[CHECK_CAN_SHUTDOWN_ROOM_CALLBACK] = None,
|
||||||
|
check_can_deactivate_user: Optional[CHECK_CAN_DEACTIVATE_USER_CALLBACK] = None,
|
||||||
on_profile_update: Optional[ON_PROFILE_UPDATE_CALLBACK] = None,
|
on_profile_update: Optional[ON_PROFILE_UPDATE_CALLBACK] = None,
|
||||||
on_user_deactivation_status_changed: Optional[
|
on_user_deactivation_status_changed: Optional[
|
||||||
ON_USER_DEACTIVATION_STATUS_CHANGED_CALLBACK
|
ON_USER_DEACTIVATION_STATUS_CHANGED_CALLBACK
|
||||||
|
@ -198,6 +208,11 @@ class ThirdPartyEventRules:
|
||||||
if on_new_event is not None:
|
if on_new_event is not None:
|
||||||
self._on_new_event_callbacks.append(on_new_event)
|
self._on_new_event_callbacks.append(on_new_event)
|
||||||
|
|
||||||
|
if check_can_shutdown_room is not None:
|
||||||
|
self._check_can_shutdown_room_callbacks.append(check_can_shutdown_room)
|
||||||
|
|
||||||
|
if check_can_deactivate_user is not None:
|
||||||
|
self._check_can_deactivate_user_callbacks.append(check_can_deactivate_user)
|
||||||
if on_profile_update is not None:
|
if on_profile_update is not None:
|
||||||
self._on_profile_update_callbacks.append(on_profile_update)
|
self._on_profile_update_callbacks.append(on_profile_update)
|
||||||
|
|
||||||
|
@ -369,6 +384,46 @@ class ThirdPartyEventRules:
|
||||||
"Failed to run module API callback %s: %s", callback, e
|
"Failed to run module API callback %s: %s", callback, e
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def check_can_shutdown_room(self, user_id: str, room_id: str) -> bool:
|
||||||
|
"""Intercept requests to shutdown a room. If `False` is returned, the
|
||||||
|
room must not be shut down.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
requester: The ID of the user requesting the shutdown.
|
||||||
|
room_id: The ID of the room.
|
||||||
|
"""
|
||||||
|
for callback in self._check_can_shutdown_room_callbacks:
|
||||||
|
try:
|
||||||
|
if await callback(user_id, room_id) is False:
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(
|
||||||
|
"Failed to run module API callback %s: %s", callback, e
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def check_can_deactivate_user(
|
||||||
|
self,
|
||||||
|
user_id: str,
|
||||||
|
by_admin: bool,
|
||||||
|
) -> bool:
|
||||||
|
"""Intercept requests to deactivate a user. If `False` is returned, the
|
||||||
|
user should not be deactivated.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
requester
|
||||||
|
user_id: The ID of the room.
|
||||||
|
"""
|
||||||
|
for callback in self._check_can_deactivate_user_callbacks:
|
||||||
|
try:
|
||||||
|
if await callback(user_id, by_admin) is False:
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(
|
||||||
|
"Failed to run module API callback %s: %s", callback, e
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
|
||||||
async def _get_state_map_for_room(self, room_id: str) -> StateMap[EventBase]:
|
async def _get_state_map_for_room(self, room_id: str) -> StateMap[EventBase]:
|
||||||
"""Given a room ID, return the state events of that room.
|
"""Given a room ID, return the state events of that room.
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ from typing import TYPE_CHECKING, Optional
|
||||||
|
|
||||||
from synapse.api.errors import SynapseError
|
from synapse.api.errors import SynapseError
|
||||||
from synapse.metrics.background_process_metrics import run_as_background_process
|
from synapse.metrics.background_process_metrics import run_as_background_process
|
||||||
from synapse.types import Requester, UserID, create_requester
|
from synapse.types import Codes, Requester, UserID, create_requester
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from synapse.server import HomeServer
|
from synapse.server import HomeServer
|
||||||
|
@ -42,6 +42,7 @@ class DeactivateAccountHandler:
|
||||||
|
|
||||||
# Flag that indicates whether the process to part users from rooms is running
|
# Flag that indicates whether the process to part users from rooms is running
|
||||||
self._user_parter_running = False
|
self._user_parter_running = False
|
||||||
|
self._third_party_rules = hs.get_third_party_event_rules()
|
||||||
|
|
||||||
# Start the user parter loop so it can resume parting users from rooms where
|
# Start the user parter loop so it can resume parting users from rooms where
|
||||||
# it left off (if it has work left to do).
|
# it left off (if it has work left to do).
|
||||||
|
@ -74,6 +75,15 @@ class DeactivateAccountHandler:
|
||||||
Returns:
|
Returns:
|
||||||
True if identity server supports removing threepids, otherwise False.
|
True if identity server supports removing threepids, otherwise False.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# Check if this user can be deactivated
|
||||||
|
if not await self._third_party_rules.check_can_deactivate_user(
|
||||||
|
user_id, by_admin
|
||||||
|
):
|
||||||
|
raise SynapseError(
|
||||||
|
403, "Deactivation of this user is forbidden", Codes.FORBIDDEN
|
||||||
|
)
|
||||||
|
|
||||||
# FIXME: Theoretically there is a race here wherein user resets
|
# FIXME: Theoretically there is a race here wherein user resets
|
||||||
# password using threepid.
|
# password using threepid.
|
||||||
|
|
||||||
|
|
|
@ -1475,6 +1475,7 @@ class RoomShutdownHandler:
|
||||||
self.room_member_handler = hs.get_room_member_handler()
|
self.room_member_handler = hs.get_room_member_handler()
|
||||||
self._room_creation_handler = hs.get_room_creation_handler()
|
self._room_creation_handler = hs.get_room_creation_handler()
|
||||||
self._replication = hs.get_replication_data_handler()
|
self._replication = hs.get_replication_data_handler()
|
||||||
|
self._third_party_rules = hs.get_third_party_event_rules()
|
||||||
self.event_creation_handler = hs.get_event_creation_handler()
|
self.event_creation_handler = hs.get_event_creation_handler()
|
||||||
self.store = hs.get_datastores().main
|
self.store = hs.get_datastores().main
|
||||||
|
|
||||||
|
@ -1548,6 +1549,13 @@ class RoomShutdownHandler:
|
||||||
if not RoomID.is_valid(room_id):
|
if not RoomID.is_valid(room_id):
|
||||||
raise SynapseError(400, "%s is not a legal room ID" % (room_id,))
|
raise SynapseError(400, "%s is not a legal room ID" % (room_id,))
|
||||||
|
|
||||||
|
if not await self._third_party_rules.check_can_shutdown_room(
|
||||||
|
requester_user_id, room_id
|
||||||
|
):
|
||||||
|
raise SynapseError(
|
||||||
|
403, "Shutdown of this room is forbidden", Codes.FORBIDDEN
|
||||||
|
)
|
||||||
|
|
||||||
# Action the block first (even if the room doesn't exist yet)
|
# Action the block first (even if the room doesn't exist yet)
|
||||||
if block:
|
if block:
|
||||||
# This will work even if the room is already blocked, but that is
|
# This will work even if the room is already blocked, but that is
|
||||||
|
|
|
@ -54,6 +54,8 @@ from synapse.events.spamcheck import (
|
||||||
USER_MAY_SEND_3PID_INVITE_CALLBACK,
|
USER_MAY_SEND_3PID_INVITE_CALLBACK,
|
||||||
)
|
)
|
||||||
from synapse.events.third_party_rules import (
|
from synapse.events.third_party_rules import (
|
||||||
|
CHECK_CAN_DEACTIVATE_USER_CALLBACK,
|
||||||
|
CHECK_CAN_SHUTDOWN_ROOM_CALLBACK,
|
||||||
CHECK_EVENT_ALLOWED_CALLBACK,
|
CHECK_EVENT_ALLOWED_CALLBACK,
|
||||||
CHECK_THREEPID_CAN_BE_INVITED_CALLBACK,
|
CHECK_THREEPID_CAN_BE_INVITED_CALLBACK,
|
||||||
CHECK_VISIBILITY_CAN_BE_MODIFIED_CALLBACK,
|
CHECK_VISIBILITY_CAN_BE_MODIFIED_CALLBACK,
|
||||||
|
@ -283,6 +285,8 @@ class ModuleApi:
|
||||||
CHECK_VISIBILITY_CAN_BE_MODIFIED_CALLBACK
|
CHECK_VISIBILITY_CAN_BE_MODIFIED_CALLBACK
|
||||||
] = None,
|
] = None,
|
||||||
on_new_event: Optional[ON_NEW_EVENT_CALLBACK] = None,
|
on_new_event: Optional[ON_NEW_EVENT_CALLBACK] = None,
|
||||||
|
check_can_shutdown_room: Optional[CHECK_CAN_SHUTDOWN_ROOM_CALLBACK] = None,
|
||||||
|
check_can_deactivate_user: Optional[CHECK_CAN_DEACTIVATE_USER_CALLBACK] = None,
|
||||||
on_profile_update: Optional[ON_PROFILE_UPDATE_CALLBACK] = None,
|
on_profile_update: Optional[ON_PROFILE_UPDATE_CALLBACK] = None,
|
||||||
on_user_deactivation_status_changed: Optional[
|
on_user_deactivation_status_changed: Optional[
|
||||||
ON_USER_DEACTIVATION_STATUS_CHANGED_CALLBACK
|
ON_USER_DEACTIVATION_STATUS_CHANGED_CALLBACK
|
||||||
|
@ -298,6 +302,8 @@ class ModuleApi:
|
||||||
check_threepid_can_be_invited=check_threepid_can_be_invited,
|
check_threepid_can_be_invited=check_threepid_can_be_invited,
|
||||||
check_visibility_can_be_modified=check_visibility_can_be_modified,
|
check_visibility_can_be_modified=check_visibility_can_be_modified,
|
||||||
on_new_event=on_new_event,
|
on_new_event=on_new_event,
|
||||||
|
check_can_shutdown_room=check_can_shutdown_room,
|
||||||
|
check_can_deactivate_user=check_can_deactivate_user,
|
||||||
on_profile_update=on_profile_update,
|
on_profile_update=on_profile_update,
|
||||||
on_user_deactivation_status_changed=on_user_deactivation_status_changed,
|
on_user_deactivation_status_changed=on_user_deactivation_status_changed,
|
||||||
)
|
)
|
||||||
|
|
|
@ -67,6 +67,7 @@ class RoomRestV2Servlet(RestServlet):
|
||||||
self._auth = hs.get_auth()
|
self._auth = hs.get_auth()
|
||||||
self._store = hs.get_datastores().main
|
self._store = hs.get_datastores().main
|
||||||
self._pagination_handler = hs.get_pagination_handler()
|
self._pagination_handler = hs.get_pagination_handler()
|
||||||
|
self._third_party_rules = hs.get_third_party_event_rules()
|
||||||
|
|
||||||
async def on_DELETE(
|
async def on_DELETE(
|
||||||
self, request: SynapseRequest, room_id: str
|
self, request: SynapseRequest, room_id: str
|
||||||
|
@ -106,6 +107,14 @@ class RoomRestV2Servlet(RestServlet):
|
||||||
HTTPStatus.BAD_REQUEST, "%s is not a legal room ID" % (room_id,)
|
HTTPStatus.BAD_REQUEST, "%s is not a legal room ID" % (room_id,)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Check this here, as otherwise we'll only fail after the background job has been started.
|
||||||
|
if not await self._third_party_rules.check_can_shutdown_room(
|
||||||
|
requester.user.to_string(), room_id
|
||||||
|
):
|
||||||
|
raise SynapseError(
|
||||||
|
403, "Shutdown of this room is forbidden", Codes.FORBIDDEN
|
||||||
|
)
|
||||||
|
|
||||||
delete_id = self._pagination_handler.start_shutdown_and_purge_room(
|
delete_id = self._pagination_handler.start_shutdown_and_purge_room(
|
||||||
room_id=room_id,
|
room_id=room_id,
|
||||||
new_room_user_id=content.get("new_room_user_id"),
|
new_room_user_id=content.get("new_room_user_id"),
|
||||||
|
|
|
@ -775,3 +775,124 @@ class ThirdPartyRulesTestCase(unittest.FederatingHomeserverTestCase):
|
||||||
self.assertEqual(args[0], user_id)
|
self.assertEqual(args[0], user_id)
|
||||||
self.assertFalse(args[1])
|
self.assertFalse(args[1])
|
||||||
self.assertTrue(args[2])
|
self.assertTrue(args[2])
|
||||||
|
|
||||||
|
def test_check_can_deactivate_user(self) -> None:
|
||||||
|
"""Tests that the on_user_deactivation_status_changed module callback is called
|
||||||
|
correctly when processing a user's deactivation.
|
||||||
|
"""
|
||||||
|
# Register a mocked callback.
|
||||||
|
deactivation_mock = Mock(return_value=make_awaitable(False))
|
||||||
|
third_party_rules = self.hs.get_third_party_event_rules()
|
||||||
|
third_party_rules._check_can_deactivate_user_callbacks.append(
|
||||||
|
deactivation_mock,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Register a user that we'll deactivate.
|
||||||
|
user_id = self.register_user("altan", "password")
|
||||||
|
tok = self.login("altan", "password")
|
||||||
|
|
||||||
|
# Deactivate that user.
|
||||||
|
channel = self.make_request(
|
||||||
|
"POST",
|
||||||
|
"/_matrix/client/v3/account/deactivate",
|
||||||
|
{
|
||||||
|
"auth": {
|
||||||
|
"type": LoginType.PASSWORD,
|
||||||
|
"password": "password",
|
||||||
|
"identifier": {
|
||||||
|
"type": "m.id.user",
|
||||||
|
"user": user_id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"erase": True,
|
||||||
|
},
|
||||||
|
access_token=tok,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check that the deactivation was blocked
|
||||||
|
self.assertEqual(channel.code, 403, channel.json_body)
|
||||||
|
|
||||||
|
# Check that the mock was called once.
|
||||||
|
deactivation_mock.assert_called_once()
|
||||||
|
args = deactivation_mock.call_args[0]
|
||||||
|
|
||||||
|
# Check that the mock was called with the right user ID
|
||||||
|
self.assertEqual(args[0], user_id)
|
||||||
|
|
||||||
|
# Check that the request was not made by an admin
|
||||||
|
self.assertEqual(args[1], False)
|
||||||
|
|
||||||
|
def test_check_can_deactivate_user_admin(self) -> None:
|
||||||
|
"""Tests that the on_user_deactivation_status_changed module callback is called
|
||||||
|
correctly when processing a user's deactivation triggered by a server admin.
|
||||||
|
"""
|
||||||
|
# Register a mocked callback.
|
||||||
|
deactivation_mock = Mock(return_value=make_awaitable(False))
|
||||||
|
third_party_rules = self.hs.get_third_party_event_rules()
|
||||||
|
third_party_rules._check_can_deactivate_user_callbacks.append(
|
||||||
|
deactivation_mock,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Register an admin user.
|
||||||
|
self.register_user("admin", "password", admin=True)
|
||||||
|
admin_tok = self.login("admin", "password")
|
||||||
|
|
||||||
|
# Register a user that we'll deactivate.
|
||||||
|
user_id = self.register_user("altan", "password")
|
||||||
|
|
||||||
|
# Deactivate the user.
|
||||||
|
channel = self.make_request(
|
||||||
|
"PUT",
|
||||||
|
"/_synapse/admin/v2/users/%s" % user_id,
|
||||||
|
{"deactivated": True},
|
||||||
|
access_token=admin_tok,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check that the deactivation was blocked
|
||||||
|
self.assertEqual(channel.code, 403, channel.json_body)
|
||||||
|
|
||||||
|
# Check that the mock was called once.
|
||||||
|
deactivation_mock.assert_called_once()
|
||||||
|
args = deactivation_mock.call_args[0]
|
||||||
|
|
||||||
|
# Check that the mock was called with the right user ID
|
||||||
|
self.assertEqual(args[0], user_id)
|
||||||
|
|
||||||
|
# Check that the mock was made by an admin
|
||||||
|
self.assertEqual(args[1], True)
|
||||||
|
|
||||||
|
def test_check_can_shutdown_room(self) -> None:
|
||||||
|
"""Tests that the check_can_shutdown_room module callback is called
|
||||||
|
correctly when processing an admin's shutdown room request.
|
||||||
|
"""
|
||||||
|
# Register a mocked callback.
|
||||||
|
shutdown_mock = Mock(return_value=make_awaitable(False))
|
||||||
|
third_party_rules = self.hs.get_third_party_event_rules()
|
||||||
|
third_party_rules._check_can_shutdown_room_callbacks.append(
|
||||||
|
shutdown_mock,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Register an admin user.
|
||||||
|
admin_user_id = self.register_user("admin", "password", admin=True)
|
||||||
|
admin_tok = self.login("admin", "password")
|
||||||
|
|
||||||
|
# Shutdown the room.
|
||||||
|
channel = self.make_request(
|
||||||
|
"DELETE",
|
||||||
|
"/_synapse/admin/v2/rooms/%s" % self.room_id,
|
||||||
|
{},
|
||||||
|
access_token=admin_tok,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check that the shutdown was blocked
|
||||||
|
self.assertEqual(channel.code, 403, channel.json_body)
|
||||||
|
|
||||||
|
# Check that the mock was called once.
|
||||||
|
shutdown_mock.assert_called_once()
|
||||||
|
args = shutdown_mock.call_args[0]
|
||||||
|
|
||||||
|
# Check that the mock was called with the right user ID
|
||||||
|
self.assertEqual(args[0], admin_user_id)
|
||||||
|
|
||||||
|
# Check that the mock was called with the right room ID
|
||||||
|
self.assertEqual(args[1], self.room_id)
|
||||||
|
|
Loading…
Reference in New Issue