From 4eca8d3fb3d906b1bc5be6c8d4b98cf555e154b7 Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Mon, 18 Mar 2019 10:16:19 +0000 Subject: Add invite_list and cloning param to create room rule --- synapse/events/spamcheck.py | 21 ++++++++++++++++----- synapse/handlers/federation.py | 2 +- synapse/handlers/room.py | 17 ++++++++++++++--- synapse/handlers/room_member.py | 26 ++++++++++++++++++++++++++ synapse/rulecheck/domain_rule_checker.py | 5 +++-- 5 files changed, 60 insertions(+), 11 deletions(-) diff --git a/synapse/events/spamcheck.py b/synapse/events/spamcheck.py index 633e068eb8..aa559e1f50 100644 --- a/synapse/events/spamcheck.py +++ b/synapse/events/spamcheck.py @@ -46,13 +46,18 @@ class SpamChecker(object): return self.spam_checker.check_event_for_spam(event) - def user_may_invite(self, inviter_userid, invitee_userid, room_id): + def user_may_invite(self, inviter_userid, invitee_userid, room_id, new_room): """Checks if a given user may send an invite If this method returns false, the invite will be rejected. Args: - userid (string): The sender's user ID + inviter_userid (str) + invitee_userid (str) + room_id (str) + new_room (bool): Wether the user is being invited to the room as + part of a room creation, if so the invitee would have been + included in the call to `user_may_create_room`. Returns: bool: True if the user may send an invite, otherwise False @@ -60,15 +65,21 @@ class SpamChecker(object): if self.spam_checker is None: return True - return self.spam_checker.user_may_invite(inviter_userid, invitee_userid, room_id) + return self.spam_checker.user_may_invite( + inviter_userid, invitee_userid, room_id, new_room, + ) - def user_may_create_room(self, userid): + def user_may_create_room(self, userid, invite_list, cloning): """Checks if a given user may create a room If this method returns false, the creation request will be rejected. Args: userid (string): The sender's user ID + invite_list (list[str]): List of user IDs that would be invited to + the new room. + cloning (bool): Whether the user is cloning an existing room, e.g. + upgrading a room. Returns: bool: True if the user may create a room, otherwise False @@ -76,7 +87,7 @@ class SpamChecker(object): if self.spam_checker is None: return True - return self.spam_checker.user_may_create_room(userid) + return self.spam_checker.user_may_create_room(userid, invite_list, cloning) def user_may_create_room_alias(self, userid, room_alias): """Checks if a given user may create a room alias diff --git a/synapse/handlers/federation.py b/synapse/handlers/federation.py index f80486102a..a222d67190 100644 --- a/synapse/handlers/federation.py +++ b/synapse/handlers/federation.py @@ -1299,7 +1299,7 @@ class FederationHandler(BaseHandler): raise SynapseError(403, "This server does not accept room invites") if not self.spam_checker.user_may_invite( - event.sender, event.state_key, event.room_id, + event.sender, event.state_key, event.room_id, new_room=False, ): raise SynapseError( 403, "This user is not permitted to send invites to this server/user" diff --git a/synapse/handlers/room.py b/synapse/handlers/room.py index eb4b437ce8..2d42a41134 100644 --- a/synapse/handlers/room.py +++ b/synapse/handlers/room.py @@ -254,7 +254,11 @@ class RoomCreationHandler(BaseHandler): """ user_id = requester.user.to_string() - if not self.spam_checker.user_may_create_room(user_id): + if not self.spam_checker.user_may_create_room( + user_id, + invite_list=[], + cloning=True, + ): raise SynapseError(403, "You are not permitted to create rooms") creation_content = { @@ -475,7 +479,13 @@ class RoomCreationHandler(BaseHandler): yield self.auth.check_auth_blocking(user_id) - if not self.spam_checker.user_may_create_room(user_id): + invite_list = config.get("invite", []) + + if not self.spam_checker.user_may_create_room( + user_id, + invite_list=invite_list, + cloning=False, + ): raise SynapseError(403, "You are not permitted to create rooms") if ratelimit: @@ -518,7 +528,6 @@ class RoomCreationHandler(BaseHandler): else: room_alias = None - invite_list = config.get("invite", []) for i in invite_list: try: UserID.from_string(i) @@ -615,6 +624,7 @@ class RoomCreationHandler(BaseHandler): "invite", ratelimit=False, content=content, + new_room=True, ) for invite_3pid in invite_3pid_list: @@ -699,6 +709,7 @@ class RoomCreationHandler(BaseHandler): "join", ratelimit=False, content=creator_join_profile, + new_room=True, ) # We treat the power levels override specially as this needs to be one diff --git a/synapse/handlers/room_member.py b/synapse/handlers/room_member.py index 190ea2c7b1..1bf500776a 100644 --- a/synapse/handlers/room_member.py +++ b/synapse/handlers/room_member.py @@ -301,7 +301,30 @@ class RoomMemberHandler(object): third_party_signed=None, ratelimit=True, content=None, + new_room=False, ): + """Update a users membership in a room + + Args: + requester (Requester) + target (UserID) + room_id (str) + action (str): The "action" the requester is performing against the + target. One of join/leave/kick/ban/invite/unban. + txn_id (str|None): The transaction ID associated with the request, + or None not provided. + remote_room_hosts (list[str]|None): List of remote servers to try + and join via if server isn't already in the room. + third_party_signed (dict|None): The signed object for third party + invites. + ratelimit (bool): Whether to apply ratelimiting to this request. + content (dict|None): Fields to include in the new events content. + new_room (bool): Whether these membership changes are happening + as part of a room creation (e.g. initial joins and invites) + + Returns: + Deferred[FrozenEvent] + """ key = (room_id,) with (yield self.member_linearizer.queue(key)): @@ -315,6 +338,7 @@ class RoomMemberHandler(object): third_party_signed=third_party_signed, ratelimit=ratelimit, content=content, + new_room=new_room, ) defer.returnValue(result) @@ -331,6 +355,7 @@ class RoomMemberHandler(object): third_party_signed=None, ratelimit=True, content=None, + new_room=False, ): content_specified = bool(content) if content is None: @@ -392,6 +417,7 @@ class RoomMemberHandler(object): if not self.spam_checker.user_may_invite( requester.user.to_string(), target.to_string(), room_id, + new_room=new_room, ): logger.info("Blocking invite due to spam checker") block_invite = True diff --git a/synapse/rulecheck/domain_rule_checker.py b/synapse/rulecheck/domain_rule_checker.py index 3caa6b34cb..ed56f16c6f 100644 --- a/synapse/rulecheck/domain_rule_checker.py +++ b/synapse/rulecheck/domain_rule_checker.py @@ -48,7 +48,8 @@ class DomainRuleChecker(object): """ return False - def user_may_invite(self, inviter_userid, invitee_userid, room_id): + def user_may_invite(self, inviter_userid, invitee_userid, room_id, + new_room): """Implements synapse.events.SpamChecker.user_may_invite """ inviter_domain = self._get_domain_from_id(inviter_userid) @@ -59,7 +60,7 @@ class DomainRuleChecker(object): return invitee_domain in self.domain_mapping[inviter_domain] - def user_may_create_room(self, userid): + def user_may_create_room(self, userid, invite_list, cloning): """Implements synapse.events.SpamChecker.user_may_create_room """ return True -- cgit 1.5.1 From b85ff4b894576d6b35a6985a1812c0affe7aa9bf Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Mon, 18 Mar 2019 13:17:49 +0000 Subject: Add user_may_join_room spam check --- synapse/events/spamcheck.py | 18 ++++++++++++++++++ synapse/handlers/room_member.py | 13 ++++++++++++- synapse/rulecheck/domain_rule_checker.py | 5 +++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/synapse/events/spamcheck.py b/synapse/events/spamcheck.py index aa559e1f50..e4fc988cfc 100644 --- a/synapse/events/spamcheck.py +++ b/synapse/events/spamcheck.py @@ -122,3 +122,21 @@ class SpamChecker(object): return True return self.spam_checker.user_may_publish_room(userid, room_id) + + def user_may_join_room(self, userid, room_id, is_invited): + """Checks if a given users is allowed to join a room. + + Is not called when the user creates a room. + + Args: + userid (str) + room_id (str) + is_invited (bool): Whether the user is invited into the room + + Returns: + bool: Whether the user may join the room + """ + if self.spam_checker is None: + return True + + return self.spam_checker.user_may_join_room(userid, room_id, is_invited) diff --git a/synapse/handlers/room_member.py b/synapse/handlers/room_member.py index 1bf500776a..cc673e940a 100644 --- a/synapse/handlers/room_member.py +++ b/synapse/handlers/room_member.py @@ -487,8 +487,19 @@ class RoomMemberHandler(object): # so don't really fit into the general auth process. raise AuthError(403, "Guest access not allowed") + inviter = yield self._get_inviter(target.to_string(), room_id) + # We assume that if the spam checker allowed the user to create + # a room then they're allowed to join it. + if not new_room and not self.spam_checker.user_may_join_room( + target.to_string(), room_id, + is_invited=inviter is not None, + new_room=new_room, + ): + raise SynapseError( + 403, "Not allowed to join this room", + ) + if not is_host_in_room: - inviter = yield self._get_inviter(target.to_string(), room_id) if inviter and not self.hs.is_mine(inviter): remote_room_hosts.append(inviter.domain) diff --git a/synapse/rulecheck/domain_rule_checker.py b/synapse/rulecheck/domain_rule_checker.py index ed56f16c6f..9addfd1c1c 100644 --- a/synapse/rulecheck/domain_rule_checker.py +++ b/synapse/rulecheck/domain_rule_checker.py @@ -75,6 +75,11 @@ class DomainRuleChecker(object): """ return True + def user_may_join_room(self, userid, room_id, is_invited, new_room): + """Implements synapse.events.SpamChecker.user_may_join_room + """ + return True + @staticmethod def parse_config(config): """Implements synapse.events.SpamChecker.parse_config -- cgit 1.5.1 From e64f7c0188e28a8486bd7f68ed0d48e9838234d5 Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Mon, 18 Mar 2019 14:23:46 +0000 Subject: Run black on tests/rulecheck/test_domainrulecheck.py --- tests/rulecheck/test_domainrulecheck.py | 68 ++++++++++++++++++--------------- 1 file changed, 38 insertions(+), 30 deletions(-) diff --git a/tests/rulecheck/test_domainrulecheck.py b/tests/rulecheck/test_domainrulecheck.py index 702862f78b..ebcf5ca44e 100644 --- a/tests/rulecheck/test_domainrulecheck.py +++ b/tests/rulecheck/test_domainrulecheck.py @@ -21,22 +21,24 @@ from tests import unittest class DomainRuleCheckerTestCase(unittest.TestCase): - def test_allowed(self): config = { "default": False, "domain_mapping": { "source_one": ["target_one", "target_two"], - "source_two": ["target_two"] - } + "source_two": ["target_two"], + }, } check = DomainRuleChecker(config) - self.assertTrue(check.user_may_invite("test:source_one", - "test:target_one", "room")) - self.assertTrue(check.user_may_invite("test:source_one", - "test:target_two", "room")) - self.assertTrue(check.user_may_invite("test:source_two", - "test:target_two", "room")) + self.assertTrue( + check.user_may_invite("test:source_one", "test:target_one", "room") + ) + self.assertTrue( + check.user_may_invite("test:source_one", "test:target_two", "room") + ) + self.assertTrue( + check.user_may_invite("test:source_two", "test:target_two", "room") + ) def test_disallowed(self): config = { @@ -44,50 +46,56 @@ class DomainRuleCheckerTestCase(unittest.TestCase): "domain_mapping": { "source_one": ["target_one", "target_two"], "source_two": ["target_two"], - "source_four": [] - } + "source_four": [], + }, } check = DomainRuleChecker(config) - self.assertFalse(check.user_may_invite("test:source_one", - "test:target_three", "room")) - self.assertFalse(check.user_may_invite("test:source_two", - "test:target_three", "room")) - self.assertFalse(check.user_may_invite("test:source_two", - "test:target_one", "room")) - self.assertFalse(check.user_may_invite("test:source_four", - "test:target_one", "room")) + self.assertFalse( + check.user_may_invite("test:source_one", "test:target_three", "room") + ) + self.assertFalse( + check.user_may_invite("test:source_two", "test:target_three", "room") + ) + self.assertFalse( + check.user_may_invite("test:source_two", "test:target_one", "room") + ) + self.assertFalse( + check.user_may_invite("test:source_four", "test:target_one", "room") + ) def test_default_allow(self): config = { "default": True, "domain_mapping": { "source_one": ["target_one", "target_two"], - "source_two": ["target_two"] - } + "source_two": ["target_two"], + }, } check = DomainRuleChecker(config) - self.assertTrue(check.user_may_invite("test:source_three", - "test:target_one", "room")) + self.assertTrue( + check.user_may_invite("test:source_three", "test:target_one", "room") + ) def test_default_deny(self): config = { "default": False, "domain_mapping": { "source_one": ["target_one", "target_two"], - "source_two": ["target_two"] - } + "source_two": ["target_two"], + }, } check = DomainRuleChecker(config) - self.assertFalse(check.user_may_invite("test:source_three", - "test:target_one", "room")) + self.assertFalse( + check.user_may_invite("test:source_three", "test:target_one", "room") + ) def test_config_parse(self): config = { "default": False, "domain_mapping": { "source_one": ["target_one", "target_two"], - "source_two": ["target_two"] - } + "source_two": ["target_two"], + }, } self.assertEquals(config, DomainRuleChecker.parse_config(config)) @@ -95,7 +103,7 @@ class DomainRuleCheckerTestCase(unittest.TestCase): config = { "domain_mapping": { "source_one": ["target_one", "target_two"], - "source_two": ["target_two"] + "source_two": ["target_two"], } } self.assertRaises(ConfigError, DomainRuleChecker.parse_config, config) -- cgit 1.5.1 From feae38757603828839670a918f50938a4a4eb2a6 Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Mon, 18 Mar 2019 15:44:37 +0000 Subject: Don't spam check actions by admins --- synapse/handlers/room.py | 25 +++++++++++++++++++++++-- synapse/handlers/room_member.py | 30 ++++++++++++++++++++---------- 2 files changed, 43 insertions(+), 12 deletions(-) diff --git a/synapse/handlers/room.py b/synapse/handlers/room.py index 2d42a41134..581cff9526 100644 --- a/synapse/handlers/room.py +++ b/synapse/handlers/room.py @@ -81,6 +81,8 @@ class RoomCreationHandler(BaseHandler): # linearizer to stop two upgrades happening at once self._upgrade_linearizer = Linearizer("room_upgrade_linearizer") + self._server_notices_mxid = hs.config.server_notices_mxid + @defer.inlineCallbacks def upgrade_room(self, requester, old_room_id, new_version): """Replace a room with a new room with a different version @@ -254,7 +256,17 @@ class RoomCreationHandler(BaseHandler): """ user_id = requester.user.to_string() - if not self.spam_checker.user_may_create_room( + if (self._server_notices_mxid is not None and + requester.user.to_string() == self._server_notices_mxid): + # allow the server notices mxid to create rooms + is_requester_admin = True + + else: + is_requester_admin = yield self.auth.is_server_admin( + requester.user, + ) + + if not is_requester_admin and not self.spam_checker.user_may_create_room( user_id, invite_list=[], cloning=True, @@ -481,7 +493,16 @@ class RoomCreationHandler(BaseHandler): invite_list = config.get("invite", []) - if not self.spam_checker.user_may_create_room( + if (self._server_notices_mxid is not None and + requester.user.to_string() == self._server_notices_mxid): + # allow the server notices mxid to create rooms + is_requester_admin = True + else: + is_requester_admin = yield self.auth.is_server_admin( + requester.user, + ) + + if not is_requester_admin and not self.spam_checker.user_may_create_room( user_id, invite_list=invite_list, cloning=False, diff --git a/synapse/handlers/room_member.py b/synapse/handlers/room_member.py index cc673e940a..dcf30327cd 100644 --- a/synapse/handlers/room_member.py +++ b/synapse/handlers/room_member.py @@ -487,18 +487,28 @@ class RoomMemberHandler(object): # so don't really fit into the general auth process. raise AuthError(403, "Guest access not allowed") - inviter = yield self._get_inviter(target.to_string(), room_id) - # We assume that if the spam checker allowed the user to create - # a room then they're allowed to join it. - if not new_room and not self.spam_checker.user_may_join_room( - target.to_string(), room_id, - is_invited=inviter is not None, - new_room=new_room, - ): - raise SynapseError( - 403, "Not allowed to join this room", + if (self._server_notices_mxid is not None and + requester.user.to_string() == self._server_notices_mxid): + # allow the server notices mxid to join rooms + is_requester_admin = True + + else: + is_requester_admin = yield self.auth.is_server_admin( + requester.user, ) + inviter = yield self._get_inviter(target.to_string(), room_id) + if not is_requester_admin: + # We assume that if the spam checker allowed the user to create + # a room then they're allowed to join it. + if not new_room and not self.spam_checker.user_may_join_room( + target.to_string(), room_id, + is_invited=inviter is not None, + ): + raise SynapseError( + 403, "Not allowed to join this room", + ) + if not is_host_in_room: if inviter and not self.hs.is_mine(inviter): remote_room_hosts.append(inviter.domain) -- cgit 1.5.1 From 68a9d1fc34beead269f715538298bef1114569b3 Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Mon, 18 Mar 2019 15:45:27 +0000 Subject: Add rules to DomainRuleChecker --- synapse/rulecheck/domain_rule_checker.py | 37 ++++++++++++++++++++++++++++++-- tests/rulecheck/test_domainrulecheck.py | 18 ++++++++-------- 2 files changed, 44 insertions(+), 11 deletions(-) diff --git a/synapse/rulecheck/domain_rule_checker.py b/synapse/rulecheck/domain_rule_checker.py index 9addfd1c1c..410757041b 100644 --- a/synapse/rulecheck/domain_rule_checker.py +++ b/synapse/rulecheck/domain_rule_checker.py @@ -34,7 +34,17 @@ class DomainRuleChecker(object): "inviter_domain": [ "invitee_domain_permitted", "other_domain_permitted" ] "other_inviter_domain": [ "invitee_domain_permitted" ] default: False - } + + # Only let local users join rooms if they were explicitly invited. + can_only_join_rooms_with_invite: false + + # Only let local users create rooms if they are inviting only one + # other user, and that user matches the rules above. + can_only_create_one_to_one_rooms: false + + # Only let local users invite during room creation, regardless of the + # domain mapping rules above. + can_only_invite_during_room_creation: false Don't forget to consider if you can invite users from your own domain. """ @@ -43,6 +53,16 @@ class DomainRuleChecker(object): self.domain_mapping = config["domain_mapping"] or {} self.default = config["default"] + self.can_only_join_rooms_with_invite = config.get( + "can_only_join_rooms_with_invite", False, + ) + self.can_only_create_one_to_one_rooms = config.get( + "can_only_create_one_to_one_rooms", False, + ) + self.can_only_invite_during_room_creation = config.get( + "can_only_invite_during_room_creation", False, + ) + def check_event_for_spam(self, event): """Implements synapse.events.SpamChecker.check_event_for_spam """ @@ -52,6 +72,9 @@ class DomainRuleChecker(object): new_room): """Implements synapse.events.SpamChecker.user_may_invite """ + if self.can_only_invite_during_room_creation and not new_room: + return False + inviter_domain = self._get_domain_from_id(inviter_userid) invitee_domain = self._get_domain_from_id(invitee_userid) @@ -63,6 +86,13 @@ class DomainRuleChecker(object): def user_may_create_room(self, userid, invite_list, cloning): """Implements synapse.events.SpamChecker.user_may_create_room """ + + if cloning: + return True + + if self.can_only_create_one_to_one_rooms and len(invite_list) != 1: + return False + return True def user_may_create_room_alias(self, userid, room_alias): @@ -75,9 +105,12 @@ class DomainRuleChecker(object): """ return True - def user_may_join_room(self, userid, room_id, is_invited, new_room): + def user_may_join_room(self, userid, room_id, is_invited): """Implements synapse.events.SpamChecker.user_may_join_room """ + if self.can_only_join_rooms_with_invite and not is_invited: + return False + return True @staticmethod diff --git a/tests/rulecheck/test_domainrulecheck.py b/tests/rulecheck/test_domainrulecheck.py index ebcf5ca44e..055fd49915 100644 --- a/tests/rulecheck/test_domainrulecheck.py +++ b/tests/rulecheck/test_domainrulecheck.py @@ -31,13 +31,13 @@ class DomainRuleCheckerTestCase(unittest.TestCase): } check = DomainRuleChecker(config) self.assertTrue( - check.user_may_invite("test:source_one", "test:target_one", "room") + check.user_may_invite("test:source_one", "test:target_one", "room", False) ) self.assertTrue( - check.user_may_invite("test:source_one", "test:target_two", "room") + check.user_may_invite("test:source_one", "test:target_two", "room", False) ) self.assertTrue( - check.user_may_invite("test:source_two", "test:target_two", "room") + check.user_may_invite("test:source_two", "test:target_two", "room", False) ) def test_disallowed(self): @@ -51,16 +51,16 @@ class DomainRuleCheckerTestCase(unittest.TestCase): } check = DomainRuleChecker(config) self.assertFalse( - check.user_may_invite("test:source_one", "test:target_three", "room") + check.user_may_invite("test:source_one", "test:target_three", "room", False) ) self.assertFalse( - check.user_may_invite("test:source_two", "test:target_three", "room") + check.user_may_invite("test:source_two", "test:target_three", "room", False) ) self.assertFalse( - check.user_may_invite("test:source_two", "test:target_one", "room") + check.user_may_invite("test:source_two", "test:target_one", "room", False) ) self.assertFalse( - check.user_may_invite("test:source_four", "test:target_one", "room") + check.user_may_invite("test:source_four", "test:target_one", "room", False) ) def test_default_allow(self): @@ -73,7 +73,7 @@ class DomainRuleCheckerTestCase(unittest.TestCase): } check = DomainRuleChecker(config) self.assertTrue( - check.user_may_invite("test:source_three", "test:target_one", "room") + check.user_may_invite("test:source_three", "test:target_one", "room", False) ) def test_default_deny(self): @@ -86,7 +86,7 @@ class DomainRuleCheckerTestCase(unittest.TestCase): } check = DomainRuleChecker(config) self.assertFalse( - check.user_may_invite("test:source_three", "test:target_one", "room") + check.user_may_invite("test:source_three", "test:target_one", "room", False) ) def test_config_parse(self): -- cgit 1.5.1 From ea89e73ebf35677a15ca1e300e49fce58e5182dd Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Mon, 18 Mar 2019 15:41:38 +0000 Subject: Add unit tests --- tests/rulecheck/test_domainrulecheck.py | 128 ++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/tests/rulecheck/test_domainrulecheck.py b/tests/rulecheck/test_domainrulecheck.py index 055fd49915..de89f95e3c 100644 --- a/tests/rulecheck/test_domainrulecheck.py +++ b/tests/rulecheck/test_domainrulecheck.py @@ -14,10 +14,14 @@ # limitations under the License. +import json + from synapse.config._base import ConfigError +from synapse.rest.client.v1 import admin, login, room from synapse.rulecheck.domain_rule_checker import DomainRuleChecker from tests import unittest +from tests.server import make_request, render class DomainRuleCheckerTestCase(unittest.TestCase): @@ -107,3 +111,127 @@ class DomainRuleCheckerTestCase(unittest.TestCase): } } self.assertRaises(ConfigError, DomainRuleChecker.parse_config, config) + + +class DomainRuleCheckerRoomTestCase(unittest.HomeserverTestCase): + servlets = [ + admin.register_servlets, + room.register_servlets, + login.register_servlets, + ] + + hijack_auth = False + + def make_homeserver(self, reactor, clock): + config = self.default_config() + + config.spam_checker = (DomainRuleChecker, { + "default": True, + "domain_mapping": {}, + "can_only_join_rooms_with_invite": True, + "can_only_create_one_to_one_rooms": True, + "can_only_invite_during_room_creation": True, + }) + + hs = self.setup_test_homeserver(config=config) + return hs + + def prepare(self, reactor, clock, hs): + self.admin_user_id = self.register_user("admin_user", "pass", admin=True) + self.admin_access_token = self.login("admin_user", "pass") + + self.normal_user_id = self.register_user("normal_user", "pass", admin=False) + self.normal_access_token = self.login("normal_user", "pass") + + self.other_user_id = self.register_user("other_user", "pass", admin=False) + + def test_admin_can_create_room(self): + channel = self._create_room(self.admin_access_token) + assert channel.result["code"] == b"200", channel.result + + def test_normal_user_cannot_create_empty_room(self): + channel = self._create_room(self.normal_access_token) + assert channel.result["code"] == b"403", channel.result + + def test_normal_user_cannot_create_room_with_multiple_invites(self): + channel = self._create_room(self.normal_access_token, content={ + "invite": [self.other_user_id, self.admin_user_id], + }) + assert channel.result["code"] == b"403", channel.result + + def test_normal_user_can_room_with_single_invites(self): + channel = self._create_room(self.normal_access_token, content={ + "invite": [self.other_user_id], + }) + assert channel.result["code"] == b"200", channel.result + + def test_cannot_join_public_room(self): + channel = self._create_room(self.admin_access_token) + assert channel.result["code"] == b"200", channel.result + + room_id = channel.json_body["room_id"] + + self.helper.join( + room_id, self.normal_user_id, + tok=self.normal_access_token, + expect_code=403, + ) + + def test_can_join_invited_room(self): + channel = self._create_room(self.admin_access_token) + assert channel.result["code"] == b"200", channel.result + + room_id = channel.json_body["room_id"] + + self.helper.invite( + room_id, + src=self.admin_user_id, + targ=self.normal_user_id, + tok=self.admin_access_token, + ) + + self.helper.join( + room_id, self.normal_user_id, + tok=self.normal_access_token, + expect_code=200, + ) + + def test_cannot_invite(self): + channel = self._create_room(self.admin_access_token) + assert channel.result["code"] == b"200", channel.result + + room_id = channel.json_body["room_id"] + + self.helper.invite( + room_id, + src=self.admin_user_id, + targ=self.normal_user_id, + tok=self.admin_access_token, + ) + + self.helper.join( + room_id, self.normal_user_id, + tok=self.normal_access_token, + expect_code=200, + ) + + self.helper.invite( + room_id, + src=self.normal_user_id, + targ=self.other_user_id, + tok=self.normal_access_token, + expect_code=403, + ) + + def _create_room(self, token, content={}): + path = "/_matrix/client/r0/createRoom?access_token=%s" % ( + token, + ) + + request, channel = make_request( + self.hs.get_reactor(), "POST", path, + content=json.dumps(content).encode("utf8"), + ) + render(request, self.resource, self.hs.get_reactor()) + + return channel -- cgit 1.5.1