mirror of
https://github.com/element-hq/synapse.git
synced 2024-12-15 17:51:10 +00:00
Merge branch 'master' into develop
This commit is contained in:
commit
1335367ca7
8 changed files with 98 additions and 18 deletions
10
CHANGES.md
10
CHANGES.md
|
@ -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
6
debian/changelog
vendored
|
@ -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.
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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}}}
|
||||||
|
|
Loading…
Reference in a new issue