Merge branch 'master' into develop

This commit is contained in:
Olivier Wilkinson (reivilibre) 2022-10-28 15:59:51 +01:00
commit 1335367ca7
8 changed files with 98 additions and 18 deletions

View file

@ -1,3 +1,13 @@
Synapse 1.70.1 (2022-10-28)
===========================
Bugfixes
--------
- Fix a bug introduced in Synapse 1.70.0rc1 where the access tokens sent to application services as headers were malformed. Application services which were obtaining access tokens from query parameters were not affected. ([\#14301](https://github.com/matrix-org/synapse/issues/14301))
- Fix room creation being rate limited too aggressively since Synapse v1.69.0. ([\#14314](https://github.com/matrix-org/synapse/issues/14314))
Synapse 1.70.0 (2022-10-26) Synapse 1.70.0 (2022-10-26)
=========================== ===========================

6
debian/changelog vendored
View file

@ -1,3 +1,9 @@
matrix-synapse-py3 (1.70.1) stable; urgency=medium
* New Synapse release 1.70.1.
-- Synapse Packaging team <packages@matrix.org> Fri, 28 Oct 2022 12:10:21 +0100
matrix-synapse-py3 (1.70.0) stable; urgency=medium matrix-synapse-py3 (1.70.0) stable; urgency=medium
* New Synapse release 1.70.0. * New Synapse release 1.70.0.

View file

@ -57,7 +57,7 @@ manifest-path = "rust/Cargo.toml"
[tool.poetry] [tool.poetry]
name = "matrix-synapse" name = "matrix-synapse"
version = "1.70.0" version = "1.70.1"
description = "Homeserver for the Matrix decentralised comms protocol" description = "Homeserver for the Matrix decentralised comms protocol"
authors = ["Matrix.org Team and Contributors <packages@matrix.org>"] authors = ["Matrix.org Team and Contributors <packages@matrix.org>"]
license = "Apache-2.0" license = "Apache-2.0"

View file

@ -343,6 +343,7 @@ class RequestRatelimiter:
requester: Requester, requester: Requester,
update: bool = True, update: bool = True,
is_admin_redaction: bool = False, is_admin_redaction: bool = False,
n_actions: int = 1,
) -> None: ) -> None:
"""Ratelimits requests. """Ratelimits requests.
@ -355,6 +356,8 @@ class RequestRatelimiter:
is_admin_redaction: Whether this is a room admin/moderator is_admin_redaction: Whether this is a room admin/moderator
redacting an event. If so then we may apply different redacting an event. If so then we may apply different
ratelimits depending on config. ratelimits depending on config.
n_actions: Multiplier for the number of actions to apply to the
rate limiter at once.
Raises: Raises:
LimitExceededError if the request should be ratelimited LimitExceededError if the request should be ratelimited
@ -383,7 +386,9 @@ class RequestRatelimiter:
if is_admin_redaction and self.admin_redaction_ratelimiter: if is_admin_redaction and self.admin_redaction_ratelimiter:
# If we have separate config for admin redactions, use a separate # If we have separate config for admin redactions, use a separate
# ratelimiter as to not have user_ids clash # ratelimiter as to not have user_ids clash
await self.admin_redaction_ratelimiter.ratelimit(requester, update=update) await self.admin_redaction_ratelimiter.ratelimit(
requester, update=update, n_actions=n_actions
)
else: else:
# Override rate and burst count per-user # Override rate and burst count per-user
await self.request_ratelimiter.ratelimit( await self.request_ratelimiter.ratelimit(
@ -391,4 +396,5 @@ class RequestRatelimiter:
rate_hz=messages_per_second, rate_hz=messages_per_second,
burst_count=burst_count, burst_count=burst_count,
update=update, update=update,
n_actions=n_actions,
) )

View file

@ -123,7 +123,7 @@ class ApplicationServiceApi(SimpleHttpClient):
response = await self.get_json( response = await self.get_json(
uri, uri,
{"access_token": service.hs_token}, {"access_token": service.hs_token},
headers={"Authorization": f"Bearer {service.hs_token}"}, headers={"Authorization": [f"Bearer {service.hs_token}"]},
) )
if response is not None: # just an empty json object if response is not None: # just an empty json object
return True return True
@ -147,7 +147,7 @@ class ApplicationServiceApi(SimpleHttpClient):
response = await self.get_json( response = await self.get_json(
uri, uri,
{"access_token": service.hs_token}, {"access_token": service.hs_token},
headers={"Authorization": f"Bearer {service.hs_token}"}, headers={"Authorization": [f"Bearer {service.hs_token}"]},
) )
if response is not None: # just an empty json object if response is not None: # just an empty json object
return True return True
@ -190,7 +190,9 @@ class ApplicationServiceApi(SimpleHttpClient):
b"access_token": service.hs_token, b"access_token": service.hs_token,
} }
response = await self.get_json( response = await self.get_json(
uri, args=args, headers={"Authorization": f"Bearer {service.hs_token}"} uri,
args=args,
headers={"Authorization": [f"Bearer {service.hs_token}"]},
) )
if not isinstance(response, list): if not isinstance(response, list):
logger.warning( logger.warning(
@ -230,7 +232,7 @@ class ApplicationServiceApi(SimpleHttpClient):
info = await self.get_json( info = await self.get_json(
uri, uri,
{"access_token": service.hs_token}, {"access_token": service.hs_token},
headers={"Authorization": f"Bearer {service.hs_token}"}, headers={"Authorization": [f"Bearer {service.hs_token}"]},
) )
if not _is_valid_3pe_metadata(info): if not _is_valid_3pe_metadata(info):
@ -327,7 +329,7 @@ class ApplicationServiceApi(SimpleHttpClient):
uri=uri, uri=uri,
json_body=body, json_body=body,
args={"access_token": service.hs_token}, args={"access_token": service.hs_token},
headers={"Authorization": f"Bearer {service.hs_token}"}, headers={"Authorization": [f"Bearer {service.hs_token}"]},
) )
if logger.isEnabledFor(logging.DEBUG): if logger.isEnabledFor(logging.DEBUG):
logger.debug( logger.debug(

View file

@ -557,7 +557,6 @@ class RoomCreationHandler:
invite_list=[], invite_list=[],
initial_state=initial_state, initial_state=initial_state,
creation_content=creation_content, creation_content=creation_content,
ratelimit=False,
) )
# Transfer membership events # Transfer membership events
@ -751,6 +750,10 @@ class RoomCreationHandler:
) )
if ratelimit: if ratelimit:
# Rate limit once in advance, but don't rate limit the individual
# events in the room — room creation isn't atomic and it's very
# janky if half the events in the initial state don't make it because
# of rate limiting.
await self.request_ratelimiter.ratelimit(requester) await self.request_ratelimiter.ratelimit(requester)
room_version_id = config.get( room_version_id = config.get(
@ -911,7 +914,6 @@ class RoomCreationHandler:
room_alias=room_alias, room_alias=room_alias,
power_level_content_override=power_level_content_override, power_level_content_override=power_level_content_override,
creator_join_profile=creator_join_profile, creator_join_profile=creator_join_profile,
ratelimit=ratelimit,
) )
if "name" in config: if "name" in config:
@ -1035,7 +1037,6 @@ class RoomCreationHandler:
room_alias: Optional[RoomAlias] = None, room_alias: Optional[RoomAlias] = None,
power_level_content_override: Optional[JsonDict] = None, power_level_content_override: Optional[JsonDict] = None,
creator_join_profile: Optional[JsonDict] = None, creator_join_profile: Optional[JsonDict] = None,
ratelimit: bool = True,
) -> Tuple[int, str, int]: ) -> Tuple[int, str, int]:
"""Sends the initial events into a new room. Sends the room creation, membership, """Sends the initial events into a new room. Sends the room creation, membership,
and power level events into the room sequentially, then creates and batches up the and power level events into the room sequentially, then creates and batches up the
@ -1044,6 +1045,8 @@ class RoomCreationHandler:
`power_level_content_override` doesn't apply when initial state has `power_level_content_override` doesn't apply when initial state has
power level state event content. power level state event content.
Rate limiting should already have been applied by this point.
Returns: Returns:
A tuple containing the stream ID, event ID and depth of the last A tuple containing the stream ID, event ID and depth of the last
event sent to the room. event sent to the room.
@ -1123,7 +1126,7 @@ class RoomCreationHandler:
creator.user, creator.user,
room_id, room_id,
"join", "join",
ratelimit=ratelimit, ratelimit=False,
content=creator_join_profile, content=creator_join_profile,
new_room=True, new_room=True,
prev_event_ids=[last_sent_event_id], prev_event_ids=[last_sent_event_id],
@ -1248,7 +1251,10 @@ class RoomCreationHandler:
events_to_send.append((encryption_event, encryption_context)) events_to_send.append((encryption_event, encryption_context))
last_event = await self.event_creation_handler.handle_new_client_event( last_event = await self.event_creation_handler.handle_new_client_event(
creator, events_to_send, ignore_shadow_ban=True creator,
events_to_send,
ignore_shadow_ban=True,
ratelimit=False,
) )
assert last_event.internal_metadata.stream_ordering is not None assert last_event.internal_metadata.stream_ordering is not None
return last_event.internal_metadata.stream_ordering, last_event.event_id, depth return last_event.internal_metadata.stream_ordering, last_event.event_id, depth

View file

@ -11,7 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# 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.
from typing import Any, List, Mapping from typing import Any, List, Mapping, Sequence, Union
from unittest.mock import Mock from unittest.mock import Mock
from twisted.test.proto_helpers import MemoryReactor from twisted.test.proto_helpers import MemoryReactor
@ -70,13 +70,15 @@ class ApplicationServiceApiTestCase(unittest.HomeserverTestCase):
self.request_url = None self.request_url = None
async def get_json( async def get_json(
url: str, args: Mapping[Any, Any], headers: Mapping[Any, Any] url: str,
args: Mapping[Any, Any],
headers: Mapping[Union[str, bytes], Sequence[Union[str, bytes]]],
) -> List[JsonDict]: ) -> List[JsonDict]:
# Ensure the access token is passed as both a header and query arg. # Ensure the access token is passed as both a header and query arg.
if not headers.get("Authorization") or not args.get(b"access_token"): if not headers.get("Authorization") or not args.get(b"access_token"):
raise RuntimeError("Access token not provided") raise RuntimeError("Access token not provided")
self.assertEqual(headers.get("Authorization"), f"Bearer {TOKEN}") self.assertEqual(headers.get("Authorization"), [f"Bearer {TOKEN}"])
self.assertEqual(args.get(b"access_token"), TOKEN) self.assertEqual(args.get(b"access_token"), TOKEN)
self.request_url = url self.request_url = url
if url == URL_USER: if url == URL_USER:

View file

@ -54,6 +54,7 @@ from tests.http.server._base import make_request_with_cancellation_test
from tests.storage.test_stream import PaginationTestCase from tests.storage.test_stream import PaginationTestCase
from tests.test_utils import make_awaitable from tests.test_utils import make_awaitable
from tests.test_utils.event_injection import create_event from tests.test_utils.event_injection import create_event
from tests.unittest import override_config
PATH_PREFIX = b"/_matrix/client/api/v1" PATH_PREFIX = b"/_matrix/client/api/v1"
@ -871,6 +872,41 @@ class RoomsCreateTestCase(RoomBase):
self.assertEqual(channel.code, HTTPStatus.OK, channel.json_body) self.assertEqual(channel.code, HTTPStatus.OK, channel.json_body)
self.assertEqual(join_mock.call_count, 0) self.assertEqual(join_mock.call_count, 0)
def _create_basic_room(self) -> Tuple[int, object]:
"""
Tries to create a basic room and returns the response code.
"""
channel = self.make_request(
"POST",
"/createRoom",
{},
)
return channel.code, channel.json_body
@override_config(
{
"rc_message": {"per_second": 0.2, "burst_count": 10},
}
)
def test_room_creation_ratelimiting(self) -> None:
"""
Regression test for #14312, where ratelimiting was made too strict.
Clients should be able to create 10 rooms in a row
without hitting rate limits, using default rate limit config.
(We override rate limiting config back to its default value.)
To ensure we don't make ratelimiting too generous accidentally,
also check that we can't create an 11th room.
"""
for _ in range(10):
code, json_body = self._create_basic_room()
self.assertEqual(code, HTTPStatus.OK, json_body)
# The 6th room hits the rate limit.
code, json_body = self._create_basic_room()
self.assertEqual(code, HTTPStatus.TOO_MANY_REQUESTS, json_body)
class RoomTopicTestCase(RoomBase): class RoomTopicTestCase(RoomBase):
"""Tests /rooms/$room_id/topic REST events.""" """Tests /rooms/$room_id/topic REST events."""
@ -1390,10 +1426,22 @@ class RoomJoinRatelimitTestCase(RoomBase):
) )
def test_join_local_ratelimit(self) -> None: def test_join_local_ratelimit(self) -> None:
"""Tests that local joins are actually rate-limited.""" """Tests that local joins are actually rate-limited."""
for _ in range(3): # Create 4 rooms
self.helper.create_room_as(self.user_id) room_ids = [
self.helper.create_room_as(self.user_id, is_public=True) for _ in range(4)
]
self.helper.create_room_as(self.user_id, expect_code=429) joiner_user_id = self.register_user("joiner", "secret")
# Now make a new user try to join some of them.
# The user can join 3 rooms
for room_id in room_ids[0:3]:
self.helper.join(room_id, joiner_user_id)
# But the user cannot join a 4th room
self.helper.join(
room_ids[3], joiner_user_id, expect_code=HTTPStatus.TOO_MANY_REQUESTS
)
@unittest.override_config( @unittest.override_config(
{"rc_joins": {"local": {"per_second": 0.5, "burst_count": 3}}} {"rc_joins": {"local": {"per_second": 0.5, "burst_count": 3}}}