1818
1919from synapse .api .errors import AuthError , Codes , NotFoundError , SynapseError
2020from synapse .http .server import HttpServer
21- from synapse .http .servlet import RestServlet , parse_boolean , parse_integer
21+ from synapse .http .servlet import RestServlet , parse_boolean , parse_integer , parse_string
2222from synapse .http .site import SynapseRequest
2323from synapse .rest .admin ._base import (
2424 admin_patterns ,
2525 assert_requester_is_admin ,
2626 assert_user_is_admin ,
2727)
28- from synapse .types import JsonDict
28+ from synapse .storage .databases .main .media_repository import MediaSortOrder
29+ from synapse .types import JsonDict , UserID
2930
3031if TYPE_CHECKING :
3132 from synapse .server import HomeServer
@@ -314,6 +315,165 @@ async def on_POST(
314315 return 200 , {"deleted_media" : deleted_media , "total" : total }
315316
316317
318+ class UserMediaRestServlet (RestServlet ):
319+ """
320+ Gets information about all uploaded local media for a specific `user_id`.
321+ With DELETE request you can delete all this media.
322+
323+ Example:
324+ http://localhost:8008/_synapse/admin/v1/users/@user:server/media
325+
326+ Args:
327+ The parameters `from` and `limit` are required for pagination.
328+ By default, a `limit` of 100 is used.
329+ Returns:
330+ A list of media and an integer representing the total number of
331+ media that exist given for this user
332+ """
333+
334+ PATTERNS = admin_patterns ("/users/(?P<user_id>[^/]+)/media$" )
335+
336+ def __init__ (self , hs : "HomeServer" ):
337+ self .is_mine = hs .is_mine
338+ self .auth = hs .get_auth ()
339+ self .store = hs .get_datastore ()
340+ self .media_repository = hs .get_media_repository ()
341+
342+ async def on_GET (
343+ self , request : SynapseRequest , user_id : str
344+ ) -> Tuple [int , JsonDict ]:
345+ # This will always be set by the time Twisted calls us.
346+ assert request .args is not None
347+
348+ await assert_requester_is_admin (self .auth , request )
349+
350+ if not self .is_mine (UserID .from_string (user_id )):
351+ raise SynapseError (400 , "Can only look up local users" )
352+
353+ user = await self .store .get_user_by_id (user_id )
354+ if user is None :
355+ raise NotFoundError ("Unknown user" )
356+
357+ start = parse_integer (request , "from" , default = 0 )
358+ limit = parse_integer (request , "limit" , default = 100 )
359+
360+ if start < 0 :
361+ raise SynapseError (
362+ 400 ,
363+ "Query parameter from must be a string representing a positive integer." ,
364+ errcode = Codes .INVALID_PARAM ,
365+ )
366+
367+ if limit < 0 :
368+ raise SynapseError (
369+ 400 ,
370+ "Query parameter limit must be a string representing a positive integer." ,
371+ errcode = Codes .INVALID_PARAM ,
372+ )
373+
374+ # If neither `order_by` nor `dir` is set, set the default order
375+ # to newest media is on top for backward compatibility.
376+ if b"order_by" not in request .args and b"dir" not in request .args :
377+ order_by = MediaSortOrder .CREATED_TS .value
378+ direction = "b"
379+ else :
380+ order_by = parse_string (
381+ request ,
382+ "order_by" ,
383+ default = MediaSortOrder .CREATED_TS .value ,
384+ allowed_values = (
385+ MediaSortOrder .MEDIA_ID .value ,
386+ MediaSortOrder .UPLOAD_NAME .value ,
387+ MediaSortOrder .CREATED_TS .value ,
388+ MediaSortOrder .LAST_ACCESS_TS .value ,
389+ MediaSortOrder .MEDIA_LENGTH .value ,
390+ MediaSortOrder .MEDIA_TYPE .value ,
391+ MediaSortOrder .QUARANTINED_BY .value ,
392+ MediaSortOrder .SAFE_FROM_QUARANTINE .value ,
393+ ),
394+ )
395+ direction = parse_string (
396+ request , "dir" , default = "f" , allowed_values = ("f" , "b" )
397+ )
398+
399+ media , total = await self .store .get_local_media_by_user_paginate (
400+ start , limit , user_id , order_by , direction
401+ )
402+
403+ ret = {"media" : media , "total" : total }
404+ if (start + limit ) < total :
405+ ret ["next_token" ] = start + len (media )
406+
407+ return 200 , ret
408+
409+ async def on_DELETE (
410+ self , request : SynapseRequest , user_id : str
411+ ) -> Tuple [int , JsonDict ]:
412+ # This will always be set by the time Twisted calls us.
413+ assert request .args is not None
414+
415+ await assert_requester_is_admin (self .auth , request )
416+
417+ if not self .is_mine (UserID .from_string (user_id )):
418+ raise SynapseError (400 , "Can only look up local users" )
419+
420+ user = await self .store .get_user_by_id (user_id )
421+ if user is None :
422+ raise NotFoundError ("Unknown user" )
423+
424+ start = parse_integer (request , "from" , default = 0 )
425+ limit = parse_integer (request , "limit" , default = 100 )
426+
427+ if start < 0 :
428+ raise SynapseError (
429+ 400 ,
430+ "Query parameter from must be a string representing a positive integer." ,
431+ errcode = Codes .INVALID_PARAM ,
432+ )
433+
434+ if limit < 0 :
435+ raise SynapseError (
436+ 400 ,
437+ "Query parameter limit must be a string representing a positive integer." ,
438+ errcode = Codes .INVALID_PARAM ,
439+ )
440+
441+ # If neither `order_by` nor `dir` is set, set the default order
442+ # to newest media is on top for backward compatibility.
443+ if b"order_by" not in request .args and b"dir" not in request .args :
444+ order_by = MediaSortOrder .CREATED_TS .value
445+ direction = "b"
446+ else :
447+ order_by = parse_string (
448+ request ,
449+ "order_by" ,
450+ default = MediaSortOrder .CREATED_TS .value ,
451+ allowed_values = (
452+ MediaSortOrder .MEDIA_ID .value ,
453+ MediaSortOrder .UPLOAD_NAME .value ,
454+ MediaSortOrder .CREATED_TS .value ,
455+ MediaSortOrder .LAST_ACCESS_TS .value ,
456+ MediaSortOrder .MEDIA_LENGTH .value ,
457+ MediaSortOrder .MEDIA_TYPE .value ,
458+ MediaSortOrder .QUARANTINED_BY .value ,
459+ MediaSortOrder .SAFE_FROM_QUARANTINE .value ,
460+ ),
461+ )
462+ direction = parse_string (
463+ request , "dir" , default = "f" , allowed_values = ("f" , "b" )
464+ )
465+
466+ media , _ = await self .store .get_local_media_by_user_paginate (
467+ start , limit , user_id , order_by , direction
468+ )
469+
470+ deleted_media , total = await self .media_repository .delete_local_media_ids (
471+ ([row ["media_id" ] for row in media ])
472+ )
473+
474+ return 200 , {"deleted_media" : deleted_media , "total" : total }
475+
476+
317477def register_servlets_for_media_repo (hs : "HomeServer" , http_server : HttpServer ) -> None :
318478 """
319479 Media repo specific APIs.
@@ -328,3 +488,4 @@ def register_servlets_for_media_repo(hs: "HomeServer", http_server: HttpServer)
328488 ListMediaInRoom (hs ).register (http_server )
329489 DeleteMediaByID (hs ).register (http_server )
330490 DeleteMediaByDateSize (hs ).register (http_server )
491+ UserMediaRestServlet (hs ).register (http_server )
0 commit comments