mirror of
https://mau.dev/maunium/synapse.git
synced 2024-12-14 19:23:53 +01:00
Stop shadow-banned users from sending non-member events. (#8142)
This commit is contained in:
parent
420484a334
commit
cbd8d83da7
8 changed files with 155 additions and 42 deletions
1
changelog.d/8142.feature
Normal file
1
changelog.d/8142.feature
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Add support for shadow-banning users (ignoring any message send requests).
|
|
@ -23,6 +23,7 @@ from synapse.api.errors import (
|
||||||
CodeMessageException,
|
CodeMessageException,
|
||||||
Codes,
|
Codes,
|
||||||
NotFoundError,
|
NotFoundError,
|
||||||
|
ShadowBanError,
|
||||||
StoreError,
|
StoreError,
|
||||||
SynapseError,
|
SynapseError,
|
||||||
)
|
)
|
||||||
|
@ -199,6 +200,8 @@ class DirectoryHandler(BaseHandler):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await self._update_canonical_alias(requester, user_id, room_id, room_alias)
|
await self._update_canonical_alias(requester, user_id, room_id, room_alias)
|
||||||
|
except ShadowBanError as e:
|
||||||
|
logger.info("Failed to update alias events due to shadow-ban: %s", e)
|
||||||
except AuthError as e:
|
except AuthError as e:
|
||||||
logger.info("Failed to update alias events: %s", e)
|
logger.info("Failed to update alias events: %s", e)
|
||||||
|
|
||||||
|
@ -292,6 +295,9 @@ class DirectoryHandler(BaseHandler):
|
||||||
"""
|
"""
|
||||||
Send an updated canonical alias event if the removed alias was set as
|
Send an updated canonical alias event if the removed alias was set as
|
||||||
the canonical alias or listed in the alt_aliases field.
|
the canonical alias or listed in the alt_aliases field.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ShadowBanError if the requester has been shadow-banned.
|
||||||
"""
|
"""
|
||||||
alias_event = await self.state.get_current_state(
|
alias_event = await self.state.get_current_state(
|
||||||
room_id, EventTypes.CanonicalAlias, ""
|
room_id, EventTypes.CanonicalAlias, ""
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
import logging
|
import logging
|
||||||
|
import random
|
||||||
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple
|
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
from canonicaljson import encode_canonical_json
|
from canonicaljson import encode_canonical_json
|
||||||
|
@ -34,6 +35,7 @@ from synapse.api.errors import (
|
||||||
Codes,
|
Codes,
|
||||||
ConsentNotGivenError,
|
ConsentNotGivenError,
|
||||||
NotFoundError,
|
NotFoundError,
|
||||||
|
ShadowBanError,
|
||||||
SynapseError,
|
SynapseError,
|
||||||
)
|
)
|
||||||
from synapse.api.room_versions import KNOWN_ROOM_VERSIONS, RoomVersions
|
from synapse.api.room_versions import KNOWN_ROOM_VERSIONS, RoomVersions
|
||||||
|
@ -716,12 +718,20 @@ class EventCreationHandler(object):
|
||||||
event_dict: dict,
|
event_dict: dict,
|
||||||
ratelimit: bool = True,
|
ratelimit: bool = True,
|
||||||
txn_id: Optional[str] = None,
|
txn_id: Optional[str] = None,
|
||||||
|
ignore_shadow_ban: bool = False,
|
||||||
) -> Tuple[EventBase, int]:
|
) -> Tuple[EventBase, int]:
|
||||||
"""
|
"""
|
||||||
Creates an event, then sends it.
|
Creates an event, then sends it.
|
||||||
|
|
||||||
See self.create_event and self.send_nonmember_event.
|
See self.create_event and self.send_nonmember_event.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ShadowBanError if the requester has been shadow-banned.
|
||||||
"""
|
"""
|
||||||
|
if not ignore_shadow_ban and requester.shadow_banned:
|
||||||
|
# We randomly sleep a bit just to annoy the requester.
|
||||||
|
await self.clock.sleep(random.randint(1, 10))
|
||||||
|
raise ShadowBanError()
|
||||||
|
|
||||||
# We limit the number of concurrent event sends in a room so that we
|
# We limit the number of concurrent event sends in a room so that we
|
||||||
# don't fork the DAG too much. If we don't limit then we can end up in
|
# don't fork the DAG too much. If we don't limit then we can end up in
|
||||||
|
|
|
@ -136,6 +136,9 @@ class RoomCreationHandler(BaseHandler):
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
the new room id
|
the new room id
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ShadowBanError if the requester is shadow-banned.
|
||||||
"""
|
"""
|
||||||
await self.ratelimit(requester)
|
await self.ratelimit(requester)
|
||||||
|
|
||||||
|
@ -171,6 +174,15 @@ class RoomCreationHandler(BaseHandler):
|
||||||
async def _upgrade_room(
|
async def _upgrade_room(
|
||||||
self, requester: Requester, old_room_id: str, new_version: RoomVersion
|
self, requester: Requester, old_room_id: str, new_version: RoomVersion
|
||||||
):
|
):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
requester: the user requesting the upgrade
|
||||||
|
old_room_id: the id of the room to be replaced
|
||||||
|
new_versions: the version to upgrade the room to
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ShadowBanError if the requester is shadow-banned.
|
||||||
|
"""
|
||||||
user_id = requester.user.to_string()
|
user_id = requester.user.to_string()
|
||||||
|
|
||||||
# start by allocating a new room id
|
# start by allocating a new room id
|
||||||
|
@ -257,6 +269,9 @@ class RoomCreationHandler(BaseHandler):
|
||||||
old_room_id: the id of the room to be replaced
|
old_room_id: the id of the room to be replaced
|
||||||
new_room_id: the id of the replacement room
|
new_room_id: the id of the replacement room
|
||||||
old_room_state: the state map for the old room
|
old_room_state: the state map for the old room
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ShadowBanError if the requester is shadow-banned.
|
||||||
"""
|
"""
|
||||||
old_room_pl_event_id = old_room_state.get((EventTypes.PowerLevels, ""))
|
old_room_pl_event_id = old_room_state.get((EventTypes.PowerLevels, ""))
|
||||||
|
|
||||||
|
@ -829,11 +844,13 @@ class RoomCreationHandler(BaseHandler):
|
||||||
async def send(etype: str, content: JsonDict, **kwargs) -> int:
|
async def send(etype: str, content: JsonDict, **kwargs) -> int:
|
||||||
event = create(etype, content, **kwargs)
|
event = create(etype, content, **kwargs)
|
||||||
logger.debug("Sending %s in new room", etype)
|
logger.debug("Sending %s in new room", etype)
|
||||||
|
# Allow these events to be sent even if the user is shadow-banned to
|
||||||
|
# allow the room creation to complete.
|
||||||
(
|
(
|
||||||
_,
|
_,
|
||||||
last_stream_id,
|
last_stream_id,
|
||||||
) = await self.event_creation_handler.create_and_send_nonmember_event(
|
) = await self.event_creation_handler.create_and_send_nonmember_event(
|
||||||
creator, event, ratelimit=False
|
creator, event, ratelimit=False, ignore_shadow_ban=True,
|
||||||
)
|
)
|
||||||
return last_stream_id
|
return last_stream_id
|
||||||
|
|
||||||
|
|
|
@ -201,8 +201,8 @@ class RoomStateEventRestServlet(TransactionRestServlet):
|
||||||
if state_key is not None:
|
if state_key is not None:
|
||||||
event_dict["state_key"] = state_key
|
event_dict["state_key"] = state_key
|
||||||
|
|
||||||
if event_type == EventTypes.Member:
|
|
||||||
try:
|
try:
|
||||||
|
if event_type == EventTypes.Member:
|
||||||
membership = content.get("membership", None)
|
membership = content.get("membership", None)
|
||||||
event_id, _ = await self.room_member_handler.update_membership(
|
event_id, _ = await self.room_member_handler.update_membership(
|
||||||
requester,
|
requester,
|
||||||
|
@ -211,8 +211,6 @@ class RoomStateEventRestServlet(TransactionRestServlet):
|
||||||
action=membership,
|
action=membership,
|
||||||
content=content,
|
content=content,
|
||||||
)
|
)
|
||||||
except ShadowBanError:
|
|
||||||
event_id = "$" + random_string(43)
|
|
||||||
else:
|
else:
|
||||||
(
|
(
|
||||||
event,
|
event,
|
||||||
|
@ -221,6 +219,8 @@ class RoomStateEventRestServlet(TransactionRestServlet):
|
||||||
requester, event_dict, txn_id=txn_id
|
requester, event_dict, txn_id=txn_id
|
||||||
)
|
)
|
||||||
event_id = event.event_id
|
event_id = event.event_id
|
||||||
|
except ShadowBanError:
|
||||||
|
event_id = "$" + random_string(43)
|
||||||
|
|
||||||
set_tag("event_id", event_id)
|
set_tag("event_id", event_id)
|
||||||
ret = {"event_id": event_id}
|
ret = {"event_id": event_id}
|
||||||
|
@ -253,12 +253,19 @@ class RoomSendEventRestServlet(TransactionRestServlet):
|
||||||
if b"ts" in request.args and requester.app_service:
|
if b"ts" in request.args and requester.app_service:
|
||||||
event_dict["origin_server_ts"] = parse_integer(request, "ts", 0)
|
event_dict["origin_server_ts"] = parse_integer(request, "ts", 0)
|
||||||
|
|
||||||
event, _ = await self.event_creation_handler.create_and_send_nonmember_event(
|
try:
|
||||||
|
(
|
||||||
|
event,
|
||||||
|
_,
|
||||||
|
) = await self.event_creation_handler.create_and_send_nonmember_event(
|
||||||
requester, event_dict, txn_id=txn_id
|
requester, event_dict, txn_id=txn_id
|
||||||
)
|
)
|
||||||
|
event_id = event.event_id
|
||||||
|
except ShadowBanError:
|
||||||
|
event_id = "$" + random_string(43)
|
||||||
|
|
||||||
set_tag("event_id", event.event_id)
|
set_tag("event_id", event_id)
|
||||||
return 200, {"event_id": event.event_id}
|
return 200, {"event_id": event_id}
|
||||||
|
|
||||||
def on_GET(self, request, room_id, event_type, txn_id):
|
def on_GET(self, request, room_id, event_type, txn_id):
|
||||||
return 200, "Not implemented"
|
return 200, "Not implemented"
|
||||||
|
@ -799,7 +806,11 @@ class RoomRedactEventRestServlet(TransactionRestServlet):
|
||||||
requester = await self.auth.get_user_by_req(request)
|
requester = await self.auth.get_user_by_req(request)
|
||||||
content = parse_json_object_from_request(request)
|
content = parse_json_object_from_request(request)
|
||||||
|
|
||||||
event, _ = await self.event_creation_handler.create_and_send_nonmember_event(
|
try:
|
||||||
|
(
|
||||||
|
event,
|
||||||
|
_,
|
||||||
|
) = await self.event_creation_handler.create_and_send_nonmember_event(
|
||||||
requester,
|
requester,
|
||||||
{
|
{
|
||||||
"type": EventTypes.Redaction,
|
"type": EventTypes.Redaction,
|
||||||
|
@ -810,9 +821,12 @@ class RoomRedactEventRestServlet(TransactionRestServlet):
|
||||||
},
|
},
|
||||||
txn_id=txn_id,
|
txn_id=txn_id,
|
||||||
)
|
)
|
||||||
|
event_id = event.event_id
|
||||||
|
except ShadowBanError:
|
||||||
|
event_id = "$" + random_string(43)
|
||||||
|
|
||||||
set_tag("event_id", event.event_id)
|
set_tag("event_id", event_id)
|
||||||
return 200, {"event_id": event.event_id}
|
return 200, {"event_id": event_id}
|
||||||
|
|
||||||
def on_PUT(self, request, room_id, event_id, txn_id):
|
def on_PUT(self, request, room_id, event_id, txn_id):
|
||||||
set_tag("txn_id", txn_id)
|
set_tag("txn_id", txn_id)
|
||||||
|
|
|
@ -22,7 +22,7 @@ any time to reflect changes in the MSC.
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from synapse.api.constants import EventTypes, RelationTypes
|
from synapse.api.constants import EventTypes, RelationTypes
|
||||||
from synapse.api.errors import SynapseError
|
from synapse.api.errors import ShadowBanError, SynapseError
|
||||||
from synapse.http.servlet import (
|
from synapse.http.servlet import (
|
||||||
RestServlet,
|
RestServlet,
|
||||||
parse_integer,
|
parse_integer,
|
||||||
|
@ -35,6 +35,7 @@ from synapse.storage.relations import (
|
||||||
PaginationChunk,
|
PaginationChunk,
|
||||||
RelationPaginationToken,
|
RelationPaginationToken,
|
||||||
)
|
)
|
||||||
|
from synapse.util.stringutils import random_string
|
||||||
|
|
||||||
from ._base import client_patterns
|
from ._base import client_patterns
|
||||||
|
|
||||||
|
@ -111,11 +112,18 @@ class RelationSendServlet(RestServlet):
|
||||||
"sender": requester.user.to_string(),
|
"sender": requester.user.to_string(),
|
||||||
}
|
}
|
||||||
|
|
||||||
event, _ = await self.event_creation_handler.create_and_send_nonmember_event(
|
try:
|
||||||
|
(
|
||||||
|
event,
|
||||||
|
_,
|
||||||
|
) = await self.event_creation_handler.create_and_send_nonmember_event(
|
||||||
requester, event_dict=event_dict, txn_id=txn_id
|
requester, event_dict=event_dict, txn_id=txn_id
|
||||||
)
|
)
|
||||||
|
event_id = event.event_id
|
||||||
|
except ShadowBanError:
|
||||||
|
event_id = "$" + random_string(43)
|
||||||
|
|
||||||
return 200, {"event_id": event.event_id}
|
return 200, {"event_id": event_id}
|
||||||
|
|
||||||
|
|
||||||
class RelationPaginationServlet(RestServlet):
|
class RelationPaginationServlet(RestServlet):
|
||||||
|
|
|
@ -15,13 +15,14 @@
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from synapse.api.errors import Codes, SynapseError
|
from synapse.api.errors import Codes, ShadowBanError, SynapseError
|
||||||
from synapse.api.room_versions import KNOWN_ROOM_VERSIONS
|
from synapse.api.room_versions import KNOWN_ROOM_VERSIONS
|
||||||
from synapse.http.servlet import (
|
from synapse.http.servlet import (
|
||||||
RestServlet,
|
RestServlet,
|
||||||
assert_params_in_dict,
|
assert_params_in_dict,
|
||||||
parse_json_object_from_request,
|
parse_json_object_from_request,
|
||||||
)
|
)
|
||||||
|
from synapse.util import stringutils
|
||||||
|
|
||||||
from ._base import client_patterns
|
from ._base import client_patterns
|
||||||
|
|
||||||
|
@ -62,7 +63,6 @@ class RoomUpgradeRestServlet(RestServlet):
|
||||||
|
|
||||||
content = parse_json_object_from_request(request)
|
content = parse_json_object_from_request(request)
|
||||||
assert_params_in_dict(content, ("new_version",))
|
assert_params_in_dict(content, ("new_version",))
|
||||||
new_version = content["new_version"]
|
|
||||||
|
|
||||||
new_version = KNOWN_ROOM_VERSIONS.get(content["new_version"])
|
new_version = KNOWN_ROOM_VERSIONS.get(content["new_version"])
|
||||||
if new_version is None:
|
if new_version is None:
|
||||||
|
@ -72,9 +72,13 @@ class RoomUpgradeRestServlet(RestServlet):
|
||||||
Codes.UNSUPPORTED_ROOM_VERSION,
|
Codes.UNSUPPORTED_ROOM_VERSION,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
new_room_id = await self._room_creation_handler.upgrade_room(
|
new_room_id = await self._room_creation_handler.upgrade_room(
|
||||||
requester, room_id, new_version
|
requester, room_id, new_version
|
||||||
)
|
)
|
||||||
|
except ShadowBanError:
|
||||||
|
# Generate a random room ID.
|
||||||
|
new_room_id = stringutils.random_string(18)
|
||||||
|
|
||||||
ret = {"replacement_room": new_room_id}
|
ret = {"replacement_room": new_room_id}
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ import synapse.rest.admin
|
||||||
from synapse.api.constants import EventContentFields, EventTypes, Membership
|
from synapse.api.constants import EventContentFields, EventTypes, Membership
|
||||||
from synapse.handlers.pagination import PurgeStatus
|
from synapse.handlers.pagination import PurgeStatus
|
||||||
from synapse.rest.client.v1 import directory, login, profile, room
|
from synapse.rest.client.v1 import directory, login, profile, room
|
||||||
from synapse.rest.client.v2_alpha import account
|
from synapse.rest.client.v2_alpha import account, room_upgrade_rest_servlet
|
||||||
from synapse.types import JsonDict, RoomAlias
|
from synapse.types import JsonDict, RoomAlias
|
||||||
from synapse.util.stringutils import random_string
|
from synapse.util.stringutils import random_string
|
||||||
|
|
||||||
|
@ -1984,6 +1984,7 @@ class ShadowBannedTestCase(unittest.HomeserverTestCase):
|
||||||
directory.register_servlets,
|
directory.register_servlets,
|
||||||
login.register_servlets,
|
login.register_servlets,
|
||||||
room.register_servlets,
|
room.register_servlets,
|
||||||
|
room_upgrade_rest_servlet.register_servlets,
|
||||||
]
|
]
|
||||||
|
|
||||||
def prepare(self, reactor, clock, homeserver):
|
def prepare(self, reactor, clock, homeserver):
|
||||||
|
@ -2076,3 +2077,55 @@ class ShadowBannedTestCase(unittest.HomeserverTestCase):
|
||||||
# Both users should be in the room.
|
# Both users should be in the room.
|
||||||
users = self.get_success(self.store.get_users_in_room(room_id))
|
users = self.get_success(self.store.get_users_in_room(room_id))
|
||||||
self.assertCountEqual(users, ["@banned:test", "@otheruser:test"])
|
self.assertCountEqual(users, ["@banned:test", "@otheruser:test"])
|
||||||
|
|
||||||
|
def test_message(self):
|
||||||
|
"""Messages from shadow-banned users don't actually get sent."""
|
||||||
|
|
||||||
|
room_id = self.helper.create_room_as(
|
||||||
|
self.other_user_id, tok=self.other_access_token
|
||||||
|
)
|
||||||
|
|
||||||
|
# The user should be in the room.
|
||||||
|
self.helper.join(room_id, self.banned_user_id, tok=self.banned_access_token)
|
||||||
|
|
||||||
|
# Sending a message should complete successfully.
|
||||||
|
result = self.helper.send_event(
|
||||||
|
room_id=room_id,
|
||||||
|
type=EventTypes.Message,
|
||||||
|
content={"msgtype": "m.text", "body": "with right label"},
|
||||||
|
tok=self.banned_access_token,
|
||||||
|
)
|
||||||
|
self.assertIn("event_id", result)
|
||||||
|
event_id = result["event_id"]
|
||||||
|
|
||||||
|
latest_events = self.get_success(
|
||||||
|
self.store.get_latest_event_ids_in_room(room_id)
|
||||||
|
)
|
||||||
|
self.assertNotIn(event_id, latest_events)
|
||||||
|
|
||||||
|
def test_upgrade(self):
|
||||||
|
"""A room upgrade should fail, but look like it succeeded."""
|
||||||
|
|
||||||
|
# The create works fine.
|
||||||
|
room_id = self.helper.create_room_as(
|
||||||
|
self.banned_user_id, tok=self.banned_access_token
|
||||||
|
)
|
||||||
|
|
||||||
|
request, channel = self.make_request(
|
||||||
|
"POST",
|
||||||
|
"/_matrix/client/r0/rooms/%s/upgrade" % (room_id,),
|
||||||
|
{"new_version": "6"},
|
||||||
|
access_token=self.banned_access_token,
|
||||||
|
)
|
||||||
|
self.render(request)
|
||||||
|
self.assertEquals(200, channel.code, channel.result)
|
||||||
|
# A new room_id should be returned.
|
||||||
|
self.assertIn("replacement_room", channel.json_body)
|
||||||
|
|
||||||
|
new_room_id = channel.json_body["replacement_room"]
|
||||||
|
|
||||||
|
# It doesn't really matter what API we use here, we just want to assert
|
||||||
|
# that the room doesn't exist.
|
||||||
|
summary = self.get_success(self.store.get_room_summary(new_room_id))
|
||||||
|
# The summary should be empty since the room doesn't exist.
|
||||||
|
self.assertEqual(summary, {})
|
||||||
|
|
Loading…
Reference in a new issue