Skip to content

Commit d0a474d

Browse files
Enable authenticated media by default (#17889)
Co-authored-by: Olivier 'reivilibre <[email protected]>
1 parent 8291aa8 commit d0a474d

File tree

11 files changed

+129
-16
lines changed

11 files changed

+129
-16
lines changed

changelog.d/17889.feature

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Enforce authenticated media by default. Administrators can revert this by configuring `enable_authenticated_media` to `false`. In a future release of Synapse, this option will be removed and become always-on.

docs/upgrade.md

+23
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,29 @@ removing the experimental support for it in this release.
128128
The `experimental_features.msc3886_endpoint` configuration option has
129129
been removed.
130130
131+
## Authenticated media is now enforced by default
132+
133+
The [`enable_authenticated_media`] configuration option now defaults to true.
134+
135+
This means that clients and remote (federated) homeservers now need to use
136+
the authenticated media endpoints in order to download media from your
137+
homeserver.
138+
139+
As an exception, existing media that was stored on the server prior to
140+
this option changing to `true` will still be accessible over the
141+
unauthenticated endpoints.
142+
143+
The matrix.org homeserver has already been running with this option enabled
144+
since September 2024, so most common clients and homeservers should already
145+
be compatible.
146+
147+
With that said, administrators who wish to disable this feature for broader
148+
compatibility can still do so by manually configuring
149+
`enable_authenticated_media: False`.
150+
151+
[`enable_authenticated_media`]: usage/configuration/config_documentation.md#enable_authenticated_media
152+
153+
131154
# Upgrading to v1.119.0
132155
133156
## Minimum supported Python version

docs/usage/configuration/config_documentation.md

+4-3
Original file line numberDiff line numberDiff line change
@@ -1887,8 +1887,7 @@ Config options related to Synapse's media store.
18871887

18881888
When set to true, all subsequent media uploads will be marked as authenticated, and will not be available over legacy
18891889
unauthenticated media endpoints (`/_matrix/media/(r0|v3|v1)/download` and `/_matrix/media/(r0|v3|v1)/thumbnail`) - requests for authenticated media over these endpoints will result in a 404. All media, including authenticated media, will be available over the authenticated media endpoints `_matrix/client/v1/media/download` and `_matrix/client/v1/media/thumbnail`. Media uploaded prior to setting this option to true will still be available over the legacy endpoints. Note if the setting is switched to false
1890-
after enabling, media marked as authenticated will be available over legacy endpoints. Defaults to false, but
1891-
this will change to true in a future Synapse release.
1890+
after enabling, media marked as authenticated will be available over legacy endpoints. Defaults to true (previously false). In a future release of Synapse, this option will be removed and become always-on.
18921891

18931892
In all cases, authenticated requests to download media will succeed, but for unauthenticated requests, this
18941893
case-by-case breakdown describes whether media downloads are permitted:
@@ -1910,9 +1909,11 @@ will perpetually be available over the legacy, unauthenticated endpoint, even af
19101909
This is for backwards compatibility with older clients and homeservers that do not yet support requesting authenticated media;
19111910
those older clients or homeservers will not be cut off from media they can already see.
19121911

1912+
_Changed in Synapse 1.120:_ This option now defaults to `True` when not set, whereas before this version it defaulted to `False`.
1913+
19131914
Example configuration:
19141915
```yaml
1915-
enable_authenticated_media: true
1916+
enable_authenticated_media: false
19161917
```
19171918
---
19181919
### `enable_media_repo`

synapse/config/repository.py

+1-3
Original file line numberDiff line numberDiff line change
@@ -272,9 +272,7 @@ def read_config(self, config: JsonDict, **kwargs: Any) -> None:
272272
remote_media_lifetime
273273
)
274274

275-
self.enable_authenticated_media = config.get(
276-
"enable_authenticated_media", False
277-
)
275+
self.enable_authenticated_media = config.get("enable_authenticated_media", True)
278276

279277
def generate_config_section(self, data_dir_path: str, **kwargs: Any) -> str:
280278
assert data_dir_path is not None

tests/media/test_media_storage.py

+76-4
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,11 @@ def _req(
419419

420420
return channel
421421

422+
@unittest.override_config(
423+
{
424+
"enable_authenticated_media": False,
425+
}
426+
)
422427
def test_handle_missing_content_type(self) -> None:
423428
channel = self._req(
424429
b"attachment; filename=out" + self.test_image.extension,
@@ -430,6 +435,11 @@ def test_handle_missing_content_type(self) -> None:
430435
headers.getRawHeaders(b"Content-Type"), [b"application/octet-stream"]
431436
)
432437

438+
@unittest.override_config(
439+
{
440+
"enable_authenticated_media": False,
441+
}
442+
)
433443
def test_disposition_filename_ascii(self) -> None:
434444
"""
435445
If the filename is filename=<ascii> then Synapse will decode it as an
@@ -450,6 +460,11 @@ def test_disposition_filename_ascii(self) -> None:
450460
],
451461
)
452462

463+
@unittest.override_config(
464+
{
465+
"enable_authenticated_media": False,
466+
}
467+
)
453468
def test_disposition_filenamestar_utf8escaped(self) -> None:
454469
"""
455470
If the filename is filename=*utf8''<utf8 escaped> then Synapse will
@@ -475,6 +490,11 @@ def test_disposition_filenamestar_utf8escaped(self) -> None:
475490
],
476491
)
477492

493+
@unittest.override_config(
494+
{
495+
"enable_authenticated_media": False,
496+
}
497+
)
478498
def test_disposition_none(self) -> None:
479499
"""
480500
If there is no filename, Content-Disposition should only
@@ -491,6 +511,11 @@ def test_disposition_none(self) -> None:
491511
[b"inline" if self.test_image.is_inline else b"attachment"],
492512
)
493513

514+
@unittest.override_config(
515+
{
516+
"enable_authenticated_media": False,
517+
}
518+
)
494519
def test_thumbnail_crop(self) -> None:
495520
"""Test that a cropped remote thumbnail is available."""
496521
self._test_thumbnail(
@@ -500,6 +525,11 @@ def test_thumbnail_crop(self) -> None:
500525
unable_to_thumbnail=self.test_image.unable_to_thumbnail,
501526
)
502527

528+
@unittest.override_config(
529+
{
530+
"enable_authenticated_media": False,
531+
}
532+
)
503533
def test_thumbnail_scale(self) -> None:
504534
"""Test that a scaled remote thumbnail is available."""
505535
self._test_thumbnail(
@@ -509,6 +539,11 @@ def test_thumbnail_scale(self) -> None:
509539
unable_to_thumbnail=self.test_image.unable_to_thumbnail,
510540
)
511541

542+
@unittest.override_config(
543+
{
544+
"enable_authenticated_media": False,
545+
}
546+
)
512547
def test_invalid_type(self) -> None:
513548
"""An invalid thumbnail type is never available."""
514549
self._test_thumbnail(
@@ -519,7 +554,10 @@ def test_invalid_type(self) -> None:
519554
)
520555

521556
@unittest.override_config(
522-
{"thumbnail_sizes": [{"width": 32, "height": 32, "method": "scale"}]}
557+
{
558+
"thumbnail_sizes": [{"width": 32, "height": 32, "method": "scale"}],
559+
"enable_authenticated_media": False,
560+
},
523561
)
524562
def test_no_thumbnail_crop(self) -> None:
525563
"""
@@ -533,7 +571,10 @@ def test_no_thumbnail_crop(self) -> None:
533571
)
534572

535573
@unittest.override_config(
536-
{"thumbnail_sizes": [{"width": 32, "height": 32, "method": "crop"}]}
574+
{
575+
"thumbnail_sizes": [{"width": 32, "height": 32, "method": "crop"}],
576+
"enable_authenticated_media": False,
577+
}
537578
)
538579
def test_no_thumbnail_scale(self) -> None:
539580
"""
@@ -546,6 +587,11 @@ def test_no_thumbnail_scale(self) -> None:
546587
unable_to_thumbnail=self.test_image.unable_to_thumbnail,
547588
)
548589

590+
@unittest.override_config(
591+
{
592+
"enable_authenticated_media": False,
593+
}
594+
)
549595
def test_thumbnail_repeated_thumbnail(self) -> None:
550596
"""Test that fetching the same thumbnail works, and deleting the on disk
551597
thumbnail regenerates it.
@@ -720,6 +766,11 @@ def test_same_quality(self, method: str, desired_size: int) -> None:
720766
)
721767
)
722768

769+
@unittest.override_config(
770+
{
771+
"enable_authenticated_media": False,
772+
}
773+
)
723774
def test_x_robots_tag_header(self) -> None:
724775
"""
725776
Tests that the `X-Robots-Tag` header is present, which informs web crawlers
@@ -733,6 +784,11 @@ def test_x_robots_tag_header(self) -> None:
733784
[b"noindex, nofollow, noarchive, noimageindex"],
734785
)
735786

787+
@unittest.override_config(
788+
{
789+
"enable_authenticated_media": False,
790+
}
791+
)
736792
def test_cross_origin_resource_policy_header(self) -> None:
737793
"""
738794
Test that the Cross-Origin-Resource-Policy header is set to "cross-origin"
@@ -747,6 +803,11 @@ def test_cross_origin_resource_policy_header(self) -> None:
747803
[b"cross-origin"],
748804
)
749805

806+
@unittest.override_config(
807+
{
808+
"enable_authenticated_media": False,
809+
}
810+
)
750811
def test_unknown_v3_endpoint(self) -> None:
751812
"""
752813
If the v3 endpoint fails, try the r0 one.
@@ -985,6 +1046,11 @@ def read_body_with_max_size_50MiB(*args: Any, **kwargs: Any) -> Deferred:
9851046
d.callback(52428800)
9861047
return d
9871048

1049+
@override_config(
1050+
{
1051+
"enable_authenticated_media": False,
1052+
}
1053+
)
9881054
@patch(
9891055
"synapse.http.matrixfederationclient.read_body_with_max_size",
9901056
read_body_with_max_size_30MiB,
@@ -1060,6 +1126,7 @@ async def _send_request(*args: Any, **kwargs: Any) -> IResponse:
10601126
{
10611127
"remote_media_download_per_second": "50M",
10621128
"remote_media_download_burst_count": "50M",
1129+
"enable_authenticated_media": False,
10631130
}
10641131
)
10651132
@patch(
@@ -1119,7 +1186,12 @@ async def _send_request(*args: Any, **kwargs: Any) -> IResponse:
11191186
)
11201187
assert channel.code == 200
11211188

1122-
@override_config({"remote_media_download_burst_count": "87M"})
1189+
@override_config(
1190+
{
1191+
"remote_media_download_burst_count": "87M",
1192+
"enable_authenticated_media": False,
1193+
}
1194+
)
11231195
@patch(
11241196
"synapse.http.matrixfederationclient.read_body_with_max_size",
11251197
read_body_with_max_size_30MiB,
@@ -1159,7 +1231,7 @@ async def _send_request(*args: Any, **kwargs: Any) -> IResponse:
11591231
)
11601232
assert channel2.code == 429
11611233

1162-
@override_config({"max_upload_size": "29M"})
1234+
@override_config({"max_upload_size": "29M", "enable_authenticated_media": False})
11631235
@patch(
11641236
"synapse.http.matrixfederationclient.read_body_with_max_size",
11651237
read_body_with_max_size_30MiB,

tests/replication/test_multi_media_repo.py

+4
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
from tests.replication._base import BaseMultiWorkerStreamTestCase
4141
from tests.server import FakeChannel, FakeTransport, make_request
4242
from tests.test_utils import SMALL_PNG
43+
from tests.unittest import override_config
4344

4445
logger = logging.getLogger(__name__)
4546

@@ -148,6 +149,7 @@ def _get_media_req(
148149

149150
return channel, request
150151

152+
@override_config({"enable_authenticated_media": False})
151153
def test_basic(self) -> None:
152154
"""Test basic fetching of remote media from a single worker."""
153155
hs1 = self.make_worker_hs("synapse.app.generic_worker")
@@ -164,6 +166,7 @@ def test_basic(self) -> None:
164166
self.assertEqual(channel.code, 200)
165167
self.assertEqual(channel.result["body"], b"Hello!")
166168

169+
@override_config({"enable_authenticated_media": False})
167170
def test_download_simple_file_race(self) -> None:
168171
"""Test that fetching remote media from two different processes at the
169172
same time works.
@@ -203,6 +206,7 @@ def test_download_simple_file_race(self) -> None:
203206
# We expect only one new file to have been persisted.
204207
self.assertEqual(start_count + 1, self._count_remote_media())
205208

209+
@override_config({"enable_authenticated_media": False})
206210
def test_download_image_race(self) -> None:
207211
"""Test that fetching remote *images* from two different processes at
208212
the same time works.

tests/rest/admin/test_admin.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
import synapse.rest.admin
3131
from synapse.http.server import JsonResource
3232
from synapse.rest.admin import VersionServlet
33-
from synapse.rest.client import login, room
33+
from synapse.rest.client import login, media, room
3434
from synapse.server import HomeServer
3535
from synapse.util import Clock
3636

@@ -60,6 +60,7 @@ class QuarantineMediaTestCase(unittest.HomeserverTestCase):
6060
synapse.rest.admin.register_servlets,
6161
synapse.rest.admin.register_servlets_for_media_repo,
6262
login.register_servlets,
63+
media.register_servlets,
6364
room.register_servlets,
6465
]
6566

@@ -74,7 +75,7 @@ def _ensure_quarantined(
7475
"""Ensure a piece of media is quarantined when trying to access it."""
7576
channel = self.make_request(
7677
"GET",
77-
f"/_matrix/media/v3/download/{server_and_media_id}",
78+
f"/_matrix/client/v1/media/download/{server_and_media_id}",
7879
shorthand=False,
7980
access_token=admin_user_tok,
8081
)
@@ -131,7 +132,7 @@ def test_quarantine_media_by_id(self) -> None:
131132
# Attempt to access the media
132133
channel = self.make_request(
133134
"GET",
134-
f"/_matrix/media/v3/download/{server_name_and_media_id}",
135+
f"/_matrix/client/v1/media/download/{server_name_and_media_id}",
135136
shorthand=False,
136137
access_token=non_admin_user_tok,
137138
)
@@ -295,7 +296,7 @@ def test_cannot_quarantine_safe_media(self) -> None:
295296
# Attempt to access each piece of media
296297
channel = self.make_request(
297298
"GET",
298-
f"/_matrix/media/v3/download/{server_and_media_id_2}",
299+
f"/_matrix/client/v1/media/download/{server_and_media_id_2}",
299300
shorthand=False,
300301
access_token=non_admin_user_tok,
301302
)

tests/rest/admin/test_media.py

+6
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636

3737
from tests import unittest
3838
from tests.test_utils import SMALL_PNG
39+
from tests.unittest import override_config
3940

4041
VALID_TIMESTAMP = 1609459200000 # 2021-01-01 in milliseconds
4142
INVALID_TIMESTAMP_IN_S = 1893456000 # 2030-01-01 in seconds
@@ -126,6 +127,7 @@ def test_media_is_not_local(self) -> None:
126127
self.assertEqual(400, channel.code, msg=channel.json_body)
127128
self.assertEqual("Can only delete local media", channel.json_body["error"])
128129

130+
@override_config({"enable_authenticated_media": False})
129131
def test_delete_media(self) -> None:
130132
"""
131133
Tests that delete a media is successfully
@@ -371,6 +373,7 @@ def test_delete_media_never_accessed(self, use_legacy_url: bool) -> None:
371373

372374
self._access_media(server_and_media_id, False)
373375

376+
@override_config({"enable_authenticated_media": False})
374377
def test_keep_media_by_date(self) -> None:
375378
"""
376379
Tests that media is not deleted if it is newer than `before_ts`
@@ -408,6 +411,7 @@ def test_keep_media_by_date(self) -> None:
408411

409412
self._access_media(server_and_media_id, False)
410413

414+
@override_config({"enable_authenticated_media": False})
411415
def test_keep_media_by_size(self) -> None:
412416
"""
413417
Tests that media is not deleted if its size is smaller than or equal
@@ -443,6 +447,7 @@ def test_keep_media_by_size(self) -> None:
443447

444448
self._access_media(server_and_media_id, False)
445449

450+
@override_config({"enable_authenticated_media": False})
446451
def test_keep_media_by_user_avatar(self) -> None:
447452
"""
448453
Tests that we do not delete media if is used as a user avatar
@@ -487,6 +492,7 @@ def test_keep_media_by_user_avatar(self) -> None:
487492

488493
self._access_media(server_and_media_id, False)
489494

495+
@override_config({"enable_authenticated_media": False})
490496
def test_keep_media_by_room_avatar(self) -> None:
491497
"""
492498
Tests that we do not delete media if it is used as a room avatar

0 commit comments

Comments
 (0)