mirror of
				https://github.com/matrix-org/synapse.git
				synced 2025-10-25 22:32:03 +02:00 
			
		
		
		
	Merge pull request #2292 from matrix-org/erikj/quarantine_media
Add API to quarantine media
This commit is contained in:
		
						commit
						7d69f2d956
					
				| @ -270,6 +270,30 @@ class ShutdownRoomRestServlet(ClientV1RestServlet): | ||||
|         })) | ||||
| 
 | ||||
| 
 | ||||
| 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. | ||||
| @ -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) | ||||
|  | ||||
| @ -66,7 +66,7 @@ class DownloadResource(Resource): | ||||
|     @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 | ||||
| 
 | ||||
|  | ||||
| @ -135,6 +135,8 @@ class MediaRepository(object): | ||||
|             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( | ||||
|  | ||||
| @ -81,7 +81,7 @@ class ThumbnailResource(Resource): | ||||
|                                  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 | ||||
| 
 | ||||
| @ -117,7 +117,7 @@ class ThumbnailResource(Resource): | ||||
|                                             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 | ||||
| 
 | ||||
|  | ||||
| @ -30,7 +30,7 @@ class MediaRepositoryStore(SQLBaseStore): | ||||
|         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", | ||||
|         ) | ||||
| @ -138,7 +138,7 @@ class MediaRepositoryStore(SQLBaseStore): | ||||
|             {"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", | ||||
|  | ||||
| @ -24,6 +24,7 @@ from .engines import PostgresEngine, Sqlite3Engine | ||||
| import collections | ||||
| import logging | ||||
| import ujson as json | ||||
| import re | ||||
| 
 | ||||
| logger = logging.getLogger(__name__) | ||||
| 
 | ||||
| @ -531,3 +532,74 @@ class RoomStore(SQLBaseStore): | ||||
|             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
									
								
								synapse/storage/schema/delta/43/quarantine_media.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								synapse/storage/schema/delta/43/quarantine_media.sql
									
									
									
									
									
										Normal file
									
								
							| @ -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; | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user