Do not propagate typing notifications from shadow-banned users. (#8176)

This commit is contained in:
Patrick Cloke 2020-08-26 12:05:36 -04:00 committed by GitHub
parent e0d6244beb
commit 6fe12c9512
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 102 additions and 24 deletions

1
changelog.d/8176.feature Normal file
View File

@ -0,0 +1 @@
Add support for shadow-banning users (ignoring any message send requests).

View File

@ -14,10 +14,11 @@
# limitations under the License. # limitations under the License.
import logging import logging
import random
from collections import namedtuple from collections import namedtuple
from typing import TYPE_CHECKING, List, Set, Tuple from typing import TYPE_CHECKING, List, Set, Tuple
from synapse.api.errors import AuthError, SynapseError from synapse.api.errors import AuthError, ShadowBanError, SynapseError
from synapse.metrics.background_process_metrics import run_as_background_process from synapse.metrics.background_process_metrics import run_as_background_process
from synapse.replication.tcp.streams import TypingStream from synapse.replication.tcp.streams import TypingStream
from synapse.types import UserID, get_domain_from_id from synapse.types import UserID, get_domain_from_id
@ -227,9 +228,9 @@ class TypingWriterHandler(FollowerTypingHandler):
self._stopped_typing(member) self._stopped_typing(member)
return return
async def started_typing(self, target_user, auth_user, room_id, timeout): async def started_typing(self, target_user, requester, room_id, timeout):
target_user_id = target_user.to_string() target_user_id = target_user.to_string()
auth_user_id = auth_user.to_string() auth_user_id = requester.user.to_string()
if not self.is_mine_id(target_user_id): if not self.is_mine_id(target_user_id):
raise SynapseError(400, "User is not hosted on this homeserver") raise SynapseError(400, "User is not hosted on this homeserver")
@ -237,6 +238,11 @@ class TypingWriterHandler(FollowerTypingHandler):
if target_user_id != auth_user_id: if target_user_id != auth_user_id:
raise AuthError(400, "Cannot set another user's typing state") raise AuthError(400, "Cannot set another user's typing state")
if requester.shadow_banned:
# We randomly sleep a bit just to annoy the requester.
await self.clock.sleep(random.randint(1, 10))
raise ShadowBanError()
await self.auth.check_user_in_room(room_id, target_user_id) await self.auth.check_user_in_room(room_id, target_user_id)
logger.debug("%s has started typing in %s", target_user_id, room_id) logger.debug("%s has started typing in %s", target_user_id, room_id)
@ -256,9 +262,9 @@ class TypingWriterHandler(FollowerTypingHandler):
self._push_update(member=member, typing=True) self._push_update(member=member, typing=True)
async def stopped_typing(self, target_user, auth_user, room_id): async def stopped_typing(self, target_user, requester, room_id):
target_user_id = target_user.to_string() target_user_id = target_user.to_string()
auth_user_id = auth_user.to_string() auth_user_id = requester.user.to_string()
if not self.is_mine_id(target_user_id): if not self.is_mine_id(target_user_id):
raise SynapseError(400, "User is not hosted on this homeserver") raise SynapseError(400, "User is not hosted on this homeserver")
@ -266,6 +272,11 @@ class TypingWriterHandler(FollowerTypingHandler):
if target_user_id != auth_user_id: if target_user_id != auth_user_id:
raise AuthError(400, "Cannot set another user's typing state") raise AuthError(400, "Cannot set another user's typing state")
if requester.shadow_banned:
# We randomly sleep a bit just to annoy the requester.
await self.clock.sleep(random.randint(1, 10))
raise ShadowBanError()
await self.auth.check_user_in_room(room_id, target_user_id) await self.auth.check_user_in_room(room_id, target_user_id)
logger.debug("%s has stopped typing in %s", target_user_id, room_id) logger.debug("%s has stopped typing in %s", target_user_id, room_id)

View File

@ -868,17 +868,21 @@ class RoomTypingRestServlet(RestServlet):
# Limit timeout to stop people from setting silly typing timeouts. # Limit timeout to stop people from setting silly typing timeouts.
timeout = min(content.get("timeout", 30000), 120000) timeout = min(content.get("timeout", 30000), 120000)
if content["typing"]: try:
await self.typing_handler.started_typing( if content["typing"]:
target_user=target_user, await self.typing_handler.started_typing(
auth_user=requester.user, target_user=target_user,
room_id=room_id, requester=requester,
timeout=timeout, room_id=room_id,
) timeout=timeout,
else: )
await self.typing_handler.stopped_typing( else:
target_user=target_user, auth_user=requester.user, room_id=room_id await self.typing_handler.stopped_typing(
) target_user=target_user, requester=requester, room_id=room_id
)
except ShadowBanError:
# Pretend this worked without error.
pass
return 200, {} return 200, {}

View File

@ -21,7 +21,7 @@ from mock import ANY, Mock, call
from twisted.internet import defer from twisted.internet import defer
from synapse.api.errors import AuthError from synapse.api.errors import AuthError
from synapse.types import UserID from synapse.types import UserID, create_requester
from tests import unittest from tests import unittest
from tests.test_utils import make_awaitable from tests.test_utils import make_awaitable
@ -167,7 +167,10 @@ class TypingNotificationsTestCase(unittest.HomeserverTestCase):
self.get_success( self.get_success(
self.handler.started_typing( self.handler.started_typing(
target_user=U_APPLE, auth_user=U_APPLE, room_id=ROOM_ID, timeout=20000 target_user=U_APPLE,
requester=create_requester(U_APPLE),
room_id=ROOM_ID,
timeout=20000,
) )
) )
@ -194,7 +197,10 @@ class TypingNotificationsTestCase(unittest.HomeserverTestCase):
self.get_success( self.get_success(
self.handler.started_typing( self.handler.started_typing(
target_user=U_APPLE, auth_user=U_APPLE, room_id=ROOM_ID, timeout=20000 target_user=U_APPLE,
requester=create_requester(U_APPLE),
room_id=ROOM_ID,
timeout=20000,
) )
) )
@ -269,7 +275,9 @@ class TypingNotificationsTestCase(unittest.HomeserverTestCase):
self.get_success( self.get_success(
self.handler.stopped_typing( self.handler.stopped_typing(
target_user=U_APPLE, auth_user=U_APPLE, room_id=ROOM_ID target_user=U_APPLE,
requester=create_requester(U_APPLE),
room_id=ROOM_ID,
) )
) )
@ -309,7 +317,10 @@ class TypingNotificationsTestCase(unittest.HomeserverTestCase):
self.get_success( self.get_success(
self.handler.started_typing( self.handler.started_typing(
target_user=U_APPLE, auth_user=U_APPLE, room_id=ROOM_ID, timeout=10000 target_user=U_APPLE,
requester=create_requester(U_APPLE),
room_id=ROOM_ID,
timeout=10000,
) )
) )
@ -348,7 +359,10 @@ class TypingNotificationsTestCase(unittest.HomeserverTestCase):
self.get_success( self.get_success(
self.handler.started_typing( self.handler.started_typing(
target_user=U_APPLE, auth_user=U_APPLE, room_id=ROOM_ID, timeout=10000 target_user=U_APPLE,
requester=create_requester(U_APPLE),
room_id=ROOM_ID,
timeout=10000,
) )
) )

View File

@ -20,7 +20,7 @@ from synapse.api.constants import EventTypes, Membership
from synapse.events.builder import EventBuilderFactory from synapse.events.builder import EventBuilderFactory
from synapse.rest.admin import register_servlets_for_client_rest_resource from synapse.rest.admin import register_servlets_for_client_rest_resource
from synapse.rest.client.v1 import login, room from synapse.rest.client.v1 import login, room
from synapse.types import UserID from synapse.types import UserID, create_requester
from tests.replication._base import BaseMultiWorkerStreamTestCase from tests.replication._base import BaseMultiWorkerStreamTestCase
from tests.test_utils import make_awaitable from tests.test_utils import make_awaitable
@ -175,7 +175,7 @@ class FederationSenderTestCase(BaseMultiWorkerStreamTestCase):
self.get_success( self.get_success(
typing_handler.started_typing( typing_handler.started_typing(
target_user=UserID.from_string(user), target_user=UserID.from_string(user),
auth_user=UserID.from_string(user), requester=create_requester(user),
room_id=room, room_id=room,
timeout=20000, timeout=20000,
) )

View File

@ -179,6 +179,54 @@ class RoomTestCase(_ShadowBannedBase):
# The summary should be empty since the room doesn't exist. # The summary should be empty since the room doesn't exist.
self.assertEqual(summary, {}) self.assertEqual(summary, {})
def test_typing(self):
"""Typing notifications should not be propagated into the room."""
# 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(
"PUT",
"/rooms/%s/typing/%s" % (room_id, self.banned_user_id),
{"typing": True, "timeout": 30000},
access_token=self.banned_access_token,
)
self.render(request)
self.assertEquals(200, channel.code)
# There should be no typing events.
event_source = self.hs.get_event_sources().sources["typing"]
self.assertEquals(event_source.get_current_key(), 0)
# The other user can join and send typing events.
self.helper.join(room_id, self.other_user_id, tok=self.other_access_token)
request, channel = self.make_request(
"PUT",
"/rooms/%s/typing/%s" % (room_id, self.other_user_id),
{"typing": True, "timeout": 30000},
access_token=self.other_access_token,
)
self.render(request)
self.assertEquals(200, channel.code)
# These appear in the room.
self.assertEquals(event_source.get_current_key(), 1)
events = self.get_success(
event_source.get_new_events(from_key=0, room_ids=[room_id])
)
self.assertEquals(
events[0],
[
{
"type": "m.typing",
"room_id": room_id,
"content": {"user_ids": [self.other_user_id]},
}
],
)
# To avoid the tests timing out don't add a delay to "annoy the requester". # To avoid the tests timing out don't add a delay to "annoy the requester".
@patch("random.randint", new=lambda a, b: 0) @patch("random.randint", new=lambda a, b: 0)