forked from MirrorHub/synapse
Add ability to un-shadow-ban via the admin API. (#11347)
This commit is contained in:
parent
0dda1a7968
commit
24b61f379a
5 changed files with 53 additions and 12 deletions
1
changelog.d/11347.feature
Normal file
1
changelog.d/11347.feature
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Add admin API to un-shadow-ban a user.
|
|
@ -948,7 +948,7 @@ The following fields are returned in the JSON response body:
|
||||||
See also the
|
See also the
|
||||||
[Client-Server API Spec on pushers](https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-pushers).
|
[Client-Server API Spec on pushers](https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-pushers).
|
||||||
|
|
||||||
## Shadow-banning users
|
## Controlling whether a user is shadow-banned
|
||||||
|
|
||||||
Shadow-banning is a useful tool for moderating malicious or egregiously abusive users.
|
Shadow-banning is a useful tool for moderating malicious or egregiously abusive users.
|
||||||
A shadow-banned users receives successful responses to their client-server API requests,
|
A shadow-banned users receives successful responses to their client-server API requests,
|
||||||
|
@ -961,16 +961,22 @@ or broken behaviour for the client. A shadow-banned user will not receive any
|
||||||
notification and it is generally more appropriate to ban or kick abusive users.
|
notification and it is generally more appropriate to ban or kick abusive users.
|
||||||
A shadow-banned user will be unable to contact anyone on the server.
|
A shadow-banned user will be unable to contact anyone on the server.
|
||||||
|
|
||||||
The API is:
|
To shadow-ban a user the API is:
|
||||||
|
|
||||||
```
|
```
|
||||||
POST /_synapse/admin/v1/users/<user_id>/shadow_ban
|
POST /_synapse/admin/v1/users/<user_id>/shadow_ban
|
||||||
```
|
```
|
||||||
|
|
||||||
|
To un-shadow-ban a user the API is:
|
||||||
|
|
||||||
|
```
|
||||||
|
DELETE /_synapse/admin/v1/users/<user_id>/shadow_ban
|
||||||
|
```
|
||||||
|
|
||||||
To use it, you will need to authenticate by providing an `access_token` for a
|
To use it, you will need to authenticate by providing an `access_token` for a
|
||||||
server admin: [Admin API](../usage/administration/admin_api)
|
server admin: [Admin API](../usage/administration/admin_api)
|
||||||
|
|
||||||
An empty JSON dict is returned.
|
An empty JSON dict is returned in both cases.
|
||||||
|
|
||||||
**Parameters**
|
**Parameters**
|
||||||
|
|
||||||
|
|
|
@ -909,7 +909,7 @@ class UserTokenRestServlet(RestServlet):
|
||||||
|
|
||||||
|
|
||||||
class ShadowBanRestServlet(RestServlet):
|
class ShadowBanRestServlet(RestServlet):
|
||||||
"""An admin API for shadow-banning a user.
|
"""An admin API for controlling whether a user is shadow-banned.
|
||||||
|
|
||||||
A shadow-banned users receives successful responses to their client-server
|
A shadow-banned users receives successful responses to their client-server
|
||||||
API requests, but the events are not propagated into rooms.
|
API requests, but the events are not propagated into rooms.
|
||||||
|
@ -917,11 +917,19 @@ class ShadowBanRestServlet(RestServlet):
|
||||||
Shadow-banning a user should be used as a tool of last resort and may lead
|
Shadow-banning a user should be used as a tool of last resort and may lead
|
||||||
to confusing or broken behaviour for the client.
|
to confusing or broken behaviour for the client.
|
||||||
|
|
||||||
Example:
|
Example of shadow-banning a user:
|
||||||
|
|
||||||
POST /_synapse/admin/v1/users/@test:example.com/shadow_ban
|
POST /_synapse/admin/v1/users/@test:example.com/shadow_ban
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
200 OK
|
||||||
|
{}
|
||||||
|
|
||||||
|
Example of removing a user from being shadow-banned:
|
||||||
|
|
||||||
|
DELETE /_synapse/admin/v1/users/@test:example.com/shadow_ban
|
||||||
|
{}
|
||||||
|
|
||||||
200 OK
|
200 OK
|
||||||
{}
|
{}
|
||||||
"""
|
"""
|
||||||
|
@ -945,6 +953,18 @@ class ShadowBanRestServlet(RestServlet):
|
||||||
|
|
||||||
return 200, {}
|
return 200, {}
|
||||||
|
|
||||||
|
async def on_DELETE(
|
||||||
|
self, request: SynapseRequest, user_id: str
|
||||||
|
) -> Tuple[int, JsonDict]:
|
||||||
|
await assert_requester_is_admin(self.auth, request)
|
||||||
|
|
||||||
|
if not self.hs.is_mine_id(user_id):
|
||||||
|
raise SynapseError(400, "Only local users can be shadow-banned")
|
||||||
|
|
||||||
|
await self.store.set_shadow_banned(UserID.from_string(user_id), False)
|
||||||
|
|
||||||
|
return 200, {}
|
||||||
|
|
||||||
|
|
||||||
class RateLimitRestServlet(RestServlet):
|
class RateLimitRestServlet(RestServlet):
|
||||||
"""An admin API to override ratelimiting for an user.
|
"""An admin API to override ratelimiting for an user.
|
||||||
|
|
|
@ -476,7 +476,7 @@ class RegistrationWorkerStore(CacheInvalidationWorkerStore):
|
||||||
shadow_banned: true iff the user is to be shadow-banned, false otherwise.
|
shadow_banned: true iff the user is to be shadow-banned, false otherwise.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def set_shadow_banned_txn(txn):
|
def set_shadow_banned_txn(txn: LoggingTransaction) -> None:
|
||||||
user_id = user.to_string()
|
user_id = user.to_string()
|
||||||
self.db_pool.simple_update_one_txn(
|
self.db_pool.simple_update_one_txn(
|
||||||
txn,
|
txn,
|
||||||
|
|
|
@ -3592,31 +3592,34 @@ class ShadowBanRestTestCase(unittest.HomeserverTestCase):
|
||||||
self.other_user
|
self.other_user
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_no_auth(self):
|
@parameterized.expand(["POST", "DELETE"])
|
||||||
|
def test_no_auth(self, method: str):
|
||||||
"""
|
"""
|
||||||
Try to get information of an user without authentication.
|
Try to get information of an user without authentication.
|
||||||
"""
|
"""
|
||||||
channel = self.make_request("POST", self.url)
|
channel = self.make_request(method, self.url)
|
||||||
self.assertEqual(401, channel.code, msg=channel.json_body)
|
self.assertEqual(401, channel.code, msg=channel.json_body)
|
||||||
self.assertEqual(Codes.MISSING_TOKEN, channel.json_body["errcode"])
|
self.assertEqual(Codes.MISSING_TOKEN, channel.json_body["errcode"])
|
||||||
|
|
||||||
def test_requester_is_not_admin(self):
|
@parameterized.expand(["POST", "DELETE"])
|
||||||
|
def test_requester_is_not_admin(self, method: str):
|
||||||
"""
|
"""
|
||||||
If the user is not a server admin, an error is returned.
|
If the user is not a server admin, an error is returned.
|
||||||
"""
|
"""
|
||||||
other_user_token = self.login("user", "pass")
|
other_user_token = self.login("user", "pass")
|
||||||
|
|
||||||
channel = self.make_request("POST", self.url, access_token=other_user_token)
|
channel = self.make_request(method, self.url, access_token=other_user_token)
|
||||||
self.assertEqual(403, channel.code, msg=channel.json_body)
|
self.assertEqual(403, channel.code, msg=channel.json_body)
|
||||||
self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
|
self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
|
||||||
|
|
||||||
def test_user_is_not_local(self):
|
@parameterized.expand(["POST", "DELETE"])
|
||||||
|
def test_user_is_not_local(self, method: str):
|
||||||
"""
|
"""
|
||||||
Tests that shadow-banning for a user that is not a local returns a 400
|
Tests that shadow-banning for a user that is not a local returns a 400
|
||||||
"""
|
"""
|
||||||
url = "/_synapse/admin/v1/whois/@unknown_person:unknown_domain"
|
url = "/_synapse/admin/v1/whois/@unknown_person:unknown_domain"
|
||||||
|
|
||||||
channel = self.make_request("POST", url, access_token=self.admin_user_tok)
|
channel = self.make_request(method, url, access_token=self.admin_user_tok)
|
||||||
self.assertEqual(400, channel.code, msg=channel.json_body)
|
self.assertEqual(400, channel.code, msg=channel.json_body)
|
||||||
|
|
||||||
def test_success(self):
|
def test_success(self):
|
||||||
|
@ -3636,6 +3639,17 @@ class ShadowBanRestTestCase(unittest.HomeserverTestCase):
|
||||||
result = self.get_success(self.store.get_user_by_access_token(other_user_token))
|
result = self.get_success(self.store.get_user_by_access_token(other_user_token))
|
||||||
self.assertTrue(result.shadow_banned)
|
self.assertTrue(result.shadow_banned)
|
||||||
|
|
||||||
|
# Un-shadow-ban the user.
|
||||||
|
channel = self.make_request(
|
||||||
|
"DELETE", self.url, access_token=self.admin_user_tok
|
||||||
|
)
|
||||||
|
self.assertEqual(200, channel.code, msg=channel.json_body)
|
||||||
|
self.assertEqual({}, channel.json_body)
|
||||||
|
|
||||||
|
# Ensure the user is no longer shadow-banned (and the cache was cleared).
|
||||||
|
result = self.get_success(self.store.get_user_by_access_token(other_user_token))
|
||||||
|
self.assertFalse(result.shadow_banned)
|
||||||
|
|
||||||
|
|
||||||
class RateLimitTestCase(unittest.HomeserverTestCase):
|
class RateLimitTestCase(unittest.HomeserverTestCase):
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue