mirror of
https://github.com/element-hq/synapse.git
synced 2024-12-26 17:36:27 +00:00
Add some useful endpoints to Admin API (#17948)
Some checks failed
Build docker images / build (push) Waiting to run
Deploy the documentation / Calculate variables for GitHub Pages deployment (push) Waiting to run
Deploy the documentation / GitHub Pages (push) Blocked by required conditions
Build release artifacts / Calculate list of debian distros (push) Waiting to run
Build release artifacts / Build .deb packages (push) Blocked by required conditions
Build release artifacts / Build wheels on ${{ matrix.os }} for ${{ matrix.arch }} (aarch64, ${{ startsWith(github.ref, 'refs/pull/') }}, ubuntu-22.04) (push) Waiting to run
Build release artifacts / Build wheels on ${{ matrix.os }} for ${{ matrix.arch }} (x86_64, ${{ startsWith(github.ref, 'refs/pull/') }}, macos-13) (push) Waiting to run
Build release artifacts / Build wheels on ${{ matrix.os }} for ${{ matrix.arch }} (x86_64, ${{ startsWith(github.ref, 'refs/pull/') }}, ubuntu-22.04) (push) Waiting to run
Tests / changes (push) Waiting to run
Build release artifacts / Build sdist (push) Waiting to run
Build release artifacts / Attach assets to release (push) Blocked by required conditions
Tests / check-sampleconfig (push) Blocked by required conditions
Tests / check-schema-delta (push) Blocked by required conditions
Tests / check-lockfile (push) Waiting to run
Tests / lint (push) Blocked by required conditions
Tests / Typechecking (push) Blocked by required conditions
Tests / lint-crlf (push) Waiting to run
Tests / lint-newsfile (push) Waiting to run
Tests / lint-pydantic (push) Blocked by required conditions
Tests / lint-clippy (push) Blocked by required conditions
Tests / lint-clippy-nightly (push) Blocked by required conditions
Tests / lint-rustfmt (push) Blocked by required conditions
Tests / lint-readme (push) Blocked by required conditions
Tests / linting-done (push) Blocked by required conditions
Tests / calculate-test-jobs (push) Blocked by required conditions
Tests / trial (push) Blocked by required conditions
Tests / trial-olddeps (push) Blocked by required conditions
Tests / trial-pypy (all, pypy-3.9) (push) Blocked by required conditions
Tests / sytest (push) Blocked by required conditions
Tests / export-data (push) Blocked by required conditions
Tests / portdb (11, 3.9) (push) Blocked by required conditions
Tests / portdb (17, 3.13) (push) Blocked by required conditions
Tests / complement (monolith, Postgres) (push) Blocked by required conditions
Tests / complement (monolith, SQLite) (push) Blocked by required conditions
Tests / complement (workers, Postgres) (push) Blocked by required conditions
Tests / cargo-test (push) Blocked by required conditions
Tests / cargo-bench (push) Blocked by required conditions
Tests / tests-done (push) Blocked by required conditions
/ Check locked dependencies have sdists (push) Has been cancelled
Some checks failed
Build docker images / build (push) Waiting to run
Deploy the documentation / Calculate variables for GitHub Pages deployment (push) Waiting to run
Deploy the documentation / GitHub Pages (push) Blocked by required conditions
Build release artifacts / Calculate list of debian distros (push) Waiting to run
Build release artifacts / Build .deb packages (push) Blocked by required conditions
Build release artifacts / Build wheels on ${{ matrix.os }} for ${{ matrix.arch }} (aarch64, ${{ startsWith(github.ref, 'refs/pull/') }}, ubuntu-22.04) (push) Waiting to run
Build release artifacts / Build wheels on ${{ matrix.os }} for ${{ matrix.arch }} (x86_64, ${{ startsWith(github.ref, 'refs/pull/') }}, macos-13) (push) Waiting to run
Build release artifacts / Build wheels on ${{ matrix.os }} for ${{ matrix.arch }} (x86_64, ${{ startsWith(github.ref, 'refs/pull/') }}, ubuntu-22.04) (push) Waiting to run
Tests / changes (push) Waiting to run
Build release artifacts / Build sdist (push) Waiting to run
Build release artifacts / Attach assets to release (push) Blocked by required conditions
Tests / check-sampleconfig (push) Blocked by required conditions
Tests / check-schema-delta (push) Blocked by required conditions
Tests / check-lockfile (push) Waiting to run
Tests / lint (push) Blocked by required conditions
Tests / Typechecking (push) Blocked by required conditions
Tests / lint-crlf (push) Waiting to run
Tests / lint-newsfile (push) Waiting to run
Tests / lint-pydantic (push) Blocked by required conditions
Tests / lint-clippy (push) Blocked by required conditions
Tests / lint-clippy-nightly (push) Blocked by required conditions
Tests / lint-rustfmt (push) Blocked by required conditions
Tests / lint-readme (push) Blocked by required conditions
Tests / linting-done (push) Blocked by required conditions
Tests / calculate-test-jobs (push) Blocked by required conditions
Tests / trial (push) Blocked by required conditions
Tests / trial-olddeps (push) Blocked by required conditions
Tests / trial-pypy (all, pypy-3.9) (push) Blocked by required conditions
Tests / sytest (push) Blocked by required conditions
Tests / export-data (push) Blocked by required conditions
Tests / portdb (11, 3.9) (push) Blocked by required conditions
Tests / portdb (17, 3.13) (push) Blocked by required conditions
Tests / complement (monolith, Postgres) (push) Blocked by required conditions
Tests / complement (monolith, SQLite) (push) Blocked by required conditions
Tests / complement (workers, Postgres) (push) Blocked by required conditions
Tests / cargo-test (push) Blocked by required conditions
Tests / cargo-bench (push) Blocked by required conditions
Tests / tests-done (push) Blocked by required conditions
/ Check locked dependencies have sdists (push) Has been cancelled
- Fetch the number of invites the provided user has sent after a given timestamp - Fetch the number of rooms the provided user has joined after a given timestamp, regardless if they have left/been banned from the rooms subsequently - Get report IDs of event reports where the provided user was the sender of the reported event
This commit is contained in:
parent
29d586311d
commit
8208186e3c
3
changelog.d/17948.feature
Normal file
3
changelog.d/17948.feature
Normal file
|
@ -0,0 +1,3 @@
|
|||
Add endpoints to Admin API to fetch the number of invites the provided user has sent after a given timestamp,
|
||||
fetch the number of rooms the provided user has joined after a given timestamp, and get report IDs of event
|
||||
reports against a provided user (ie where the user was the sender of the reported event).
|
|
@ -60,10 +60,11 @@ paginate through.
|
|||
anything other than the return value of `next_token` from a previous call. Defaults to `0`.
|
||||
* `dir`: string - Direction of event report order. Whether to fetch the most recent
|
||||
first (`b`) or the oldest first (`f`). Defaults to `b`.
|
||||
* `user_id`: string - Is optional and filters to only return users with user IDs that
|
||||
contain this value. This is the user who reported the event and wrote the reason.
|
||||
* `room_id`: string - Is optional and filters to only return rooms with room IDs that
|
||||
contain this value.
|
||||
* `user_id`: optional string - Filter by the user ID of the reporter. This is the user who reported the event
|
||||
and wrote the reason.
|
||||
* `room_id`: optional string - Filter by room id.
|
||||
* `event_sender_user_id`: optional string - Filter by the sender of the reported event. This is the user who
|
||||
the report was made against.
|
||||
|
||||
**Response**
|
||||
|
||||
|
|
|
@ -477,9 +477,9 @@ with a body of:
|
|||
}
|
||||
```
|
||||
|
||||
## List room memberships of a user
|
||||
## List joined rooms of a user
|
||||
|
||||
Gets a list of all `room_id` that a specific `user_id` is member.
|
||||
Gets a list of all `room_id` that a specific `user_id` is joined to and is a member of (participating in).
|
||||
|
||||
The API is:
|
||||
|
||||
|
@ -516,6 +516,73 @@ The following fields are returned in the JSON response body:
|
|||
- `joined_rooms` - An array of `room_id`.
|
||||
- `total` - Number of rooms.
|
||||
|
||||
## Get the number of invites sent by the user
|
||||
|
||||
Fetches the number of invites sent by the provided user ID across all rooms
|
||||
after the given timestamp.
|
||||
|
||||
```
|
||||
GET /_synapse/admin/v1/users/$user_id/sent_invite_count
|
||||
```
|
||||
|
||||
**Parameters**
|
||||
|
||||
The following parameters should be set in the URL:
|
||||
|
||||
* `user_id`: fully qualified: for example, `@user:server.com`
|
||||
|
||||
The following should be set as query parameters in the URL:
|
||||
|
||||
* `from_ts`: int, required. A timestamp in ms from the unix epoch. Only
|
||||
invites sent at or after the provided timestamp will be returned.
|
||||
This works by comparing the provided timestamp to the `received_ts`
|
||||
column in the `events` table.
|
||||
Note: https://currentmillis.com/ is a useful tool for converting dates
|
||||
into timestamps and vice versa.
|
||||
|
||||
A response body like the following is returned:
|
||||
|
||||
```json
|
||||
{
|
||||
"invite_count": 30
|
||||
}
|
||||
```
|
||||
|
||||
_Added in Synapse 1.122.0_
|
||||
|
||||
## Get the cumulative number of rooms a user has joined after a given timestamp
|
||||
|
||||
Fetches the number of rooms that the user joined after the given timestamp, even
|
||||
if they have subsequently left/been banned from those rooms.
|
||||
|
||||
```
|
||||
GET /_synapse/admin/v1/users/$<user_id/cumulative_joined_room_count
|
||||
```
|
||||
|
||||
**Parameters**
|
||||
|
||||
The following parameters should be set in the URL:
|
||||
|
||||
* `user_id`: fully qualified: for example, `@user:server.com`
|
||||
|
||||
The following should be set as query parameters in the URL:
|
||||
|
||||
* `from_ts`: int, required. A timestamp in ms from the unix epoch. Only
|
||||
invites sent at or after the provided timestamp will be returned.
|
||||
This works by comparing the provided timestamp to the `received_ts`
|
||||
column in the `events` table.
|
||||
Note: https://currentmillis.com/ is a useful tool for converting dates
|
||||
into timestamps and vice versa.
|
||||
|
||||
A response body like the following is returned:
|
||||
|
||||
```json
|
||||
{
|
||||
"cumulative_joined_room_count": 30
|
||||
}
|
||||
```
|
||||
_Added in Synapse 1.122.0_
|
||||
|
||||
## Account Data
|
||||
Gets information about account data for a specific `user_id`.
|
||||
|
||||
|
@ -1444,4 +1511,6 @@ The following fields are returned in the JSON response body:
|
|||
- `failed_redactions` - dictionary - the keys of the dict are event ids the process was unable to redact, if any, and the values are
|
||||
the corresponding error that caused the redaction to fail
|
||||
|
||||
_Added in Synapse 1.116.0._
|
||||
_Added in Synapse 1.116.0._
|
||||
|
||||
|
||||
|
|
|
@ -107,6 +107,8 @@ from synapse.rest.admin.users import (
|
|||
UserAdminServlet,
|
||||
UserByExternalId,
|
||||
UserByThreePid,
|
||||
UserInvitesCount,
|
||||
UserJoinedRoomCount,
|
||||
UserMembershipRestServlet,
|
||||
UserRegisterServlet,
|
||||
UserReplaceMasterCrossSigningKeyRestServlet,
|
||||
|
@ -323,6 +325,8 @@ def register_servlets(hs: "HomeServer", http_server: HttpServer) -> None:
|
|||
UserByThreePid(hs).register(http_server)
|
||||
RedactUser(hs).register(http_server)
|
||||
RedactUserStatus(hs).register(http_server)
|
||||
UserInvitesCount(hs).register(http_server)
|
||||
UserJoinedRoomCount(hs).register(http_server)
|
||||
|
||||
DeviceRestServlet(hs).register(http_server)
|
||||
DevicesRestServlet(hs).register(http_server)
|
||||
|
|
|
@ -50,8 +50,10 @@ class EventReportsRestServlet(RestServlet):
|
|||
The parameters `from` and `limit` are required only for pagination.
|
||||
By default, a `limit` of 100 is used.
|
||||
The parameter `dir` can be used to define the order of results.
|
||||
The parameter `user_id` can be used to filter by user id.
|
||||
The parameter `room_id` can be used to filter by room id.
|
||||
The `user_id` query parameter filters by the user ID of the reporter of the event.
|
||||
The `room_id` query parameter filters by room id.
|
||||
The `event_sender_user_id` query parameter can be used to filter by the user id
|
||||
of the sender of the reported event.
|
||||
Returns:
|
||||
A list of reported events and an integer representing the total number of
|
||||
reported events that exist given this query
|
||||
|
@ -71,6 +73,7 @@ class EventReportsRestServlet(RestServlet):
|
|||
direction = parse_enum(request, "dir", Direction, Direction.BACKWARDS)
|
||||
user_id = parse_string(request, "user_id")
|
||||
room_id = parse_string(request, "room_id")
|
||||
event_sender_user_id = parse_string(request, "event_sender_user_id")
|
||||
|
||||
if start < 0:
|
||||
raise SynapseError(
|
||||
|
@ -87,7 +90,7 @@ class EventReportsRestServlet(RestServlet):
|
|||
)
|
||||
|
||||
event_reports, total = await self._store.get_event_reports_paginate(
|
||||
start, limit, direction, user_id, room_id
|
||||
start, limit, direction, user_id, room_id, event_sender_user_id
|
||||
)
|
||||
ret = {"event_reports": event_reports, "total": total}
|
||||
if (start + limit) < total:
|
||||
|
|
|
@ -983,7 +983,7 @@ class UserAdminServlet(RestServlet):
|
|||
|
||||
class UserMembershipRestServlet(RestServlet):
|
||||
"""
|
||||
Get room list of an user.
|
||||
Get list of joined room ID's for a user.
|
||||
"""
|
||||
|
||||
PATTERNS = admin_patterns("/users/(?P<user_id>[^/]*)/joined_rooms$")
|
||||
|
@ -999,8 +999,9 @@ class UserMembershipRestServlet(RestServlet):
|
|||
await assert_requester_is_admin(self.auth, request)
|
||||
|
||||
room_ids = await self.store.get_rooms_for_user(user_id)
|
||||
ret = {"joined_rooms": list(room_ids), "total": len(room_ids)}
|
||||
return HTTPStatus.OK, ret
|
||||
rooms_response = {"joined_rooms": list(room_ids), "total": len(room_ids)}
|
||||
|
||||
return HTTPStatus.OK, rooms_response
|
||||
|
||||
|
||||
class PushersRestServlet(RestServlet):
|
||||
|
@ -1501,3 +1502,50 @@ class RedactUserStatus(RestServlet):
|
|||
}
|
||||
else:
|
||||
raise NotFoundError("redact id '%s' not found" % redact_id)
|
||||
|
||||
|
||||
class UserInvitesCount(RestServlet):
|
||||
"""
|
||||
Return the count of invites that the user has sent after the given timestamp
|
||||
"""
|
||||
|
||||
PATTERNS = admin_patterns("/users/(?P<user_id>[^/]*)/sent_invite_count")
|
||||
|
||||
def __init__(self, hs: "HomeServer"):
|
||||
self._auth = hs.get_auth()
|
||||
self.store = hs.get_datastores().main
|
||||
|
||||
async def on_GET(
|
||||
self, request: SynapseRequest, user_id: str
|
||||
) -> Tuple[int, JsonDict]:
|
||||
await assert_requester_is_admin(self._auth, request)
|
||||
from_ts = parse_integer(request, "from_ts", required=True)
|
||||
|
||||
sent_invite_count = await self.store.get_sent_invite_count_by_user(
|
||||
user_id, from_ts
|
||||
)
|
||||
|
||||
return HTTPStatus.OK, {"invite_count": sent_invite_count}
|
||||
|
||||
|
||||
class UserJoinedRoomCount(RestServlet):
|
||||
"""
|
||||
Return the count of rooms that the user has joined at or after the given timestamp, even
|
||||
if they have subsequently left/been banned from those rooms.
|
||||
"""
|
||||
|
||||
PATTERNS = admin_patterns("/users/(?P<user_id>[^/]*)/cumulative_joined_room_count")
|
||||
|
||||
def __init__(self, hs: "HomeServer"):
|
||||
self._auth = hs.get_auth()
|
||||
self.store = hs.get_datastores().main
|
||||
|
||||
async def on_GET(
|
||||
self, request: SynapseRequest, user_id: str
|
||||
) -> Tuple[int, JsonDict]:
|
||||
await assert_requester_is_admin(self._auth, request)
|
||||
from_ts = parse_integer(request, "from_ts", required=True)
|
||||
|
||||
joined_rooms = await self.store.get_rooms_for_user_by_date(user_id, from_ts)
|
||||
|
||||
return HTTPStatus.OK, {"cumulative_joined_room_count": len(joined_rooms)}
|
||||
|
|
|
@ -339,6 +339,16 @@ class EventsWorkerStore(SQLBaseStore):
|
|||
writers=["master"],
|
||||
)
|
||||
|
||||
# Added to accommodate some queries for the admin API in order to fetch/filter
|
||||
# membership events by when it was received
|
||||
self.db_pool.updates.register_background_index_update(
|
||||
update_name="events_received_ts_index",
|
||||
index_name="received_ts_idx",
|
||||
table="events",
|
||||
columns=("received_ts",),
|
||||
where_clause="type = 'm.room.member'",
|
||||
)
|
||||
|
||||
def get_un_partial_stated_events_token(self, instance_name: str) -> int:
|
||||
return (
|
||||
self._un_partial_stated_events_stream_id_gen.get_current_token_for_writer(
|
||||
|
@ -2589,6 +2599,44 @@ class EventsWorkerStore(SQLBaseStore):
|
|||
)
|
||||
)
|
||||
|
||||
async def get_sent_invite_count_by_user(self, user_id: str, from_ts: int) -> int:
|
||||
"""
|
||||
Get the number of invites sent by the given user at or after the provided timestamp.
|
||||
|
||||
Args:
|
||||
user_id: user ID to search against
|
||||
from_ts: a timestamp in milliseconds from the unix epoch. Filters against
|
||||
`events.received_ts`
|
||||
|
||||
"""
|
||||
|
||||
def _get_sent_invite_count_by_user_txn(
|
||||
txn: LoggingTransaction, user_id: str, from_ts: int
|
||||
) -> int:
|
||||
sql = """
|
||||
SELECT COUNT(rm.event_id)
|
||||
FROM room_memberships AS rm
|
||||
INNER JOIN events AS e USING(event_id)
|
||||
WHERE rm.sender = ?
|
||||
AND rm.membership = 'invite'
|
||||
AND e.type = 'm.room.member'
|
||||
AND e.received_ts >= ?
|
||||
"""
|
||||
|
||||
txn.execute(sql, (user_id, from_ts))
|
||||
res = txn.fetchone()
|
||||
|
||||
if res is None:
|
||||
return 0
|
||||
return int(res[0])
|
||||
|
||||
return await self.db_pool.runInteraction(
|
||||
"_get_sent_invite_count_by_user_txn",
|
||||
_get_sent_invite_count_by_user_txn,
|
||||
user_id,
|
||||
from_ts,
|
||||
)
|
||||
|
||||
@cached(tree=True)
|
||||
async def get_metadata_for_event(
|
||||
self, room_id: str, event_id: str
|
||||
|
|
|
@ -1586,6 +1586,7 @@ class RoomWorkerStore(CacheInvalidationWorkerStore):
|
|||
direction: Direction = Direction.BACKWARDS,
|
||||
user_id: Optional[str] = None,
|
||||
room_id: Optional[str] = None,
|
||||
event_sender_user_id: Optional[str] = None,
|
||||
) -> Tuple[List[Dict[str, Any]], int]:
|
||||
"""Retrieve a paginated list of event reports
|
||||
|
||||
|
@ -1596,6 +1597,8 @@ class RoomWorkerStore(CacheInvalidationWorkerStore):
|
|||
oldest first (forwards)
|
||||
user_id: search for user_id. Ignored if user_id is None
|
||||
room_id: search for room_id. Ignored if room_id is None
|
||||
event_sender_user_id: search for the sender of the reported event. Ignored if
|
||||
event_sender_user_id is None
|
||||
Returns:
|
||||
Tuple of:
|
||||
json list of event reports
|
||||
|
@ -1615,6 +1618,10 @@ class RoomWorkerStore(CacheInvalidationWorkerStore):
|
|||
filters.append("er.room_id LIKE ?")
|
||||
args.extend(["%" + room_id + "%"])
|
||||
|
||||
if event_sender_user_id:
|
||||
filters.append("events.sender = ?")
|
||||
args.extend([event_sender_user_id])
|
||||
|
||||
if direction == Direction.BACKWARDS:
|
||||
order = "DESC"
|
||||
else:
|
||||
|
@ -1630,6 +1637,7 @@ class RoomWorkerStore(CacheInvalidationWorkerStore):
|
|||
sql = """
|
||||
SELECT COUNT(*) as total_event_reports
|
||||
FROM event_reports AS er
|
||||
LEFT JOIN events USING(event_id)
|
||||
JOIN room_stats_state ON room_stats_state.room_id = er.room_id
|
||||
{}
|
||||
""".format(where_clause)
|
||||
|
@ -1648,8 +1656,7 @@ class RoomWorkerStore(CacheInvalidationWorkerStore):
|
|||
room_stats_state.canonical_alias,
|
||||
room_stats_state.name
|
||||
FROM event_reports AS er
|
||||
LEFT JOIN events
|
||||
ON events.event_id = er.event_id
|
||||
LEFT JOIN events USING(event_id)
|
||||
JOIN room_stats_state
|
||||
ON room_stats_state.room_id = er.room_id
|
||||
{where_clause}
|
||||
|
|
|
@ -1572,6 +1572,40 @@ class RoomMemberWorkerStore(EventsWorkerStore, CacheInvalidationWorkerStore):
|
|||
get_sliding_sync_room_for_user_batch_txn,
|
||||
)
|
||||
|
||||
async def get_rooms_for_user_by_date(
|
||||
self, user_id: str, from_ts: int
|
||||
) -> FrozenSet[str]:
|
||||
"""
|
||||
Fetch a list of rooms that the user has joined at or after the given timestamp, including
|
||||
those they subsequently have left/been banned from.
|
||||
|
||||
Args:
|
||||
user_id: user ID of the user to search for
|
||||
from_ts: a timestamp in ms from the unix epoch at which to begin the search at
|
||||
"""
|
||||
|
||||
def _get_rooms_for_user_by_join_date_txn(
|
||||
txn: LoggingTransaction, user_id: str, timestamp: int
|
||||
) -> frozenset:
|
||||
sql = """
|
||||
SELECT rm.room_id
|
||||
FROM room_memberships AS rm
|
||||
INNER JOIN events AS e USING (event_id)
|
||||
WHERE rm.user_id = ?
|
||||
AND rm.membership = 'join'
|
||||
AND e.type = 'm.room.member'
|
||||
AND e.received_ts >= ?
|
||||
"""
|
||||
txn.execute(sql, (user_id, timestamp))
|
||||
return frozenset([r[0] for r in txn])
|
||||
|
||||
return await self.db_pool.runInteraction(
|
||||
"_get_rooms_for_user_by_join_date_txn",
|
||||
_get_rooms_for_user_by_join_date_txn,
|
||||
user_id,
|
||||
from_ts,
|
||||
)
|
||||
|
||||
|
||||
class RoomMemberBackgroundUpdateStore(SQLBaseStore):
|
||||
def __init__(
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
--
|
||||
-- This file is licensed under the Affero General Public License (AGPL) version 3.
|
||||
--
|
||||
-- Copyright (C) 2024 New Vector, Ltd
|
||||
--
|
||||
-- This program is free software: you can redistribute it and/or modify
|
||||
-- it under the terms of the GNU Affero General Public License as
|
||||
-- published by the Free Software Foundation, either version 3 of the
|
||||
-- License, or (at your option) any later version.
|
||||
--
|
||||
-- See the GNU Affero General Public License for more details:
|
||||
-- <https://www.gnu.org/licenses/agpl-3.0.html>.
|
||||
|
||||
-- Add an index on `events.received_ts` for `m.room.member` events to allow for
|
||||
-- efficient lookup of events by timestamp in some Admin API's
|
||||
INSERT INTO background_updates (ordering, update_name, progress_json) VALUES
|
||||
(8806, 'events_received_ts_index', '{}');
|
|
@ -378,6 +378,41 @@ class EventReportsTestCase(unittest.HomeserverTestCase):
|
|||
self.assertEqual(len(channel.json_body["event_reports"]), 1)
|
||||
self.assertNotIn("next_token", channel.json_body)
|
||||
|
||||
def test_filter_against_event_sender(self) -> None:
|
||||
"""
|
||||
Tests filtering by the sender of the reported event
|
||||
"""
|
||||
# first grab all the reports
|
||||
channel = self.make_request(
|
||||
"GET",
|
||||
self.url,
|
||||
access_token=self.admin_user_tok,
|
||||
)
|
||||
self.assertEqual(channel.code, 200)
|
||||
|
||||
# filter out set of report ids of events sent by one of the users
|
||||
locally_filtered_report_ids = set()
|
||||
for event_report in channel.json_body["event_reports"]:
|
||||
if event_report["sender"] == self.other_user:
|
||||
locally_filtered_report_ids.add(event_report["id"])
|
||||
|
||||
# grab the report ids by sender and compare to filtered report ids
|
||||
channel = self.make_request(
|
||||
"GET",
|
||||
f"{self.url}?event_sender_user_id={self.other_user}",
|
||||
access_token=self.admin_user_tok,
|
||||
)
|
||||
self.assertEqual(200, channel.code)
|
||||
self.assertEqual(channel.json_body["total"], len(locally_filtered_report_ids))
|
||||
|
||||
event_reports = channel.json_body["event_reports"]
|
||||
server_filtered_report_ids = set()
|
||||
for event_report in event_reports:
|
||||
server_filtered_report_ids.add(event_report["id"])
|
||||
self.assertIncludes(
|
||||
locally_filtered_report_ids, server_filtered_report_ids, exact=True
|
||||
)
|
||||
|
||||
def _create_event_and_report(self, room_id: str, user_tok: str) -> None:
|
||||
"""Create and report events"""
|
||||
resp = self.helper.send(room_id, tok=user_tok)
|
||||
|
|
|
@ -5502,3 +5502,254 @@ class UserRedactionBackgroundTaskTestCase(BaseMultiWorkerStreamTestCase):
|
|||
redaction_ids.add(event["redacts"])
|
||||
|
||||
self.assertIncludes(redaction_ids, original_event_ids, exact=True)
|
||||
|
||||
|
||||
class GetInvitesFromUserTestCase(unittest.HomeserverTestCase):
|
||||
servlets = [
|
||||
synapse.rest.admin.register_servlets,
|
||||
login.register_servlets,
|
||||
admin.register_servlets,
|
||||
room.register_servlets,
|
||||
]
|
||||
|
||||
def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
|
||||
self.admin = self.register_user("thomas", "pass", True)
|
||||
self.admin_tok = self.login("thomas", "pass")
|
||||
|
||||
self.bad_user = self.register_user("teresa", "pass")
|
||||
self.bad_user_tok = self.login("teresa", "pass")
|
||||
|
||||
self.random_users = []
|
||||
for i in range(4):
|
||||
self.random_users.append(self.register_user(f"user{i}", f"pass{i}"))
|
||||
|
||||
self.room1 = self.helper.create_room_as(self.bad_user, tok=self.bad_user_tok)
|
||||
self.room2 = self.helper.create_room_as(self.bad_user, tok=self.bad_user_tok)
|
||||
self.room3 = self.helper.create_room_as(self.bad_user, tok=self.bad_user_tok)
|
||||
|
||||
@unittest.override_config(
|
||||
{"rc_invites": {"per_issuer": {"per_second": 1000, "burst_count": 1000}}}
|
||||
)
|
||||
def test_get_user_invite_count_new_invites_test_case(self) -> None:
|
||||
"""
|
||||
Test that new invites that arrive after a provided timestamp are counted
|
||||
"""
|
||||
# grab a current timestamp
|
||||
before_invites_sent_ts = self.hs.get_clock().time_msec()
|
||||
|
||||
# bad user sends some invites
|
||||
for room_id in [self.room1, self.room2]:
|
||||
for user in self.random_users:
|
||||
self.helper.invite(room_id, self.bad_user, user, tok=self.bad_user_tok)
|
||||
|
||||
# fetch using timestamp, all should be returned
|
||||
channel = self.make_request(
|
||||
"GET",
|
||||
f"/_synapse/admin/v1/users/{self.bad_user}/sent_invite_count?from_ts={before_invites_sent_ts}",
|
||||
access_token=self.admin_tok,
|
||||
)
|
||||
self.assertEqual(channel.code, 200)
|
||||
self.assertEqual(channel.json_body["invite_count"], 8)
|
||||
|
||||
# send some more invites, they should show up in addition to original 8 using same timestamp
|
||||
for user in self.random_users:
|
||||
self.helper.invite(
|
||||
self.room3, src=self.bad_user, targ=user, tok=self.bad_user_tok
|
||||
)
|
||||
|
||||
channel = self.make_request(
|
||||
"GET",
|
||||
f"/_synapse/admin/v1/users/{self.bad_user}/sent_invite_count?from_ts={before_invites_sent_ts}",
|
||||
access_token=self.admin_tok,
|
||||
)
|
||||
self.assertEqual(channel.code, 200)
|
||||
self.assertEqual(channel.json_body["invite_count"], 12)
|
||||
|
||||
def test_get_user_invite_count_invites_before_ts_test_case(self) -> None:
|
||||
"""
|
||||
Test that invites sent before provided ts are not counted
|
||||
"""
|
||||
# bad user sends some invites
|
||||
for room_id in [self.room1, self.room2]:
|
||||
for user in self.random_users:
|
||||
self.helper.invite(room_id, self.bad_user, user, tok=self.bad_user_tok)
|
||||
|
||||
# add a msec between last invite and ts
|
||||
after_invites_sent_ts = self.hs.get_clock().time_msec() + 1
|
||||
|
||||
# fetch invites with timestamp, none should be returned
|
||||
channel = self.make_request(
|
||||
"GET",
|
||||
f"/_synapse/admin/v1/users/{self.bad_user}/sent_invite_count?from_ts={after_invites_sent_ts}",
|
||||
access_token=self.admin_tok,
|
||||
)
|
||||
self.assertEqual(channel.code, 200)
|
||||
self.assertEqual(channel.json_body["invite_count"], 0)
|
||||
|
||||
def test_user_invite_count_kick_ban_not_counted(self) -> None:
|
||||
"""
|
||||
Test that kicks and bans are not counted in invite count
|
||||
"""
|
||||
to_kick_user_id = self.register_user("kick_me", "pass")
|
||||
to_kick_tok = self.login("kick_me", "pass")
|
||||
|
||||
self.helper.join(self.room1, to_kick_user_id, tok=to_kick_tok)
|
||||
|
||||
# grab a current timestamp
|
||||
before_invites_sent_ts = self.hs.get_clock().time_msec()
|
||||
|
||||
# bad user sends some invites (8)
|
||||
for room_id in [self.room1, self.room2]:
|
||||
for user in self.random_users:
|
||||
self.helper.invite(
|
||||
room_id, src=self.bad_user, targ=user, tok=self.bad_user_tok
|
||||
)
|
||||
|
||||
# fetch using timestamp, all invites sent should be counted
|
||||
channel = self.make_request(
|
||||
"GET",
|
||||
f"/_synapse/admin/v1/users/{self.bad_user}/sent_invite_count?from_ts={before_invites_sent_ts}",
|
||||
access_token=self.admin_tok,
|
||||
)
|
||||
self.assertEqual(channel.code, 200)
|
||||
self.assertEqual(channel.json_body["invite_count"], 8)
|
||||
|
||||
# send a kick and some bans and make sure these aren't counted against invite total
|
||||
for user in self.random_users:
|
||||
self.helper.ban(
|
||||
self.room1, src=self.bad_user, targ=user, tok=self.bad_user_tok
|
||||
)
|
||||
|
||||
channel = self.make_request(
|
||||
"POST",
|
||||
f"/_matrix/client/v3/rooms/{self.room1}/kick",
|
||||
content={"user_id": to_kick_user_id},
|
||||
access_token=self.bad_user_tok,
|
||||
)
|
||||
self.assertEqual(channel.code, 200)
|
||||
|
||||
channel = self.make_request(
|
||||
"GET",
|
||||
f"/_synapse/admin/v1/users/{self.bad_user}/sent_invite_count?from_ts={before_invites_sent_ts}",
|
||||
access_token=self.admin_tok,
|
||||
)
|
||||
self.assertEqual(channel.code, 200)
|
||||
self.assertEqual(channel.json_body["invite_count"], 8)
|
||||
|
||||
|
||||
class GetCumulativeJoinedRoomCountForUserTestCase(unittest.HomeserverTestCase):
|
||||
servlets = [
|
||||
synapse.rest.admin.register_servlets,
|
||||
login.register_servlets,
|
||||
admin.register_servlets,
|
||||
room.register_servlets,
|
||||
]
|
||||
|
||||
def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
|
||||
self.admin = self.register_user("thomas", "pass", True)
|
||||
self.admin_tok = self.login("thomas", "pass")
|
||||
|
||||
self.bad_user = self.register_user("teresa", "pass")
|
||||
self.bad_user_tok = self.login("teresa", "pass")
|
||||
|
||||
def test_user_cumulative_joined_room_count(self) -> None:
|
||||
"""
|
||||
Tests proper count returned from /cumulative_joined_room_count endpoint
|
||||
"""
|
||||
# Create rooms and join, grab timestamp before room creation
|
||||
before_room_creation_timestamp = self.hs.get_clock().time_msec()
|
||||
|
||||
joined_rooms = []
|
||||
for _ in range(3):
|
||||
room = self.helper.create_room_as(self.admin, tok=self.admin_tok)
|
||||
self.helper.join(
|
||||
room, user=self.bad_user, expect_code=200, tok=self.bad_user_tok
|
||||
)
|
||||
joined_rooms.append(room)
|
||||
|
||||
# get a timestamp after room creation and join, add a msec between last join and ts
|
||||
after_room_creation = self.hs.get_clock().time_msec() + 1
|
||||
|
||||
# Get rooms using this timestamp, there should be none since all rooms were created and joined
|
||||
# before provided timestamp
|
||||
channel = self.make_request(
|
||||
"GET",
|
||||
f"/_synapse/admin/v1/users/{self.bad_user}/cumulative_joined_room_count?from_ts={int(after_room_creation)}",
|
||||
access_token=self.admin_tok,
|
||||
)
|
||||
self.assertEqual(200, channel.code, msg=channel.json_body)
|
||||
self.assertEqual(0, channel.json_body["cumulative_joined_room_count"])
|
||||
|
||||
# fetch rooms with the older timestamp before they were created and joined, this should
|
||||
# return the rooms
|
||||
channel = self.make_request(
|
||||
"GET",
|
||||
f"/_synapse/admin/v1/users/{self.bad_user}/cumulative_joined_room_count?from_ts={int(before_room_creation_timestamp)}",
|
||||
access_token=self.admin_tok,
|
||||
)
|
||||
self.assertEqual(200, channel.code, msg=channel.json_body)
|
||||
self.assertEqual(
|
||||
len(joined_rooms), channel.json_body["cumulative_joined_room_count"]
|
||||
)
|
||||
|
||||
def test_user_joined_room_count_includes_left_and_banned_rooms(self) -> None:
|
||||
"""
|
||||
Tests proper count returned from /joined_room_count endpoint when user has left
|
||||
or been banned from joined rooms
|
||||
"""
|
||||
# Create rooms and join, grab timestamp before room creation
|
||||
before_room_creation_timestamp = self.hs.get_clock().time_msec()
|
||||
|
||||
joined_rooms = []
|
||||
for _ in range(3):
|
||||
room = self.helper.create_room_as(self.admin, tok=self.admin_tok)
|
||||
self.helper.join(
|
||||
room, user=self.bad_user, expect_code=200, tok=self.bad_user_tok
|
||||
)
|
||||
joined_rooms.append(room)
|
||||
|
||||
# fetch rooms with the older timestamp before they were created and joined
|
||||
channel = self.make_request(
|
||||
"GET",
|
||||
f"/_synapse/admin/v1/users/{self.bad_user}/cumulative_joined_room_count?from_ts={int(before_room_creation_timestamp)}",
|
||||
access_token=self.admin_tok,
|
||||
)
|
||||
self.assertEqual(200, channel.code, msg=channel.json_body)
|
||||
self.assertEqual(
|
||||
len(joined_rooms), channel.json_body["cumulative_joined_room_count"]
|
||||
)
|
||||
|
||||
# have the user banned from/leave the joined rooms
|
||||
self.helper.ban(
|
||||
joined_rooms[0],
|
||||
src=self.admin,
|
||||
targ=self.bad_user,
|
||||
expect_code=200,
|
||||
tok=self.admin_tok,
|
||||
)
|
||||
self.helper.change_membership(
|
||||
joined_rooms[1],
|
||||
src=self.bad_user,
|
||||
targ=self.bad_user,
|
||||
membership="leave",
|
||||
expect_code=200,
|
||||
tok=self.bad_user_tok,
|
||||
)
|
||||
self.helper.ban(
|
||||
joined_rooms[2],
|
||||
src=self.admin,
|
||||
targ=self.bad_user,
|
||||
expect_code=200,
|
||||
tok=self.admin_tok,
|
||||
)
|
||||
|
||||
# fetch the joined room count again, the number should remain the same as the collected joined rooms
|
||||
channel = self.make_request(
|
||||
"GET",
|
||||
f"/_synapse/admin/v1/users/{self.bad_user}/cumulative_joined_room_count?from_ts={int(before_room_creation_timestamp)}",
|
||||
access_token=self.admin_tok,
|
||||
)
|
||||
self.assertEqual(200, channel.code, msg=channel.json_body)
|
||||
self.assertEqual(
|
||||
len(joined_rooms), channel.json_body["cumulative_joined_room_count"]
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue