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
@@ -0,0 +1 @@ | |||
Update registration of media repository URLs. |
@@ -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: | |||
@@ -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, | |||
) | |||
@@ -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 | |||
@@ -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) |
@@ -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 | |||
) |
@@ -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) |
@@ -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) |
@@ -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, | |||
@@ -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: | |||
@@ -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, | |||
@@ -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")) | |||
@@ -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, | |||
@@ -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, | |||
) | |||
@@ -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, | |||
@@ -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 | |||
@@ -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, | |||
) | |||
@@ -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, | |||
@@ -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, | |||
) | |||
@@ -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 | |||