Add ability to un-shadow-ban via the admin API. (#11347)

This commit is contained in:
Patrick Cloke 2021-11-16 07:43:53 -05:00 committed by GitHub
parent 0dda1a7968
commit 24b61f379a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 53 additions and 12 deletions

View file

@ -0,0 +1 @@
Add admin API to un-shadow-ban a user.

View file

@ -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**

View file

@ -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.

View file

@ -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,

View file

@ -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):