Skip to content
This repository was archived by the owner on Apr 26, 2024. It is now read-only.

Show error when timestamp in seconds is provided to the /purge_media_cache API #11101

Merged
merged 7 commits into from
Oct 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/11101.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Show an error when timestamp in seconds is provided to the `/purge_media_cache` Admin API.
6 changes: 3 additions & 3 deletions docs/admin_api/media_admin_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,9 +257,9 @@ POST /_synapse/admin/v1/media/<server_name>/delete?before_ts=<before_ts>
URL Parameters

* `server_name`: string - The name of your local server (e.g `matrix.org`).
* `before_ts`: string representing a positive integer - Unix timestamp in ms.
* `before_ts`: string representing a positive integer - Unix timestamp in milliseconds.
Files that were last used before this timestamp will be deleted. It is the timestamp of
last access and not the timestamp creation.
last access, not the timestamp when the file was created.
* `size_gt`: Optional - string representing a positive integer - Size of the media in bytes.
Files that are larger will be deleted. Defaults to `0`.
* `keep_profiles`: Optional - string representing a boolean - Switch to also delete files
Expand Down Expand Up @@ -302,7 +302,7 @@ POST /_synapse/admin/v1/purge_media_cache?before_ts=<unix_timestamp_in_ms>

URL Parameters

* `unix_timestamp_in_ms`: string representing a positive integer - Unix timestamp in ms.
* `unix_timestamp_in_ms`: string representing a positive integer - Unix timestamp in milliseconds.
All cached media that was last accessed before this timestamp will be removed.

Response:
Expand Down
33 changes: 27 additions & 6 deletions synapse/rest/admin/media.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class QuarantineMediaInRoom(RestServlet):
"""

PATTERNS = [
*admin_patterns("/room/(?P<room_id>[^/]+)/media/quarantine"),
*admin_patterns("/room/(?P<room_id>[^/]+)/media/quarantine$"),
# This path kept around for legacy reasons
*admin_patterns("/quarantine_media/(?P<room_id>[^/]+)"),
]
Expand Down Expand Up @@ -70,7 +70,7 @@ class QuarantineMediaByUser(RestServlet):
this server.
"""

PATTERNS = admin_patterns("/user/(?P<user_id>[^/]+)/media/quarantine")
PATTERNS = admin_patterns("/user/(?P<user_id>[^/]+)/media/quarantine$")

def __init__(self, hs: "HomeServer"):
self.store = hs.get_datastore()
Expand Down Expand Up @@ -199,7 +199,7 @@ async def on_POST(
class ListMediaInRoom(RestServlet):
"""Lists all of the media in a given room."""

PATTERNS = admin_patterns("/room/(?P<room_id>[^/]+)/media")
PATTERNS = admin_patterns("/room/(?P<room_id>[^/]+)/media$")

def __init__(self, hs: "HomeServer"):
self.store = hs.get_datastore()
Expand All @@ -219,7 +219,7 @@ async def on_GET(


class PurgeMediaCacheRestServlet(RestServlet):
PATTERNS = admin_patterns("/purge_media_cache")
PATTERNS = admin_patterns("/purge_media_cache$")

def __init__(self, hs: "HomeServer"):
self.media_repository = hs.get_media_repository()
Expand All @@ -231,6 +231,20 @@ async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
before_ts = parse_integer(request, "before_ts", required=True)
logger.info("before_ts: %r", before_ts)

if before_ts < 0:
raise SynapseError(
400,
"Query parameter before_ts must be a positive integer.",
errcode=Codes.INVALID_PARAM,
)
elif before_ts < 30000000000: # Dec 1970 in milliseconds, Aug 2920 in seconds
raise SynapseError(
400,
"Query parameter before_ts you provided is from the year 1970. "
+ "Double check that you are providing a timestamp in milliseconds.",
errcode=Codes.INVALID_PARAM,
)

ret = await self.media_repository.delete_old_remote_media(before_ts)

return 200, ret
Expand Down Expand Up @@ -271,7 +285,7 @@ class DeleteMediaByDateSize(RestServlet):
timestamp and size.
"""

PATTERNS = admin_patterns("/media/(?P<server_name>[^/]+)/delete")
PATTERNS = admin_patterns("/media/(?P<server_name>[^/]+)/delete$")

def __init__(self, hs: "HomeServer"):
self.store = hs.get_datastore()
Expand All @@ -291,7 +305,14 @@ async def on_POST(
if before_ts < 0:
raise SynapseError(
400,
"Query parameter before_ts must be a string representing a positive integer.",
"Query parameter before_ts must be a positive integer.",
errcode=Codes.INVALID_PARAM,
)
elif before_ts < 30000000000: # Dec 1970 in milliseconds, Aug 2920 in seconds
raise SynapseError(
400,
"Query parameter before_ts you provided is from the year 1970. "
+ "Double check that you are providing a timestamp in milliseconds.",
errcode=Codes.INVALID_PARAM,
)
if size_gt < 0:
Expand Down
106 changes: 102 additions & 4 deletions tests/rest/admin/test_media.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
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):

Expand Down Expand Up @@ -203,6 +206,9 @@ def prepare(self, reactor, clock, hs):
self.filepaths = MediaFilePaths(hs.config.media.media_store_path)
self.url = "/_synapse/admin/v1/media/%s/delete" % self.server_name

# Move clock up to somewhat realistic time
self.reactor.advance(1000000000)

def test_no_auth(self):
"""
Try to delete media without authentication.
Expand Down Expand Up @@ -237,7 +243,7 @@ def test_media_is_not_local(self):

channel = self.make_request(
"POST",
url + "?before_ts=1234",
url + f"?before_ts={VALID_TIMESTAMP}",
access_token=self.admin_user_tok,
)

Expand Down Expand Up @@ -273,13 +279,27 @@ def test_invalid_parameter(self):
self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"])
self.assertEqual(
"Query parameter before_ts must be a string representing a positive integer.",
"Query parameter before_ts must be a positive integer.",
channel.json_body["error"],
)

channel = self.make_request(
"POST",
self.url + "?before_ts=1234&size_gt=-1234",
self.url + f"?before_ts={INVALID_TIMESTAMP_IN_S}",
access_token=self.admin_user_tok,
)

self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"])
self.assertEqual(
"Query parameter before_ts you provided is from the year 1970. "
+ "Double check that you are providing a timestamp in milliseconds.",
channel.json_body["error"],
)

channel = self.make_request(
"POST",
self.url + f"?before_ts={VALID_TIMESTAMP}&size_gt=-1234",
access_token=self.admin_user_tok,
)

Expand All @@ -292,7 +312,7 @@ def test_invalid_parameter(self):

channel = self.make_request(
"POST",
self.url + "?before_ts=1234&keep_profiles=not_bool",
self.url + f"?before_ts={VALID_TIMESTAMP}&keep_profiles=not_bool",
access_token=self.admin_user_tok,
)

Expand Down Expand Up @@ -767,3 +787,81 @@ def test_protect_media(self):

media_info = self.get_success(self.store.get_local_media(self.media_id))
self.assertFalse(media_info["safe_from_quarantine"])


class PurgeMediaCacheTestCase(unittest.HomeserverTestCase):

servlets = [
synapse.rest.admin.register_servlets,
synapse.rest.admin.register_servlets_for_media_repo,
login.register_servlets,
profile.register_servlets,
room.register_servlets,
]

def prepare(self, reactor, clock, hs):
self.media_repo = hs.get_media_repository_resource()
self.server_name = hs.hostname

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

self.filepaths = MediaFilePaths(hs.config.media.media_store_path)
self.url = "/_synapse/admin/v1/purge_media_cache"

def test_no_auth(self):
"""
Try to delete media without authentication.
"""

channel = self.make_request("POST", self.url, b"{}")

self.assertEqual(401, int(channel.result["code"]), msg=channel.result["body"])
self.assertEqual(Codes.MISSING_TOKEN, channel.json_body["errcode"])

def test_requester_is_not_admin(self):
"""
If the user is not a server admin, an error is returned.
"""
self.other_user = self.register_user("user", "pass")
self.other_user_token = self.login("user", "pass")

channel = self.make_request(
"POST",
self.url,
access_token=self.other_user_token,
)

self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])

def test_invalid_parameter(self):
"""
If parameters are invalid, an error is returned.
"""
channel = self.make_request(
"POST",
self.url + "?before_ts=-1234",
access_token=self.admin_user_tok,
)

self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"])
self.assertEqual(
"Query parameter before_ts must be a positive integer.",
channel.json_body["error"],
)

channel = self.make_request(
"POST",
self.url + f"?before_ts={INVALID_TIMESTAMP_IN_S}",
access_token=self.admin_user_tok,
)

self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"])
self.assertEqual(
"Query parameter before_ts you provided is from the year 1970. "
+ "Double check that you are providing a timestamp in milliseconds.",
channel.json_body["error"],
)