mirror of
https://mau.dev/maunium/synapse.git
synced 2024-10-01 01:36:05 -04:00
Show erasure status when listing users in the Admin API (#14205)
* Show erasure status when listing users in the Admin API * Use USING when joining erased_users * Add changelog entry * Revert "Use USING when joining erased_users" This reverts commit 30bd2bf106415caadcfdbdd1b234ef2b106cc394. * Make the erased check work on postgres * Add a testcase for showing erased user status * Appease the style linter * Explicitly convert `erased` to bool to make SQLite consistent with Postgres This also adds us an easy way in to fix the other accidentally integered columns. * Move erasure status test to UsersListTestCase * Include user erased status when fetching user info via the admin API * Document the erase status in user_admin_api * Appease the linter and mypy * Signpost comments in tests Co-authored-by: Tadeusz Sośnierz <tadeusz@sosnierz.com> Co-authored-by: David Robertson <david.m.robertson1@gmail.com>
This commit is contained in:
parent
fab495a9e1
commit
1433b5d5b6
1
changelog.d/14205.feature
Normal file
1
changelog.d/14205.feature
Normal file
@ -0,0 +1 @@
|
|||||||
|
Show erasure status when listing users in the Admin API.
|
@ -37,6 +37,7 @@ It returns a JSON body like the following:
|
|||||||
"is_guest": 0,
|
"is_guest": 0,
|
||||||
"admin": 0,
|
"admin": 0,
|
||||||
"deactivated": 0,
|
"deactivated": 0,
|
||||||
|
"erased": false,
|
||||||
"shadow_banned": 0,
|
"shadow_banned": 0,
|
||||||
"creation_ts": 1560432506,
|
"creation_ts": 1560432506,
|
||||||
"appservice_id": null,
|
"appservice_id": null,
|
||||||
@ -167,6 +168,7 @@ A response body like the following is returned:
|
|||||||
"admin": 0,
|
"admin": 0,
|
||||||
"user_type": null,
|
"user_type": null,
|
||||||
"deactivated": 0,
|
"deactivated": 0,
|
||||||
|
"erased": false,
|
||||||
"shadow_banned": 0,
|
"shadow_banned": 0,
|
||||||
"displayname": "<User One>",
|
"displayname": "<User One>",
|
||||||
"avatar_url": null,
|
"avatar_url": null,
|
||||||
@ -177,6 +179,7 @@ A response body like the following is returned:
|
|||||||
"admin": 1,
|
"admin": 1,
|
||||||
"user_type": null,
|
"user_type": null,
|
||||||
"deactivated": 0,
|
"deactivated": 0,
|
||||||
|
"erased": false,
|
||||||
"shadow_banned": 0,
|
"shadow_banned": 0,
|
||||||
"displayname": "<User Two>",
|
"displayname": "<User Two>",
|
||||||
"avatar_url": "<avatar_url>",
|
"avatar_url": "<avatar_url>",
|
||||||
@ -247,6 +250,7 @@ The following fields are returned in the JSON response body:
|
|||||||
- `user_type` - string - Type of the user. Normal users are type `None`.
|
- `user_type` - string - Type of the user. Normal users are type `None`.
|
||||||
This allows user type specific behaviour. There are also types `support` and `bot`.
|
This allows user type specific behaviour. There are also types `support` and `bot`.
|
||||||
- `deactivated` - bool - Status if that user has been marked as deactivated.
|
- `deactivated` - bool - Status if that user has been marked as deactivated.
|
||||||
|
- `erased` - bool - Status if that user has been marked as erased.
|
||||||
- `shadow_banned` - bool - Status if that user has been marked as shadow banned.
|
- `shadow_banned` - bool - Status if that user has been marked as shadow banned.
|
||||||
- `displayname` - string - The user's display name if they have set one.
|
- `displayname` - string - The user's display name if they have set one.
|
||||||
- `avatar_url` - string - The user's avatar URL if they have set one.
|
- `avatar_url` - string - The user's avatar URL if they have set one.
|
||||||
|
@ -100,6 +100,7 @@ class AdminHandler:
|
|||||||
user_info_dict["avatar_url"] = profile.avatar_url
|
user_info_dict["avatar_url"] = profile.avatar_url
|
||||||
user_info_dict["threepids"] = threepids
|
user_info_dict["threepids"] = threepids
|
||||||
user_info_dict["external_ids"] = external_ids
|
user_info_dict["external_ids"] = external_ids
|
||||||
|
user_info_dict["erased"] = await self.store.is_user_erased(user.to_string())
|
||||||
|
|
||||||
return user_info_dict
|
return user_info_dict
|
||||||
|
|
||||||
|
@ -201,7 +201,7 @@ class DataStore(
|
|||||||
name: Optional[str] = None,
|
name: Optional[str] = None,
|
||||||
guests: bool = True,
|
guests: bool = True,
|
||||||
deactivated: bool = False,
|
deactivated: bool = False,
|
||||||
order_by: str = UserSortOrder.USER_ID.value,
|
order_by: str = UserSortOrder.NAME.value,
|
||||||
direction: str = "f",
|
direction: str = "f",
|
||||||
approved: bool = True,
|
approved: bool = True,
|
||||||
) -> Tuple[List[JsonDict], int]:
|
) -> Tuple[List[JsonDict], int]:
|
||||||
@ -261,6 +261,7 @@ class DataStore(
|
|||||||
sql_base = f"""
|
sql_base = f"""
|
||||||
FROM users as u
|
FROM users as u
|
||||||
LEFT JOIN profiles AS p ON u.name = '@' || p.user_id || ':' || ?
|
LEFT JOIN profiles AS p ON u.name = '@' || p.user_id || ':' || ?
|
||||||
|
LEFT JOIN erased_users AS eu ON u.name = eu.user_id
|
||||||
{where_clause}
|
{where_clause}
|
||||||
"""
|
"""
|
||||||
sql = "SELECT COUNT(*) as total_users " + sql_base
|
sql = "SELECT COUNT(*) as total_users " + sql_base
|
||||||
@ -269,7 +270,8 @@ class DataStore(
|
|||||||
|
|
||||||
sql = f"""
|
sql = f"""
|
||||||
SELECT name, user_type, is_guest, admin, deactivated, shadow_banned,
|
SELECT name, user_type, is_guest, admin, deactivated, shadow_banned,
|
||||||
displayname, avatar_url, creation_ts * 1000 as creation_ts, approved
|
displayname, avatar_url, creation_ts * 1000 as creation_ts, approved,
|
||||||
|
eu.user_id is not null as erased
|
||||||
{sql_base}
|
{sql_base}
|
||||||
ORDER BY {order_by_column} {order}, u.name ASC
|
ORDER BY {order_by_column} {order}, u.name ASC
|
||||||
LIMIT ? OFFSET ?
|
LIMIT ? OFFSET ?
|
||||||
@ -277,6 +279,13 @@ class DataStore(
|
|||||||
args += [limit, start]
|
args += [limit, start]
|
||||||
txn.execute(sql, args)
|
txn.execute(sql, args)
|
||||||
users = self.db_pool.cursor_to_dict(txn)
|
users = self.db_pool.cursor_to_dict(txn)
|
||||||
|
|
||||||
|
# some of those boolean values are returned as integers when we're on SQLite
|
||||||
|
columns_to_boolify = ["erased"]
|
||||||
|
for user in users:
|
||||||
|
for column in columns_to_boolify:
|
||||||
|
user[column] = bool(user[column])
|
||||||
|
|
||||||
return users, count
|
return users, count
|
||||||
|
|
||||||
return await self.db_pool.runInteraction(
|
return await self.db_pool.runInteraction(
|
||||||
|
@ -31,7 +31,7 @@ from synapse.api.room_versions import RoomVersions
|
|||||||
from synapse.rest.client import devices, login, logout, profile, register, room, sync
|
from synapse.rest.client import devices, login, logout, profile, register, room, sync
|
||||||
from synapse.rest.media.v1.filepath import MediaFilePaths
|
from synapse.rest.media.v1.filepath import MediaFilePaths
|
||||||
from synapse.server import HomeServer
|
from synapse.server import HomeServer
|
||||||
from synapse.types import JsonDict, UserID
|
from synapse.types import JsonDict, UserID, create_requester
|
||||||
from synapse.util import Clock
|
from synapse.util import Clock
|
||||||
|
|
||||||
from tests import unittest
|
from tests import unittest
|
||||||
@ -924,6 +924,36 @@ class UsersListTestCase(unittest.HomeserverTestCase):
|
|||||||
self.assertEqual(1, len(non_admin_user_ids), non_admin_user_ids)
|
self.assertEqual(1, len(non_admin_user_ids), non_admin_user_ids)
|
||||||
self.assertEqual(not_approved_user, non_admin_user_ids[0])
|
self.assertEqual(not_approved_user, non_admin_user_ids[0])
|
||||||
|
|
||||||
|
def test_erasure_status(self) -> None:
|
||||||
|
# Create a new user.
|
||||||
|
user_id = self.register_user("eraseme", "eraseme")
|
||||||
|
|
||||||
|
# They should appear in the list users API, marked as not erased.
|
||||||
|
channel = self.make_request(
|
||||||
|
"GET",
|
||||||
|
self.url + "?deactivated=true",
|
||||||
|
access_token=self.admin_user_tok,
|
||||||
|
)
|
||||||
|
users = {user["name"]: user for user in channel.json_body["users"]}
|
||||||
|
self.assertIs(users[user_id]["erased"], False)
|
||||||
|
|
||||||
|
# Deactivate that user, requesting erasure.
|
||||||
|
deactivate_account_handler = self.hs.get_deactivate_account_handler()
|
||||||
|
self.get_success(
|
||||||
|
deactivate_account_handler.deactivate_account(
|
||||||
|
user_id, erase_data=True, requester=create_requester(user_id)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Repeat the list users query. They should now be marked as erased.
|
||||||
|
channel = self.make_request(
|
||||||
|
"GET",
|
||||||
|
self.url + "?deactivated=true",
|
||||||
|
access_token=self.admin_user_tok,
|
||||||
|
)
|
||||||
|
users = {user["name"]: user for user in channel.json_body["users"]}
|
||||||
|
self.assertIs(users[user_id]["erased"], True)
|
||||||
|
|
||||||
def _order_test(
|
def _order_test(
|
||||||
self,
|
self,
|
||||||
expected_user_list: List[str],
|
expected_user_list: List[str],
|
||||||
@ -1195,6 +1225,7 @@ class DeactivateAccountTestCase(unittest.HomeserverTestCase):
|
|||||||
self.assertEqual("foo@bar.com", channel.json_body["threepids"][0]["address"])
|
self.assertEqual("foo@bar.com", channel.json_body["threepids"][0]["address"])
|
||||||
self.assertEqual("mxc://servername/mediaid", channel.json_body["avatar_url"])
|
self.assertEqual("mxc://servername/mediaid", channel.json_body["avatar_url"])
|
||||||
self.assertEqual("User1", channel.json_body["displayname"])
|
self.assertEqual("User1", channel.json_body["displayname"])
|
||||||
|
self.assertFalse(channel.json_body["erased"])
|
||||||
|
|
||||||
# Deactivate and erase user
|
# Deactivate and erase user
|
||||||
channel = self.make_request(
|
channel = self.make_request(
|
||||||
@ -1219,6 +1250,7 @@ class DeactivateAccountTestCase(unittest.HomeserverTestCase):
|
|||||||
self.assertEqual(0, len(channel.json_body["threepids"]))
|
self.assertEqual(0, len(channel.json_body["threepids"]))
|
||||||
self.assertIsNone(channel.json_body["avatar_url"])
|
self.assertIsNone(channel.json_body["avatar_url"])
|
||||||
self.assertIsNone(channel.json_body["displayname"])
|
self.assertIsNone(channel.json_body["displayname"])
|
||||||
|
self.assertTrue(channel.json_body["erased"])
|
||||||
|
|
||||||
self._is_erased("@user:test", True)
|
self._is_erased("@user:test", True)
|
||||||
|
|
||||||
@ -2757,6 +2789,7 @@ class UserRestTestCase(unittest.HomeserverTestCase):
|
|||||||
self.assertIn("avatar_url", content)
|
self.assertIn("avatar_url", content)
|
||||||
self.assertIn("admin", content)
|
self.assertIn("admin", content)
|
||||||
self.assertIn("deactivated", content)
|
self.assertIn("deactivated", content)
|
||||||
|
self.assertIn("erased", content)
|
||||||
self.assertIn("shadow_banned", content)
|
self.assertIn("shadow_banned", content)
|
||||||
self.assertIn("creation_ts", content)
|
self.assertIn("creation_ts", content)
|
||||||
self.assertIn("appservice_id", content)
|
self.assertIn("appservice_id", content)
|
||||||
|
Loading…
Reference in New Issue
Block a user