Merge pull request #4904 from matrix-org/erikj/fix_shutdown

Fixup shutdown room API
This commit is contained in:
Erik Johnston 2019-03-21 11:24:42 +00:00 committed by GitHub
commit 3959858eaa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 137 additions and 25 deletions

1
changelog.d/4904.bugfix Normal file
View 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.

View File

@ -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,))

View File

@ -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(

View File

@ -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:

View File

@ -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)

View File

@ -490,40 +490,54 @@ 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)
target_requester = create_requester(user_id) try:
yield self.room_member_handler.update_membership( target_requester = create_requester(user_id)
requester=target_requester, yield self.room_member_handler.update_membership(
target=target_requester.user, requester=target_requester,
room_id=room_id, target=target_requester.user,
action=Membership.LEAVE, room_id=room_id,
content={}, action=Membership.LEAVE,
ratelimit=False content={},
) 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)
yield self.room_member_handler.update_membership( yield self.room_member_handler.update_membership(
requester=target_requester, requester=target_requester,
target=target_requester.user, target=target_requester.user,
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,
})) }))

View File

@ -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",

View File

@ -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)