forked from MirrorHub/synapse
Merge pull request #4904 from matrix-org/erikj/fix_shutdown
Fixup shutdown room API
This commit is contained in:
commit
3959858eaa
8 changed files with 137 additions and 25 deletions
1
changelog.d/4904.bugfix
Normal file
1
changelog.d/4904.bugfix
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Fix bug in shutdown room admin API where it would fail if a user in the room hadn't consented to the privacy policy.
|
|
@ -165,6 +165,7 @@ class BaseHandler(object):
|
||||||
member_event.room_id,
|
member_event.room_id,
|
||||||
"leave",
|
"leave",
|
||||||
ratelimit=False,
|
ratelimit=False,
|
||||||
|
require_consent=False,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception("Error kicking guest user: %s" % (e,))
|
logger.exception("Error kicking guest user: %s" % (e,))
|
||||||
|
|
|
@ -164,6 +164,7 @@ class DeactivateAccountHandler(BaseHandler):
|
||||||
room_id,
|
room_id,
|
||||||
"leave",
|
"leave",
|
||||||
ratelimit=False,
|
ratelimit=False,
|
||||||
|
require_consent=False,
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception(
|
logger.exception(
|
||||||
|
|
|
@ -255,7 +255,7 @@ class EventCreationHandler(object):
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def create_event(self, requester, event_dict, token_id=None, txn_id=None,
|
def create_event(self, requester, event_dict, token_id=None, txn_id=None,
|
||||||
prev_events_and_hashes=None):
|
prev_events_and_hashes=None, require_consent=True):
|
||||||
"""
|
"""
|
||||||
Given a dict from a client, create a new event.
|
Given a dict from a client, create a new event.
|
||||||
|
|
||||||
|
@ -276,6 +276,9 @@ class EventCreationHandler(object):
|
||||||
where *hashes* is a map from algorithm to hash.
|
where *hashes* is a map from algorithm to hash.
|
||||||
|
|
||||||
If None, they will be requested from the database.
|
If None, they will be requested from the database.
|
||||||
|
|
||||||
|
require_consent (bool): Whether to check if the requester has
|
||||||
|
consented to privacy policy.
|
||||||
Raises:
|
Raises:
|
||||||
ResourceLimitError if server is blocked to some resource being
|
ResourceLimitError if server is blocked to some resource being
|
||||||
exceeded
|
exceeded
|
||||||
|
@ -317,7 +320,7 @@ class EventCreationHandler(object):
|
||||||
)
|
)
|
||||||
|
|
||||||
is_exempt = yield self._is_exempt_from_privacy_policy(builder, requester)
|
is_exempt = yield self._is_exempt_from_privacy_policy(builder, requester)
|
||||||
if not is_exempt:
|
if require_consent and not is_exempt:
|
||||||
yield self.assert_accepted_privacy_policy(requester)
|
yield self.assert_accepted_privacy_policy(requester)
|
||||||
|
|
||||||
if token_id is not None:
|
if token_id is not None:
|
||||||
|
|
|
@ -160,6 +160,7 @@ class RoomMemberHandler(object):
|
||||||
txn_id=None,
|
txn_id=None,
|
||||||
ratelimit=True,
|
ratelimit=True,
|
||||||
content=None,
|
content=None,
|
||||||
|
require_consent=True,
|
||||||
):
|
):
|
||||||
user_id = target.to_string()
|
user_id = target.to_string()
|
||||||
|
|
||||||
|
@ -185,6 +186,7 @@ class RoomMemberHandler(object):
|
||||||
token_id=requester.access_token_id,
|
token_id=requester.access_token_id,
|
||||||
txn_id=txn_id,
|
txn_id=txn_id,
|
||||||
prev_events_and_hashes=prev_events_and_hashes,
|
prev_events_and_hashes=prev_events_and_hashes,
|
||||||
|
require_consent=require_consent,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check if this event matches the previous membership event for the user.
|
# Check if this event matches the previous membership event for the user.
|
||||||
|
@ -305,6 +307,7 @@ class RoomMemberHandler(object):
|
||||||
third_party_signed=None,
|
third_party_signed=None,
|
||||||
ratelimit=True,
|
ratelimit=True,
|
||||||
content=None,
|
content=None,
|
||||||
|
require_consent=True,
|
||||||
):
|
):
|
||||||
key = (room_id,)
|
key = (room_id,)
|
||||||
|
|
||||||
|
@ -319,6 +322,7 @@ class RoomMemberHandler(object):
|
||||||
third_party_signed=third_party_signed,
|
third_party_signed=third_party_signed,
|
||||||
ratelimit=ratelimit,
|
ratelimit=ratelimit,
|
||||||
content=content,
|
content=content,
|
||||||
|
require_consent=require_consent,
|
||||||
)
|
)
|
||||||
|
|
||||||
defer.returnValue(result)
|
defer.returnValue(result)
|
||||||
|
@ -335,6 +339,7 @@ class RoomMemberHandler(object):
|
||||||
third_party_signed=None,
|
third_party_signed=None,
|
||||||
ratelimit=True,
|
ratelimit=True,
|
||||||
content=None,
|
content=None,
|
||||||
|
require_consent=True,
|
||||||
):
|
):
|
||||||
content_specified = bool(content)
|
content_specified = bool(content)
|
||||||
if content is None:
|
if content is None:
|
||||||
|
@ -516,6 +521,7 @@ class RoomMemberHandler(object):
|
||||||
ratelimit=ratelimit,
|
ratelimit=ratelimit,
|
||||||
prev_events_and_hashes=prev_events_and_hashes,
|
prev_events_and_hashes=prev_events_and_hashes,
|
||||||
content=content,
|
content=content,
|
||||||
|
require_consent=require_consent,
|
||||||
)
|
)
|
||||||
defer.returnValue(res)
|
defer.returnValue(res)
|
||||||
|
|
||||||
|
|
|
@ -490,18 +490,25 @@ class ShutdownRoomRestServlet(ClientV1RestServlet):
|
||||||
|
|
||||||
requester_user_id = requester.user.to_string()
|
requester_user_id = requester.user.to_string()
|
||||||
|
|
||||||
logger.info("Shutting down room %r", room_id)
|
logger.info(
|
||||||
|
"Shutting down room %r, joining to new room: %r",
|
||||||
|
room_id, new_room_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
# This will work even if the room is already blocked, but that is
|
||||||
|
# desirable in case the first attempt at blocking the room failed below.
|
||||||
yield self.store.block_room(room_id, requester_user_id)
|
yield self.store.block_room(room_id, requester_user_id)
|
||||||
|
|
||||||
users = yield self.state.get_current_user_in_room(room_id)
|
users = yield self.state.get_current_user_in_room(room_id)
|
||||||
kicked_users = []
|
kicked_users = []
|
||||||
|
failed_to_kick_users = []
|
||||||
for user_id in users:
|
for user_id in users:
|
||||||
if not self.hs.is_mine_id(user_id):
|
if not self.hs.is_mine_id(user_id):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
logger.info("Kicking %r from %r...", user_id, room_id)
|
logger.info("Kicking %r from %r...", user_id, room_id)
|
||||||
|
|
||||||
|
try:
|
||||||
target_requester = create_requester(user_id)
|
target_requester = create_requester(user_id)
|
||||||
yield self.room_member_handler.update_membership(
|
yield self.room_member_handler.update_membership(
|
||||||
requester=target_requester,
|
requester=target_requester,
|
||||||
|
@ -509,7 +516,8 @@ class ShutdownRoomRestServlet(ClientV1RestServlet):
|
||||||
room_id=room_id,
|
room_id=room_id,
|
||||||
action=Membership.LEAVE,
|
action=Membership.LEAVE,
|
||||||
content={},
|
content={},
|
||||||
ratelimit=False
|
ratelimit=False,
|
||||||
|
require_consent=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
yield self.room_member_handler.forget(target_requester.user, room_id)
|
yield self.room_member_handler.forget(target_requester.user, room_id)
|
||||||
|
@ -520,10 +528,16 @@ class ShutdownRoomRestServlet(ClientV1RestServlet):
|
||||||
room_id=new_room_id,
|
room_id=new_room_id,
|
||||||
action=Membership.JOIN,
|
action=Membership.JOIN,
|
||||||
content={},
|
content={},
|
||||||
ratelimit=False
|
ratelimit=False,
|
||||||
|
require_consent=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
kicked_users.append(user_id)
|
kicked_users.append(user_id)
|
||||||
|
except Exception:
|
||||||
|
logger.exception(
|
||||||
|
"Failed to leave old room and join new room for %r", user_id,
|
||||||
|
)
|
||||||
|
failed_to_kick_users.append(user_id)
|
||||||
|
|
||||||
yield self.event_creation_handler.create_and_send_nonmember_event(
|
yield self.event_creation_handler.create_and_send_nonmember_event(
|
||||||
room_creator_requester,
|
room_creator_requester,
|
||||||
|
@ -544,6 +558,7 @@ class ShutdownRoomRestServlet(ClientV1RestServlet):
|
||||||
|
|
||||||
defer.returnValue((200, {
|
defer.returnValue((200, {
|
||||||
"kicked_users": kicked_users,
|
"kicked_users": kicked_users,
|
||||||
|
"failed_to_kick_users": failed_to_kick_users,
|
||||||
"local_aliases": aliases_for_room,
|
"local_aliases": aliases_for_room,
|
||||||
"new_room_id": new_room_id,
|
"new_room_id": new_room_id,
|
||||||
}))
|
}))
|
||||||
|
|
|
@ -500,10 +500,22 @@ class RoomStore(RoomWorkerStore, SearchStore):
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def block_room(self, room_id, user_id):
|
def block_room(self, room_id, user_id):
|
||||||
yield self._simple_insert(
|
"""Marks the room as blocked. Can be called multiple times.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
room_id (str): Room to block
|
||||||
|
user_id (str): Who blocked it
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Deferred
|
||||||
|
"""
|
||||||
|
yield self._simple_upsert(
|
||||||
table="blocked_rooms",
|
table="blocked_rooms",
|
||||||
values={
|
keyvalues={
|
||||||
"room_id": room_id,
|
"room_id": room_id,
|
||||||
|
},
|
||||||
|
values={},
|
||||||
|
insertion_values={
|
||||||
"user_id": user_id,
|
"user_id": user_id,
|
||||||
},
|
},
|
||||||
desc="block_room",
|
desc="block_room",
|
||||||
|
|
|
@ -20,7 +20,7 @@ import json
|
||||||
from mock import Mock
|
from mock import Mock
|
||||||
|
|
||||||
from synapse.api.constants import UserTypes
|
from synapse.api.constants import UserTypes
|
||||||
from synapse.rest.client.v1 import admin, login
|
from synapse.rest.client.v1 import admin, login, room
|
||||||
|
|
||||||
from tests import unittest
|
from tests import unittest
|
||||||
|
|
||||||
|
@ -353,3 +353,76 @@ class UserRegisterTestCase(unittest.HomeserverTestCase):
|
||||||
|
|
||||||
self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
|
self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
|
||||||
self.assertEqual('Invalid user type', channel.json_body["error"])
|
self.assertEqual('Invalid user type', channel.json_body["error"])
|
||||||
|
|
||||||
|
|
||||||
|
class ShutdownRoomTestCase(unittest.HomeserverTestCase):
|
||||||
|
servlets = [
|
||||||
|
admin.register_servlets,
|
||||||
|
login.register_servlets,
|
||||||
|
room.register_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 = "admin/shutdown_room/" + room_id
|
||||||
|
request, channel = self.make_request(
|
||||||
|
"POST",
|
||||||
|
url.encode('ascii'),
|
||||||
|
json.dumps({"new_room_user_id": self.admin_user}),
|
||||||
|
access_token=self.admin_user_tok,
|
||||||
|
)
|
||||||
|
self.render(request)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
Loading…
Reference in a new issue