Skip to content

Commit 11a1141

Browse files
authored
Add an option to issue redactions as admin user on admin redaction endpoint (#18671)
Currently the [admin redaction endpoint](https://element-hq.github.io/synapse/latest/admin_api/user_admin_api.html#redact-all-the-events-of-a-user) defaults to puppeting the user being redacted. This PR adds an optional param `use_admin`, which when provided issues the redactions as the admin user instead.
1 parent 8a4e2e8 commit 11a1141

File tree

5 files changed

+80
-9
lines changed

5 files changed

+80
-9
lines changed

changelog.d/18671.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add an option to issue redactions as admin user on via the [admin redaction endpoint](https://element-hq.github.io/synapse/latest/admin_api/user_admin_api.html#redact-all-the-events-of-a-user).

docs/admin_api/user_admin_api.md

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1227,7 +1227,7 @@ See also the
12271227

12281228
## Controlling whether a user is shadow-banned
12291229

1230-
Shadow-banning is a useful tool for moderating malicious or egregiously abusive users.
1230+
Shadow-banning is a useful tool for moderating malicious or egregiously abusive users.
12311231
A shadow-banned users receives successful responses to their client-server API requests,
12321232
but the events are not propagated into rooms. This can be an effective tool as it
12331233
(hopefully) takes longer for the user to realise they are being moderated before
@@ -1464,8 +1464,11 @@ _Added in Synapse 1.72.0._
14641464

14651465
## Redact all the events of a user
14661466

1467-
This endpoint allows an admin to redact the events of a given user. There are no restrictions on redactions for a
1468-
local user. By default, we puppet the user who sent the message to redact it themselves. Redactions for non-local users are issued using the admin user, and will fail in rooms where the admin user is not admin/does not have the specified power level to issue redactions.
1467+
This endpoint allows an admin to redact the events of a given user. There are no restrictions on
1468+
redactions for a local user. By default, we puppet the user who sent the message to redact it themselves.
1469+
Redactions for non-local users are issued using the admin user, and will fail in rooms where the
1470+
admin user is not admin/does not have the specified power level to issue redactions. An option
1471+
is provided to override the default and allow the admin to issue the redactions in all cases.
14691472

14701473
The API is
14711474
```
@@ -1475,7 +1478,7 @@ POST /_synapse/admin/v1/user/$user_id/redact
14751478
"rooms": ["!roomid1", "!roomid2"]
14761479
}
14771480
```
1478-
If an empty list is provided as the key for `rooms`, all events in all the rooms the user is member of will be redacted,
1481+
If an empty list is provided as the key for `rooms`, all events in all the rooms the user is member of will be redacted,
14791482
otherwise all the events in the rooms provided in the request will be redacted.
14801483

14811484
The API starts redaction process running, and returns immediately with a JSON body with
@@ -1501,7 +1504,10 @@ The following JSON body parameter must be provided:
15011504
The following JSON body parameters are optional:
15021505

15031506
- `reason` - Reason the redaction is being requested, ie "spam", "abuse", etc. This will be included in each redaction event, and be visible to users.
1504-
- `limit` - a limit on the number of the user's events to search for ones that can be redacted (events are redacted newest to oldest) in each room, defaults to 1000 if not provided
1507+
- `limit` - a limit on the number of the user's events to search for ones that can be redacted (events are redacted newest to oldest) in each room, defaults to 1000 if not provided.
1508+
- `use_admin` - If set to `true`, the admin user is used to issue the redactions, rather than puppeting the user. Useful
1509+
when the admin is also the moderator of the rooms that require redactions. Note that the redactions will fail in rooms
1510+
where the admin does not have the sufficient power level to issue the redactions.
15051511

15061512
_Added in Synapse 1.116.0._
15071513

synapse/handlers/admin.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,7 @@ async def start_redact_events(
358358
user_id: str,
359359
rooms: list,
360360
requester: JsonMapping,
361+
use_admin: bool,
361362
reason: Optional[str],
362363
limit: Optional[int],
363364
) -> str:
@@ -368,6 +369,7 @@ async def start_redact_events(
368369
user_id: the user ID of the user whose events should be redacted
369370
rooms: the rooms in which to redact the user's events
370371
requester: the user requesting the events
372+
use_admin: whether to use the admin account to issue the redactions
371373
reason: reason for requesting the redaction, ie spam, etc
372374
limit: limit on the number of events in each room to redact
373375
@@ -395,6 +397,7 @@ async def start_redact_events(
395397
"rooms": rooms,
396398
"requester": requester,
397399
"user_id": user_id,
400+
"use_admin": use_admin,
398401
"reason": reason,
399402
"limit": limit,
400403
},
@@ -426,9 +429,17 @@ async def _redact_all_events(
426429
user_id = task.params.get("user_id")
427430
assert user_id is not None
428431

429-
# puppet the user if they're ours, otherwise use admin to redact
432+
use_admin = task.params.get("use_admin", False)
433+
434+
# default to puppeting the user unless they are not local or it's been requested to
435+
# use the admin user to issue the redactions
436+
requester_id = (
437+
admin.user.to_string()
438+
if use_admin or not self.hs.is_mine_id(user_id)
439+
else user_id
440+
)
430441
requester = create_requester(
431-
user_id if self.hs.is_mine_id(user_id) else admin.user.to_string(),
442+
requester_id,
432443
authenticated_entity=admin.user.to_string(),
433444
)
434445

synapse/rest/admin/users.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1414,7 +1414,7 @@ class RedactUser(RestServlet):
14141414
"""
14151415
Redact all the events of a given user in the given rooms or if empty dict is provided
14161416
then all events in all rooms user is member of. Kicks off a background process and
1417-
returns an id that can be used to check on the progress of the redaction progress
1417+
returns an id that can be used to check on the progress of the redaction progress.
14181418
"""
14191419

14201420
PATTERNS = admin_patterns("/user/(?P<user_id>[^/]*)/redact")
@@ -1428,6 +1428,7 @@ class PostBody(RequestBodyModel):
14281428
rooms: List[StrictStr]
14291429
reason: Optional[StrictStr]
14301430
limit: Optional[StrictInt]
1431+
use_admin: Optional[StrictBool]
14311432

14321433
async def on_POST(
14331434
self, request: SynapseRequest, user_id: str
@@ -1455,8 +1456,12 @@ async def on_POST(
14551456
)
14561457
rooms = current_rooms + banned_rooms
14571458

1459+
use_admin = body.use_admin
1460+
if not use_admin:
1461+
use_admin = False
1462+
14581463
redact_id = await self.admin_handler.start_redact_events(
1459-
user_id, rooms, requester.serialize(), body.reason, limit
1464+
user_id, rooms, requester.serialize(), use_admin, body.reason, limit
14601465
)
14611466

14621467
return HTTPStatus.OK, {"redact_id": redact_id}

tests/rest/admin/test_user.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5667,6 +5667,54 @@ def test_redact_redacts_encrypted_messages(self) -> None:
56675667
matched.append(event_id)
56685668
self.assertEqual(len(matched), len(originals))
56695669

5670+
def test_use_admin_param_for_redactions(self) -> None:
5671+
"""
5672+
Test that if the `use_admin` param is set to true, the admin user is used to issue
5673+
the redactions and that they succeed in a room where the admin user has sufficient
5674+
power to issue redactions
5675+
"""
5676+
5677+
originals = []
5678+
join = self.helper.join(self.rm1, self.bad_user, tok=self.bad_user_tok)
5679+
originals.append(join["event_id"])
5680+
for i in range(15):
5681+
event = {"body": f"hello{i}", "msgtype": "m.text"}
5682+
res = self.helper.send_event(
5683+
self.rm1, "m.room.message", event, tok=self.bad_user_tok
5684+
)
5685+
originals.append(res["event_id"])
5686+
5687+
# redact messages
5688+
channel = self.make_request(
5689+
"POST",
5690+
f"/_synapse/admin/v1/user/{self.bad_user}/redact",
5691+
content={"rooms": [self.rm1], "use_admin": True},
5692+
access_token=self.admin_tok,
5693+
)
5694+
self.assertEqual(channel.code, 200)
5695+
5696+
# messages are redacted, and redactions are issued by the admin user
5697+
filter = json.dumps({"types": [EventTypes.Redaction]})
5698+
channel = self.make_request(
5699+
"GET",
5700+
f"rooms/{self.rm1}/messages?filter={filter}&limit=50",
5701+
access_token=self.admin_tok,
5702+
)
5703+
self.assertEqual(channel.code, 200)
5704+
5705+
matches = []
5706+
for event in channel.json_body["chunk"]:
5707+
for event_id in originals:
5708+
if event["type"] == "m.room.redaction" and event["redacts"] == event_id:
5709+
matches.append((event_id, event))
5710+
# we redacted 16 messages
5711+
self.assertEqual(len(matches), 16)
5712+
5713+
for redaction_tuple in matches:
5714+
redaction = redaction_tuple[1]
5715+
if redaction["sender"] != self.admin:
5716+
self.fail("Redaction was not issued by admin account")
5717+
56705718

56715719
class UserRedactionBackgroundTaskTestCase(BaseMultiWorkerStreamTestCase):
56725720
servlets = [

0 commit comments

Comments
 (0)