Browse Source

Register media servlets via regex. (#16419)

This converts the media servlet URLs in the same way as
(most) of the rest of Synapse. This will give more flexibility
in the versions each endpoint exists under.
tags/v1.95.0rc1
Patrick Cloke 7 months ago
committed by GitHub
parent
commit
26b960b08b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 297 additions and 337 deletions
  1. +1
    -0
      changelog.d/16419.misc
  2. +1
    -1
      synapse/http/server.py
  3. +4
    -44
      synapse/media/_base.py
  4. +9
    -1
      synapse/media/media_repository.py
  5. +6
    -7
      synapse/rest/media/config_resource.py
  6. +26
    -14
      synapse/rest/media/download_resource.py
  7. +22
    -11
      synapse/rest/media/media_repository_resource.py
  8. +9
    -17
      synapse/rest/media/preview_url_resource.py
  9. +20
    -15
      synapse/rest/media/thumbnail_resource.py
  10. +6
    -8
      synapse/rest/media/upload_resource.py
  11. +40
    -48
      tests/media/test_media_storage.py
  12. +3
    -3
      tests/media/test_url_previewer.py
  13. +14
    -5
      tests/replication/test_multi_media_repo.py
  14. +19
    -39
      tests/rest/admin/test_admin.py
  15. +21
    -50
      tests/rest/admin/test_media.py
  16. +8
    -7
      tests/rest/admin/test_statistics.py
  17. +10
    -11
      tests/rest/admin/test_user.py
  18. +2
    -4
      tests/rest/client/utils.py
  19. +74
    -50
      tests/rest/media/test_url_preview.py
  20. +2
    -2
      tests/unittest.py

+ 1
- 0
changelog.d/16419.misc View File

@@ -0,0 +1 @@
Update registration of media repository URLs.

+ 1
- 1
synapse/http/server.py View File

@@ -266,7 +266,7 @@ class HttpServer(Protocol):
def register_paths(
self,
method: str,
path_patterns: Iterable[Pattern],
path_patterns: Iterable[Pattern[str]],
callback: ServletCallback,
servlet_classname: str,
) -> None:


+ 4
- 44
synapse/media/_base.py View File

@@ -26,11 +26,11 @@ from twisted.internet.interfaces import IConsumer
from twisted.protocols.basic import FileSender
from twisted.web.server import Request

from synapse.api.errors import Codes, SynapseError, cs_error
from synapse.api.errors import Codes, cs_error
from synapse.http.server import finish_request, respond_with_json
from synapse.http.site import SynapseRequest
from synapse.logging.context import make_deferred_yieldable
from synapse.util.stringutils import is_ascii, parse_and_validate_server_name
from synapse.util.stringutils import is_ascii

logger = logging.getLogger(__name__)

@@ -84,52 +84,12 @@ INLINE_CONTENT_TYPES = [
]


def parse_media_id(request: Request) -> Tuple[str, str, Optional[str]]:
"""Parses the server name, media ID and optional file name from the request URI

Also performs some rough validation on the server name.

Args:
request: The `Request`.

Returns:
A tuple containing the parsed server name, media ID and optional file name.

Raises:
SynapseError(404): if parsing or validation fail for any reason
"""
try:
# The type on postpath seems incorrect in Twisted 21.2.0.
postpath: List[bytes] = request.postpath # type: ignore
assert postpath

# This allows users to append e.g. /test.png to the URL. Useful for
# clients that parse the URL to see content type.
server_name_bytes, media_id_bytes = postpath[:2]
server_name = server_name_bytes.decode("utf-8")
media_id = media_id_bytes.decode("utf8")

# Validate the server name, raising if invalid
parse_and_validate_server_name(server_name)

file_name = None
if len(postpath) > 2:
try:
file_name = urllib.parse.unquote(postpath[-1].decode("utf-8"))
except UnicodeDecodeError:
pass
return server_name, media_id, file_name
except Exception:
raise SynapseError(
404, "Invalid media id token %r" % (request.postpath,), Codes.UNKNOWN
)


def respond_404(request: SynapseRequest) -> None:
assert request.path is not None
respond_with_json(
request,
404,
cs_error("Not found %r" % (request.postpath,), code=Codes.NOT_FOUND),
cs_error("Not found '%s'" % (request.path.decode(),), code=Codes.NOT_FOUND),
send_cors=True,
)



+ 9
- 1
synapse/media/media_repository.py View File

@@ -48,6 +48,7 @@ from synapse.media.filepath import MediaFilePaths
from synapse.media.media_storage import MediaStorage
from synapse.media.storage_provider import StorageProviderWrapper
from synapse.media.thumbnailer import Thumbnailer, ThumbnailError
from synapse.media.url_previewer import UrlPreviewer
from synapse.metrics.background_process_metrics import run_as_background_process
from synapse.types import UserID
from synapse.util.async_helpers import Linearizer
@@ -114,7 +115,7 @@ class MediaRepository:
)
storage_providers.append(provider)

self.media_storage = MediaStorage(
self.media_storage: MediaStorage = MediaStorage(
self.hs, self.primary_base_path, self.filepaths, storage_providers
)

@@ -142,6 +143,13 @@ class MediaRepository:
MEDIA_RETENTION_CHECK_PERIOD_MS,
)

if hs.config.media.url_preview_enabled:
self.url_previewer: Optional[UrlPreviewer] = UrlPreviewer(
hs, self, self.media_storage
)
else:
self.url_previewer = None

def _start_update_recently_accessed(self) -> Deferred:
return run_as_background_process(
"update_recently_accessed_media", self._update_recently_accessed


+ 6
- 7
synapse/rest/media/config_resource.py View File

@@ -14,17 +14,19 @@
# limitations under the License.
#

import re
from typing import TYPE_CHECKING

from synapse.http.server import DirectServeJsonResource, respond_with_json
from synapse.http.server import respond_with_json
from synapse.http.servlet import RestServlet
from synapse.http.site import SynapseRequest

if TYPE_CHECKING:
from synapse.server import HomeServer


class MediaConfigResource(DirectServeJsonResource):
isLeaf = True
class MediaConfigResource(RestServlet):
PATTERNS = [re.compile("/_matrix/media/(r0|v3|v1)/config$")]

def __init__(self, hs: "HomeServer"):
super().__init__()
@@ -33,9 +35,6 @@ class MediaConfigResource(DirectServeJsonResource):
self.auth = hs.get_auth()
self.limits_dict = {"m.upload.size": config.media.max_upload_size}

async def _async_render_GET(self, request: SynapseRequest) -> None:
async def on_GET(self, request: SynapseRequest) -> None:
await self.auth.get_user_by_req(request)
respond_with_json(request, 200, self.limits_dict, send_cors=True)

async def _async_render_OPTIONS(self, request: SynapseRequest) -> None:
respond_with_json(request, 200, {}, send_cors=True)

+ 26
- 14
synapse/rest/media/download_resource.py View File

@@ -13,16 +13,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
from typing import TYPE_CHECKING
import re
from typing import TYPE_CHECKING, Optional

from synapse.http.server import (
DirectServeJsonResource,
set_corp_headers,
set_cors_headers,
)
from synapse.http.servlet import parse_boolean
from synapse.http.server import set_corp_headers, set_cors_headers
from synapse.http.servlet import RestServlet, parse_boolean
from synapse.http.site import SynapseRequest
from synapse.media._base import parse_media_id, respond_404
from synapse.media._base import respond_404
from synapse.util.stringutils import parse_and_validate_server_name

if TYPE_CHECKING:
from synapse.media.media_repository import MediaRepository
@@ -31,15 +29,28 @@ if TYPE_CHECKING:
logger = logging.getLogger(__name__)


class DownloadResource(DirectServeJsonResource):
isLeaf = True
class DownloadResource(RestServlet):
PATTERNS = [
re.compile(
"/_matrix/media/(r0|v3|v1)/download/(?P<server_name>[^/]*)/(?P<media_id>[^/]*)(/(?P<file_name>[^/]*))?$"
)
]

def __init__(self, hs: "HomeServer", media_repo: "MediaRepository"):
super().__init__()
self.media_repo = media_repo
self._is_mine_server_name = hs.is_mine_server_name

async def _async_render_GET(self, request: SynapseRequest) -> None:
async def on_GET(
self,
request: SynapseRequest,
server_name: str,
media_id: str,
file_name: Optional[str] = None,
) -> None:
# Validate the server name, raising if invalid
parse_and_validate_server_name(server_name)

set_cors_headers(request)
set_corp_headers(request)
request.setHeader(
@@ -58,9 +69,8 @@ class DownloadResource(DirectServeJsonResource):
b"Referrer-Policy",
b"no-referrer",
)
server_name, media_id, name = parse_media_id(request)
if self._is_mine_server_name(server_name):
await self.media_repo.get_local_media(request, media_id, name)
await self.media_repo.get_local_media(request, media_id, file_name)
else:
allow_remote = parse_boolean(request, "allow_remote", default=True)
if not allow_remote:
@@ -72,4 +82,6 @@ class DownloadResource(DirectServeJsonResource):
respond_404(request)
return

await self.media_repo.get_remote_media(request, server_name, media_id, name)
await self.media_repo.get_remote_media(
request, server_name, media_id, file_name
)

+ 22
- 11
synapse/rest/media/media_repository_resource.py View File

@@ -15,7 +15,7 @@
from typing import TYPE_CHECKING

from synapse.config._base import ConfigError
from synapse.http.server import UnrecognizedRequestResource
from synapse.http.server import HttpServer, JsonResource

from .config_resource import MediaConfigResource
from .download_resource import DownloadResource
@@ -27,7 +27,7 @@ if TYPE_CHECKING:
from synapse.server import HomeServer


class MediaRepositoryResource(UnrecognizedRequestResource):
class MediaRepositoryResource(JsonResource):
"""File uploading and downloading.

Uploads are POSTed to a resource which returns a token which is used to GET
@@ -70,6 +70,11 @@ class MediaRepositoryResource(UnrecognizedRequestResource):
width and height are close to the requested size and the aspect matches
the requested size. The client should scale the image if it needs to fit
within a given rectangle.

This gets mounted at various points under /_matrix/media, including:
* /_matrix/media/r0
* /_matrix/media/v1
* /_matrix/media/v3
"""

def __init__(self, hs: "HomeServer"):
@@ -77,17 +82,23 @@ class MediaRepositoryResource(UnrecognizedRequestResource):
if not hs.config.media.can_load_media_repo:
raise ConfigError("Synapse is not configured to use a media repo.")

super().__init__()
JsonResource.__init__(self, hs, canonical_json=False)
self.register_servlets(self, hs)

@staticmethod
def register_servlets(http_server: HttpServer, hs: "HomeServer") -> None:
media_repo = hs.get_media_repository()

self.putChild(b"upload", UploadResource(hs, media_repo))
self.putChild(b"download", DownloadResource(hs, media_repo))
self.putChild(
b"thumbnail", ThumbnailResource(hs, media_repo, media_repo.media_storage)
# Note that many of these should not exist as v1 endpoints, but empirically
# a lot of traffic still goes to them.

UploadResource(hs, media_repo).register(http_server)
DownloadResource(hs, media_repo).register(http_server)
ThumbnailResource(hs, media_repo, media_repo.media_storage).register(
http_server
)
if hs.config.media.url_preview_enabled:
self.putChild(
b"preview_url",
PreviewUrlResource(hs, media_repo, media_repo.media_storage),
PreviewUrlResource(hs, media_repo, media_repo.media_storage).register(
http_server
)
self.putChild(b"config", MediaConfigResource(hs))
MediaConfigResource(hs).register(http_server)

+ 9
- 17
synapse/rest/media/preview_url_resource.py View File

@@ -13,24 +13,20 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import re
from typing import TYPE_CHECKING

from synapse.http.server import (
DirectServeJsonResource,
respond_with_json,
respond_with_json_bytes,
)
from synapse.http.servlet import parse_integer, parse_string
from synapse.http.server import respond_with_json_bytes
from synapse.http.servlet import RestServlet, parse_integer, parse_string
from synapse.http.site import SynapseRequest
from synapse.media.media_storage import MediaStorage
from synapse.media.url_previewer import UrlPreviewer

if TYPE_CHECKING:
from synapse.media.media_repository import MediaRepository
from synapse.server import HomeServer


class PreviewUrlResource(DirectServeJsonResource):
class PreviewUrlResource(RestServlet):
"""
The `GET /_matrix/media/r0/preview_url` endpoint provides a generic preview API
for URLs which outputs Open Graph (https://ogp.me/) responses (with some Matrix
@@ -48,7 +44,7 @@ class PreviewUrlResource(DirectServeJsonResource):
* Matrix cannot be used to distribute the metadata between homeservers.
"""

isLeaf = True
PATTERNS = [re.compile("/_matrix/media/(r0|v3|v1)/preview_url$")]

def __init__(
self,
@@ -62,14 +58,10 @@ class PreviewUrlResource(DirectServeJsonResource):
self.clock = hs.get_clock()
self.media_repo = media_repo
self.media_storage = media_storage
assert self.media_repo.url_previewer is not None
self.url_previewer = self.media_repo.url_previewer

self._url_previewer = UrlPreviewer(hs, media_repo, media_storage)

async def _async_render_OPTIONS(self, request: SynapseRequest) -> None:
request.setHeader(b"Allow", b"OPTIONS, GET")
respond_with_json(request, 200, {}, send_cors=True)

async def _async_render_GET(self, request: SynapseRequest) -> None:
async def on_GET(self, request: SynapseRequest) -> None:
# XXX: if get_user_by_req fails, what should we do in an async render?
requester = await self.auth.get_user_by_req(request)
url = parse_string(request, "url", required=True)
@@ -77,5 +69,5 @@ class PreviewUrlResource(DirectServeJsonResource):
if ts is None:
ts = self.clock.time_msec()

og = await self._url_previewer.preview(url, requester.user, ts)
og = await self.url_previewer.preview(url, requester.user, ts)
respond_with_json_bytes(request, 200, og, send_cors=True)

+ 20
- 15
synapse/rest/media/thumbnail_resource.py View File

@@ -13,29 +13,24 @@
# See the License for the specific language governing permissions and
# limitations under the License.


import logging
import re
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple

from synapse.api.errors import Codes, SynapseError, cs_error
from synapse.config.repository import THUMBNAIL_SUPPORTED_MEDIA_FORMAT_MAP
from synapse.http.server import (
DirectServeJsonResource,
respond_with_json,
set_corp_headers,
set_cors_headers,
)
from synapse.http.servlet import parse_integer, parse_string
from synapse.http.server import respond_with_json, set_corp_headers, set_cors_headers
from synapse.http.servlet import RestServlet, parse_integer, parse_string
from synapse.http.site import SynapseRequest
from synapse.media._base import (
FileInfo,
ThumbnailInfo,
parse_media_id,
respond_404,
respond_with_file,
respond_with_responder,
)
from synapse.media.media_storage import MediaStorage
from synapse.util.stringutils import parse_and_validate_server_name

if TYPE_CHECKING:
from synapse.media.media_repository import MediaRepository
@@ -44,8 +39,12 @@ if TYPE_CHECKING:
logger = logging.getLogger(__name__)


class ThumbnailResource(DirectServeJsonResource):
isLeaf = True
class ThumbnailResource(RestServlet):
PATTERNS = [
re.compile(
"/_matrix/media/(r0|v3|v1)/thumbnail/(?P<server_name>[^/]*)/(?P<media_id>[^/]*)$"
)
]

def __init__(
self,
@@ -60,12 +59,17 @@ class ThumbnailResource(DirectServeJsonResource):
self.media_storage = media_storage
self.dynamic_thumbnails = hs.config.media.dynamic_thumbnails
self._is_mine_server_name = hs.is_mine_server_name
self._server_name = hs.hostname
self.prevent_media_downloads_from = hs.config.media.prevent_media_downloads_from

async def _async_render_GET(self, request: SynapseRequest) -> None:
async def on_GET(
self, request: SynapseRequest, server_name: str, media_id: str
) -> None:
# Validate the server name, raising if invalid
parse_and_validate_server_name(server_name)

set_cors_headers(request)
set_corp_headers(request)
server_name, media_id, _ = parse_media_id(request)
width = parse_integer(request, "width", required=True)
height = parse_integer(request, "height", required=True)
method = parse_string(request, "method", "scale")
@@ -418,13 +422,14 @@ class ThumbnailResource(DirectServeJsonResource):
# `dynamic_thumbnails` is disabled.
logger.info("Failed to find any generated thumbnails")

assert request.path is not None
respond_with_json(
request,
400,
cs_error(
"Cannot find any thumbnails for the requested media (%r). This might mean the media is not a supported_media_format=(%s) or that thumbnailing failed for some other reason. (Dynamic thumbnails are disabled on this server.)"
"Cannot find any thumbnails for the requested media ('%s'). This might mean the media is not a supported_media_format=(%s) or that thumbnailing failed for some other reason. (Dynamic thumbnails are disabled on this server.)"
% (
request.postpath,
request.path.decode(),
", ".join(THUMBNAIL_SUPPORTED_MEDIA_FORMAT_MAP.keys()),
),
code=Codes.UNKNOWN,


+ 6
- 8
synapse/rest/media/upload_resource.py View File

@@ -14,11 +14,12 @@
# limitations under the License.

import logging
import re
from typing import IO, TYPE_CHECKING, Dict, List, Optional

from synapse.api.errors import Codes, SynapseError
from synapse.http.server import DirectServeJsonResource, respond_with_json
from synapse.http.servlet import parse_bytes_from_args
from synapse.http.server import respond_with_json
from synapse.http.servlet import RestServlet, parse_bytes_from_args
from synapse.http.site import SynapseRequest
from synapse.media.media_storage import SpamMediaException

@@ -29,8 +30,8 @@ if TYPE_CHECKING:
logger = logging.getLogger(__name__)


class UploadResource(DirectServeJsonResource):
isLeaf = True
class UploadResource(RestServlet):
PATTERNS = [re.compile("/_matrix/media/(r0|v3|v1)/upload")]

def __init__(self, hs: "HomeServer", media_repo: "MediaRepository"):
super().__init__()
@@ -43,10 +44,7 @@ class UploadResource(DirectServeJsonResource):
self.max_upload_size = hs.config.media.max_upload_size
self.clock = hs.get_clock()

async def _async_render_OPTIONS(self, request: SynapseRequest) -> None:
respond_with_json(request, 200, {}, send_cors=True)

async def _async_render_POST(self, request: SynapseRequest) -> None:
async def on_POST(self, request: SynapseRequest) -> None:
requester = await self.auth.get_user_by_req(request)
raw_content_length = request.getHeader("Content-Length")
if raw_content_length is None:


+ 40
- 48
tests/media/test_media_storage.py View File

@@ -28,6 +28,7 @@ from typing_extensions import Literal
from twisted.internet import defer
from twisted.internet.defer import Deferred
from twisted.test.proto_helpers import MemoryReactor
from twisted.web.resource import Resource

from synapse.api.errors import Codes
from synapse.events import EventBase
@@ -41,12 +42,13 @@ from synapse.module_api import ModuleApi
from synapse.module_api.callbacks.spamchecker_callbacks import load_legacy_spam_checkers
from synapse.rest import admin
from synapse.rest.client import login
from synapse.rest.media.thumbnail_resource import ThumbnailResource
from synapse.server import HomeServer
from synapse.types import JsonDict, RoomAlias
from synapse.util import Clock

from tests import unittest
from tests.server import FakeChannel, FakeSite, make_request
from tests.server import FakeChannel
from tests.test_utils import SMALL_PNG
from tests.utils import default_config

@@ -288,22 +290,22 @@ class MediaRepoTests(unittest.HomeserverTestCase):
return hs

def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
media_resource = hs.get_media_repository_resource()
self.download_resource = media_resource.children[b"download"]
self.thumbnail_resource = media_resource.children[b"thumbnail"]
self.store = hs.get_datastores().main
self.media_repo = hs.get_media_repository()

self.media_id = "example.com/12345"

def create_resource_dict(self) -> Dict[str, Resource]:
resources = super().create_resource_dict()
resources["/_matrix/media"] = self.hs.get_media_repository_resource()
return resources

def _req(
self, content_disposition: Optional[bytes], include_content_type: bool = True
) -> FakeChannel:
channel = make_request(
self.reactor,
FakeSite(self.download_resource, self.reactor),
channel = self.make_request(
"GET",
self.media_id,
f"/_matrix/media/v3/download/{self.media_id}",
shorthand=False,
await_result=False,
)
@@ -481,11 +483,9 @@ class MediaRepoTests(unittest.HomeserverTestCase):
# Fetching again should work, without re-requesting the image from the
# remote.
params = "?width=32&height=32&method=scale"
channel = make_request(
self.reactor,
FakeSite(self.thumbnail_resource, self.reactor),
channel = self.make_request(
"GET",
self.media_id + params,
f"/_matrix/media/v3/thumbnail/{self.media_id}{params}",
shorthand=False,
await_result=False,
)
@@ -511,11 +511,9 @@ class MediaRepoTests(unittest.HomeserverTestCase):
)
shutil.rmtree(thumbnail_dir, ignore_errors=True)

channel = make_request(
self.reactor,
FakeSite(self.thumbnail_resource, self.reactor),
channel = self.make_request(
"GET",
self.media_id + params,
f"/_matrix/media/v3/thumbnail/{self.media_id}{params}",
shorthand=False,
await_result=False,
)
@@ -549,11 +547,9 @@ class MediaRepoTests(unittest.HomeserverTestCase):
"""

params = "?width=32&height=32&method=" + method
channel = make_request(
self.reactor,
FakeSite(self.thumbnail_resource, self.reactor),
channel = self.make_request(
"GET",
self.media_id + params,
f"/_matrix/media/r0/thumbnail/{self.media_id}{params}",
shorthand=False,
await_result=False,
)
@@ -590,7 +586,7 @@ class MediaRepoTests(unittest.HomeserverTestCase):
channel.json_body,
{
"errcode": "M_UNKNOWN",
"error": "Cannot find any thumbnails for the requested media ([b'example.com', b'12345']). This might mean the media is not a supported_media_format=(image/jpeg, image/jpg, image/webp, image/gif, image/png) or that thumbnailing failed for some other reason. (Dynamic thumbnails are disabled on this server.)",
"error": "Cannot find any thumbnails for the requested media ('/_matrix/media/r0/thumbnail/example.com/12345'). This might mean the media is not a supported_media_format=(image/jpeg, image/jpg, image/webp, image/gif, image/png) or that thumbnailing failed for some other reason. (Dynamic thumbnails are disabled on this server.)",
},
)
else:
@@ -600,7 +596,7 @@ class MediaRepoTests(unittest.HomeserverTestCase):
channel.json_body,
{
"errcode": "M_NOT_FOUND",
"error": "Not found [b'example.com', b'12345']",
"error": "Not found '/_matrix/media/r0/thumbnail/example.com/12345'",
},
)

@@ -609,12 +605,17 @@ class MediaRepoTests(unittest.HomeserverTestCase):
"""Test that choosing between thumbnails with the same quality rating succeeds.

We are not particular about which thumbnail is chosen."""
media_repo = self.hs.get_media_repository()
thumbnail_resouce = ThumbnailResource(
self.hs, media_repo, media_repo.media_storage
)

self.assertIsNotNone(
self.thumbnail_resource._select_thumbnail(
thumbnail_resouce._select_thumbnail(
desired_width=desired_size,
desired_height=desired_size,
desired_method=method,
desired_type=self.test_image.content_type,
desired_type=self.test_image.content_type, # type: ignore[arg-type]
# Provide two identical thumbnails which are guaranteed to have the same
# quality rating.
thumbnail_infos=[
@@ -636,7 +637,7 @@ class MediaRepoTests(unittest.HomeserverTestCase):
},
],
file_id=f"image{self.test_image.extension.decode()}",
url_cache=None,
url_cache=False,
server_name=None,
)
)
@@ -725,13 +726,13 @@ class SpamCheckerTestCaseLegacy(unittest.HomeserverTestCase):
self.user = self.register_user("user", "pass")
self.tok = self.login("user", "pass")

# Allow for uploading and downloading to/from the media repo
self.media_repo = hs.get_media_repository_resource()
self.download_resource = self.media_repo.children[b"download"]
self.upload_resource = self.media_repo.children[b"upload"]

load_legacy_spam_checkers(hs)

def create_resource_dict(self) -> Dict[str, Resource]:
resources = super().create_resource_dict()
resources["/_matrix/media"] = self.hs.get_media_repository_resource()
return resources

def default_config(self) -> Dict[str, Any]:
config = default_config("test")

@@ -751,9 +752,7 @@ class SpamCheckerTestCaseLegacy(unittest.HomeserverTestCase):

def test_upload_innocent(self) -> None:
"""Attempt to upload some innocent data that should be allowed."""
self.helper.upload_media(
self.upload_resource, SMALL_PNG, tok=self.tok, expect_code=200
)
self.helper.upload_media(SMALL_PNG, tok=self.tok, expect_code=200)

def test_upload_ban(self) -> None:
"""Attempt to upload some data that includes bytes "evil", which should
@@ -762,9 +761,7 @@ class SpamCheckerTestCaseLegacy(unittest.HomeserverTestCase):

data = b"Some evil data"

self.helper.upload_media(
self.upload_resource, data, tok=self.tok, expect_code=400
)
self.helper.upload_media(data, tok=self.tok, expect_code=400)


EVIL_DATA = b"Some evil data"
@@ -781,15 +778,15 @@ class SpamCheckerTestCase(unittest.HomeserverTestCase):
self.user = self.register_user("user", "pass")
self.tok = self.login("user", "pass")

# Allow for uploading and downloading to/from the media repo
self.media_repo = hs.get_media_repository_resource()
self.download_resource = self.media_repo.children[b"download"]
self.upload_resource = self.media_repo.children[b"upload"]

hs.get_module_api().register_spam_checker_callbacks(
check_media_file_for_spam=self.check_media_file_for_spam
)

def create_resource_dict(self) -> Dict[str, Resource]:
resources = super().create_resource_dict()
resources["/_matrix/media"] = self.hs.get_media_repository_resource()
return resources

async def check_media_file_for_spam(
self, file_wrapper: ReadableFileWrapper, file_info: FileInfo
) -> Union[Codes, Literal["NOT_SPAM"], Tuple[Codes, JsonDict]]:
@@ -805,21 +802,16 @@ class SpamCheckerTestCase(unittest.HomeserverTestCase):

def test_upload_innocent(self) -> None:
"""Attempt to upload some innocent data that should be allowed."""
self.helper.upload_media(
self.upload_resource, SMALL_PNG, tok=self.tok, expect_code=200
)
self.helper.upload_media(SMALL_PNG, tok=self.tok, expect_code=200)

def test_upload_ban(self) -> None:
"""Attempt to upload some data that includes bytes "evil", which should
get rejected by the spam checker.
"""

self.helper.upload_media(
self.upload_resource, EVIL_DATA, tok=self.tok, expect_code=400
)
self.helper.upload_media(EVIL_DATA, tok=self.tok, expect_code=400)

self.helper.upload_media(
self.upload_resource,
EVIL_DATA_EXPERIMENT,
tok=self.tok,
expect_code=400,


+ 3
- 3
tests/media/test_url_previewer.py View File

@@ -61,9 +61,9 @@ class URLPreviewTests(unittest.HomeserverTestCase):
return self.setup_test_homeserver(config=config)

def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
media_repo_resource = hs.get_media_repository_resource()
preview_url = media_repo_resource.children[b"preview_url"]
self.url_previewer = preview_url._url_previewer
media_repo = hs.get_media_repository()
assert media_repo.url_previewer is not None
self.url_previewer = media_repo.url_previewer

def test_all_urls_allowed(self) -> None:
self.assertFalse(self.url_previewer._is_url_blocked("http://matrix.org"))


+ 14
- 5
tests/replication/test_multi_media_repo.py View File

@@ -13,7 +13,7 @@
# limitations under the License.
import logging
import os
from typing import Optional, Tuple
from typing import Any, Optional, Tuple

from twisted.internet.interfaces import IOpenSSLServerConnectionCreator
from twisted.internet.protocol import Factory
@@ -29,7 +29,7 @@ from synapse.util import Clock

from tests.http import TestServerTLSConnectionFactory, get_test_ca_cert_file
from tests.replication._base import BaseMultiWorkerStreamTestCase
from tests.server import FakeChannel, FakeSite, FakeTransport, make_request
from tests.server import FakeChannel, FakeTransport, make_request
from tests.test_utils import SMALL_PNG

logger = logging.getLogger(__name__)
@@ -56,6 +56,16 @@ class MediaRepoShardTestCase(BaseMultiWorkerStreamTestCase):
conf["federation_custom_ca_list"] = [get_test_ca_cert_file()]
return conf

def make_worker_hs(
self, worker_app: str, extra_config: Optional[dict] = None, **kwargs: Any
) -> HomeServer:
worker_hs = super().make_worker_hs(worker_app, extra_config, **kwargs)
# Force the media paths onto the replication resource.
worker_hs.get_media_repository_resource().register_servlets(
self._hs_to_site[worker_hs].resource, worker_hs
)
return worker_hs

def _get_media_req(
self, hs: HomeServer, target: str, media_id: str
) -> Tuple[FakeChannel, Request]:
@@ -68,12 +78,11 @@ class MediaRepoShardTestCase(BaseMultiWorkerStreamTestCase):
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, self.reactor),
self._hs_to_site[hs],
"GET",
f"/{target}/{media_id}",
f"/_matrix/media/r0/download/{target}/{media_id}",
shorthand=False,
access_token=self.access_token,
await_result=False,


+ 19
- 39
tests/rest/admin/test_admin.py View File

@@ -13,10 +13,12 @@
# limitations under the License.

import urllib.parse
from typing import Dict

from parameterized import parameterized

from twisted.test.proto_helpers import MemoryReactor
from twisted.web.resource import Resource

import synapse.rest.admin
from synapse.http.server import JsonResource
@@ -26,7 +28,6 @@ from synapse.server import HomeServer
from synapse.util import Clock

from tests import unittest
from tests.server import FakeSite, make_request
from tests.test_utils import SMALL_PNG


@@ -55,21 +56,18 @@ class QuarantineMediaTestCase(unittest.HomeserverTestCase):
room.register_servlets,
]

def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
# Allow for uploading and downloading to/from the media repo
self.media_repo = hs.get_media_repository_resource()
self.download_resource = self.media_repo.children[b"download"]
self.upload_resource = self.media_repo.children[b"upload"]
def create_resource_dict(self) -> Dict[str, Resource]:
resources = super().create_resource_dict()
resources["/_matrix/media"] = self.hs.get_media_repository_resource()
return resources

def _ensure_quarantined(
self, admin_user_tok: str, server_and_media_id: str
) -> None:
"""Ensure a piece of media is quarantined when trying to access it."""
channel = make_request(
self.reactor,
FakeSite(self.download_resource, self.reactor),
channel = self.make_request(
"GET",
server_and_media_id,
f"/_matrix/media/v3/download/{server_and_media_id}",
shorthand=False,
access_token=admin_user_tok,
)
@@ -117,20 +115,16 @@ class QuarantineMediaTestCase(unittest.HomeserverTestCase):
non_admin_user_tok = self.login("id_nonadmin", "pass")

# Upload some media into the room
response = self.helper.upload_media(
self.upload_resource, SMALL_PNG, tok=admin_user_tok
)
response = self.helper.upload_media(SMALL_PNG, tok=admin_user_tok)

# Extract media ID from the response
server_name_and_media_id = response["content_uri"][6:] # Cut off 'mxc://'
server_name, media_id = server_name_and_media_id.split("/")

# Attempt to access the media
channel = make_request(
self.reactor,
FakeSite(self.download_resource, self.reactor),
channel = self.make_request(
"GET",
server_name_and_media_id,
f"/_matrix/media/v3/download/{server_name_and_media_id}",
shorthand=False,
access_token=non_admin_user_tok,
)
@@ -173,12 +167,8 @@ class QuarantineMediaTestCase(unittest.HomeserverTestCase):
self.helper.join(room_id, non_admin_user, tok=non_admin_user_tok)

# 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
)
response_1 = self.helper.upload_media(SMALL_PNG, tok=non_admin_user_tok)
response_2 = self.helper.upload_media(SMALL_PNG, tok=non_admin_user_tok)

# Extract mxcs
mxc_1 = response_1["content_uri"]
@@ -227,12 +217,8 @@ class QuarantineMediaTestCase(unittest.HomeserverTestCase):
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
)
response_1 = self.helper.upload_media(SMALL_PNG, tok=non_admin_user_tok)
response_2 = self.helper.upload_media(SMALL_PNG, tok=non_admin_user_tok)

# Extract media IDs
server_and_media_id_1 = response_1["content_uri"][6:]
@@ -265,12 +251,8 @@ class QuarantineMediaTestCase(unittest.HomeserverTestCase):
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
)
response_1 = self.helper.upload_media(SMALL_PNG, tok=non_admin_user_tok)
response_2 = self.helper.upload_media(SMALL_PNG, tok=non_admin_user_tok)

# Extract media IDs
server_and_media_id_1 = response_1["content_uri"][6:]
@@ -304,11 +286,9 @@ class QuarantineMediaTestCase(unittest.HomeserverTestCase):
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),
channel = self.make_request(
"GET",
server_and_media_id_2,
f"/_matrix/media/v3/download/{server_and_media_id_2}",
shorthand=False,
access_token=non_admin_user_tok,
)


+ 21
- 50
tests/rest/admin/test_media.py View File

@@ -13,10 +13,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import os
from typing import Dict

from parameterized import parameterized

from twisted.test.proto_helpers import MemoryReactor
from twisted.web.resource import Resource

import synapse.rest.admin
from synapse.api.errors import Codes
@@ -26,22 +28,27 @@ from synapse.server import HomeServer
from synapse.util import Clock

from tests import unittest
from tests.server import FakeSite, make_request
from tests.test_utils import SMALL_PNG

VALID_TIMESTAMP = 1609459200000 # 2021-01-01 in milliseconds
INVALID_TIMESTAMP_IN_S = 1893456000 # 2030-01-01 in seconds


class DeleteMediaByIDTestCase(unittest.HomeserverTestCase):
class _AdminMediaTests(unittest.HomeserverTestCase):
servlets = [
synapse.rest.admin.register_servlets,
synapse.rest.admin.register_servlets_for_media_repo,
login.register_servlets,
]

def create_resource_dict(self) -> Dict[str, Resource]:
resources = super().create_resource_dict()
resources["/_matrix/media"] = self.hs.get_media_repository_resource()
return resources


class DeleteMediaByIDTestCase(_AdminMediaTests):
def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
self.media_repo = hs.get_media_repository_resource()
self.server_name = hs.hostname

self.admin_user = self.register_user("admin", "pass", admin=True)
@@ -117,12 +124,8 @@ class DeleteMediaByIDTestCase(unittest.HomeserverTestCase):
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=200,
@@ -134,11 +137,9 @@ class DeleteMediaByIDTestCase(unittest.HomeserverTestCase):
self.assertEqual(server_name, self.server_name)

# Attempt to access media
channel = make_request(
self.reactor,
FakeSite(download_resource, self.reactor),
channel = self.make_request(
"GET",
server_and_media_id,
f"/_matrix/media/v3/download/{server_and_media_id}",
shorthand=False,
access_token=self.admin_user_tok,
)
@@ -173,11 +174,9 @@ class DeleteMediaByIDTestCase(unittest.HomeserverTestCase):
)

# Attempt to access media
channel = make_request(
self.reactor,
FakeSite(download_resource, self.reactor),
channel = self.make_request(
"GET",
server_and_media_id,
f"/_matrix/media/v3/download/{server_and_media_id}",
shorthand=False,
access_token=self.admin_user_tok,
)
@@ -194,7 +193,7 @@ class DeleteMediaByIDTestCase(unittest.HomeserverTestCase):
self.assertFalse(os.path.exists(local_path))


class DeleteMediaByDateSizeTestCase(unittest.HomeserverTestCase):
class DeleteMediaByDateSizeTestCase(_AdminMediaTests):
servlets = [
synapse.rest.admin.register_servlets,
synapse.rest.admin.register_servlets_for_media_repo,
@@ -529,11 +528,8 @@ class DeleteMediaByDateSizeTestCase(unittest.HomeserverTestCase):
"""
Create a media and return media_id and server_and_media_id
"""
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=200,
@@ -553,16 +549,12 @@ class DeleteMediaByDateSizeTestCase(unittest.HomeserverTestCase):
"""
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, self.reactor),
channel = self.make_request(
"GET",
server_and_media_id,
f"/_matrix/media/v3/download/{server_and_media_id}",
shorthand=False,
access_token=self.admin_user_tok,
)
@@ -591,27 +583,16 @@ class DeleteMediaByDateSizeTestCase(unittest.HomeserverTestCase):
self.assertFalse(os.path.exists(local_path))


class QuarantineMediaByIDTestCase(unittest.HomeserverTestCase):
servlets = [
synapse.rest.admin.register_servlets,
synapse.rest.admin.register_servlets_for_media_repo,
login.register_servlets,
]

class QuarantineMediaByIDTestCase(_AdminMediaTests):
def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
media_repo = hs.get_media_repository_resource()
self.store = hs.get_datastores().main
self.server_name = hs.hostname

self.admin_user = self.register_user("admin", "pass", admin=True)
self.admin_user_tok = self.login("admin", "pass")

# Create media
upload_resource = 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=200,
@@ -720,26 +701,16 @@ class QuarantineMediaByIDTestCase(unittest.HomeserverTestCase):
self.assertFalse(media_info["quarantined_by"])


class ProtectMediaByIDTestCase(unittest.HomeserverTestCase):
servlets = [
synapse.rest.admin.register_servlets,
synapse.rest.admin.register_servlets_for_media_repo,
login.register_servlets,
]

class ProtectMediaByIDTestCase(_AdminMediaTests):
def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
media_repo = hs.get_media_repository_resource()
hs.get_media_repository_resource()
self.store = hs.get_datastores().main

self.admin_user = self.register_user("admin", "pass", admin=True)
self.admin_user_tok = self.login("admin", "pass")

# Create media
upload_resource = 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=200,
@@ -816,7 +787,7 @@ class ProtectMediaByIDTestCase(unittest.HomeserverTestCase):
self.assertFalse(media_info["safe_from_quarantine"])


class PurgeMediaCacheTestCase(unittest.HomeserverTestCase):
class PurgeMediaCacheTestCase(_AdminMediaTests):
servlets = [
synapse.rest.admin.register_servlets,
synapse.rest.admin.register_servlets_for_media_repo,


+ 8
- 7
tests/rest/admin/test_statistics.py View File

@@ -12,9 +12,10 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from typing import List, Optional
from typing import Dict, List, Optional

from twisted.test.proto_helpers import MemoryReactor
from twisted.web.resource import Resource

import synapse.rest.admin
from synapse.api.errors import Codes
@@ -34,8 +35,6 @@ class UserMediaStatisticsTestCase(unittest.HomeserverTestCase):
]

def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
self.media_repo = hs.get_media_repository_resource()

self.admin_user = self.register_user("admin", "pass", admin=True)
self.admin_user_tok = self.login("admin", "pass")

@@ -44,6 +43,11 @@ class UserMediaStatisticsTestCase(unittest.HomeserverTestCase):

self.url = "/_synapse/admin/v1/statistics/users/media"

def create_resource_dict(self) -> Dict[str, Resource]:
resources = super().create_resource_dict()
resources["/_matrix/media"] = self.hs.get_media_repository_resource()
return resources

def test_no_auth(self) -> None:
"""
Try to list users without authentication.
@@ -470,12 +474,9 @@ class UserMediaStatisticsTestCase(unittest.HomeserverTestCase):
user_token: Access token of the user
number_media: Number of media to be created for the user
"""
upload_resource = self.media_repo.children[b"upload"]
for _ in range(number_media):
# Upload some media into the room
self.helper.upload_media(
upload_resource, SMALL_PNG, tok=user_token, expect_code=200
)
self.helper.upload_media(SMALL_PNG, tok=user_token, expect_code=200)

def _check_fields(self, content: List[JsonDict]) -> None:
"""Checks that all attributes are present in content


+ 10
- 11
tests/rest/admin/test_user.py View File

@@ -17,12 +17,13 @@ import hmac
import os
import urllib.parse
from binascii import unhexlify
from typing import List, Optional
from typing import Dict, List, Optional
from unittest.mock import AsyncMock, Mock, patch

from parameterized import parameterized, parameterized_class

from twisted.test.proto_helpers import MemoryReactor
from twisted.web.resource import Resource

import synapse.rest.admin
from synapse.api.constants import ApprovalNoticeMedium, LoginType, UserTypes
@@ -45,7 +46,6 @@ from synapse.types import JsonDict, UserID, create_requester
from synapse.util import Clock

from tests import unittest
from tests.server import FakeSite, make_request
from tests.test_utils import SMALL_PNG
from tests.unittest import override_config

@@ -3421,7 +3421,6 @@ class UserMediaRestTestCase(unittest.HomeserverTestCase):

def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
self.store = hs.get_datastores().main
self.media_repo = hs.get_media_repository_resource()
self.filepaths = MediaFilePaths(hs.config.media.media_store_path)

self.admin_user = self.register_user("admin", "pass", admin=True)
@@ -3432,6 +3431,11 @@ class UserMediaRestTestCase(unittest.HomeserverTestCase):
self.other_user
)

def create_resource_dict(self) -> Dict[str, Resource]:
resources = super().create_resource_dict()
resources["/_matrix/media"] = self.hs.get_media_repository_resource()
return resources

@parameterized.expand(["GET", "DELETE"])
def test_no_auth(self, method: str) -> None:
"""Try to list media of an user without authentication."""
@@ -3907,12 +3911,9 @@ class UserMediaRestTestCase(unittest.HomeserverTestCase):
Returns:
The ID of the newly created media.
"""
upload_resource = self.media_repo.children[b"upload"]
download_resource = self.media_repo.children[b"download"]

# Upload some media into the room
response = self.helper.upload_media(
upload_resource, image_data, user_token, filename, expect_code=200
image_data, user_token, filename, expect_code=200
)

# Extract media ID from the response
@@ -3920,11 +3921,9 @@ class UserMediaRestTestCase(unittest.HomeserverTestCase):
media_id = server_and_media_id.split("/")[1]

# Try to access a media and to create `last_access_ts`
channel = make_request(
self.reactor,
FakeSite(download_resource, self.reactor),
channel = self.make_request(
"GET",
server_and_media_id,
f"/_matrix/media/v3/download/{server_and_media_id}",
shorthand=False,
access_token=user_token,
)


+ 2
- 4
tests/rest/client/utils.py View File

@@ -37,7 +37,6 @@ import attr
from typing_extensions import Literal

from twisted.test.proto_helpers import MemoryReactorClock
from twisted.web.resource import Resource
from twisted.web.server import Site

from synapse.api.constants import Membership
@@ -45,7 +44,7 @@ from synapse.api.errors import Codes
from synapse.server import HomeServer
from synapse.types import JsonDict

from tests.server import FakeChannel, FakeSite, make_request
from tests.server import FakeChannel, make_request
from tests.test_utils.html_parsers import TestHtmlParser
from tests.test_utils.oidc import FakeAuthorizationGrant, FakeOidcServer

@@ -558,7 +557,6 @@ class RestHelper:

def upload_media(
self,
resource: Resource,
image_data: bytes,
tok: str,
filename: str = "test.png",
@@ -576,7 +574,7 @@ class RestHelper:
path = "/_matrix/media/r0/upload?filename=%s" % (filename,)
channel = make_request(
self.reactor,
FakeSite(resource, self.reactor),
self.site,
"POST",
path,
content=image_data,


+ 74
- 50
tests/rest/media/test_url_preview.py View File

@@ -24,10 +24,10 @@ from twisted.internet.address import IPv4Address, IPv6Address
from twisted.internet.error import DNSLookupError
from twisted.internet.interfaces import IAddress, IResolutionReceiver
from twisted.test.proto_helpers import AccumulatingProtocol, MemoryReactor
from twisted.web.resource import Resource

from synapse.config.oembed import OEmbedEndpointConfig
from synapse.media.url_previewer import IMAGE_CACHE_EXPIRY_MS
from synapse.rest.media.media_repository_resource import MediaRepositoryResource
from synapse.server import HomeServer
from synapse.types import JsonDict
from synapse.util import Clock
@@ -117,8 +117,8 @@ class URLPreviewTests(unittest.HomeserverTestCase):

def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
self.media_repo = hs.get_media_repository()
media_repo_resource = hs.get_media_repository_resource()
self.preview_url = media_repo_resource.children[b"preview_url"]
assert self.media_repo.url_previewer is not None
self.url_previewer = self.media_repo.url_previewer

self.lookups: Dict[str, Any] = {}

@@ -143,8 +143,15 @@ class URLPreviewTests(unittest.HomeserverTestCase):

self.reactor.nameResolver = Resolver() # type: ignore[assignment]

def create_test_resource(self) -> MediaRepositoryResource:
return self.hs.get_media_repository_resource()
def create_resource_dict(self) -> Dict[str, Resource]:
"""Create a resource tree for the test server

A resource tree is a mapping from path to twisted.web.resource.

The default implementation creates a JsonResource and calls each function in
`servlets` to register servlets against it.
"""
return {"/_matrix/media": self.hs.get_media_repository_resource()}

def _assert_small_png(self, json_body: JsonDict) -> None:
"""Assert properties from the SMALL_PNG test image."""
@@ -159,7 +166,7 @@ class URLPreviewTests(unittest.HomeserverTestCase):

channel = self.make_request(
"GET",
"preview_url?url=http://matrix.org",
"/_matrix/media/v3/preview_url?url=http://matrix.org",
shorthand=False,
await_result=False,
)
@@ -183,7 +190,9 @@ class URLPreviewTests(unittest.HomeserverTestCase):

# Check the cache returns the correct response
channel = self.make_request(
"GET", "preview_url?url=http://matrix.org", shorthand=False
"GET",
"/_matrix/media/v3/preview_url?url=http://matrix.org",
shorthand=False,
)

# Check the cache response has the same content
@@ -193,13 +202,15 @@ class URLPreviewTests(unittest.HomeserverTestCase):
)

# Clear the in-memory cache
self.assertIn("http://matrix.org", self.preview_url._url_previewer._cache)
self.preview_url._url_previewer._cache.pop("http://matrix.org")
self.assertNotIn("http://matrix.org", self.preview_url._url_previewer._cache)
self.assertIn("http://matrix.org", self.url_previewer._cache)
self.url_previewer._cache.pop("http://matrix.org")
self.assertNotIn("http://matrix.org", self.url_previewer._cache)

# Check the database cache returns the correct response
channel = self.make_request(
"GET", "preview_url?url=http://matrix.org", shorthand=False
"GET",
"/_matrix/media/v3/preview_url?url=http://matrix.org",
shorthand=False,
)

# Check the cache response has the same content
@@ -221,7 +232,7 @@ class URLPreviewTests(unittest.HomeserverTestCase):

channel = self.make_request(
"GET",
"preview_url?url=http://matrix.org",
"/_matrix/media/v3/preview_url?url=http://matrix.org",
shorthand=False,
await_result=False,
)
@@ -251,7 +262,7 @@ class URLPreviewTests(unittest.HomeserverTestCase):

channel = self.make_request(
"GET",
"preview_url?url=http://matrix.org",
"/_matrix/media/v3/preview_url?url=http://matrix.org",
shorthand=False,
await_result=False,
)
@@ -287,7 +298,7 @@ class URLPreviewTests(unittest.HomeserverTestCase):

channel = self.make_request(
"GET",
"preview_url?url=http://matrix.org",
"/_matrix/media/v3/preview_url?url=http://matrix.org",
shorthand=False,
await_result=False,
)
@@ -328,7 +339,7 @@ class URLPreviewTests(unittest.HomeserverTestCase):

channel = self.make_request(
"GET",
"preview_url?url=http://matrix.org",
"/_matrix/media/v3/preview_url?url=http://matrix.org",
shorthand=False,
await_result=False,
)
@@ -363,7 +374,7 @@ class URLPreviewTests(unittest.HomeserverTestCase):

channel = self.make_request(
"GET",
"preview_url?url=http://matrix.org",
"/_matrix/media/v3/preview_url?url=http://matrix.org",
shorthand=False,
await_result=False,
)
@@ -396,7 +407,7 @@ class URLPreviewTests(unittest.HomeserverTestCase):

channel = self.make_request(
"GET",
"preview_url?url=http://example.com",
"/_matrix/media/v3/preview_url?url=http://example.com",
shorthand=False,
await_result=False,
)
@@ -425,7 +436,9 @@ class URLPreviewTests(unittest.HomeserverTestCase):
self.lookups["example.com"] = [(IPv4Address, "192.168.1.1")]

channel = self.make_request(
"GET", "preview_url?url=http://example.com", shorthand=False
"GET",
"/_matrix/media/v3/preview_url?url=http://example.com",
shorthand=False,
)

# No requests made.
@@ -446,7 +459,9 @@ class URLPreviewTests(unittest.HomeserverTestCase):
self.lookups["example.com"] = [(IPv4Address, "1.1.1.2")]

channel = self.make_request(
"GET", "preview_url?url=http://example.com", shorthand=False
"GET",
"/_matrix/media/v3/preview_url?url=http://example.com",
shorthand=False,
)

self.assertEqual(channel.code, 502)
@@ -463,7 +478,9 @@ class URLPreviewTests(unittest.HomeserverTestCase):
Blocked IP addresses, accessed directly, are not spidered.
"""
channel = self.make_request(
"GET", "preview_url?url=http://192.168.1.1", shorthand=False
"GET",
"/_matrix/media/v3/preview_url?url=http://192.168.1.1",
shorthand=False,
)

# No requests made.
@@ -479,7 +496,7 @@ class URLPreviewTests(unittest.HomeserverTestCase):
Blocked IP ranges, accessed directly, are not spidered.
"""
channel = self.make_request(
"GET", "preview_url?url=http://1.1.1.2", shorthand=False
"GET", "/_matrix/media/v3/preview_url?url=http://1.1.1.2", shorthand=False
)

self.assertEqual(channel.code, 403)
@@ -497,7 +514,7 @@ class URLPreviewTests(unittest.HomeserverTestCase):

channel = self.make_request(
"GET",
"preview_url?url=http://example.com",
"/_matrix/media/v3/preview_url?url=http://example.com",
shorthand=False,
await_result=False,
)
@@ -533,7 +550,9 @@ class URLPreviewTests(unittest.HomeserverTestCase):
]

channel = self.make_request(
"GET", "preview_url?url=http://example.com", shorthand=False
"GET",
"/_matrix/media/v3/preview_url?url=http://example.com",
shorthand=False,
)
self.assertEqual(channel.code, 502)
self.assertEqual(
@@ -553,7 +572,9 @@ class URLPreviewTests(unittest.HomeserverTestCase):
]

channel = self.make_request(
"GET", "preview_url?url=http://example.com", shorthand=False
"GET",
"/_matrix/media/v3/preview_url?url=http://example.com",
shorthand=False,
)

# No requests made.
@@ -574,7 +595,9 @@ class URLPreviewTests(unittest.HomeserverTestCase):
self.lookups["example.com"] = [(IPv6Address, "2001:800::1")]

channel = self.make_request(
"GET", "preview_url?url=http://example.com", shorthand=False
"GET",
"/_matrix/media/v3/preview_url?url=http://example.com",
shorthand=False,
)

self.assertEqual(channel.code, 502)
@@ -591,10 +614,11 @@ class URLPreviewTests(unittest.HomeserverTestCase):
OPTIONS returns the OPTIONS.
"""
channel = self.make_request(
"OPTIONS", "preview_url?url=http://example.com", shorthand=False
"OPTIONS",
"/_matrix/media/v3/preview_url?url=http://example.com",
shorthand=False,
)
self.assertEqual(channel.code, 200)
self.assertEqual(channel.json_body, {})
self.assertEqual(channel.code, 204)

def test_accept_language_config_option(self) -> None:
"""
@@ -605,7 +629,7 @@ class URLPreviewTests(unittest.HomeserverTestCase):
# Build and make a request to the server
channel = self.make_request(
"GET",
"preview_url?url=http://example.com",
"/_matrix/media/v3/preview_url?url=http://example.com",
shorthand=False,
await_result=False,
)
@@ -658,7 +682,7 @@ class URLPreviewTests(unittest.HomeserverTestCase):

channel = self.make_request(
"GET",
"preview_url?url=http://matrix.org",
"/_matrix/media/v3/preview_url?url=http://matrix.org",
shorthand=False,
await_result=False,
)
@@ -708,7 +732,7 @@ class URLPreviewTests(unittest.HomeserverTestCase):

channel = self.make_request(
"GET",
"preview_url?url=http://matrix.org",
"/_matrix/media/v3/preview_url?url=http://matrix.org",
shorthand=False,
await_result=False,
)
@@ -750,7 +774,7 @@ class URLPreviewTests(unittest.HomeserverTestCase):

channel = self.make_request(
"GET",
"preview_url?url=http://matrix.org",
"/_matrix/media/v3/preview_url?url=http://matrix.org",
shorthand=False,
await_result=False,
)
@@ -790,7 +814,7 @@ class URLPreviewTests(unittest.HomeserverTestCase):

channel = self.make_request(
"GET",
"preview_url?url=http://matrix.org",
"/_matrix/media/v3/preview_url?url=http://matrix.org",
shorthand=False,
await_result=False,
)
@@ -831,7 +855,7 @@ class URLPreviewTests(unittest.HomeserverTestCase):

channel = self.make_request(
"GET",
f"preview_url?{query_params}",
f"/_matrix/media/v3/preview_url?{query_params}",
shorthand=False,
)
self.pump()
@@ -852,7 +876,7 @@ class URLPreviewTests(unittest.HomeserverTestCase):

channel = self.make_request(
"GET",
"preview_url?url=http://matrix.org",
"/_matrix/media/v3/preview_url?url=http://matrix.org",
shorthand=False,
await_result=False,
)
@@ -889,7 +913,7 @@ class URLPreviewTests(unittest.HomeserverTestCase):

channel = self.make_request(
"GET",
"preview_url?url=http://twitter.com/matrixdotorg/status/12345",
"/_matrix/media/v3/preview_url?url=http://twitter.com/matrixdotorg/status/12345",
shorthand=False,
await_result=False,
)
@@ -949,7 +973,7 @@ class URLPreviewTests(unittest.HomeserverTestCase):

channel = self.make_request(
"GET",
"preview_url?url=http://twitter.com/matrixdotorg/status/12345",
"/_matrix/media/v3/preview_url?url=http://twitter.com/matrixdotorg/status/12345",
shorthand=False,
await_result=False,
)
@@ -998,7 +1022,7 @@ class URLPreviewTests(unittest.HomeserverTestCase):

channel = self.make_request(
"GET",
"preview_url?url=http://www.hulu.com/watch/12345",
"/_matrix/media/v3/preview_url?url=http://www.hulu.com/watch/12345",
shorthand=False,
await_result=False,
)
@@ -1043,7 +1067,7 @@ class URLPreviewTests(unittest.HomeserverTestCase):

channel = self.make_request(
"GET",
"preview_url?url=http://twitter.com/matrixdotorg/status/12345",
"/_matrix/media/v3/preview_url?url=http://twitter.com/matrixdotorg/status/12345",
shorthand=False,
await_result=False,
)
@@ -1072,7 +1096,7 @@ class URLPreviewTests(unittest.HomeserverTestCase):

channel = self.make_request(
"GET",
"preview_url?url=http://www.twitter.com/matrixdotorg/status/12345",
"/_matrix/media/v3/preview_url?url=http://www.twitter.com/matrixdotorg/status/12345",
shorthand=False,
await_result=False,
)
@@ -1164,7 +1188,7 @@ class URLPreviewTests(unittest.HomeserverTestCase):

channel = self.make_request(
"GET",
"preview_url?url=http://www.twitter.com/matrixdotorg/status/12345",
"/_matrix/media/v3/preview_url?url=http://www.twitter.com/matrixdotorg/status/12345",
shorthand=False,
await_result=False,
)
@@ -1205,7 +1229,7 @@ class URLPreviewTests(unittest.HomeserverTestCase):

channel = self.make_request(
"GET",
"preview_url?url=http://cdn.twitter.com/matrixdotorg",
"/_matrix/media/v3/preview_url?url=http://cdn.twitter.com/matrixdotorg",
shorthand=False,
await_result=False,
)
@@ -1247,7 +1271,7 @@ class URLPreviewTests(unittest.HomeserverTestCase):
# Check fetching
channel = self.make_request(
"GET",
f"download/{host}/{media_id}",
f"/_matrix/media/v3/download/{host}/{media_id}",
shorthand=False,
await_result=False,
)
@@ -1260,7 +1284,7 @@ class URLPreviewTests(unittest.HomeserverTestCase):

channel = self.make_request(
"GET",
f"download/{host}/{media_id}",
f"/_matrix/media/v3/download/{host}/{media_id}",
shorthand=False,
await_result=False,
)
@@ -1295,7 +1319,7 @@ class URLPreviewTests(unittest.HomeserverTestCase):
# Check fetching
channel = self.make_request(
"GET",
f"thumbnail/{host}/{media_id}?width=32&height=32&method=scale",
f"/_matrix/media/v3/thumbnail/{host}/{media_id}?width=32&height=32&method=scale",
shorthand=False,
await_result=False,
)
@@ -1313,7 +1337,7 @@ class URLPreviewTests(unittest.HomeserverTestCase):

channel = self.make_request(
"GET",
f"thumbnail/{host}/{media_id}?width=32&height=32&method=scale",
f"/_matrix/media/v3/thumbnail/{host}/{media_id}?width=32&height=32&method=scale",
shorthand=False,
await_result=False,
)
@@ -1343,7 +1367,7 @@ class URLPreviewTests(unittest.HomeserverTestCase):
self.assertTrue(os.path.isdir(thumbnail_dir))

self.reactor.advance(IMAGE_CACHE_EXPIRY_MS * 1000 + 1)
self.get_success(self.preview_url._url_previewer._expire_url_cache_data())
self.get_success(self.url_previewer._expire_url_cache_data())

for path in [file_path] + file_dirs + [thumbnail_dir] + thumbnail_dirs:
self.assertFalse(
@@ -1363,7 +1387,7 @@ class URLPreviewTests(unittest.HomeserverTestCase):

channel = self.make_request(
"GET",
"preview_url?url=" + bad_url,
"/_matrix/media/v3/preview_url?url=" + bad_url,
shorthand=False,
await_result=False,
)
@@ -1372,7 +1396,7 @@ class URLPreviewTests(unittest.HomeserverTestCase):

channel = self.make_request(
"GET",
"preview_url?url=" + good_url,
"/_matrix/media/v3/preview_url?url=" + good_url,
shorthand=False,
await_result=False,
)
@@ -1404,7 +1428,7 @@ class URLPreviewTests(unittest.HomeserverTestCase):

channel = self.make_request(
"GET",
"preview_url?url=" + bad_url,
"/_matrix/media/v3/preview_url?url=" + bad_url,
shorthand=False,
await_result=False,
)


+ 2
- 2
tests/unittest.py View File

@@ -60,7 +60,7 @@ from synapse.config.homeserver import HomeServerConfig
from synapse.config.server import DEFAULT_ROOM_VERSION
from synapse.crypto.event_signing import add_hashes_and_signatures
from synapse.federation.transport.server import TransportLayerServer
from synapse.http.server import JsonResource
from synapse.http.server import JsonResource, OptionsResource
from synapse.http.site import SynapseRequest, SynapseSite
from synapse.logging.context import (
SENTINEL_CONTEXT,
@@ -459,7 +459,7 @@ class HomeserverTestCase(TestCase):
The default calls `self.create_resource_dict` and builds the resultant dict
into a tree.
"""
root_resource = Resource()
root_resource = OptionsResource()
create_resource_tree(self.create_resource_dict(), root_resource)
return root_resource



Loading…
Cancel
Save