diff --git a/tests/rest/client/v1/test_events.py b/tests/rest/client/v1/test_events.py
index f340b7e851..ffb2de1505 100644
--- a/tests/rest/client/v1/test_events.py
+++ b/tests/rest/client/v1/test_events.py
@@ -134,3 +134,30 @@ class EventStreamPermissionsTestCase(unittest.HomeserverTestCase):
# someone else set topic, expect 6 (join,send,topic,join,send,topic)
pass
+
+
+class GetEventsTestCase(unittest.HomeserverTestCase):
+ servlets = [
+ events.register_servlets,
+ room.register_servlets,
+ synapse.rest.admin.register_servlets_for_client_rest_resource,
+ login.register_servlets,
+ ]
+
+ def prepare(self, hs, reactor, clock):
+
+ # register an account
+ self.user_id = self.register_user("sid1", "pass")
+ self.token = self.login(self.user_id, "pass")
+
+ self.room_id = self.helper.create_room_as(self.user_id, tok=self.token)
+
+ def test_get_event_via_events(self):
+ resp = self.helper.send(self.room_id, tok=self.token)
+ event_id = resp["event_id"]
+
+ request, channel = self.make_request(
+ "GET", "/events/" + event_id, access_token=self.token,
+ )
+ self.render(request)
+ self.assertEquals(channel.code, 200, msg=channel.result)
diff --git a/tests/rest/client/v1/test_login.py b/tests/rest/client/v1/test_login.py
index 0397f91a9e..da2c9bfa1e 100644
--- a/tests/rest/client/v1/test_login.py
+++ b/tests/rest/client/v1/test_login.py
@@ -1,11 +1,18 @@
import json
+import urllib.parse
+
+from mock import Mock
import synapse.rest.admin
from synapse.rest.client.v1 import login
+from synapse.rest.client.v2_alpha import devices
+from synapse.rest.client.v2_alpha.account import WhoamiRestServlet
from tests import unittest
+from tests.unittest import override_config
LOGIN_URL = b"/_matrix/client/r0/login"
+TEST_URL = b"/_matrix/client/r0/account/whoami"
class LoginRestServletTestCase(unittest.HomeserverTestCase):
@@ -13,6 +20,8 @@ class LoginRestServletTestCase(unittest.HomeserverTestCase):
servlets = [
synapse.rest.admin.register_servlets_for_client_rest_resource,
login.register_servlets,
+ devices.register_servlets,
+ lambda hs, http_server: WhoamiRestServlet(hs).register(http_server),
]
def make_homeserver(self, reactor, clock):
@@ -144,3 +153,213 @@ class LoginRestServletTestCase(unittest.HomeserverTestCase):
self.render(request)
self.assertEquals(channel.result["code"], b"403", channel.result)
+
+ @override_config({"session_lifetime": "24h"})
+ def test_soft_logout(self):
+ self.register_user("kermit", "monkey")
+
+ # we shouldn't be able to make requests without an access token
+ request, channel = self.make_request(b"GET", TEST_URL)
+ self.render(request)
+ self.assertEquals(channel.result["code"], b"401", channel.result)
+ self.assertEquals(channel.json_body["errcode"], "M_MISSING_TOKEN")
+
+ # log in as normal
+ params = {
+ "type": "m.login.password",
+ "identifier": {"type": "m.id.user", "user": "kermit"},
+ "password": "monkey",
+ }
+ request, channel = self.make_request(b"POST", LOGIN_URL, params)
+ self.render(request)
+
+ self.assertEquals(channel.code, 200, channel.result)
+ access_token = channel.json_body["access_token"]
+ device_id = channel.json_body["device_id"]
+
+ # we should now be able to make requests with the access token
+ request, channel = self.make_request(
+ b"GET", TEST_URL, access_token=access_token
+ )
+ self.render(request)
+ self.assertEquals(channel.code, 200, channel.result)
+
+ # time passes
+ self.reactor.advance(24 * 3600)
+
+ # ... and we should be soft-logouted
+ request, channel = self.make_request(
+ b"GET", TEST_URL, access_token=access_token
+ )
+ self.render(request)
+ self.assertEquals(channel.code, 401, channel.result)
+ self.assertEquals(channel.json_body["errcode"], "M_UNKNOWN_TOKEN")
+ self.assertEquals(channel.json_body["soft_logout"], True)
+
+ #
+ # test behaviour after deleting the expired device
+ #
+
+ # we now log in as a different device
+ access_token_2 = self.login("kermit", "monkey")
+
+ # more requests with the expired token should still return a soft-logout
+ self.reactor.advance(3600)
+ request, channel = self.make_request(
+ b"GET", TEST_URL, access_token=access_token
+ )
+ self.render(request)
+ self.assertEquals(channel.code, 401, channel.result)
+ self.assertEquals(channel.json_body["errcode"], "M_UNKNOWN_TOKEN")
+ self.assertEquals(channel.json_body["soft_logout"], True)
+
+ # ... but if we delete that device, it will be a proper logout
+ self._delete_device(access_token_2, "kermit", "monkey", device_id)
+
+ request, channel = self.make_request(
+ b"GET", TEST_URL, access_token=access_token
+ )
+ self.render(request)
+ self.assertEquals(channel.code, 401, channel.result)
+ self.assertEquals(channel.json_body["errcode"], "M_UNKNOWN_TOKEN")
+ self.assertEquals(channel.json_body["soft_logout"], False)
+
+ def _delete_device(self, access_token, user_id, password, device_id):
+ """Perform the UI-Auth to delete a device"""
+ request, channel = self.make_request(
+ b"DELETE", "devices/" + device_id, access_token=access_token
+ )
+ self.render(request)
+ self.assertEquals(channel.code, 401, channel.result)
+ # check it's a UI-Auth fail
+ self.assertEqual(
+ set(channel.json_body.keys()),
+ {"flows", "params", "session"},
+ channel.result,
+ )
+
+ auth = {
+ "type": "m.login.password",
+ # https://github.com/matrix-org/synapse/issues/5665
+ # "identifier": {"type": "m.id.user", "user": user_id},
+ "user": user_id,
+ "password": password,
+ "session": channel.json_body["session"],
+ }
+
+ request, channel = self.make_request(
+ b"DELETE",
+ "devices/" + device_id,
+ access_token=access_token,
+ content={"auth": auth},
+ )
+ self.render(request)
+ self.assertEquals(channel.code, 200, channel.result)
+
+
+class CASRedirectConfirmTestCase(unittest.HomeserverTestCase):
+
+ servlets = [
+ login.register_servlets,
+ ]
+
+ def make_homeserver(self, reactor, clock):
+ self.base_url = "https://matrix.goodserver.com/"
+ self.redirect_path = "_synapse/client/login/sso/redirect/confirm"
+
+ config = self.default_config()
+ config["cas_config"] = {
+ "enabled": True,
+ "server_url": "https://fake.test",
+ "service_url": "https://matrix.goodserver.com:8448",
+ }
+
+ async def get_raw(uri, args):
+ """Return an example response payload from a call to the `/proxyValidate`
+ endpoint of a CAS server, copied from
+ https://apereo.github.io/cas/5.0.x/protocol/CAS-Protocol-V2-Specification.html#26-proxyvalidate-cas-20
+
+ This needs to be returned by an async function (as opposed to set as the
+ mock's return value) because the corresponding Synapse code awaits on it.
+ """
+ return """
+ <cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
+ <cas:authenticationSuccess>
+ <cas:user>username</cas:user>
+ <cas:proxyGrantingTicket>PGTIOU-84678-8a9d...</cas:proxyGrantingTicket>
+ <cas:proxies>
+ <cas:proxy>https://proxy2/pgtUrl</cas:proxy>
+ <cas:proxy>https://proxy1/pgtUrl</cas:proxy>
+ </cas:proxies>
+ </cas:authenticationSuccess>
+ </cas:serviceResponse>
+ """
+
+ mocked_http_client = Mock(spec=["get_raw"])
+ mocked_http_client.get_raw.side_effect = get_raw
+
+ self.hs = self.setup_test_homeserver(
+ config=config, proxied_http_client=mocked_http_client,
+ )
+
+ return self.hs
+
+ def test_cas_redirect_confirm(self):
+ """Tests that the SSO login flow serves a confirmation page before redirecting a
+ user to the redirect URL.
+ """
+ base_url = "/_matrix/client/r0/login/cas/ticket?redirectUrl"
+ redirect_url = "https://dodgy-site.com/"
+
+ url_parts = list(urllib.parse.urlparse(base_url))
+ query = dict(urllib.parse.parse_qsl(url_parts[4]))
+ query.update({"redirectUrl": redirect_url})
+ query.update({"ticket": "ticket"})
+ url_parts[4] = urllib.parse.urlencode(query)
+ cas_ticket_url = urllib.parse.urlunparse(url_parts)
+
+ # Get Synapse to call the fake CAS and serve the template.
+ request, channel = self.make_request("GET", cas_ticket_url)
+ self.render(request)
+
+ # Test that the response is HTML.
+ self.assertEqual(channel.code, 200)
+ content_type_header_value = ""
+ for header in channel.result.get("headers", []):
+ if header[0] == b"Content-Type":
+ content_type_header_value = header[1].decode("utf8")
+
+ self.assertTrue(content_type_header_value.startswith("text/html"))
+
+ # Test that the body isn't empty.
+ self.assertTrue(len(channel.result["body"]) > 0)
+
+ # And that it contains our redirect link
+ self.assertIn(redirect_url, channel.result["body"].decode("UTF-8"))
+
+ @override_config(
+ {
+ "sso": {
+ "client_whitelist": [
+ "https://legit-site.com/",
+ "https://other-site.com/",
+ ]
+ }
+ }
+ )
+ def test_cas_redirect_whitelisted(self):
+ """Tests that the SSO login flow serves a redirect to a whitelisted url
+ """
+ redirect_url = "https://legit-site.com/"
+ cas_ticket_url = (
+ "/_matrix/client/r0/login/cas/ticket?redirectUrl=%s&ticket=ticket"
+ % (urllib.parse.quote(redirect_url))
+ )
+
+ # Get Synapse to call the fake CAS and serve the template.
+ request, channel = self.make_request("GET", cas_ticket_url)
+ self.render(request)
+
+ self.assertEqual(channel.code, 302)
+ location_headers = channel.headers.getRawHeaders("Location")
+ self.assertEqual(location_headers[0][: len(redirect_url)], redirect_url)
diff --git a/tests/rest/client/v1/test_presence.py b/tests/rest/client/v1/test_presence.py
index 66c2b68707..0fdff79aa7 100644
--- a/tests/rest/client/v1/test_presence.py
+++ b/tests/rest/client/v1/test_presence.py
@@ -15,6 +15,8 @@
from mock import Mock
+from twisted.internet import defer
+
from synapse.rest.client.v1 import presence
from synapse.types import UserID
@@ -36,6 +38,7 @@ class PresenceTestCase(unittest.HomeserverTestCase):
)
hs.presence_handler = Mock()
+ hs.presence_handler.set_state.return_value = defer.succeed(None)
return hs
diff --git a/tests/rest/client/v1/test_profile.py b/tests/rest/client/v1/test_profile.py
index 6958430608..8df58b4a63 100644
--- a/tests/rest/client/v1/test_profile.py
+++ b/tests/rest/client/v1/test_profile.py
@@ -52,6 +52,14 @@ class MockHandlerProfileTestCase(unittest.TestCase):
]
)
+ self.mock_handler.get_displayname.return_value = defer.succeed(Mock())
+ self.mock_handler.set_displayname.return_value = defer.succeed(Mock())
+ self.mock_handler.get_avatar_url.return_value = defer.succeed(Mock())
+ self.mock_handler.set_avatar_url.return_value = defer.succeed(Mock())
+ self.mock_handler.check_profile_query_allowed.return_value = defer.succeed(
+ Mock()
+ )
+
hs = yield setup_test_homeserver(
self.addCleanup,
"test",
@@ -63,7 +71,7 @@ class MockHandlerProfileTestCase(unittest.TestCase):
)
def _get_user_by_req(request=None, allow_guest=False):
- return synapse.types.create_requester(myid)
+ return defer.succeed(synapse.types.create_requester(myid))
hs.get_auth().get_user_by_req = _get_user_by_req
@@ -183,7 +191,7 @@ class ProfileTestCase(unittest.HomeserverTestCase):
def test_set_displayname(self):
request, channel = self.make_request(
"PUT",
- "/profile/%s/displayname" % (self.owner, ),
+ "/profile/%s/displayname" % (self.owner,),
content=json.dumps({"displayname": "test"}),
access_token=self.owner_tok,
)
@@ -197,7 +205,7 @@ class ProfileTestCase(unittest.HomeserverTestCase):
"""Attempts to set a stupid displayname should get a 400"""
request, channel = self.make_request(
"PUT",
- "/profile/%s/displayname" % (self.owner, ),
+ "/profile/%s/displayname" % (self.owner,),
content=json.dumps({"displayname": "test" * 100}),
access_token=self.owner_tok,
)
@@ -209,8 +217,7 @@ class ProfileTestCase(unittest.HomeserverTestCase):
def get_displayname(self):
request, channel = self.make_request(
- "GET",
- "/profile/%s/displayname" % (self.owner, ),
+ "GET", "/profile/%s/displayname" % (self.owner,)
)
self.render(request)
self.assertEqual(channel.code, 200, channel.result)
@@ -230,7 +237,7 @@ class ProfilesRestrictedTestCase(unittest.HomeserverTestCase):
config = self.default_config()
config["require_auth_for_profile_requests"] = True
- config["limit_profile_requests_to_known_users"] = True
+ config["limit_profile_requests_to_users_who_share_rooms"] = True
self.hs = self.setup_test_homeserver(config=config)
return self.hs
@@ -303,6 +310,7 @@ class OwnProfileUnrestrictedTestCase(unittest.HomeserverTestCase):
def make_homeserver(self, reactor, clock):
config = self.default_config()
config["require_auth_for_profile_requests"] = True
+ config["limit_profile_requests_to_users_who_share_rooms"] = True
self.hs = self.setup_test_homeserver(config=config)
return self.hs
diff --git a/tests/rest/client/v1/test_rooms.py b/tests/rest/client/v1/test_rooms.py
index 2d64b338be..7dd86d0c27 100644
--- a/tests/rest/client/v1/test_rooms.py
+++ b/tests/rest/client/v1/test_rooms.py
@@ -1,5 +1,7 @@
# -*- coding: utf-8 -*-
# Copyright 2014-2016 OpenMarket Ltd
+# Copyright 2017 Vector Creations Ltd
+# Copyright 2018-2019 New Vector Ltd
# Copyright 2019 The Matrix.org Foundation C.I.C.
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -24,8 +26,12 @@ from six.moves.urllib import parse as urlparse
from twisted.internet import defer
import synapse.rest.admin
-from synapse.api.constants import Membership
-from synapse.rest.client.v1 import login, profile, room
+from synapse.api.constants import EventContentFields, EventTypes, Membership
+from synapse.handlers.pagination import PurgeStatus
+from synapse.rest.client.v1 import directory, login, profile, room
+from synapse.rest.client.v2_alpha import account
+from synapse.types import JsonDict, RoomAlias
+from synapse.util.stringutils import random_string
from tests import unittest
@@ -79,7 +85,7 @@ class RoomPermissionsTestCase(RoomBase):
# send a message in one of the rooms
self.created_rmid_msg_path = (
"rooms/%s/send/m.room.message/a1" % (self.created_rmid)
- ).encode('ascii')
+ ).encode("ascii")
request, channel = self.make_request(
"PUT", self.created_rmid_msg_path, b'{"msgtype":"m.text","body":"test msg"}'
)
@@ -89,7 +95,7 @@ class RoomPermissionsTestCase(RoomBase):
# set topic for public room
request, channel = self.make_request(
"PUT",
- ("rooms/%s/state/m.room.topic" % self.created_public_rmid).encode('ascii'),
+ ("rooms/%s/state/m.room.topic" % self.created_public_rmid).encode("ascii"),
b'{"topic":"Public Room Topic"}',
)
self.render(request)
@@ -193,7 +199,7 @@ class RoomPermissionsTestCase(RoomBase):
request, channel = self.make_request("GET", topic_path)
self.render(request)
self.assertEquals(200, channel.code, msg=channel.result["body"])
- self.assert_dict(json.loads(topic_content.decode('utf8')), channel.json_body)
+ self.assert_dict(json.loads(topic_content.decode("utf8")), channel.json_body)
# set/get topic in created PRIVATE room and left, expect 403
self.helper.leave(room=self.created_rmid, user=self.user_id)
@@ -484,6 +490,15 @@ class RoomsCreateTestCase(RoomBase):
self.render(request)
self.assertEquals(400, channel.code)
+ def test_post_room_invitees_invalid_mxid(self):
+ # POST with invalid invitee, see https://github.com/matrix-org/synapse/issues/4088
+ # Note the trailing space in the MXID here!
+ request, channel = self.make_request(
+ "POST", "/createRoom", b'{"invite":["@alice:example.com "]}'
+ )
+ self.render(request)
+ self.assertEquals(400, channel.code)
+
class RoomTopicTestCase(RoomBase):
""" Tests /rooms/$room_id/topic REST events. """
@@ -497,7 +512,7 @@ class RoomTopicTestCase(RoomBase):
def test_invalid_puts(self):
# missing keys or invalid json
- request, channel = self.make_request("PUT", self.path, '{}')
+ request, channel = self.make_request("PUT", self.path, "{}")
self.render(request)
self.assertEquals(400, channel.code, msg=channel.result["body"])
@@ -515,11 +530,11 @@ class RoomTopicTestCase(RoomBase):
self.render(request)
self.assertEquals(400, channel.code, msg=channel.result["body"])
- request, channel = self.make_request("PUT", self.path, 'text only')
+ request, channel = self.make_request("PUT", self.path, "text only")
self.render(request)
self.assertEquals(400, channel.code, msg=channel.result["body"])
- request, channel = self.make_request("PUT", self.path, '')
+ request, channel = self.make_request("PUT", self.path, "")
self.render(request)
self.assertEquals(400, channel.code, msg=channel.result["body"])
@@ -572,7 +587,7 @@ class RoomMemberStateTestCase(RoomBase):
def test_invalid_puts(self):
path = "/rooms/%s/state/m.room.member/%s" % (self.room_id, self.user_id)
# missing keys or invalid json
- request, channel = self.make_request("PUT", path, '{}')
+ request, channel = self.make_request("PUT", path, "{}")
self.render(request)
self.assertEquals(400, channel.code, msg=channel.result["body"])
@@ -590,11 +605,11 @@ class RoomMemberStateTestCase(RoomBase):
self.render(request)
self.assertEquals(400, channel.code, msg=channel.result["body"])
- request, channel = self.make_request("PUT", path, 'text only')
+ request, channel = self.make_request("PUT", path, "text only")
self.render(request)
self.assertEquals(400, channel.code, msg=channel.result["body"])
- request, channel = self.make_request("PUT", path, '')
+ request, channel = self.make_request("PUT", path, "")
self.render(request)
self.assertEquals(400, channel.code, msg=channel.result["body"])
@@ -604,7 +619,7 @@ class RoomMemberStateTestCase(RoomBase):
Membership.JOIN,
Membership.LEAVE,
)
- request, channel = self.make_request("PUT", path, content.encode('ascii'))
+ request, channel = self.make_request("PUT", path, content.encode("ascii"))
self.render(request)
self.assertEquals(400, channel.code, msg=channel.result["body"])
@@ -616,7 +631,7 @@ class RoomMemberStateTestCase(RoomBase):
# valid join message (NOOP since we made the room)
content = '{"membership":"%s"}' % Membership.JOIN
- request, channel = self.make_request("PUT", path, content.encode('ascii'))
+ request, channel = self.make_request("PUT", path, content.encode("ascii"))
self.render(request)
self.assertEquals(200, channel.code, msg=channel.result["body"])
@@ -678,7 +693,7 @@ class RoomMessagesTestCase(RoomBase):
def test_invalid_puts(self):
path = "/rooms/%s/send/m.room.message/mid1" % (urlparse.quote(self.room_id))
# missing keys or invalid json
- request, channel = self.make_request("PUT", path, b'{}')
+ request, channel = self.make_request("PUT", path, b"{}")
self.render(request)
self.assertEquals(400, channel.code, msg=channel.result["body"])
@@ -696,11 +711,11 @@ class RoomMessagesTestCase(RoomBase):
self.render(request)
self.assertEquals(400, channel.code, msg=channel.result["body"])
- request, channel = self.make_request("PUT", path, b'text only')
+ request, channel = self.make_request("PUT", path, b"text only")
self.render(request)
self.assertEquals(400, channel.code, msg=channel.result["body"])
- request, channel = self.make_request("PUT", path, b'')
+ request, channel = self.make_request("PUT", path, b"")
self.render(request)
self.assertEquals(400, channel.code, msg=channel.result["body"])
@@ -786,7 +801,7 @@ class RoomMessageListTestCase(RoomBase):
self.render(request)
self.assertEquals(200, channel.code)
self.assertTrue("start" in channel.json_body)
- self.assertEquals(token, channel.json_body['start'])
+ self.assertEquals(token, channel.json_body["start"])
self.assertTrue("chunk" in channel.json_body)
self.assertTrue("end" in channel.json_body)
@@ -798,10 +813,82 @@ class RoomMessageListTestCase(RoomBase):
self.render(request)
self.assertEquals(200, channel.code)
self.assertTrue("start" in channel.json_body)
- self.assertEquals(token, channel.json_body['start'])
+ self.assertEquals(token, channel.json_body["start"])
self.assertTrue("chunk" in channel.json_body)
self.assertTrue("end" in channel.json_body)
+ def test_room_messages_purge(self):
+ store = self.hs.get_datastore()
+ pagination_handler = self.hs.get_pagination_handler()
+
+ # Send a first message in the room, which will be removed by the purge.
+ first_event_id = self.helper.send(self.room_id, "message 1")["event_id"]
+ first_token = self.get_success(
+ store.get_topological_token_for_event(first_event_id)
+ )
+
+ # Send a second message in the room, which won't be removed, and which we'll
+ # use as the marker to purge events before.
+ second_event_id = self.helper.send(self.room_id, "message 2")["event_id"]
+ second_token = self.get_success(
+ store.get_topological_token_for_event(second_event_id)
+ )
+
+ # Send a third event in the room to ensure we don't fall under any edge case
+ # due to our marker being the latest forward extremity in the room.
+ self.helper.send(self.room_id, "message 3")
+
+ # Check that we get the first and second message when querying /messages.
+ request, channel = self.make_request(
+ "GET",
+ "/rooms/%s/messages?access_token=x&from=%s&dir=b&filter=%s"
+ % (self.room_id, second_token, json.dumps({"types": [EventTypes.Message]})),
+ )
+ self.render(request)
+ self.assertEqual(channel.code, 200, channel.json_body)
+
+ chunk = channel.json_body["chunk"]
+ self.assertEqual(len(chunk), 2, [event["content"] for event in chunk])
+
+ # Purge every event before the second event.
+ purge_id = random_string(16)
+ pagination_handler._purges_by_id[purge_id] = PurgeStatus()
+ self.get_success(
+ pagination_handler._purge_history(
+ purge_id=purge_id,
+ room_id=self.room_id,
+ token=second_token,
+ delete_local_events=True,
+ )
+ )
+
+ # Check that we only get the second message through /message now that the first
+ # has been purged.
+ request, channel = self.make_request(
+ "GET",
+ "/rooms/%s/messages?access_token=x&from=%s&dir=b&filter=%s"
+ % (self.room_id, second_token, json.dumps({"types": [EventTypes.Message]})),
+ )
+ self.render(request)
+ self.assertEqual(channel.code, 200, channel.json_body)
+
+ chunk = channel.json_body["chunk"]
+ self.assertEqual(len(chunk), 1, [event["content"] for event in chunk])
+
+ # Check that we get no event, but also no error, when querying /messages with
+ # the token that was pointing at the first event, because we don't have it
+ # anymore.
+ request, channel = self.make_request(
+ "GET",
+ "/rooms/%s/messages?access_token=x&from=%s&dir=b&filter=%s"
+ % (self.room_id, first_token, json.dumps({"types": [EventTypes.Message]})),
+ )
+ self.render(request)
+ self.assertEqual(channel.code, 200, channel.json_body)
+
+ chunk = channel.json_body["chunk"]
+ self.assertEqual(len(chunk), 0, [event["content"] for event in chunk])
+
class RoomSearchTestCase(unittest.HomeserverTestCase):
servlets = [
@@ -961,9 +1048,7 @@ class PerRoomProfilesForbiddenTestCase(unittest.HomeserverTestCase):
# Set a profile for the test user
self.displayname = "test user"
- data = {
- "displayname": self.displayname,
- }
+ data = {"displayname": self.displayname}
request_data = json.dumps(data)
request, channel = self.make_request(
"PUT",
@@ -977,16 +1062,12 @@ class PerRoomProfilesForbiddenTestCase(unittest.HomeserverTestCase):
self.room_id = self.helper.create_room_as(self.user_id, tok=self.tok)
def test_per_room_profile_forbidden(self):
- data = {
- "membership": "join",
- "displayname": "other test user"
- }
+ data = {"membership": "join", "displayname": "other test user"}
request_data = json.dumps(data)
request, channel = self.make_request(
"PUT",
- "/_matrix/client/r0/rooms/%s/state/m.room.member/%s" % (
- self.room_id, self.user_id,
- ),
+ "/_matrix/client/r0/rooms/%s/state/m.room.member/%s"
+ % (self.room_id, self.user_id),
request_data,
access_token=self.tok,
)
@@ -1004,3 +1085,899 @@ class PerRoomProfilesForbiddenTestCase(unittest.HomeserverTestCase):
res_displayname = channel.json_body["content"]["displayname"]
self.assertEqual(res_displayname, self.displayname, channel.result)
+
+
+class RoomMembershipReasonTestCase(unittest.HomeserverTestCase):
+ """Tests that clients can add a "reason" field to membership events and
+ that they get correctly added to the generated events and propagated.
+ """
+
+ servlets = [
+ synapse.rest.admin.register_servlets_for_client_rest_resource,
+ room.register_servlets,
+ login.register_servlets,
+ ]
+
+ def prepare(self, reactor, clock, homeserver):
+ self.creator = self.register_user("creator", "test")
+ self.creator_tok = self.login("creator", "test")
+
+ self.second_user_id = self.register_user("second", "test")
+ self.second_tok = self.login("second", "test")
+
+ self.room_id = self.helper.create_room_as(self.creator, tok=self.creator_tok)
+
+ def test_join_reason(self):
+ reason = "hello"
+ request, channel = self.make_request(
+ "POST",
+ "/_matrix/client/r0/rooms/{}/join".format(self.room_id),
+ content={"reason": reason},
+ access_token=self.second_tok,
+ )
+ self.render(request)
+ self.assertEqual(channel.code, 200, channel.result)
+
+ self._check_for_reason(reason)
+
+ def test_leave_reason(self):
+ self.helper.join(self.room_id, user=self.second_user_id, tok=self.second_tok)
+
+ reason = "hello"
+ request, channel = self.make_request(
+ "POST",
+ "/_matrix/client/r0/rooms/{}/leave".format(self.room_id),
+ content={"reason": reason},
+ access_token=self.second_tok,
+ )
+ self.render(request)
+ self.assertEqual(channel.code, 200, channel.result)
+
+ self._check_for_reason(reason)
+
+ def test_kick_reason(self):
+ self.helper.join(self.room_id, user=self.second_user_id, tok=self.second_tok)
+
+ reason = "hello"
+ request, channel = self.make_request(
+ "POST",
+ "/_matrix/client/r0/rooms/{}/kick".format(self.room_id),
+ content={"reason": reason, "user_id": self.second_user_id},
+ access_token=self.second_tok,
+ )
+ self.render(request)
+ self.assertEqual(channel.code, 200, channel.result)
+
+ self._check_for_reason(reason)
+
+ def test_ban_reason(self):
+ self.helper.join(self.room_id, user=self.second_user_id, tok=self.second_tok)
+
+ reason = "hello"
+ request, channel = self.make_request(
+ "POST",
+ "/_matrix/client/r0/rooms/{}/ban".format(self.room_id),
+ content={"reason": reason, "user_id": self.second_user_id},
+ access_token=self.creator_tok,
+ )
+ self.render(request)
+ self.assertEqual(channel.code, 200, channel.result)
+
+ self._check_for_reason(reason)
+
+ def test_unban_reason(self):
+ reason = "hello"
+ request, channel = self.make_request(
+ "POST",
+ "/_matrix/client/r0/rooms/{}/unban".format(self.room_id),
+ content={"reason": reason, "user_id": self.second_user_id},
+ access_token=self.creator_tok,
+ )
+ self.render(request)
+ self.assertEqual(channel.code, 200, channel.result)
+
+ self._check_for_reason(reason)
+
+ def test_invite_reason(self):
+ reason = "hello"
+ request, channel = self.make_request(
+ "POST",
+ "/_matrix/client/r0/rooms/{}/invite".format(self.room_id),
+ content={"reason": reason, "user_id": self.second_user_id},
+ access_token=self.creator_tok,
+ )
+ self.render(request)
+ self.assertEqual(channel.code, 200, channel.result)
+
+ self._check_for_reason(reason)
+
+ def test_reject_invite_reason(self):
+ self.helper.invite(
+ self.room_id,
+ src=self.creator,
+ targ=self.second_user_id,
+ tok=self.creator_tok,
+ )
+
+ reason = "hello"
+ request, channel = self.make_request(
+ "POST",
+ "/_matrix/client/r0/rooms/{}/leave".format(self.room_id),
+ content={"reason": reason},
+ access_token=self.second_tok,
+ )
+ self.render(request)
+ self.assertEqual(channel.code, 200, channel.result)
+
+ self._check_for_reason(reason)
+
+ def _check_for_reason(self, reason):
+ request, channel = self.make_request(
+ "GET",
+ "/_matrix/client/r0/rooms/{}/state/m.room.member/{}".format(
+ self.room_id, self.second_user_id
+ ),
+ access_token=self.creator_tok,
+ )
+ self.render(request)
+ self.assertEqual(channel.code, 200, channel.result)
+
+ event_content = channel.json_body
+
+ self.assertEqual(event_content.get("reason"), reason, channel.result)
+
+
+class LabelsTestCase(unittest.HomeserverTestCase):
+ servlets = [
+ synapse.rest.admin.register_servlets_for_client_rest_resource,
+ room.register_servlets,
+ login.register_servlets,
+ profile.register_servlets,
+ ]
+
+ # Filter that should only catch messages with the label "#fun".
+ FILTER_LABELS = {
+ "types": [EventTypes.Message],
+ "org.matrix.labels": ["#fun"],
+ }
+ # Filter that should only catch messages without the label "#fun".
+ FILTER_NOT_LABELS = {
+ "types": [EventTypes.Message],
+ "org.matrix.not_labels": ["#fun"],
+ }
+ # Filter that should only catch messages with the label "#work" but without the label
+ # "#notfun".
+ FILTER_LABELS_NOT_LABELS = {
+ "types": [EventTypes.Message],
+ "org.matrix.labels": ["#work"],
+ "org.matrix.not_labels": ["#notfun"],
+ }
+
+ def prepare(self, reactor, clock, homeserver):
+ self.user_id = self.register_user("test", "test")
+ self.tok = self.login("test", "test")
+ self.room_id = self.helper.create_room_as(self.user_id, tok=self.tok)
+
+ def test_context_filter_labels(self):
+ """Test that we can filter by a label on a /context request."""
+ event_id = self._send_labelled_messages_in_room()
+
+ request, channel = self.make_request(
+ "GET",
+ "/rooms/%s/context/%s?filter=%s"
+ % (self.room_id, event_id, json.dumps(self.FILTER_LABELS)),
+ access_token=self.tok,
+ )
+ self.render(request)
+ self.assertEqual(channel.code, 200, channel.result)
+
+ events_before = channel.json_body["events_before"]
+
+ self.assertEqual(
+ len(events_before), 1, [event["content"] for event in events_before]
+ )
+ self.assertEqual(
+ events_before[0]["content"]["body"], "with right label", events_before[0]
+ )
+
+ events_after = channel.json_body["events_before"]
+
+ self.assertEqual(
+ len(events_after), 1, [event["content"] for event in events_after]
+ )
+ self.assertEqual(
+ events_after[0]["content"]["body"], "with right label", events_after[0]
+ )
+
+ def test_context_filter_not_labels(self):
+ """Test that we can filter by the absence of a label on a /context request."""
+ event_id = self._send_labelled_messages_in_room()
+
+ request, channel = self.make_request(
+ "GET",
+ "/rooms/%s/context/%s?filter=%s"
+ % (self.room_id, event_id, json.dumps(self.FILTER_NOT_LABELS)),
+ access_token=self.tok,
+ )
+ self.render(request)
+ self.assertEqual(channel.code, 200, channel.result)
+
+ events_before = channel.json_body["events_before"]
+
+ self.assertEqual(
+ len(events_before), 1, [event["content"] for event in events_before]
+ )
+ self.assertEqual(
+ events_before[0]["content"]["body"], "without label", events_before[0]
+ )
+
+ events_after = channel.json_body["events_after"]
+
+ self.assertEqual(
+ len(events_after), 2, [event["content"] for event in events_after]
+ )
+ self.assertEqual(
+ events_after[0]["content"]["body"], "with wrong label", events_after[0]
+ )
+ self.assertEqual(
+ events_after[1]["content"]["body"], "with two wrong labels", events_after[1]
+ )
+
+ def test_context_filter_labels_not_labels(self):
+ """Test that we can filter by both a label and the absence of another label on a
+ /context request.
+ """
+ event_id = self._send_labelled_messages_in_room()
+
+ request, channel = self.make_request(
+ "GET",
+ "/rooms/%s/context/%s?filter=%s"
+ % (self.room_id, event_id, json.dumps(self.FILTER_LABELS_NOT_LABELS)),
+ access_token=self.tok,
+ )
+ self.render(request)
+ self.assertEqual(channel.code, 200, channel.result)
+
+ events_before = channel.json_body["events_before"]
+
+ self.assertEqual(
+ len(events_before), 0, [event["content"] for event in events_before]
+ )
+
+ events_after = channel.json_body["events_after"]
+
+ self.assertEqual(
+ len(events_after), 1, [event["content"] for event in events_after]
+ )
+ self.assertEqual(
+ events_after[0]["content"]["body"], "with wrong label", events_after[0]
+ )
+
+ def test_messages_filter_labels(self):
+ """Test that we can filter by a label on a /messages request."""
+ self._send_labelled_messages_in_room()
+
+ token = "s0_0_0_0_0_0_0_0_0"
+ request, channel = self.make_request(
+ "GET",
+ "/rooms/%s/messages?access_token=%s&from=%s&filter=%s"
+ % (self.room_id, self.tok, token, json.dumps(self.FILTER_LABELS)),
+ )
+ self.render(request)
+
+ events = channel.json_body["chunk"]
+
+ self.assertEqual(len(events), 2, [event["content"] for event in events])
+ self.assertEqual(events[0]["content"]["body"], "with right label", events[0])
+ self.assertEqual(events[1]["content"]["body"], "with right label", events[1])
+
+ def test_messages_filter_not_labels(self):
+ """Test that we can filter by the absence of a label on a /messages request."""
+ self._send_labelled_messages_in_room()
+
+ token = "s0_0_0_0_0_0_0_0_0"
+ request, channel = self.make_request(
+ "GET",
+ "/rooms/%s/messages?access_token=%s&from=%s&filter=%s"
+ % (self.room_id, self.tok, token, json.dumps(self.FILTER_NOT_LABELS)),
+ )
+ self.render(request)
+
+ events = channel.json_body["chunk"]
+
+ self.assertEqual(len(events), 4, [event["content"] for event in events])
+ self.assertEqual(events[0]["content"]["body"], "without label", events[0])
+ self.assertEqual(events[1]["content"]["body"], "without label", events[1])
+ self.assertEqual(events[2]["content"]["body"], "with wrong label", events[2])
+ self.assertEqual(
+ events[3]["content"]["body"], "with two wrong labels", events[3]
+ )
+
+ def test_messages_filter_labels_not_labels(self):
+ """Test that we can filter by both a label and the absence of another label on a
+ /messages request.
+ """
+ self._send_labelled_messages_in_room()
+
+ token = "s0_0_0_0_0_0_0_0_0"
+ request, channel = self.make_request(
+ "GET",
+ "/rooms/%s/messages?access_token=%s&from=%s&filter=%s"
+ % (
+ self.room_id,
+ self.tok,
+ token,
+ json.dumps(self.FILTER_LABELS_NOT_LABELS),
+ ),
+ )
+ self.render(request)
+
+ events = channel.json_body["chunk"]
+
+ self.assertEqual(len(events), 1, [event["content"] for event in events])
+ self.assertEqual(events[0]["content"]["body"], "with wrong label", events[0])
+
+ def test_search_filter_labels(self):
+ """Test that we can filter by a label on a /search request."""
+ request_data = json.dumps(
+ {
+ "search_categories": {
+ "room_events": {
+ "search_term": "label",
+ "filter": self.FILTER_LABELS,
+ }
+ }
+ }
+ )
+
+ self._send_labelled_messages_in_room()
+
+ request, channel = self.make_request(
+ "POST", "/search?access_token=%s" % self.tok, request_data
+ )
+ self.render(request)
+
+ results = channel.json_body["search_categories"]["room_events"]["results"]
+
+ self.assertEqual(
+ len(results), 2, [result["result"]["content"] for result in results],
+ )
+ self.assertEqual(
+ results[0]["result"]["content"]["body"],
+ "with right label",
+ results[0]["result"]["content"]["body"],
+ )
+ self.assertEqual(
+ results[1]["result"]["content"]["body"],
+ "with right label",
+ results[1]["result"]["content"]["body"],
+ )
+
+ def test_search_filter_not_labels(self):
+ """Test that we can filter by the absence of a label on a /search request."""
+ request_data = json.dumps(
+ {
+ "search_categories": {
+ "room_events": {
+ "search_term": "label",
+ "filter": self.FILTER_NOT_LABELS,
+ }
+ }
+ }
+ )
+
+ self._send_labelled_messages_in_room()
+
+ request, channel = self.make_request(
+ "POST", "/search?access_token=%s" % self.tok, request_data
+ )
+ self.render(request)
+
+ results = channel.json_body["search_categories"]["room_events"]["results"]
+
+ self.assertEqual(
+ len(results), 4, [result["result"]["content"] for result in results],
+ )
+ self.assertEqual(
+ results[0]["result"]["content"]["body"],
+ "without label",
+ results[0]["result"]["content"]["body"],
+ )
+ self.assertEqual(
+ results[1]["result"]["content"]["body"],
+ "without label",
+ results[1]["result"]["content"]["body"],
+ )
+ self.assertEqual(
+ results[2]["result"]["content"]["body"],
+ "with wrong label",
+ results[2]["result"]["content"]["body"],
+ )
+ self.assertEqual(
+ results[3]["result"]["content"]["body"],
+ "with two wrong labels",
+ results[3]["result"]["content"]["body"],
+ )
+
+ def test_search_filter_labels_not_labels(self):
+ """Test that we can filter by both a label and the absence of another label on a
+ /search request.
+ """
+ request_data = json.dumps(
+ {
+ "search_categories": {
+ "room_events": {
+ "search_term": "label",
+ "filter": self.FILTER_LABELS_NOT_LABELS,
+ }
+ }
+ }
+ )
+
+ self._send_labelled_messages_in_room()
+
+ request, channel = self.make_request(
+ "POST", "/search?access_token=%s" % self.tok, request_data
+ )
+ self.render(request)
+
+ results = channel.json_body["search_categories"]["room_events"]["results"]
+
+ self.assertEqual(
+ len(results), 1, [result["result"]["content"] for result in results],
+ )
+ self.assertEqual(
+ results[0]["result"]["content"]["body"],
+ "with wrong label",
+ results[0]["result"]["content"]["body"],
+ )
+
+ def _send_labelled_messages_in_room(self):
+ """Sends several messages to a room with different labels (or without any) to test
+ filtering by label.
+ Returns:
+ The ID of the event to use if we're testing filtering on /context.
+ """
+ self.helper.send_event(
+ room_id=self.room_id,
+ type=EventTypes.Message,
+ content={
+ "msgtype": "m.text",
+ "body": "with right label",
+ EventContentFields.LABELS: ["#fun"],
+ },
+ tok=self.tok,
+ )
+
+ self.helper.send_event(
+ room_id=self.room_id,
+ type=EventTypes.Message,
+ content={"msgtype": "m.text", "body": "without label"},
+ tok=self.tok,
+ )
+
+ res = self.helper.send_event(
+ room_id=self.room_id,
+ type=EventTypes.Message,
+ content={"msgtype": "m.text", "body": "without label"},
+ tok=self.tok,
+ )
+ # Return this event's ID when we test filtering in /context requests.
+ event_id = res["event_id"]
+
+ self.helper.send_event(
+ room_id=self.room_id,
+ type=EventTypes.Message,
+ content={
+ "msgtype": "m.text",
+ "body": "with wrong label",
+ EventContentFields.LABELS: ["#work"],
+ },
+ tok=self.tok,
+ )
+
+ self.helper.send_event(
+ room_id=self.room_id,
+ type=EventTypes.Message,
+ content={
+ "msgtype": "m.text",
+ "body": "with two wrong labels",
+ EventContentFields.LABELS: ["#work", "#notfun"],
+ },
+ tok=self.tok,
+ )
+
+ self.helper.send_event(
+ room_id=self.room_id,
+ type=EventTypes.Message,
+ content={
+ "msgtype": "m.text",
+ "body": "with right label",
+ EventContentFields.LABELS: ["#fun"],
+ },
+ tok=self.tok,
+ )
+
+ return event_id
+
+
+class ContextTestCase(unittest.HomeserverTestCase):
+
+ servlets = [
+ synapse.rest.admin.register_servlets_for_client_rest_resource,
+ room.register_servlets,
+ login.register_servlets,
+ account.register_servlets,
+ ]
+
+ def prepare(self, reactor, clock, homeserver):
+ self.user_id = self.register_user("user", "password")
+ self.tok = self.login("user", "password")
+ self.room_id = self.helper.create_room_as(
+ self.user_id, tok=self.tok, is_public=False
+ )
+
+ self.other_user_id = self.register_user("user2", "password")
+ self.other_tok = self.login("user2", "password")
+
+ self.helper.invite(self.room_id, self.user_id, self.other_user_id, tok=self.tok)
+ self.helper.join(self.room_id, self.other_user_id, tok=self.other_tok)
+
+ def test_erased_sender(self):
+ """Test that an erasure request results in the requester's events being hidden
+ from any new member of the room.
+ """
+
+ # Send a bunch of events in the room.
+
+ self.helper.send(self.room_id, "message 1", tok=self.tok)
+ self.helper.send(self.room_id, "message 2", tok=self.tok)
+ event_id = self.helper.send(self.room_id, "message 3", tok=self.tok)["event_id"]
+ self.helper.send(self.room_id, "message 4", tok=self.tok)
+ self.helper.send(self.room_id, "message 5", tok=self.tok)
+
+ # Check that we can still see the messages before the erasure request.
+
+ request, channel = self.make_request(
+ "GET",
+ '/rooms/%s/context/%s?filter={"types":["m.room.message"]}'
+ % (self.room_id, event_id),
+ access_token=self.tok,
+ )
+ self.render(request)
+ self.assertEqual(channel.code, 200, channel.result)
+
+ events_before = channel.json_body["events_before"]
+
+ self.assertEqual(len(events_before), 2, events_before)
+ self.assertEqual(
+ events_before[0].get("content", {}).get("body"),
+ "message 2",
+ events_before[0],
+ )
+ self.assertEqual(
+ events_before[1].get("content", {}).get("body"),
+ "message 1",
+ events_before[1],
+ )
+
+ self.assertEqual(
+ channel.json_body["event"].get("content", {}).get("body"),
+ "message 3",
+ channel.json_body["event"],
+ )
+
+ events_after = channel.json_body["events_after"]
+
+ self.assertEqual(len(events_after), 2, events_after)
+ self.assertEqual(
+ events_after[0].get("content", {}).get("body"),
+ "message 4",
+ events_after[0],
+ )
+ self.assertEqual(
+ events_after[1].get("content", {}).get("body"),
+ "message 5",
+ events_after[1],
+ )
+
+ # Deactivate the first account and erase the user's data.
+
+ deactivate_account_handler = self.hs.get_deactivate_account_handler()
+ self.get_success(
+ deactivate_account_handler.deactivate_account(self.user_id, erase_data=True)
+ )
+
+ # Invite another user in the room. This is needed because messages will be
+ # pruned only if the user wasn't a member of the room when the messages were
+ # sent.
+
+ invited_user_id = self.register_user("user3", "password")
+ invited_tok = self.login("user3", "password")
+
+ self.helper.invite(
+ self.room_id, self.other_user_id, invited_user_id, tok=self.other_tok
+ )
+ self.helper.join(self.room_id, invited_user_id, tok=invited_tok)
+
+ # Check that a user that joined the room after the erasure request can't see
+ # the messages anymore.
+
+ request, channel = self.make_request(
+ "GET",
+ '/rooms/%s/context/%s?filter={"types":["m.room.message"]}'
+ % (self.room_id, event_id),
+ access_token=invited_tok,
+ )
+ self.render(request)
+ self.assertEqual(channel.code, 200, channel.result)
+
+ events_before = channel.json_body["events_before"]
+
+ self.assertEqual(len(events_before), 2, events_before)
+ self.assertDictEqual(events_before[0].get("content"), {}, events_before[0])
+ self.assertDictEqual(events_before[1].get("content"), {}, events_before[1])
+
+ self.assertDictEqual(
+ channel.json_body["event"].get("content"), {}, channel.json_body["event"]
+ )
+
+ events_after = channel.json_body["events_after"]
+
+ self.assertEqual(len(events_after), 2, events_after)
+ self.assertDictEqual(events_after[0].get("content"), {}, events_after[0])
+ self.assertEqual(events_after[1].get("content"), {}, events_after[1])
+
+
+class RoomAliasListTestCase(unittest.HomeserverTestCase):
+ servlets = [
+ synapse.rest.admin.register_servlets_for_client_rest_resource,
+ directory.register_servlets,
+ login.register_servlets,
+ room.register_servlets,
+ ]
+
+ def prepare(self, reactor, clock, homeserver):
+ self.room_owner = self.register_user("room_owner", "test")
+ self.room_owner_tok = self.login("room_owner", "test")
+
+ self.room_id = self.helper.create_room_as(
+ self.room_owner, tok=self.room_owner_tok
+ )
+
+ def test_no_aliases(self):
+ res = self._get_aliases(self.room_owner_tok)
+ self.assertEqual(res["aliases"], [])
+
+ def test_not_in_room(self):
+ self.register_user("user", "test")
+ user_tok = self.login("user", "test")
+ res = self._get_aliases(user_tok, expected_code=403)
+ self.assertEqual(res["errcode"], "M_FORBIDDEN")
+
+ def test_admin_user(self):
+ alias1 = self._random_alias()
+ self._set_alias_via_directory(alias1)
+
+ self.register_user("user", "test", admin=True)
+ user_tok = self.login("user", "test")
+
+ res = self._get_aliases(user_tok)
+ self.assertEqual(res["aliases"], [alias1])
+
+ def test_with_aliases(self):
+ alias1 = self._random_alias()
+ alias2 = self._random_alias()
+
+ self._set_alias_via_directory(alias1)
+ self._set_alias_via_directory(alias2)
+
+ res = self._get_aliases(self.room_owner_tok)
+ self.assertEqual(set(res["aliases"]), {alias1, alias2})
+
+ def test_peekable_room(self):
+ alias1 = self._random_alias()
+ self._set_alias_via_directory(alias1)
+
+ self.helper.send_state(
+ self.room_id,
+ EventTypes.RoomHistoryVisibility,
+ body={"history_visibility": "world_readable"},
+ tok=self.room_owner_tok,
+ )
+
+ self.register_user("user", "test")
+ user_tok = self.login("user", "test")
+
+ res = self._get_aliases(user_tok)
+ self.assertEqual(res["aliases"], [alias1])
+
+ def _get_aliases(self, access_token: str, expected_code: int = 200) -> JsonDict:
+ """Calls the endpoint under test. returns the json response object."""
+ request, channel = self.make_request(
+ "GET",
+ "/_matrix/client/unstable/org.matrix.msc2432/rooms/%s/aliases"
+ % (self.room_id,),
+ access_token=access_token,
+ )
+ self.render(request)
+ self.assertEqual(channel.code, expected_code, channel.result)
+ res = channel.json_body
+ self.assertIsInstance(res, dict)
+ if expected_code == 200:
+ self.assertIsInstance(res["aliases"], list)
+ return res
+
+ def _random_alias(self) -> str:
+ return RoomAlias(random_string(5), self.hs.hostname).to_string()
+
+ def _set_alias_via_directory(self, alias: str, expected_code: int = 200):
+ url = "/_matrix/client/r0/directory/room/" + alias
+ data = {"room_id": self.room_id}
+ request_data = json.dumps(data)
+
+ request, channel = self.make_request(
+ "PUT", url, request_data, access_token=self.room_owner_tok
+ )
+ self.render(request)
+ self.assertEqual(channel.code, expected_code, channel.result)
+
+
+class RoomCanonicalAliasTestCase(unittest.HomeserverTestCase):
+ servlets = [
+ synapse.rest.admin.register_servlets_for_client_rest_resource,
+ directory.register_servlets,
+ login.register_servlets,
+ room.register_servlets,
+ ]
+
+ def prepare(self, reactor, clock, homeserver):
+ self.room_owner = self.register_user("room_owner", "test")
+ self.room_owner_tok = self.login("room_owner", "test")
+
+ self.room_id = self.helper.create_room_as(
+ self.room_owner, tok=self.room_owner_tok
+ )
+
+ self.alias = "#alias:test"
+ self._set_alias_via_directory(self.alias)
+
+ def _set_alias_via_directory(self, alias: str, expected_code: int = 200):
+ url = "/_matrix/client/r0/directory/room/" + alias
+ data = {"room_id": self.room_id}
+ request_data = json.dumps(data)
+
+ request, channel = self.make_request(
+ "PUT", url, request_data, access_token=self.room_owner_tok
+ )
+ self.render(request)
+ self.assertEqual(channel.code, expected_code, channel.result)
+
+ def _get_canonical_alias(self, expected_code: int = 200) -> JsonDict:
+ """Calls the endpoint under test. returns the json response object."""
+ request, channel = self.make_request(
+ "GET",
+ "rooms/%s/state/m.room.canonical_alias" % (self.room_id,),
+ access_token=self.room_owner_tok,
+ )
+ self.render(request)
+ self.assertEqual(channel.code, expected_code, channel.result)
+ res = channel.json_body
+ self.assertIsInstance(res, dict)
+ return res
+
+ def _set_canonical_alias(self, content: str, expected_code: int = 200) -> JsonDict:
+ """Calls the endpoint under test. returns the json response object."""
+ request, channel = self.make_request(
+ "PUT",
+ "rooms/%s/state/m.room.canonical_alias" % (self.room_id,),
+ json.dumps(content),
+ access_token=self.room_owner_tok,
+ )
+ self.render(request)
+ self.assertEqual(channel.code, expected_code, channel.result)
+ res = channel.json_body
+ self.assertIsInstance(res, dict)
+ return res
+
+ def test_canonical_alias(self):
+ """Test a basic alias message."""
+ # There is no canonical alias to start with.
+ self._get_canonical_alias(expected_code=404)
+
+ # Create an alias.
+ self._set_canonical_alias({"alias": self.alias})
+
+ # Canonical alias now exists!
+ res = self._get_canonical_alias()
+ self.assertEqual(res, {"alias": self.alias})
+
+ # Now remove the alias.
+ self._set_canonical_alias({})
+
+ # There is an alias event, but it is empty.
+ res = self._get_canonical_alias()
+ self.assertEqual(res, {})
+
+ def test_alt_aliases(self):
+ """Test a canonical alias message with alt_aliases."""
+ # Create an alias.
+ self._set_canonical_alias({"alt_aliases": [self.alias]})
+
+ # Canonical alias now exists!
+ res = self._get_canonical_alias()
+ self.assertEqual(res, {"alt_aliases": [self.alias]})
+
+ # Now remove the alt_aliases.
+ self._set_canonical_alias({})
+
+ # There is an alias event, but it is empty.
+ res = self._get_canonical_alias()
+ self.assertEqual(res, {})
+
+ def test_alias_alt_aliases(self):
+ """Test a canonical alias message with an alias and alt_aliases."""
+ # Create an alias.
+ self._set_canonical_alias({"alias": self.alias, "alt_aliases": [self.alias]})
+
+ # Canonical alias now exists!
+ res = self._get_canonical_alias()
+ self.assertEqual(res, {"alias": self.alias, "alt_aliases": [self.alias]})
+
+ # Now remove the alias and alt_aliases.
+ self._set_canonical_alias({})
+
+ # There is an alias event, but it is empty.
+ res = self._get_canonical_alias()
+ self.assertEqual(res, {})
+
+ def test_partial_modify(self):
+ """Test removing only the alt_aliases."""
+ # Create an alias.
+ self._set_canonical_alias({"alias": self.alias, "alt_aliases": [self.alias]})
+
+ # Canonical alias now exists!
+ res = self._get_canonical_alias()
+ self.assertEqual(res, {"alias": self.alias, "alt_aliases": [self.alias]})
+
+ # Now remove the alt_aliases.
+ self._set_canonical_alias({"alias": self.alias})
+
+ # There is an alias event, but it is empty.
+ res = self._get_canonical_alias()
+ self.assertEqual(res, {"alias": self.alias})
+
+ def test_add_alias(self):
+ """Test removing only the alt_aliases."""
+ # Create an additional alias.
+ second_alias = "#second:test"
+ self._set_alias_via_directory(second_alias)
+
+ # Add the canonical alias.
+ self._set_canonical_alias({"alias": self.alias, "alt_aliases": [self.alias]})
+
+ # Then add the second alias.
+ self._set_canonical_alias(
+ {"alias": self.alias, "alt_aliases": [self.alias, second_alias]}
+ )
+
+ # Canonical alias now exists!
+ res = self._get_canonical_alias()
+ self.assertEqual(
+ res, {"alias": self.alias, "alt_aliases": [self.alias, second_alias]}
+ )
+
+ def test_bad_data(self):
+ """Invalid data for alt_aliases should cause errors."""
+ self._set_canonical_alias({"alt_aliases": "@bad:test"}, expected_code=400)
+ self._set_canonical_alias({"alt_aliases": None}, expected_code=400)
+ self._set_canonical_alias({"alt_aliases": 0}, expected_code=400)
+ self._set_canonical_alias({"alt_aliases": 1}, expected_code=400)
+ self._set_canonical_alias({"alt_aliases": False}, expected_code=400)
+ self._set_canonical_alias({"alt_aliases": True}, expected_code=400)
+ self._set_canonical_alias({"alt_aliases": {}}, expected_code=400)
+
+ def test_bad_alias(self):
+ """An alias which does not point to the room raises a SynapseError."""
+ self._set_canonical_alias({"alias": "@unknown:test"}, expected_code=400)
+ self._set_canonical_alias({"alt_aliases": ["@unknown:test"]}, expected_code=400)
diff --git a/tests/rest/client/v1/test_typing.py b/tests/rest/client/v1/test_typing.py
index 30fb77bac8..4bc3aaf02d 100644
--- a/tests/rest/client/v1/test_typing.py
+++ b/tests/rest/client/v1/test_typing.py
@@ -109,7 +109,9 @@ class RoomTypingTestCase(unittest.HomeserverTestCase):
self.assertEquals(200, channel.code)
self.assertEquals(self.event_source.get_current_key(), 1)
- events = self.event_source.get_new_events(from_key=0, room_ids=[self.room_id])
+ events = self.get_success(
+ self.event_source.get_new_events(from_key=0, room_ids=[self.room_id])
+ )
self.assertEquals(
events[0],
[
diff --git a/tests/rest/client/v1/utils.py b/tests/rest/client/v1/utils.py
index 449a69183f..873d5ef99c 100644
--- a/tests/rest/client/v1/utils.py
+++ b/tests/rest/client/v1/utils.py
@@ -1,5 +1,8 @@
# -*- coding: utf-8 -*-
# Copyright 2014-2016 OpenMarket Ltd
+# Copyright 2017 Vector Creations Ltd
+# Copyright 2018-2019 New Vector Ltd
+# 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.
@@ -18,6 +21,8 @@ import time
import attr
+from twisted.web.resource import Resource
+
from synapse.api.constants import Membership
from tests.server import make_request, render
@@ -44,7 +49,7 @@ class RestHelper(object):
path = path + "?access_token=%s" % tok
request, channel = make_request(
- self.hs.get_reactor(), "POST", path, json.dumps(content).encode('utf8')
+ self.hs.get_reactor(), "POST", path, json.dumps(content).encode("utf8")
)
render(request, self.resource, self.hs.get_reactor())
@@ -93,7 +98,7 @@ class RestHelper(object):
data = {"membership": membership}
request, channel = make_request(
- self.hs.get_reactor(), "PUT", path, json.dumps(data).encode('utf8')
+ self.hs.get_reactor(), "PUT", path, json.dumps(data).encode("utf8")
)
render(request, self.resource, self.hs.get_reactor())
@@ -106,18 +111,27 @@ class RestHelper(object):
self.auth_user_id = temp_id
def send(self, room_id, body=None, txn_id=None, tok=None, expect_code=200):
- if txn_id is None:
- txn_id = "m%s" % (str(time.time()))
if body is None:
body = "body_text_here"
- path = "/_matrix/client/r0/rooms/%s/send/m.room.message/%s" % (room_id, txn_id)
content = {"msgtype": "m.text", "body": body}
+
+ return self.send_event(
+ room_id, "m.room.message", content, txn_id, tok, expect_code
+ )
+
+ def send_event(
+ self, room_id, type, content={}, txn_id=None, tok=None, expect_code=200
+ ):
+ if txn_id is None:
+ txn_id = "m%s" % (str(time.time()))
+
+ path = "/_matrix/client/r0/rooms/%s/send/%s/%s" % (room_id, type, txn_id)
if tok:
path = path + "?access_token=%s" % tok
request, channel = make_request(
- self.hs.get_reactor(), "PUT", path, json.dumps(content).encode('utf8')
+ self.hs.get_reactor(), "PUT", path, json.dumps(content).encode("utf8")
)
render(request, self.resource, self.hs.get_reactor())
@@ -148,3 +162,38 @@ class RestHelper(object):
)
return channel.json_body
+
+ def upload_media(
+ self,
+ resource: Resource,
+ image_data: bytes,
+ tok: str,
+ filename: str = "test.png",
+ expect_code: int = 200,
+ ) -> dict:
+ """Upload a piece of test media to the media repo
+ Args:
+ resource: The resource that will handle the upload request
+ image_data: The image data to upload
+ tok: The user token to use during the upload
+ filename: The filename of the media to be uploaded
+ expect_code: The return code to expect from attempting to upload the media
+ """
+ image_length = len(image_data)
+ path = "/_matrix/media/r0/upload?filename=%s" % (filename,)
+ request, channel = make_request(
+ self.hs.get_reactor(), "POST", path, content=image_data, access_token=tok
+ )
+ request.requestHeaders.addRawHeader(
+ b"Content-Length", str(image_length).encode("UTF-8")
+ )
+ request.render(resource)
+ self.hs.get_reactor().pump([100])
+
+ assert channel.code == expect_code, "Expected: %d, got: %d, resp: %r" % (
+ expect_code,
+ int(channel.result["code"]),
+ channel.result["body"],
+ )
+
+ return channel.json_body
|