Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ __pycache__/
/media_store/
/uploads
/homeserver-config-overrides.d
tmp/

# For direnv users
/.envrc
Expand Down
1 change: 1 addition & 0 deletions changelog.d/19802.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add before and after filter to redact user method (/_synapse/admin/v1/user/$user_id/redact) (Issue #19441).
14 changes: 10 additions & 4 deletions docs/admin_api/user_admin_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1512,24 +1512,28 @@ Returns a `404` HTTP status code if no user was found, with a response body like
_Added in Synapse 1.72.0._


## Redact all the events of a user
## Redact events of a user

This endpoint allows an admin to redact the events of a given user. There are no restrictions on
redactions for a 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. An option
is provided to override the default and allow the admin to issue the redactions in all cases.
is provided to override the default and allow the admin to issue the redactions in all cases.
There are optional parameters to filter for events that happened in the given time period.

The API is
```
POST /_synapse/admin/v1/user/<user_id>/redact

{
"rooms": ["!roomid1", "!roomid2"]
"rooms": ["!roomid1", "!roomid2"],
"after_ts": 1779564103728,
"before_ts": 1779564103730
}
```
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,
otherwise all the events in the rooms provided in the request will be redacted.
If neither `after_ts` nor `before_ts` is provided, events will be redacted regardless of when they happened. If only one parameter is provided, all events occurring on or before/after given time will be redacted.

The API starts redaction process running, and returns immediately with a JSON body with
a redact id which can be used to query the status of the redaction process:
Expand Down Expand Up @@ -1557,7 +1561,9 @@ The following JSON body parameters are optional:
- `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.
- `use_admin` - If set to `true`, the admin user is used to issue the redactions, rather than puppeting the user. Useful
when the admin is also the moderator of the rooms that require redactions. Note that the redactions will fail in rooms
where the admin does not have the sufficient power level to issue the redactions.
where the admin does not have the sufficient power level to issue the redactions.
- `after_ts` - Redact only events that happened at this time or after. Format: milliseconds timestamp in the server timezone.
- `before_ts` - Redact only events that happened at this time or before. Format: milliseconds timestamp in the server timezone.

_Added in Synapse 1.116.0._

Expand Down
16 changes: 13 additions & 3 deletions synapse/handlers/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,9 @@ async def start_redact_events(
requester: JsonMapping,
use_admin: bool,
reason: str | None,
limit: int | None,
before_ts: int | None = None,
after_ts: int | None = None,
limit: int | None = None,
) -> str:
"""
Start a task redacting the events of the given user in the given rooms
Expand All @@ -374,6 +376,8 @@ async def start_redact_events(
requester: the user requesting the events
use_admin: whether to use the admin account to issue the redactions
reason: reason for requesting the redaction, ie spam, etc
before_ts: only redact events that happened before this time
after_ts: only redact events that happened after this time
limit: limit on the number of events in each room to redact

Returns:
Expand Down Expand Up @@ -402,6 +406,8 @@ async def start_redact_events(
"user_id": user_id,
"use_admin": use_admin,
"reason": reason,
"before_ts": before_ts,
"after_ts": after_ts,
"limit": limit,
},
)
Expand All @@ -417,8 +423,8 @@ async def _redact_all_events(
self, task: ScheduledTask
) -> tuple[TaskStatus, Mapping[str, Any] | None, str | None]:
"""
Task to redact all of a users events in the given rooms, tracking which, if any, events
whose redaction failed
Task to redact all of a users events in the given rooms in the given time period,
tracking which, if any, events whose redaction failed
"""

assert task.params is not None
Expand Down Expand Up @@ -446,6 +452,8 @@ async def _redact_all_events(
authenticated_entity=admin.user.to_string(),
)

before_ts = task.params.get("before_ts")
after_ts = task.params.get("after_ts")
reason = task.params.get("reason")
limit = task.params.get("limit")
assert limit is not None
Expand All @@ -460,6 +468,8 @@ async def _redact_all_events(
room,
limit,
["m.room.member", "m.room.message", "m.room.encrypted"],
before_ts,
after_ts,
)
if not event_ids:
# nothing to redact in this room
Expand Down
25 changes: 21 additions & 4 deletions synapse/rest/admin/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -1495,9 +1495,14 @@ async def on_GET(

class RedactUser(RestServlet):
"""
Redact all the events of a given user in the given rooms or if empty dict is provided
then all events in all rooms user is member of. Kicks off a background process and
returns an id that can be used to check on the progress of the redaction progress.
Redact all the events of a given user in the given rooms in the given time period.
Kicks off a background process and returns an id that can be used to check on the
progress of the redaction progress.
If empty rooms dict is provided then all events in all rooms user is member of will
be affected.
Parameters before_ts and after_ts are millisecond timestamps in server timezone.
If both are omitted, then messages will be redacted regardless the time they were sent.
If only one parameter is sent, then all messages before or after given time will be redacted.
"""

PATTERNS = admin_patterns("/user/(?P<user_id>[^/]*)/redact")
Expand All @@ -1512,6 +1517,8 @@ class PostBody(RequestBodyModel):
reason: StrictStr | None = None
limit: StrictInt | None = None
use_admin: StrictBool | None = None
before_ts: StrictInt | None = None
after_ts: StrictInt | None = None

async def on_POST(
self, request: SynapseRequest, user_id: str
Expand Down Expand Up @@ -1543,8 +1550,18 @@ async def on_POST(
if not use_admin:
use_admin = False

before_ts = body.before_ts
after_ts = body.after_ts

redact_id = await self.admin_handler.start_redact_events(
user_id, rooms, requester.serialize(), use_admin, body.reason, limit
user_id,
rooms,
requester.serialize(),
use_admin,
body.reason,
before_ts,
after_ts,
limit,
)

return HTTPStatus.OK, {"redact_id": redact_id}
Expand Down
36 changes: 31 additions & 5 deletions synapse/storage/databases/main/events_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -2701,15 +2701,23 @@ def mark_event_rejected_txn(
self.invalidate_get_event_cache_after_txn(txn, event_id)

async def get_events_sent_by_user_in_room(
self, user_id: str, room_id: str, limit: int, filter: list[str] | None = None
self,
user_id: str,
room_id: str,
limit: int,
filter: list[str] | None = None,
before_ts: int | None = None,
after_ts: int | None = None,
) -> list[str] | None:
"""
Get a list of event ids of events sent by the user in the specified room
Get a list of event ids of events sent by the user in the specified room in the specified time period

Args:
user_id: user ID to search against
room_id: room ID of the room to search for events in
filter: type of events to filter for
before_ts: filter for events that happened before this time (optional)
after_ts: filter for events that happened after this time (optional)
limit: maximum number of event ids to return
"""

Expand All @@ -2720,16 +2728,32 @@ def _get_events_by_user_in_room_txn(
filter: list[str] | None,
batch_size: int,
offset: int,
before_ts: int | None = None,
after_ts: int | None = None,
) -> tuple[list[str] | None, int]:
clause = ""
if filter:
base_clause, args = make_in_list_sql_clause(
txn.database_engine, "type", filter
)
clause = f"AND {base_clause}"
parameters = (user_id, room_id, *args, batch_size, offset)
parameters = (user_id, room_id, *args)
else:
clause = ""
parameters = (user_id, room_id, batch_size, offset)
parameters = (user_id, room_id)

if before_ts:
if clause:
clause += " AND "
clause += "origin_server_ts <= ?"
parameters += (before_ts,)

if after_ts:
if clause:
clause += " AND "
clause += "origin_server_ts >= ?"
parameters += (after_ts,)

parameters += (batch_size, offset)

sql = f"""
SELECT event_id FROM events
Expand Down Expand Up @@ -2763,6 +2787,8 @@ def _get_events_by_user_in_room_txn(
filter,
batch_size,
offset,
before_ts,
after_ts,
)
if res:
selected_ids = selected_ids + res
Expand Down