18
18
19
19
from synapse .api .errors import AuthError , Codes , NotFoundError , SynapseError
20
20
from 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
22
22
from synapse .http .site import SynapseRequest
23
23
from synapse .rest .admin ._base import (
24
24
admin_patterns ,
25
25
assert_requester_is_admin ,
26
26
assert_user_is_admin ,
27
27
)
28
- from synapse .types import JsonDict
28
+ from synapse .storage .databases .main .media_repository import MediaSortOrder
29
+ from synapse .types import JsonDict , UserID
29
30
30
31
if TYPE_CHECKING :
31
32
from synapse .server import HomeServer
@@ -314,6 +315,165 @@ async def on_POST(
314
315
return 200 , {"deleted_media" : deleted_media , "total" : total }
315
316
316
317
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
+
317
477
def register_servlets_for_media_repo (hs : "HomeServer" , http_server : HttpServer ) -> None :
318
478
"""
319
479
Media repo specific APIs.
@@ -328,3 +488,4 @@ def register_servlets_for_media_repo(hs: "HomeServer", http_server: HttpServer)
328
488
ListMediaInRoom (hs ).register (http_server )
329
489
DeleteMediaByID (hs ).register (http_server )
330
490
DeleteMediaByDateSize (hs ).register (http_server )
491
+ UserMediaRestServlet (hs ).register (http_server )
0 commit comments