Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

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

Merged
merged 3 commits into from
Nov 16, 2021
Merged
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 changelog.d/11347.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add admin API to un-shadow-ban a user.
12 changes: 9 additions & 3 deletions docs/admin_api/user_admin_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -948,7 +948,7 @@ The following fields are returned in the JSON response body:
See also the
[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.
A shadow-banned users receives successful responses to their client-server API requests,
Expand All @@ -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.
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
```

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
server admin: [Admin API](../usage/administration/admin_api)

An empty JSON dict is returned.
An empty JSON dict is returned in both cases.

**Parameters**

Expand Down
24 changes: 22 additions & 2 deletions synapse/rest/admin/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -909,19 +909,27 @@ async def on_POST(


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
API requests, but the events are not propagated into rooms.

Shadow-banning a user should be used as a tool of last resort and may lead
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
{}

200 OK
{}

Example of removing a user from being shadow-banned:

DELETE /_synapse/admin/v1/users/@test:example.com/shadow_ban
{}

200 OK
{}
"""
Expand All @@ -945,6 +953,18 @@ async def on_POST(

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):
"""An admin API to override ratelimiting for an user.
Expand Down
2 changes: 1 addition & 1 deletion synapse/storage/databases/main/registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,7 @@ async def set_shadow_banned(self, user: UserID, shadow_banned: bool) -> None:
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()
self.db_pool.simple_update_one_txn(
txn,
Expand Down
26 changes: 20 additions & 6 deletions tests/rest/admin/test_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -3592,31 +3592,34 @@ def prepare(self, reactor, clock, hs):
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.
"""
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(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.
"""
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(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
"""
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)

def test_success(self):
Expand All @@ -3636,6 +3639,17 @@ def test_success(self):
result = self.get_success(self.store.get_user_by_access_token(other_user_token))
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):

Expand Down