Exemple #1
0
    def make_request(
        self,
        method,
        path,
        content=b"",
        access_token=None,
        request=SynapseRequest,
        shorthand=True,
    ):
        """
        Create a SynapseRequest at the path using the method and containing the
        given content.

        Args:
            method (bytes/unicode): The HTTP request method ("verb").
            path (bytes/unicode): The HTTP path, suitably URL encoded (e.g.
            escaped UTF-8 & spaces and such).
            content (bytes or dict): The body of the request. JSON-encoded, if
            a dict.
            shorthand: Whether to try and be helpful and prefix the given URL
            with the usual REST API path, if it doesn't contain it.

        Returns:
            A synapse.http.site.SynapseRequest.
        """
        if isinstance(content, dict):
            content = json.dumps(content).encode('utf8')

        return make_request(
            self.reactor, method, path, content, access_token, request, shorthand
        )
    def test_POST_createuser_with_valid_user(self):

        res = JsonResource(self.hs)
        register_servlets(self.hs, res)

        request_data = json.dumps(
            {
                "localpart": "someone",
                "displayname": "someone interesting",
                "duration_seconds": 200,
            }
        )

        url = b'/_matrix/client/api/v1/createUser?access_token=i_am_an_app_service'

        user_id = "@someone:interesting"
        token = "my token"

        self.registration_handler.get_or_create_user = Mock(
            return_value=(user_id, token)
        )

        request, channel = make_request(self.reactor, b"POST", url, request_data)
        render(request, res, self.reactor)

        self.assertEquals(channel.result["code"], b"200")

        det_data = {
            "user_id": user_id,
            "access_token": token,
            "home_server": self.hs.hostname,
        }
        self.assertDictContainsSubset(det_data, json.loads(channel.result["body"]))
Exemple #3
0
    def test_callback_direct_exception(self):
        """
        If the web callback raises an uncaught exception, it will be translated
        into a 500.
        """

        def _callback(request, **kwargs):
            raise Exception("boo")

        res = JsonResource(self.homeserver)
        res.register_paths("GET", [re.compile("^/_matrix/foo$")], _callback)

        request, channel = make_request(self.reactor, b"GET", b"/_matrix/foo")
        render(request, res, self.reactor)

        self.assertEqual(channel.result["code"], b'500')
Exemple #4
0
    def test_callback_synapseerror(self):
        """
        If the web callback raises a SynapseError, it returns the appropriate
        status code and message set in it.
        """

        def _callback(request, **kwargs):
            raise SynapseError(403, "Forbidden!!one!", Codes.FORBIDDEN)

        res = JsonResource(self.homeserver)
        res.register_paths("GET", [re.compile("^/_matrix/foo$")], _callback)

        request, channel = make_request(self.reactor, b"GET", b"/_matrix/foo")
        render(request, res, self.reactor)

        self.assertEqual(channel.result["code"], b'403')
        self.assertEqual(channel.json_body["error"], "Forbidden!!one!")
        self.assertEqual(channel.json_body["errcode"], "M_FORBIDDEN")
Exemple #5
0
    def create_room_as(self, room_creator, is_public=True, tok=None):
        temp_id = self.auth_user_id
        self.auth_user_id = room_creator
        path = "/_matrix/client/r0/createRoom"
        content = {}
        if not is_public:
            content["visibility"] = "private"
        if tok:
            path = path + "?access_token=%s" % tok

        request, channel = make_request(
            self.hs.get_reactor(), "POST", path, json.dumps(content).encode('utf8')
        )
        render(request, self.resource, self.hs.get_reactor())

        assert channel.result["code"] == b"200", channel.result
        self.auth_user_id = temp_id
        return channel.json_body["room_id"]
Exemple #6
0
    def test_no_handler(self):
        """
        If there is no handler to process the request, Synapse will return 400.
        """

        def _callback(request, **kwargs):
            """
            Not ever actually called!
            """
            self.fail("shouldn't ever get here")

        res = JsonResource(self.homeserver)
        res.register_paths("GET", [re.compile("^/_matrix/foo$")], _callback)

        request, channel = make_request(self.reactor, b"GET", b"/_matrix/foobar")
        render(request, res, self.reactor)

        self.assertEqual(channel.result["code"], b'400')
        self.assertEqual(channel.json_body["error"], "Unrecognized request")
        self.assertEqual(channel.json_body["errcode"], "M_UNRECOGNIZED")
Exemple #7
0
    def make_request(
        self,
        method,
        path,
        content=b"",
        access_token=None,
        request=SynapseRequest,
        shorthand=True,
        federation_auth_origin=None,
    ):
        """
        Create a SynapseRequest at the path using the method and containing the
        given content.

        Args:
            method (bytes/unicode): The HTTP request method ("verb").
            path (bytes/unicode): The HTTP path, suitably URL encoded (e.g.
            escaped UTF-8 & spaces and such).
            content (bytes or dict): The body of the request. JSON-encoded, if
            a dict.
            shorthand: Whether to try and be helpful and prefix the given URL
            with the usual REST API path, if it doesn't contain it.
            federation_auth_origin (bytes|None): if set to not-None, we will add a fake
                Authorization header pretenting to be the given server name.

        Returns:
            Tuple[synapse.http.site.SynapseRequest, channel]
        """
        if isinstance(content, dict):
            content = json.dumps(content).encode('utf8')

        return make_request(
            self.reactor,
            method,
            path,
            content,
            access_token,
            request,
            shorthand,
            federation_auth_origin,
        )
Exemple #8
0
    def test_callback_indirect_exception(self):
        """
        If the web callback raises an uncaught exception in a Deferred, it will
        be translated into a 500.
        """

        def _throw(*args):
            raise Exception("boo")

        def _callback(request, **kwargs):
            d = Deferred()
            d.addCallback(_throw)
            self.reactor.callLater(1, d.callback, True)
            return make_deferred_yieldable(d)

        res = JsonResource(self.homeserver)
        res.register_paths("GET", [re.compile("^/_matrix/foo$")], _callback)

        request, channel = make_request(self.reactor, b"GET", b"/_matrix/foo")
        render(request, res, self.reactor)

        self.assertEqual(channel.result["code"], b'500')
Exemple #9
0
    def change_membership(self, room, src, targ, membership, tok=None, expect_code=200):
        temp_id = self.auth_user_id
        self.auth_user_id = src

        path = "/_matrix/client/r0/rooms/%s/state/m.room.member/%s" % (room, targ)
        if tok:
            path = path + "?access_token=%s" % tok

        data = {"membership": membership}

        request, channel = make_request(
            self.hs.get_reactor(), "PUT", path, json.dumps(data).encode('utf8')
        )

        render(request, self.resource, self.hs.get_reactor())

        assert int(channel.result["code"]) == expect_code, (
            "Expected: %d, got: %d, resp: %r"
            % (expect_code, int(channel.result["code"]), channel.result["body"])
        )

        self.auth_user_id = temp_id
Exemple #10
0
    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}
        if tok:
            path = path + "?access_token=%s" % tok

        request, channel = make_request(
            self.hs.get_reactor(), "PUT", path, json.dumps(content).encode('utf8')
        )
        render(request, self.resource, self.hs.get_reactor())

        assert int(channel.result["code"]) == expect_code, (
            "Expected: %d, got: %d, resp: %r"
            % (expect_code, int(channel.result["code"]), channel.result["body"])
        )

        return channel.json_body
Exemple #11
0
    def _req(self, content_disposition):

        request, channel = make_request(
            self.reactor,
            FakeSite(self.download_resource),
            "GET",
            self.media_id,
            shorthand=False,
            await_result=False,
        )
        self.pump()

        # We've made one fetch, to example.com, using the media URL, and asking
        # the other server not to do a remote fetch
        self.assertEqual(len(self.fetches), 1)
        self.assertEqual(self.fetches[0][1], "example.com")
        self.assertEqual(
            self.fetches[0][2], "/_matrix/media/r0/download/" + self.media_id
        )
        self.assertEqual(self.fetches[0][3], {"allow_remote": "false"})

        headers = {
            b"Content-Length": [b"%d" % (len(self.test_image.data))],
            b"Content-Type": [self.test_image.content_type],
        }
        if content_disposition:
            headers[b"Content-Disposition"] = [content_disposition]

        self.fetches[0][0].callback(
            (self.test_image.data, (len(self.test_image.data), headers))
        )

        self.pump()
        self.assertEqual(channel.code, 200)

        return channel
Exemple #12
0
    def _access_media(self, server_and_media_id, expect_success=True):
        """
        Try to access a media and check the result
        """
        download_resource = self.media_repo.children[b"download"]

        media_id = server_and_media_id.split("/")[1]
        local_path = self.filepaths.local_media_filepath(media_id)

        channel = make_request(
            self.reactor,
            FakeSite(download_resource),
            "GET",
            server_and_media_id,
            shorthand=False,
            access_token=self.admin_user_tok,
        )

        if expect_success:
            self.assertEqual(
                200,
                channel.code,
                msg=("Expected to receive a 200 on accessing media: %s" %
                     server_and_media_id),
            )
            # Test that the file exists
            self.assertTrue(os.path.exists(local_path))
        else:
            self.assertEqual(
                404,
                channel.code,
                msg=("Expected to receive a 404 on accessing deleted media: %s"
                     % (server_and_media_id)),
            )
            # Test that the file is deleted
            self.assertFalse(os.path.exists(local_path))
Exemple #13
0
    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"))
        render(request, self.resource, self.hs.get_reactor())

        assert int(channel.result["code"]) == expect_code, (
            "Expected: %d, got: %d, resp: %r" %
            (expect_code, int(channel.result["code"]), channel.result["body"]))

        return channel.json_body
Exemple #14
0
    def send_state(self,
                   room_id,
                   event_type,
                   body,
                   tok,
                   expect_code=200,
                   state_key=""):
        path = "/_matrix/client/r0/rooms/%s/state/%s/%s" % (
            room_id,
            event_type,
            state_key,
        )
        if tok:
            path = path + "?access_token=%s" % tok

        request, channel = make_request(self.hs.get_reactor(), "PUT", path,
                                        json.dumps(body).encode("utf8"))
        render(request, self.resource, self.hs.get_reactor())

        assert int(channel.result["code"]) == expect_code, (
            "Expected: %d, got: %d, resp: %r" %
            (expect_code, int(channel.result["code"]), channel.result["body"]))

        return channel.json_body
Exemple #15
0
    def test_handler_for_request(self):
        """
        JsonResource.handler_for_request gives correctly decoded URL args to
        the callback, while Twisted will give the raw bytes of URL query
        arguments.
        """
        got_kwargs = {}

        def _callback(request, **kwargs):
            got_kwargs.update(kwargs)
            return (200, kwargs)

        res = JsonResource(self.homeserver)
        res.register_paths(
            "GET", [re.compile("^/_matrix/foo/(?P<room_id>[^/]*)$")], _callback
        )

        request, channel = make_request(
            self.reactor, b"GET", b"/_matrix/foo/%E2%98%83?a=%E2%98%83"
        )
        render(request, res, self.reactor)

        self.assertEqual(request.args, {b'a': [u"\N{SNOWMAN}".encode('utf8')]})
        self.assertEqual(got_kwargs, {u"room_id": u"\N{SNOWMAN}"})
Exemple #16
0
    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 = make_request(b"PUT", path, '{}')
        render(request, self.resource, self.clock)
        self.assertEquals(400,
                          int(channel.result["code"]),
                          msg=channel.result["body"])

        request, channel = make_request(b"PUT", path, '{"_name":"bob"}')
        render(request, self.resource, self.clock)
        self.assertEquals(400,
                          int(channel.result["code"]),
                          msg=channel.result["body"])

        request, channel = make_request(b"PUT", path, '{"nao')
        render(request, self.resource, self.clock)
        self.assertEquals(400,
                          int(channel.result["code"]),
                          msg=channel.result["body"])

        request, channel = make_request(b"PUT", path,
                                        '[{"_name":"bob"},{"_name":"jill"}]')
        render(request, self.resource, self.clock)
        self.assertEquals(400,
                          int(channel.result["code"]),
                          msg=channel.result["body"])

        request, channel = make_request(b"PUT", path, 'text only')
        render(request, self.resource, self.clock)
        self.assertEquals(400,
                          int(channel.result["code"]),
                          msg=channel.result["body"])

        request, channel = make_request(b"PUT", path, '')
        render(request, self.resource, self.clock)
        self.assertEquals(400,
                          int(channel.result["code"]),
                          msg=channel.result["body"])
Exemple #17
0
    def test_cannot_quarantine_safe_media(self):
        self.register_user("user_admin", "pass", admin=True)
        admin_user_tok = self.login("user_admin", "pass")

        non_admin_user = self.register_user("user_nonadmin",
                                            "pass",
                                            admin=False)
        non_admin_user_tok = self.login("user_nonadmin", "pass")

        # Upload some media
        response_1 = self.helper.upload_media(self.upload_resource,
                                              self.image_data,
                                              tok=non_admin_user_tok)
        response_2 = self.helper.upload_media(self.upload_resource,
                                              self.image_data,
                                              tok=non_admin_user_tok)

        # Extract media IDs
        server_and_media_id_1 = response_1["content_uri"][6:]
        server_and_media_id_2 = response_2["content_uri"][6:]

        # Mark the second item as safe from quarantine.
        _, media_id_2 = server_and_media_id_2.split("/")
        self.get_success(self.store.mark_local_media_as_safe(media_id_2))

        # Quarantine all media by this user
        url = "/_synapse/admin/v1/user/%s/media/quarantine" % urllib.parse.quote(
            non_admin_user)
        request, channel = self.make_request(
            "POST",
            url.encode("ascii"),
            access_token=admin_user_tok,
        )
        self.pump(1.0)
        self.assertEqual(200,
                         int(channel.result["code"]),
                         msg=channel.result["body"])
        self.assertEqual(
            json.loads(channel.result["body"].decode("utf-8")),
            {"num_quarantined": 1},
            "Expected 1 quarantined item",
        )

        # Attempt to access each piece of media, the first should fail, the
        # second should succeed.
        self._ensure_quarantined(admin_user_tok, server_and_media_id_1)

        # Attempt to access each piece of media
        request, channel = make_request(
            self.reactor,
            FakeSite(self.download_resource),
            "GET",
            server_and_media_id_2,
            shorthand=False,
            access_token=non_admin_user_tok,
        )

        # Shouldn't be quarantined
        self.assertEqual(
            200,
            int(channel.code),
            msg=
            ("Expected to receive a 200 on accessing not-quarantined media: %s"
             % server_and_media_id_2),
        )
    def test_delete_media(self) -> None:
        """
        Tests that delete a media is successfully
        """

        download_resource = self.media_repo.children[b"download"]
        upload_resource = self.media_repo.children[b"upload"]

        # Upload some media into the room
        response = self.helper.upload_media(
            upload_resource,
            SMALL_PNG,
            tok=self.admin_user_tok,
            expect_code=HTTPStatus.OK,
        )
        # Extract media ID from the response
        server_and_media_id = response["content_uri"][6:]  # Cut off 'mxc://'
        server_name, media_id = server_and_media_id.split("/")

        self.assertEqual(server_name, self.server_name)

        # Attempt to access media
        channel = make_request(
            self.reactor,
            FakeSite(download_resource, self.reactor),
            "GET",
            server_and_media_id,
            shorthand=False,
            access_token=self.admin_user_tok,
        )

        # Should be successful
        self.assertEqual(
            HTTPStatus.OK,
            channel.code,
            msg=("Expected to receive a HTTPStatus.OK on accessing media: %s" %
                 server_and_media_id),
        )

        # Test if the file exists
        local_path = self.filepaths.local_media_filepath(media_id)
        self.assertTrue(os.path.exists(local_path))

        url = "/_synapse/admin/v1/media/%s/%s" % (self.server_name, media_id)

        # Delete media
        channel = self.make_request(
            "DELETE",
            url,
            access_token=self.admin_user_tok,
        )

        self.assertEqual(HTTPStatus.OK, channel.code, msg=channel.json_body)
        self.assertEqual(1, channel.json_body["total"])
        self.assertEqual(
            media_id,
            channel.json_body["deleted_media"][0],
        )

        # Attempt to access media
        channel = make_request(
            self.reactor,
            FakeSite(download_resource, self.reactor),
            "GET",
            server_and_media_id,
            shorthand=False,
            access_token=self.admin_user_tok,
        )
        self.assertEqual(
            HTTPStatus.NOT_FOUND,
            channel.code,
            msg=
            ("Expected to receive a HTTPStatus.NOT_FOUND on accessing deleted media: %s"
             % server_and_media_id),
        )

        # Test if the file is deleted
        self.assertFalse(os.path.exists(local_path))
    def _get_media_req(
        self, hs: HomeServer, target: str, media_id: str
    ) -> Tuple[FakeChannel, Request]:
        """Request some remote media from the given HS by calling the download
        API.

        This then triggers an outbound request from the HS to the target.

        Returns:
            The channel for the *client* request and the *outbound* request for
            the media which the caller should respond to.
        """
        resource = hs.get_media_repository_resource().children[b"download"]
        channel = make_request(
            self.reactor,
            FakeSite(resource),
            "GET",
            "/{}/{}".format(target, media_id),
            shorthand=False,
            access_token=self.access_token,
            await_result=False,
        )
        self.pump()

        clients = self.reactor.tcpClients
        self.assertGreaterEqual(len(clients), 1)
        (host, port, client_factory, _timeout, _bindAddress) = clients.pop()

        # build the test server
        server_tls_protocol = _build_test_server(get_connection_factory())

        # now, tell the client protocol factory to build the client protocol (it will be a
        # _WrappingProtocol, around a TLSMemoryBIOProtocol, around an
        # HTTP11ClientProtocol) and wire the output of said protocol up to the server via
        # a FakeTransport.
        #
        # Normally this would be done by the TCP socket code in Twisted, but we are
        # stubbing that out here.
        client_protocol = client_factory.buildProtocol(None)
        client_protocol.makeConnection(
            FakeTransport(server_tls_protocol, self.reactor, client_protocol)
        )

        # tell the server tls protocol to send its stuff back to the client, too
        server_tls_protocol.makeConnection(
            FakeTransport(client_protocol, self.reactor, server_tls_protocol)
        )

        # fish the test server back out of the server-side TLS protocol.
        http_server = server_tls_protocol.wrappedProtocol

        # give the reactor a pump to get the TLS juices flowing.
        self.reactor.pump((0.1,))

        self.assertEqual(len(http_server.requests), 1)
        request = http_server.requests[0]

        self.assertEqual(request.method, b"GET")
        self.assertEqual(
            request.path,
            "/_matrix/media/r0/download/{}/{}".format(target, media_id).encode("utf-8"),
        )
        self.assertEqual(
            request.requestHeaders.getRawHeaders(b"host"), [target.encode("utf-8")]
        )

        return channel, request
Exemple #20
0
 def nonce():
     request, channel = make_request("GET", self.url)
     render(request, self.resource, self.clock)
     return channel.json_body["nonce"]
Exemple #21
0
    def test_missing_parts(self):
        """
        Synapse will complain if you don't give nonce, username, password, and
        mac.  Admin is optional.  Additional checks are done for length and
        type.
        """
        def nonce():
            request, channel = make_request("GET", self.url)
            render(request, self.resource, self.clock)
            return channel.json_body["nonce"]

        #
        # Nonce check
        #

        # Must be present
        body = json.dumps({})
        request, channel = make_request("POST", self.url, body.encode('utf8'))
        render(request, self.resource, self.clock)

        self.assertEqual(400,
                         int(channel.result["code"]),
                         msg=channel.result["body"])
        self.assertEqual('nonce must be specified', channel.json_body["error"])

        #
        # Username checks
        #

        # Must be present
        body = json.dumps({"nonce": nonce()})
        request, channel = make_request("POST", self.url, body.encode('utf8'))
        render(request, self.resource, self.clock)

        self.assertEqual(400,
                         int(channel.result["code"]),
                         msg=channel.result["body"])
        self.assertEqual('username must be specified',
                         channel.json_body["error"])

        # Must be a string
        body = json.dumps({"nonce": nonce(), "username": 1234})
        request, channel = make_request("POST", self.url, body.encode('utf8'))
        render(request, self.resource, self.clock)

        self.assertEqual(400,
                         int(channel.result["code"]),
                         msg=channel.result["body"])
        self.assertEqual('Invalid username', channel.json_body["error"])

        # Must not have null bytes
        body = json.dumps({"nonce": nonce(), "username": u"abcd\u0000"})
        request, channel = make_request("POST", self.url, body.encode('utf8'))
        render(request, self.resource, self.clock)

        self.assertEqual(400,
                         int(channel.result["code"]),
                         msg=channel.result["body"])
        self.assertEqual('Invalid username', channel.json_body["error"])

        # Must not have null bytes
        body = json.dumps({"nonce": nonce(), "username": "******" * 1000})
        request, channel = make_request("POST", self.url, body.encode('utf8'))
        render(request, self.resource, self.clock)

        self.assertEqual(400,
                         int(channel.result["code"]),
                         msg=channel.result["body"])
        self.assertEqual('Invalid username', channel.json_body["error"])

        #
        # Username checks
        #

        # Must be present
        body = json.dumps({"nonce": nonce(), "username": "******"})
        request, channel = make_request("POST", self.url, body.encode('utf8'))
        render(request, self.resource, self.clock)

        self.assertEqual(400,
                         int(channel.result["code"]),
                         msg=channel.result["body"])
        self.assertEqual('password must be specified',
                         channel.json_body["error"])

        # Must be a string
        body = json.dumps({
            "nonce": nonce(),
            "username": "******",
            "password": 1234
        })
        request, channel = make_request("POST", self.url, body.encode('utf8'))
        render(request, self.resource, self.clock)

        self.assertEqual(400,
                         int(channel.result["code"]),
                         msg=channel.result["body"])
        self.assertEqual('Invalid password', channel.json_body["error"])

        # Must not have null bytes
        body = json.dumps({
            "nonce": nonce(),
            "username": "******",
            "password": u"abcd\u0000"
        })
        request, channel = make_request("POST", self.url, body.encode('utf8'))
        render(request, self.resource, self.clock)

        self.assertEqual(400,
                         int(channel.result["code"]),
                         msg=channel.result["body"])
        self.assertEqual('Invalid password', channel.json_body["error"])

        # Super long
        body = json.dumps({
            "nonce": nonce(),
            "username": "******",
            "password": "******" * 1000
        })
        request, channel = make_request("POST", self.url, body.encode('utf8'))
        render(request, self.resource, self.clock)

        self.assertEqual(400,
                         int(channel.result["code"]),
                         msg=channel.result["body"])
        self.assertEqual('Invalid password', channel.json_body["error"])
Exemple #22
0
    def test_delete_media(self):
        """
        Tests that delete a media is successfully
        """

        download_resource = self.media_repo.children[b"download"]
        upload_resource = self.media_repo.children[b"upload"]
        image_data = unhexlify(
            b"89504e470d0a1a0a0000000d4948445200000001000000010806"
            b"0000001f15c4890000000a49444154789c63000100000500010d"
            b"0a2db40000000049454e44ae426082"
        )

        # Upload some media into the room
        response = self.helper.upload_media(
            upload_resource, image_data, tok=self.admin_user_tok, expect_code=200
        )
        # Extract media ID from the response
        server_and_media_id = response["content_uri"][6:]  # Cut off 'mxc://'
        server_name, media_id = server_and_media_id.split("/")

        self.assertEqual(server_name, self.server_name)

        # Attempt to access media
        channel = make_request(
            self.reactor,
            FakeSite(download_resource),
            "GET",
            server_and_media_id,
            shorthand=False,
            access_token=self.admin_user_tok,
        )

        # Should be successful
        self.assertEqual(
            200,
            channel.code,
            msg=(
                "Expected to receive a 200 on accessing media: %s" % server_and_media_id
            ),
        )

        # Test if the file exists
        local_path = self.filepaths.local_media_filepath(media_id)
        self.assertTrue(os.path.exists(local_path))

        url = "/_synapse/admin/v1/media/%s/%s" % (self.server_name, media_id)

        # Delete media
        channel = self.make_request(
            "DELETE",
            url,
            access_token=self.admin_user_tok,
        )

        self.assertEqual(200, channel.code, msg=channel.json_body)
        self.assertEqual(1, channel.json_body["total"])
        self.assertEqual(
            media_id,
            channel.json_body["deleted_media"][0],
        )

        # Attempt to access media
        channel = make_request(
            self.reactor,
            FakeSite(download_resource),
            "GET",
            server_and_media_id,
            shorthand=False,
            access_token=self.admin_user_tok,
        )
        self.assertEqual(
            404,
            channel.code,
            msg=(
                "Expected to receive a 404 on accessing deleted media: %s"
                % server_and_media_id
            ),
        )

        # Test if the file is deleted
        self.assertFalse(os.path.exists(local_path))
Exemple #23
0
    def change_membership(
        self,
        room: str,
        src: Optional[str],
        targ: Optional[str],
        membership: str,
        extra_data: Optional[dict] = None,
        tok: Optional[str] = None,
        appservice_user_id: Optional[str] = None,
        expect_code: int = HTTPStatus.OK,
        expect_errcode: Optional[str] = None,
    ) -> None:
        """
        Send a membership state event into a room.

        Args:
            room: The ID of the room to send to
            src: The mxid of the event sender
            targ: The mxid of the event's target. The state key
            membership: The type of membership event
            extra_data: Extra information to include in the content of the event
            tok: The user access token to use
            appservice_user_id: The `user_id` URL parameter to pass.
                This allows driving an application service user
                using an application service access token in `tok`.
            expect_code: The expected HTTP response code
            expect_errcode: The expected Matrix error code
        """
        temp_id = self.auth_user_id
        self.auth_user_id = src

        path = f"/_matrix/client/r0/rooms/{room}/state/m.room.member/{targ}"
        url_params: Dict[str, str] = {}

        if tok:
            url_params["access_token"] = tok

        if appservice_user_id:
            url_params["user_id"] = appservice_user_id

        if url_params:
            path += "?" + urlencode(url_params)

        data = {"membership": membership}
        data.update(extra_data or {})

        channel = make_request(
            self.hs.get_reactor(),
            self.site,
            "PUT",
            path,
            json.dumps(data).encode("utf8"),
        )

        assert (int(channel.result["code"]) == expect_code
                ), "Expected: %d, got: %d, resp: %r" % (
                    expect_code,
                    int(channel.result["code"]),
                    channel.result["body"],
                )

        if expect_errcode:
            assert (str(channel.json_body["errcode"]) == expect_errcode
                    ), "Expected: %r, got: %r, resp: %r" % (
                        expect_errcode,
                        channel.json_body["errcode"],
                        channel.result["body"],
                    )

        self.auth_user_id = temp_id
Exemple #24
0
 def _test_get_membership(self, room=None, members=[], expect_code=None):
     for member in members:
         path = b"/rooms/%s/state/m.room.member/%s" % (room, member)
         request, channel = make_request(b"GET", path)
         render(request, self.resource, self.clock)
         self.assertEquals(expect_code, int(channel.result["code"]))
Exemple #25
0
    def test_topic_perms(self):
        topic_content = b'{"topic":"My Topic Name"}'
        topic_path = b"/rooms/%s/state/m.room.topic" % self.created_rmid

        # set/get topic in uncreated room, expect 403
        request, channel = make_request(
            b"PUT", b"/rooms/%s/state/m.room.topic" % self.uncreated_rmid,
            topic_content)
        render(request, self.resource, self.clock)
        self.assertEquals(403,
                          int(channel.result["code"]),
                          msg=channel.result["body"])
        request, channel = make_request(
            b"GET", "/rooms/%s/state/m.room.topic" % self.uncreated_rmid)
        render(request, self.resource, self.clock)
        self.assertEquals(403,
                          int(channel.result["code"]),
                          msg=channel.result["body"])

        # set/get topic in created PRIVATE room not joined, expect 403
        request, channel = make_request(b"PUT", topic_path, topic_content)
        render(request, self.resource, self.clock)
        self.assertEquals(403,
                          int(channel.result["code"]),
                          msg=channel.result["body"])
        request, channel = make_request(b"GET", topic_path)
        render(request, self.resource, self.clock)
        self.assertEquals(403,
                          int(channel.result["code"]),
                          msg=channel.result["body"])

        # set topic in created PRIVATE room and invited, expect 403
        self.helper.invite(room=self.created_rmid,
                           src=self.rmcreator_id,
                           targ=self.user_id)
        request, channel = make_request(b"PUT", topic_path, topic_content)
        render(request, self.resource, self.clock)
        self.assertEquals(403,
                          int(channel.result["code"]),
                          msg=channel.result["body"])

        # get topic in created PRIVATE room and invited, expect 403
        request, channel = make_request(b"GET", topic_path)
        render(request, self.resource, self.clock)
        self.assertEquals(403,
                          int(channel.result["code"]),
                          msg=channel.result["body"])

        # set/get topic in created PRIVATE room and joined, expect 200
        self.helper.join(room=self.created_rmid, user=self.user_id)

        # Only room ops can set topic by default
        self.helper.auth_user_id = self.rmcreator_id
        request, channel = make_request(b"PUT", topic_path, topic_content)
        render(request, self.resource, self.clock)
        self.assertEquals(200,
                          int(channel.result["code"]),
                          msg=channel.result["body"])
        self.helper.auth_user_id = self.user_id

        request, channel = make_request(b"GET", topic_path)
        render(request, self.resource, self.clock)
        self.assertEquals(200,
                          int(channel.result["code"]),
                          msg=channel.result["body"])
        self.assert_dict(json.loads(topic_content), 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)
        request, channel = make_request(b"PUT", topic_path, topic_content)
        render(request, self.resource, self.clock)
        self.assertEquals(403,
                          int(channel.result["code"]),
                          msg=channel.result["body"])
        request, channel = make_request(b"GET", topic_path)
        render(request, self.resource, self.clock)
        self.assertEquals(200,
                          int(channel.result["code"]),
                          msg=channel.result["body"])

        # get topic in PUBLIC room, not joined, expect 403
        request, channel = make_request(
            b"GET", b"/rooms/%s/state/m.room.topic" % self.created_public_rmid)
        render(request, self.resource, self.clock)
        self.assertEquals(403,
                          int(channel.result["code"]),
                          msg=channel.result["body"])

        # set topic in PUBLIC room, not joined, expect 403
        request, channel = make_request(
            b"PUT",
            b"/rooms/%s/state/m.room.topic" % self.created_public_rmid,
            topic_content,
        )
        render(request, self.resource, self.clock)
        self.assertEquals(403,
                          int(channel.result["code"]),
                          msg=channel.result["body"])
Exemple #26
0
    def complete_oidc_auth(
        self,
        oauth_uri: str,
        cookies: Mapping[str, str],
        user_info_dict: JsonDict,
    ) -> FakeChannel:
        """Mock out an OIDC authentication flow

        Assumes that an OIDC auth has been initiated by one of initiate_sso_login or
        initiate_sso_ui_auth; completes the OIDC bits of the flow by making a request to
        Synapse's OIDC callback endpoint, intercepting the HTTP requests that will get
        sent back to the OIDC provider.

        Requires the OIDC callback resource to be mounted at the normal place.

        Args:
            oauth_uri: the OIDC URI returned by synapse's redirect endpoint (ie,
               from initiate_sso_login or initiate_sso_ui_auth).
            cookies: the cookies set by synapse's redirect endpoint, which will be
               sent back to the callback endpoint.
            user_info_dict: the remote userinfo that the OIDC provider should present.
                Typically this should be '{"sub": "<remote user id>"}'.

        Returns:
            A FakeChannel containing the result of calling the OIDC callback endpoint.
        """
        _, oauth_uri_qs = oauth_uri.split("?", 1)
        params = urllib.parse.parse_qs(oauth_uri_qs)
        callback_uri = "%s?%s" % (
            urllib.parse.urlparse(params["redirect_uri"][0]).path,
            urllib.parse.urlencode({"state": params["state"][0], "code": "TEST_CODE"}),
        )

        # before we hit the callback uri, stub out some methods in the http client so
        # that we don't have to handle full HTTPS requests.
        # (expected url, json response) pairs, in the order we expect them.
        expected_requests = [
            # first we get a hit to the token endpoint, which we tell to return
            # a dummy OIDC access token
            (TEST_OIDC_TOKEN_ENDPOINT, {"access_token": "TEST"}),
            # and then one to the user_info endpoint, which returns our remote user id.
            (TEST_OIDC_USERINFO_ENDPOINT, user_info_dict),
        ]

        async def mock_req(method: str, uri: str, data=None, headers=None):
            (expected_uri, resp_obj) = expected_requests.pop(0)
            assert uri == expected_uri
            resp = FakeResponse(
                code=200,
                phrase=b"OK",
                body=json.dumps(resp_obj).encode("utf-8"),
            )
            return resp

        with patch.object(self.hs.get_proxied_http_client(), "request", mock_req):
            # now hit the callback URI with the right params and a made-up code
            channel = make_request(
                self.hs.get_reactor(),
                self.site,
                "GET",
                callback_uri,
                custom_headers=[
                    ("Cookie", "%s=%s" % (k, v)) for (k, v) in cookies.items()
                ],
            )
        return channel
Exemple #27
0
    def login_via_oidc(self, remote_user_id: str) -> JsonDict:
        """Log in (as a new user) via OIDC

        Returns the result of the final token login.

        Requires that "oidc_config" in the homeserver config be set appropriately
        (TEST_OIDC_CONFIG is a suitable example) - and by implication, needs a
        "public_base_url".

        Also requires the login servlet and the OIDC callback resource to be mounted at
        the normal places.
        """
        client_redirect_url = "https://x"

        # first hit the redirect url (which will issue a cookie and state)
        channel = make_request(
            self.hs.get_reactor(),
            self.site,
            "GET",
            "/login/sso/redirect?redirectUrl=" + client_redirect_url,
        )
        # that will redirect to the OIDC IdP, but we skip that and go straight
        # back to synapse's OIDC callback resource. However, we do need the "state"
        # param that synapse passes to the IdP via query params, and the cookie that
        # synapse passes to the client.
        assert channel.code == 302
        oauth_uri = channel.headers.getRawHeaders("Location")[0]
        params = urllib.parse.parse_qs(urllib.parse.urlparse(oauth_uri).query)
        redirect_uri = "%s?%s" % (
            urllib.parse.urlparse(params["redirect_uri"][0]).path,
            urllib.parse.urlencode({
                "state": params["state"][0],
                "code": "TEST_CODE"
            }),
        )
        cookies = {}
        for h in channel.headers.getRawHeaders("Set-Cookie"):
            parts = h.split(";")
            k, v = parts[0].split("=", maxsplit=1)
            cookies[k] = v

        # before we hit the callback uri, stub out some methods in the http client so
        # that we don't have to handle full HTTPS requests.

        # (expected url, json response) pairs, in the order we expect them.
        expected_requests = [
            # first we get a hit to the token endpoint, which we tell to return
            # a dummy OIDC access token
            ("https://issuer.test/token", {
                "access_token": "TEST"
            }),
            # and then one to the user_info endpoint, which returns our remote user id.
            ("https://issuer.test/userinfo", {
                "sub": remote_user_id
            }),
        ]

        async def mock_req(method: str, uri: str, data=None, headers=None):
            (expected_uri, resp_obj) = expected_requests.pop(0)
            assert uri == expected_uri
            resp = FakeResponse(
                code=200,
                phrase=b"OK",
                body=json.dumps(resp_obj).encode("utf-8"),
            )
            return resp

        with patch.object(self.hs.get_proxied_http_client(), "request",
                          mock_req):
            # now hit the callback URI with the right params and a made-up code
            channel = make_request(
                self.hs.get_reactor(),
                self.site,
                "GET",
                redirect_uri,
                custom_headers=[("Cookie", "%s=%s" % (k, v))
                                for (k, v) in cookies.items()],
            )

        # expect a confirmation page
        assert channel.code == 200

        # fish the matrix login token out of the body of the confirmation page
        m = re.search(
            'a href="%s.*loginToken=([^"]*)"' % (client_redirect_url, ),
            channel.result["body"].decode("utf-8"),
        )
        assert m
        login_token = m.group(1)

        # finally, submit the matrix login token to the login API, which gives us our
        # matrix access token and device id.
        channel = make_request(
            self.hs.get_reactor(),
            self.site,
            "POST",
            "/login",
            content={
                "type": "m.login.token",
                "token": login_token
            },
        )
        assert channel.code == 200
        return channel.json_body
    def test_vector_clock_token(self):
        """Tests that using a stream token with a vector clock component works
        correctly with basic /sync and /messages usage.
        """

        self.make_worker_hs(
            "synapse.app.generic_worker",
            {"worker_name": "worker1"},
        )

        worker_hs2 = self.make_worker_hs(
            "synapse.app.generic_worker",
            {"worker_name": "worker2"},
        )

        sync_hs = self.make_worker_hs(
            "synapse.app.generic_worker",
            {"worker_name": "sync"},
        )
        sync_hs_site = self._hs_to_site[sync_hs]

        # Specially selected room IDs that get persisted on different workers.
        room_id1 = "!foo:test"
        room_id2 = "!baz:test"

        self.assertEqual(
            self.hs.config.worker.events_shard_config.get_instance(room_id1), "worker1"
        )
        self.assertEqual(
            self.hs.config.worker.events_shard_config.get_instance(room_id2), "worker2"
        )

        user_id = self.register_user("user", "pass")
        access_token = self.login("user", "pass")

        store = self.hs.get_datastore()

        # Create two room on the different workers.
        self._create_room(room_id1, user_id, access_token)
        self._create_room(room_id2, user_id, access_token)

        # The other user joins
        self.helper.join(
            room=room_id1, user=self.other_user_id, tok=self.other_access_token
        )
        self.helper.join(
            room=room_id2, user=self.other_user_id, tok=self.other_access_token
        )

        # Do an initial sync so that we're up to date.
        channel = make_request(
            self.reactor, sync_hs_site, "GET", "/sync", access_token=access_token
        )
        next_batch = channel.json_body["next_batch"]

        # We now gut wrench into the events stream MultiWriterIdGenerator on
        # worker2 to mimic it getting stuck persisting an event. This ensures
        # that when we send an event on worker1 we end up in a state where
        # worker2 events stream position lags that on worker1, resulting in a
        # RoomStreamToken with a non-empty instance map component.
        #
        # Worker2's event stream position will not advance until we call
        # __aexit__ again.
        actx = worker_hs2.get_datastore()._stream_id_gen.get_next()
        self.get_success(actx.__aenter__())

        response = self.helper.send(room_id1, body="Hi!", tok=self.other_access_token)
        first_event_in_room1 = response["event_id"]

        # Assert that the current stream token has an instance map component, as
        # we are trying to test vector clock tokens.
        room_stream_token = store.get_room_max_token()
        self.assertNotEqual(len(room_stream_token.instance_map), 0)

        # Check that syncing still gets the new event, despite the gap in the
        # stream IDs.
        channel = make_request(
            self.reactor,
            sync_hs_site,
            "GET",
            "/sync?since={}".format(next_batch),
            access_token=access_token,
        )

        # We should only see the new event and nothing else
        self.assertIn(room_id1, channel.json_body["rooms"]["join"])
        self.assertNotIn(room_id2, channel.json_body["rooms"]["join"])

        events = channel.json_body["rooms"]["join"][room_id1]["timeline"]["events"]
        self.assertListEqual(
            [first_event_in_room1], [event["event_id"] for event in events]
        )

        # Get the next batch and makes sure its a vector clock style token.
        vector_clock_token = channel.json_body["next_batch"]
        self.assertTrue(vector_clock_token.startswith("m"))

        # Now that we've got a vector clock token we finish the fake persisting
        # an event we started above.
        self.get_success(actx.__aexit__(None, None, None))

        # Now try and send an event to the other rooom so that we can test that
        # the vector clock style token works as a `since` token.
        response = self.helper.send(room_id2, body="Hi!", tok=self.other_access_token)
        first_event_in_room2 = response["event_id"]

        channel = make_request(
            self.reactor,
            sync_hs_site,
            "GET",
            "/sync?since={}".format(vector_clock_token),
            access_token=access_token,
        )

        self.assertNotIn(room_id1, channel.json_body["rooms"]["join"])
        self.assertIn(room_id2, channel.json_body["rooms"]["join"])

        events = channel.json_body["rooms"]["join"][room_id2]["timeline"]["events"]
        self.assertListEqual(
            [first_event_in_room2], [event["event_id"] for event in events]
        )

        next_batch = channel.json_body["next_batch"]

        # We also want to test that the vector clock style token works with
        # pagination. We do this by sending a couple of new events into the room
        # and syncing again to get a prev_batch token for each room, then
        # paginating from there back to the vector clock token.
        self.helper.send(room_id1, body="Hi again!", tok=self.other_access_token)
        self.helper.send(room_id2, body="Hi again!", tok=self.other_access_token)

        channel = make_request(
            self.reactor,
            sync_hs_site,
            "GET",
            "/sync?since={}".format(next_batch),
            access_token=access_token,
        )

        prev_batch1 = channel.json_body["rooms"]["join"][room_id1]["timeline"][
            "prev_batch"
        ]
        prev_batch2 = channel.json_body["rooms"]["join"][room_id2]["timeline"][
            "prev_batch"
        ]

        # Paginating back in the first room should not produce any results, as
        # no events have happened in it. This tests that we are correctly
        # filtering results based on the vector clock portion.
        channel = make_request(
            self.reactor,
            sync_hs_site,
            "GET",
            "/rooms/{}/messages?from={}&to={}&dir=b".format(
                room_id1, prev_batch1, vector_clock_token
            ),
            access_token=access_token,
        )
        self.assertListEqual([], channel.json_body["chunk"])

        # Paginating back on the second room should produce the first event
        # again. This tests that pagination isn't completely broken.
        channel = make_request(
            self.reactor,
            sync_hs_site,
            "GET",
            "/rooms/{}/messages?from={}&to={}&dir=b".format(
                room_id2, prev_batch2, vector_clock_token
            ),
            access_token=access_token,
        )
        self.assertEqual(len(channel.json_body["chunk"]), 1)
        self.assertEqual(
            channel.json_body["chunk"][0]["event_id"], first_event_in_room2
        )

        # Paginating forwards should give the same results
        channel = make_request(
            self.reactor,
            sync_hs_site,
            "GET",
            "/rooms/{}/messages?from={}&to={}&dir=f".format(
                room_id1, vector_clock_token, prev_batch1
            ),
            access_token=access_token,
        )
        self.assertListEqual([], channel.json_body["chunk"])

        channel = make_request(
            self.reactor,
            sync_hs_site,
            "GET",
            "/rooms/{}/messages?from={}&to={}&dir=f".format(
                room_id2,
                vector_clock_token,
                prev_batch2,
            ),
            access_token=access_token,
        )
        self.assertEqual(len(channel.json_body["chunk"]), 1)
        self.assertEqual(
            channel.json_body["chunk"][0]["event_id"], first_event_in_room2
        )
Exemple #29
0
    def test_cannot_quarantine_safe_media(self) -> None:
        self.register_user("user_admin", "pass", admin=True)
        admin_user_tok = self.login("user_admin", "pass")

        non_admin_user = self.register_user("user_nonadmin",
                                            "pass",
                                            admin=False)
        non_admin_user_tok = self.login("user_nonadmin", "pass")

        # Upload some media
        response_1 = self.helper.upload_media(self.upload_resource,
                                              SMALL_PNG,
                                              tok=non_admin_user_tok)
        response_2 = self.helper.upload_media(self.upload_resource,
                                              SMALL_PNG,
                                              tok=non_admin_user_tok)

        # Extract media IDs
        server_and_media_id_1 = response_1["content_uri"][6:]
        server_and_media_id_2 = response_2["content_uri"][6:]

        # Mark the second item as safe from quarantine.
        _, media_id_2 = server_and_media_id_2.split("/")
        # Quarantine the media
        url = "/_synapse/admin/v1/media/protect/%s" % (
            urllib.parse.quote(media_id_2), )
        channel = self.make_request("POST", url, access_token=admin_user_tok)
        self.pump(1.0)
        self.assertEqual(HTTPStatus.OK, channel.code, msg=channel.json_body)

        # Quarantine all media by this user
        url = "/_synapse/admin/v1/user/%s/media/quarantine" % urllib.parse.quote(
            non_admin_user)
        channel = self.make_request(
            "POST",
            url.encode("ascii"),
            access_token=admin_user_tok,
        )
        self.pump(1.0)
        self.assertEqual(HTTPStatus.OK, channel.code, msg=channel.json_body)
        self.assertEqual(channel.json_body, {"num_quarantined": 1},
                         "Expected 1 quarantined item")

        # Attempt to access each piece of media, the first should fail, the
        # second should succeed.
        self._ensure_quarantined(admin_user_tok, server_and_media_id_1)

        # Attempt to access each piece of media
        channel = make_request(
            self.reactor,
            FakeSite(self.download_resource, self.reactor),
            "GET",
            server_and_media_id_2,
            shorthand=False,
            access_token=non_admin_user_tok,
        )

        # Shouldn't be quarantined
        self.assertEqual(
            HTTPStatus.OK,
            channel.code,
            msg=
            ("Expected to receive a HTTPStatus.OK on accessing not-quarantined media: %s"
             % server_and_media_id_2),
        )
Exemple #30
0
    def create_room_as(
        self,
        room_creator: Optional[str] = None,
        is_public: Optional[bool] = True,
        room_version: Optional[str] = None,
        tok: Optional[str] = None,
        expect_code: int = HTTPStatus.OK,
        extra_content: Optional[Dict] = None,
        custom_headers: Optional[Iterable[Tuple[AnyStr, AnyStr]]] = None,
    ) -> Optional[str]:
        """
        Create a room.

        Args:
            room_creator: The user ID to create the room with.
            is_public: If True, the `visibility` parameter will be set to
                "public". If False, it will be set to "private".
                If None, doesn't specify the `visibility` parameter in which
                case the server is supposed to make the room private according to
                the CS API.
                Defaults to public, since that is commonly needed in tests
                for convenience where room privacy is not a problem.
            room_version: The room version to create the room as. Defaults to Synapse's
                default room version.
            tok: The access token to use in the request.
            expect_code: The expected HTTP response code.
            extra_content: Extra keys to include in the body of the /createRoom request.
                Note that if is_public is set, the "visibility" key will be overridden.
                If room_version is set, the "room_version" key will be overridden.
            custom_headers: HTTP headers to include in the request.

        Returns:
            The ID of the newly created room, or None if the request failed.
        """
        temp_id = self.auth_user_id
        self.auth_user_id = room_creator
        path = "/_matrix/client/r0/createRoom"
        content = extra_content or {}
        if is_public is not None:
            content["visibility"] = "public" if is_public else "private"
        if room_version:
            content["room_version"] = room_version
        if tok:
            path = path + "?access_token=%s" % tok

        channel = make_request(
            self.hs.get_reactor(),
            self.site,
            "POST",
            path,
            json.dumps(content).encode("utf8"),
            custom_headers=custom_headers,
        )

        assert channel.result["code"] == b"%d" % expect_code, channel.result
        self.auth_user_id = temp_id

        if expect_code == HTTPStatus.OK:
            return channel.json_body["room_id"]
        else:
            return None
    def test_get_filter_no_id(self):
        request, channel = make_request(
            "GET", "/_matrix/client/r0/user/%s/filter/" % (self.USER_ID))
        render(request, self.resource, self.clock)

        self.assertEqual(channel.result["code"], b"400")
Exemple #32
0
def make_request_with_cancellation_test(
    test_name: str,
    reactor: MemoryReactorClock,
    site: Site,
    method: str,
    path: str,
    content: Union[bytes, str, JsonDict] = b"",
) -> FakeChannel:
    """Performs a request repeatedly, disconnecting at successive `await`s, until
    one completes.

    Fails if:
        * A logging context is lost during cancellation.
        * A logging context get restarted after it is marked as finished, eg. if
            a request's logging context is used by some processing started by the
            request, but the request neglects to cancel that processing or wait for it
            to complete.

            Note that "Re-starting finished log context" errors get raised within the
            request handling code and may or may not get caught. These errors will
            likely manifest as a different logging context error at a later point. When
            debugging logging context failures, setting a breakpoint in
            `logcontext_error` can prove useful.
        * A request gets stuck, possibly due to a previous cancellation.
        * The request does not return a 499 when the client disconnects.
            This implies that a `CancelledError` was swallowed somewhere.

    It is up to the caller to verify that the request returns the correct data when
    it finally runs to completion.

    Note that this function can only cover a single code path and does not guarantee
    that an endpoint is compatible with cancellation on every code path.
    To allow inspection of the code path that is being tested, this function will
    log the stack trace at every `await` that gets cancelled. To view these log
    lines, `trial` can be run with the `SYNAPSE_TEST_LOG_LEVEL=INFO` environment
    variable, which will include the log lines in `_trial_temp/test.log`.
    Alternatively, `_log_for_request` can be modified to write to `sys.stdout`.

    Args:
        test_name: The name of the test, which will be logged.
        reactor: The twisted reactor running the request handler.
        site: The twisted `Site` to use to render the request.
        method: The HTTP request method ("verb").
        path: The HTTP path, suitably URL encoded (e.g. escaped UTF-8 & spaces and
            such).
        content: The body of the request.

    Returns:
        The `FakeChannel` object which stores the result of the final request that
        runs to completion.
    """
    # To process a request, a coroutine run is created for the async method handling
    # the request. That method may then start other coroutine runs, wrapped in
    # `Deferred`s.
    #
    # We would like to trigger a cancellation at the first `await`, re-run the
    # request and cancel at the second `await`, and so on. By patching
    # `Deferred.__next__`, we can intercept `await`s, track which ones we have or
    # have not seen, and force them to block when they wouldn't have.

    # The set of previously seen `await`s.
    # Each element is a stringified stack trace.
    seen_awaits: Set[Tuple[str, ...]] = set()

    _log_for_request(
        0, f"Running make_request_with_cancellation_test for {test_name}...")

    for request_number in itertools.count(1):
        deferred_patch = Deferred__next__Patch(seen_awaits, request_number)

        try:
            with mock.patch("synapse.http.server.respond_with_json",
                            wraps=respond_with_json) as respond_mock:
                with deferred_patch.patch():
                    # Start the request.
                    channel = make_request(reactor,
                                           site,
                                           method,
                                           path,
                                           content,
                                           await_result=False)
                    request = channel.request

                    # Run the request until we see a new `await` which we have not
                    # yet cancelled at, or it completes.
                    while not respond_mock.called and not deferred_patch.new_await_seen:
                        previous_awaits_seen = deferred_patch.awaits_seen

                        reactor.advance(0.0)

                        if deferred_patch.awaits_seen == previous_awaits_seen:
                            # We didn't see any progress. Try advancing the clock.
                            reactor.advance(1.0)

                        if deferred_patch.awaits_seen == previous_awaits_seen:
                            # We still didn't see any progress. The request might be
                            # stuck.
                            raise AssertionError(
                                "Request appears to be stuck, possibly due to a "
                                "previous cancelled request")

                if respond_mock.called:
                    # The request ran to completion and we are done with testing it.

                    # `respond_with_json` writes the response asynchronously, so we
                    # might have to give the reactor a kick before the channel gets
                    # the response.
                    deferred_patch.unblock_awaits()
                    channel.await_result()

                    return channel

                # Disconnect the client and wait for the response.
                request.connectionLost(reason=ConnectionDone())

                _log_for_request(request_number, "--- disconnected ---")

                # Advance the reactor just enough to get a response.
                # We don't want to advance the reactor too far, because we can only
                # detect re-starts of finished logging contexts after we set the
                # finished flag below.
                for _ in range(2):
                    # We may need to pump the reactor to allow `delay_cancellation`s to
                    # finish.
                    if not respond_mock.called:
                        reactor.advance(0.0)

                    # Try advancing the clock if that didn't work.
                    if not respond_mock.called:
                        reactor.advance(1.0)

                    # `delay_cancellation`s may be waiting for processing that we've
                    # forced to block. Try unblocking them, followed by another round of
                    # pumping the reactor.
                    if not respond_mock.called:
                        deferred_patch.unblock_awaits()

                # Mark the request's logging context as finished. If it gets
                # activated again, an `AssertionError` will be raised and bubble up
                # through request handling code. This `AssertionError` may or may not be
                # caught. Eventually some other code will deactivate the logging
                # context which will raise a different `AssertionError` because
                # resource usage won't have been correctly tracked.
                if isinstance(request, SynapseRequest) and request.logcontext:
                    request.logcontext.finished = True

                # Check that the request finished with a 499,
                # ie. the `CancelledError` wasn't swallowed.
                respond_mock.assert_called_once()

                if request.code != HTTP_STATUS_REQUEST_CANCELLED:
                    raise AssertionError(
                        f"{request.code} != {HTTP_STATUS_REQUEST_CANCELLED} : "
                        "Cancelled request did not finish with the correct status code."
                    )
        finally:
            # Unblock any processing that might be shared between requests, if we
            # haven't already done so.
            deferred_patch.unblock_awaits()

    assert False, "unreachable"  # noqa: B011