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

Commit

Permalink
Merge pull request #2292 from matrix-org/erikj/quarantine_media
Browse files Browse the repository at this point in the history
Add API to quarantine media
  • Loading branch information
erikjohnston authored Jun 19, 2017
2 parents b5f665d + 385dcb7 commit 7d69f2d
Show file tree
Hide file tree
Showing 7 changed files with 121 additions and 5 deletions.
25 changes: 25 additions & 0 deletions synapse/rest/client/v1/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,30 @@ def on_POST(self, request, room_id):
}))


class QuarantineMediaInRoom(ClientV1RestServlet):
"""Quarantines all media in a room so that no one can download it via
this server.
"""
PATTERNS = client_path_patterns("/admin/quarantine_media/(?P<room_id>[^/]+)")

def __init__(self, hs):
super(QuarantineMediaInRoom, self).__init__(hs)
self.store = hs.get_datastore()

@defer.inlineCallbacks
def on_POST(self, request, room_id):
requester = yield self.auth.get_user_by_req(request)
is_admin = yield self.auth.is_server_admin(requester.user)
if not is_admin:
raise AuthError(403, "You are not a server admin")

num_quarantined = yield self.store.quarantine_media_ids_in_room(
room_id, requester.user.to_string(),
)

defer.returnValue((200, {"num_quarantined": num_quarantined}))


class ResetPasswordRestServlet(ClientV1RestServlet):
"""Post request to allow an administrator reset password for a user.
This need a user have a administrator access in Synapse.
Expand Down Expand Up @@ -467,3 +491,4 @@ def register_servlets(hs, http_server):
GetUsersPaginatedRestServlet(hs).register(http_server)
SearchUsersRestServlet(hs).register(http_server)
ShutdownRoomRestServlet(hs).register(http_server)
QuarantineMediaInRoom(hs).register(http_server)
2 changes: 1 addition & 1 deletion synapse/rest/media/v1/download_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def _async_render_GET(self, request):
@defer.inlineCallbacks
def _respond_local_file(self, request, media_id, name):
media_info = yield self.store.get_local_media(media_id)
if not media_info:
if not media_info or media_info["quarantined_by"]:
respond_404(request)
return

Expand Down
2 changes: 2 additions & 0 deletions synapse/rest/media/v1/media_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ def _get_remote_media_impl(self, server_name, media_id):
media_info = yield self._download_remote_file(
server_name, media_id
)
elif media_info["quarantined_by"]:
raise NotFoundError()
else:
self.recently_accessed_remotes.add((server_name, media_id))
yield self.store.update_cached_last_access_time(
Expand Down
4 changes: 2 additions & 2 deletions synapse/rest/media/v1/thumbnail_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ def _respond_local_thumbnail(self, request, media_id, width, height,
method, m_type):
media_info = yield self.store.get_local_media(media_id)

if not media_info:
if not media_info or media_info["quarantined_by"]:
respond_404(request)
return

Expand Down Expand Up @@ -117,7 +117,7 @@ def _select_or_generate_local_thumbnail(self, request, media_id, desired_width,
desired_type):
media_info = yield self.store.get_local_media(media_id)

if not media_info:
if not media_info or media_info["quarantined_by"]:
respond_404(request)
return

Expand Down
4 changes: 2 additions & 2 deletions synapse/storage/media_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def get_local_media(self, media_id):
return self._simple_select_one(
"local_media_repository",
{"media_id": media_id},
("media_type", "media_length", "upload_name", "created_ts"),
("media_type", "media_length", "upload_name", "created_ts", "quarantined_by"),
allow_none=True,
desc="get_local_media",
)
Expand Down Expand Up @@ -138,7 +138,7 @@ def get_cached_remote_media(self, origin, media_id):
{"media_origin": origin, "media_id": media_id},
(
"media_type", "media_length", "upload_name", "created_ts",
"filesystem_id",
"filesystem_id", "quarantined_by",
),
allow_none=True,
desc="get_cached_remote_media",
Expand Down
72 changes: 72 additions & 0 deletions synapse/storage/room.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import collections
import logging
import ujson as json
import re

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -531,3 +532,74 @@ def block_room(self, room_id, user_id):
desc="block_room",
)
self.is_room_blocked.invalidate((room_id,))

def quarantine_media_ids_in_room(self, room_id, quarantined_by):
"""For a room loops through all events with media and quarantines
the associated media
"""
def _get_media_ids_in_room(txn):
mxc_re = re.compile("^mxc://([^/]+)/([^/#?]+)")

next_token = self.get_current_events_token() + 1

total_media_quarantined = 0

while next_token:
sql = """
SELECT stream_ordering, content FROM events
WHERE room_id = ?
AND stream_ordering < ?
AND contains_url = ? AND outlier = ?
ORDER BY stream_ordering DESC
LIMIT ?
"""
txn.execute(sql, (room_id, next_token, True, False, 100))

next_token = None
local_media_mxcs = []
remote_media_mxcs = []
for stream_ordering, content_json in txn:
next_token = stream_ordering
content = json.loads(content_json)

content_url = content.get("url")
thumbnail_url = content.get("info", {}).get("thumbnail_url")

for url in (content_url, thumbnail_url):
if not url:
continue
matches = mxc_re.match(url)
if matches:
hostname = matches.group(1)
media_id = matches.group(2)
if hostname == self.hostname:
local_media_mxcs.append(media_id)
else:
remote_media_mxcs.append((hostname, media_id))

# Now update all the tables to set the quarantined_by flag

txn.executemany("""
UPDATE local_media_repository
SET quarantined_by = ?
WHERE media_id = ?
""", ((quarantined_by, media_id) for media_id in local_media_mxcs))

txn.executemany(
"""
UPDATE remote_media_cache
SET quarantined_by = ?
WHERE media_origin AND media_id = ?
""",
(
(quarantined_by, origin, media_id)
for origin, media_id in remote_media_mxcs
)
)

total_media_quarantined += len(local_media_mxcs)
total_media_quarantined += len(remote_media_mxcs)

return total_media_quarantined

return self.runInteraction("get_media_ids_in_room", _get_media_ids_in_room)
17 changes: 17 additions & 0 deletions synapse/storage/schema/delta/43/quarantine_media.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/* Copyright 2017 Vector Creations Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* 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.
*/

ALTER TABLE local_media_repository ADD COLUMN quarantined_by TEXT;
ALTER TABLE remote_media_cache ADD COLUMN quarantined_by TEXT;

0 comments on commit 7d69f2d

Please sign in to comment.