Compare commits

..

2 Commits

Author SHA1 Message Date
Eric Eastwood
c50223e50a Add changelog 2025-01-02 16:28:30 -06:00
Eric Eastwood
62945266a8 Add docs for setting tls for a worker instance in instance_map 2025-01-02 16:24:02 -06:00
38 changed files with 3918 additions and 4005 deletions

3838
CHANGES.md

File diff suppressed because it is too large Load Diff

8
Cargo.lock generated
View File

@@ -431,18 +431,18 @@ checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]]
name = "serde"
version = "1.0.217"
version = "1.0.216"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.217"
version = "1.0.216"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e"
dependencies = [
"proc-macro2",
"quote",

1
changelog.d/17846.misc Normal file
View File

@@ -0,0 +1 @@
Update Alpine Linux Synapse Package Maintainer within installation.md.

View File

@@ -0,0 +1 @@
Added the `email.tlsname` config option. This allows specifying the domain name used to validate the SMTP server's TLS certificate separately from the `email.smtp_host` to connect to.

View File

@@ -0,0 +1 @@
Module developers will have access to user id of requester when adding `check_username_for_spam` callbacks to `spam_checker_module_callbacks`. Contributed by Wilson@Pangea.chat.

1
changelog.d/17930.bugfix Normal file
View File

@@ -0,0 +1 @@
Fix bug when rejecting withdrew invite with a third_party_rules module, where the invite would be stuck for the client.

View File

@@ -0,0 +1,3 @@
Add endpoints to Admin API to fetch the number of invites the provided user has sent after a given timestamp,
fetch the number of rooms the provided user has joined after a given timestamp, and get report IDs of event
reports against a provided user (ie where the user was the sender of the reported event).

1
changelog.d/17954.doc Normal file
View File

@@ -0,0 +1 @@
Update `synapse.app.generic_worker` documentation to only recommend `GET` requests for stream writer routes by default, unless the worker is also configured as a stream writer. Contributed by @evoL.

View File

@@ -0,0 +1 @@
Support stable account suspension from [MSC3823](https://github.com/matrix-org/matrix-spec-proposals/pull/3823).

1
changelog.d/17976.doc Normal file
View File

@@ -0,0 +1 @@
Add previously-undocumented `last_seen_ts` to query user admin API.

View File

@@ -0,0 +1 @@
Add `macaroon_secret_key_path` config option.

1
changelog.d/17992.doc Normal file
View File

@@ -0,0 +1 @@
Improve documentation for the `TaskScheduler` class.

1
changelog.d/17994.doc Normal file
View File

@@ -0,0 +1 @@
Fix example in reverse proxy docs to include server port.

1
changelog.d/17996.misc Normal file
View File

@@ -0,0 +1 @@
Add `RoomID` & `EventID` rust types.

1
changelog.d/17998.misc Normal file
View File

@@ -0,0 +1 @@
Fix various type errors across the codebase.

1
changelog.d/17999.misc Normal file
View File

@@ -0,0 +1 @@
Bump mypy from 1.11.2 to 1.12.1.

1
changelog.d/18017.misc Normal file
View File

@@ -0,0 +1 @@
Disable DB statement timeout when doing a purge room since it can be quite long.

1
changelog.d/18020.misc Normal file
View File

@@ -0,0 +1 @@
Remove some remaining uses of `twisted.internet.defer.returnValue`. Contributed by Colin Watson.

1
changelog.d/18029.bugfix Normal file
View File

@@ -0,0 +1 @@
Fix a bug preventing the admin redaction endpoint from working on messages from remote users.

View File

@@ -0,0 +1 @@
Remove support for PostgreSQL 11 and 12. Contributed by @clokep.

1
changelog.d/18064.doc Normal file
View File

@@ -0,0 +1 @@
Document `tls` option for a worker instance in `instance_map`.

12
debian/changelog vendored
View File

@@ -1,15 +1,3 @@
matrix-synapse-py3 (1.122.0) stable; urgency=medium
* New Synapse release 1.122.0.
-- Synapse Packaging team <packages@matrix.org> Tue, 14 Jan 2025 14:14:14 +0000
matrix-synapse-py3 (1.122.0~rc1) stable; urgency=medium
* New Synapse release 1.122.0rc1.
-- Synapse Packaging team <packages@matrix.org> Tue, 07 Jan 2025 14:06:19 +0000
matrix-synapse-py3 (1.121.1) stable; urgency=medium
* New Synapse release 1.121.1.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -4465,6 +4465,10 @@ instance_map:
worker1:
host: localhost
port: 8034
other:
host: localhost
port: 8035
tls: true
```
Example configuration(#2, for UNIX sockets):
```yaml

16
poetry.lock generated
View File

@@ -1825,12 +1825,12 @@ plugins = ["importlib-metadata"]
[[package]]
name = "pyicu"
version = "2.14"
version = "2.13.1"
description = "Python extension wrapping the ICU C++ API"
optional = true
python-versions = "*"
files = [
{file = "PyICU-2.14.tar.gz", hash = "sha256:acc7eb92bd5c554ed577249c6978450a4feda0aa6f01470152b3a7b382a02132"},
{file = "PyICU-2.13.1.tar.gz", hash = "sha256:d4919085eaa07da12bade8ee721e7bbf7ade0151ca0f82946a26c8f4b98cdceb"},
]
[[package]]
@@ -2321,13 +2321,13 @@ doc = ["Sphinx", "sphinx-rtd-theme"]
[[package]]
name = "sentry-sdk"
version = "2.19.2"
version = "2.17.0"
description = "Python client for Sentry (https://sentry.io)"
optional = true
python-versions = ">=3.6"
files = [
{file = "sentry_sdk-2.19.2-py2.py3-none-any.whl", hash = "sha256:ebdc08228b4d131128e568d696c210d846e5b9d70aa0327dec6b1272d9d40b84"},
{file = "sentry_sdk-2.19.2.tar.gz", hash = "sha256:467df6e126ba242d39952375dd816fbee0f217d119bf454a8ce74cf1e7909e8d"},
{file = "sentry_sdk-2.17.0-py2.py3-none-any.whl", hash = "sha256:625955884b862cc58748920f9e21efdfb8e0d4f98cca4ab0d3918576d5b606ad"},
{file = "sentry_sdk-2.17.0.tar.gz", hash = "sha256:dd0a05352b78ffeacced73a94e86f38b32e2eae15fff5f30ca5abb568a72eacf"},
]
[package.dependencies]
@@ -2353,16 +2353,14 @@ grpcio = ["grpcio (>=1.21.1)", "protobuf (>=3.8.0)"]
http2 = ["httpcore[http2] (==1.*)"]
httpx = ["httpx (>=0.16.0)"]
huey = ["huey (>=2)"]
huggingface-hub = ["huggingface_hub (>=0.22)"]
huggingface-hub = ["huggingface-hub (>=0.22)"]
langchain = ["langchain (>=0.0.210)"]
launchdarkly = ["launchdarkly-server-sdk (>=9.8.0)"]
litestar = ["litestar (>=2.0.0)"]
loguru = ["loguru (>=0.5)"]
openai = ["openai (>=1.0.0)", "tiktoken (>=0.3.0)"]
openfeature = ["openfeature-sdk (>=0.7.1)"]
opentelemetry = ["opentelemetry-distro (>=0.35b0)"]
opentelemetry-experimental = ["opentelemetry-distro"]
pure-eval = ["asttokens", "executing", "pure_eval"]
pure-eval = ["asttokens", "executing", "pure-eval"]
pymongo = ["pymongo (>=3.1)"]
pyspark = ["pyspark (>=2.4.4)"]
quart = ["blinker (>=1.1)", "quart (>=0.16.1)"]

View File

@@ -97,7 +97,7 @@ module-name = "synapse.synapse_rust"
[tool.poetry]
name = "matrix-synapse"
version = "1.122.0"
version = "1.121.1"
description = "Homeserver for the Matrix decentralised comms protocol"
authors = ["Matrix.org Team and Contributors <packages@matrix.org>"]
license = "AGPL-3.0-or-later"

View File

@@ -320,8 +320,3 @@ class ApprovalNoticeMedium:
class Direction(enum.Enum):
BACKWARDS = "b"
FORWARDS = "f"
class ProfileFields:
DISPLAYNAME: Final = "displayname"
AVATAR_URL: Final = "avatar_url"

View File

@@ -22,7 +22,6 @@ import logging
import random
from typing import TYPE_CHECKING, List, Optional, Union
from synapse.api.constants import ProfileFields
from synapse.api.errors import (
AuthError,
Codes,
@@ -84,7 +83,7 @@ class ProfileHandler:
Returns:
A JSON dictionary. For local queries this will include the displayname and avatar_url
fields, if set. For remote queries it may contain arbitrary information.
fields. For remote queries it may contain arbitrary information.
"""
target_user = UserID.from_string(user_id)
@@ -93,13 +92,10 @@ class ProfileHandler:
if profileinfo.display_name is None and profileinfo.avatar_url is None:
raise SynapseError(404, "Profile was not found", Codes.NOT_FOUND)
# Do not include display name or avatar if unset.
ret = {}
if profileinfo.display_name is not None:
ret[ProfileFields.DISPLAYNAME] = profileinfo.display_name
if profileinfo.avatar_url is not None:
ret[ProfileFields.AVATAR_URL] = profileinfo.avatar_url
return ret
return {
"displayname": profileinfo.display_name,
"avatar_url": profileinfo.avatar_url,
}
else:
try:
result = await self.federation.make_query(

View File

@@ -43,7 +43,7 @@ from typing_extensions import Protocol
from twisted.web.iweb import IRequest
from twisted.web.server import Request
from synapse.api.constants import LoginType, ProfileFields
from synapse.api.constants import LoginType
from synapse.api.errors import Codes, NotFoundError, RedirectException, SynapseError
from synapse.config.sso import SsoAttributeRequirement
from synapse.handlers.device import DeviceHandler
@@ -813,10 +813,9 @@ class SsoHandler:
# bail if user already has the same avatar
profile = await self._profile_handler.get_profile(user_id)
if ProfileFields.AVATAR_URL in profile:
avatar_url_parts = profile[ProfileFields.AVATAR_URL].split("/")
server_name = avatar_url_parts[-2]
media_id = avatar_url_parts[-1]
if profile["avatar_url"] is not None:
server_name = profile["avatar_url"].split("/")[-2]
media_id = profile["avatar_url"].split("/")[-1]
if self._is_mine_server_name(server_name):
media = await self._media_repo.store.get_local_media(media_id) # type: ignore[has-type]
if media is not None and upload_name == media.upload_name:

View File

@@ -26,13 +26,7 @@ from typing import TYPE_CHECKING, List, Optional, Set, Tuple
from twisted.internet.interfaces import IDelayedCall
import synapse.metrics
from synapse.api.constants import (
EventTypes,
HistoryVisibility,
JoinRules,
Membership,
ProfileFields,
)
from synapse.api.constants import EventTypes, HistoryVisibility, JoinRules, Membership
from synapse.api.errors import Codes, SynapseError
from synapse.handlers.state_deltas import MatchChange, StateDeltasHandler
from synapse.metrics.background_process_metrics import run_as_background_process
@@ -762,10 +756,6 @@ class UserDirectoryHandler(StateDeltasHandler):
await self.store.update_profile_in_user_dir(
user_id,
display_name=non_null_str_or_none(
profile.get(ProfileFields.DISPLAYNAME)
),
avatar_url=non_null_str_or_none(
profile.get(ProfileFields.AVATAR_URL)
),
display_name=non_null_str_or_none(profile.get("displayname")),
avatar_url=non_null_str_or_none(profile.get("avatar_url")),
)

View File

@@ -45,7 +45,6 @@ from twisted.internet.interfaces import IDelayedCall
from twisted.web.resource import Resource
from synapse.api import errors
from synapse.api.constants import ProfileFields
from synapse.api.errors import SynapseError
from synapse.api.presence import UserPresenceState
from synapse.config import ConfigError
@@ -1087,10 +1086,7 @@ class ModuleApi:
content = {}
# Set the profile if not already done by the module.
if (
ProfileFields.AVATAR_URL not in content
or ProfileFields.DISPLAYNAME not in content
):
if "avatar_url" not in content or "displayname" not in content:
try:
# Try to fetch the user's profile.
profile = await self._hs.get_profile_handler().get_profile(
@@ -1099,8 +1095,8 @@ class ModuleApi:
except SynapseError as e:
# If the profile couldn't be found, use default values.
profile = {
ProfileFields.DISPLAYNAME: target_user_id.localpart,
ProfileFields.AVATAR_URL: None,
"displayname": target_user_id.localpart,
"avatar_url": None,
}
if e.code != 404:
@@ -1113,9 +1109,11 @@ class ModuleApi:
)
# Set the profile where it needs to be set.
for field_name in [ProfileFields.AVATAR_URL, ProfileFields.DISPLAYNAME]:
if field_name not in content and field_name in profile:
content[field_name] = profile[field_name]
if "avatar_url" not in content:
content["avatar_url"] = profile["avatar_url"]
if "displayname" not in content:
content["displayname"] = profile["displayname"]
event_id, _ = await self._hs.get_room_member_handler().update_membership(
requester=requester,

View File

@@ -227,7 +227,14 @@ class ProfileRestServlet(RestServlet):
user = UserID.from_string(user_id)
await self.profile_handler.check_profile_query_allowed(user, requester_user)
ret = await self.profile_handler.get_profile(user_id)
displayname = await self.profile_handler.get_displayname(user)
avatar_url = await self.profile_handler.get_avatar_url(user)
ret = {}
if displayname is not None:
ret["displayname"] = displayname
if avatar_url is not None:
ret["avatar_url"] = avatar_url
return 200, ret

View File

@@ -42,8 +42,8 @@ class PurgeEventsStorageController:
"""Deletes all record of a room"""
with nested_logging_context(room_id):
await self.stores.main.purge_room(room_id)
await self.stores.state.purge_room_state(room_id)
state_groups_to_delete = await self.stores.main.purge_room(room_id)
await self.stores.state.purge_room_state(room_id, state_groups_to_delete)
async def purge_history(
self, room_id: str, token: str, delete_local_events: bool

View File

@@ -20,7 +20,7 @@
#
import logging
from typing import Any, Set, Tuple, cast
from typing import Any, List, Set, Tuple, cast
from synapse.api.errors import SynapseError
from synapse.storage.database import LoggingTransaction
@@ -332,7 +332,7 @@ class PurgeEventsStore(StateGroupWorkerStore, CacheInvalidationWorkerStore):
return referenced_state_groups
async def purge_room(self, room_id: str) -> None:
async def purge_room(self, room_id: str) -> List[int]:
"""Deletes all record of a room
Args:
@@ -348,7 +348,7 @@ class PurgeEventsStore(StateGroupWorkerStore, CacheInvalidationWorkerStore):
# purge any of those rows which were added during the first.
logger.info("[purge] Starting initial main purge of [1/2]")
await self.db_pool.runInteraction(
state_groups_to_delete = await self.db_pool.runInteraction(
"purge_room",
self._purge_room_txn,
room_id=room_id,
@@ -356,15 +356,18 @@ class PurgeEventsStore(StateGroupWorkerStore, CacheInvalidationWorkerStore):
)
logger.info("[purge] Starting secondary main purge of [2/2]")
await self.db_pool.runInteraction(
"purge_room",
self._purge_room_txn,
room_id=room_id,
state_groups_to_delete.extend(
await self.db_pool.runInteraction(
"purge_room",
self._purge_room_txn,
room_id=room_id,
),
)
logger.info("[purge] Done with main purge")
def _purge_room_txn(self, txn: LoggingTransaction, room_id: str) -> None:
return state_groups_to_delete
def _purge_room_txn(self, txn: LoggingTransaction, room_id: str) -> List[int]:
# This collides with event persistence so we cannot write new events and metadata into
# a room while deleting it or this transaction will fail.
if isinstance(self.database_engine, PostgresEngine):
@@ -378,6 +381,19 @@ class PurgeEventsStore(StateGroupWorkerStore, CacheInvalidationWorkerStore):
# take a while!
txn.execute("SET LOCAL statement_timeout = 0")
# First, fetch all the state groups that should be deleted, before
# we delete that information.
txn.execute(
"""
SELECT DISTINCT state_group FROM events
INNER JOIN event_to_state_groups USING(event_id)
WHERE events.room_id = ?
""",
(room_id,),
)
state_groups = [row[0] for row in txn]
# Get all the auth chains that are referenced by events that are to be
# deleted.
txn.execute(
@@ -497,3 +513,5 @@ class PurgeEventsStore(StateGroupWorkerStore, CacheInvalidationWorkerStore):
# periodically anyway (https://github.com/matrix-org/synapse/issues/5888)
self._invalidate_caches_for_room_and_stream(txn, room_id)
return state_groups

View File

@@ -840,42 +840,60 @@ class StateGroupDataStore(StateBackgroundUpdateStore, SQLBaseStore):
return dict(rows)
async def purge_room_state(self, room_id: str) -> None:
return await self.db_pool.runInteraction(
async def purge_room_state(
self, room_id: str, state_groups_to_delete: Collection[int]
) -> None:
"""Deletes all record of a room from state tables
Args:
room_id:
state_groups_to_delete: State groups to delete
"""
logger.info("[purge] Starting state purge")
await self.db_pool.runInteraction(
"purge_room_state",
self._purge_room_state_txn,
room_id,
state_groups_to_delete,
)
logger.info("[purge] Done with state purge")
def _purge_room_state_txn(
self,
txn: LoggingTransaction,
room_id: str,
state_groups_to_delete: Collection[int],
) -> None:
# Delete all edges that reference a state group linked to room_id
logger.info("[purge] removing %s from state_group_edges", room_id)
txn.execute(
"""
DELETE FROM state_group_edges AS sge WHERE sge.state_group IN (
SELECT id FROM state_groups AS sg WHERE sg.room_id = ?
)""",
(room_id,),
)
# state_groups_state table has a room_id column but no index on it, unlike state_groups,
# so we delete them by matching the room_id through the state_groups table.
# first we have to delete the state groups states
logger.info("[purge] removing %s from state_groups_state", room_id)
txn.execute(
"""
DELETE FROM state_groups_state AS sgs WHERE sgs.state_group IN (
SELECT id FROM state_groups AS sg WHERE sg.room_id = ?
)""",
(room_id,),
self.db_pool.simple_delete_many_txn(
txn,
table="state_groups_state",
column="state_group",
values=state_groups_to_delete,
keyvalues={},
)
# ... and the state group edges
logger.info("[purge] removing %s from state_group_edges", room_id)
self.db_pool.simple_delete_many_txn(
txn,
table="state_group_edges",
column="state_group",
values=state_groups_to_delete,
keyvalues={},
)
# ... and the state groups
logger.info("[purge] removing %s from state_groups", room_id)
self.db_pool.simple_delete_txn(
self.db_pool.simple_delete_many_txn(
txn,
table="state_groups",
keyvalues={"room_id": room_id},
column="id",
values=state_groups_to_delete,
keyvalues={},
)

View File

@@ -3050,7 +3050,7 @@ PURGE_TABLES = [
"pusher_throttle",
"room_account_data",
"room_tags",
"state_groups",
# "state_groups", # Current impl leaves orphaned state groups around.
"state_groups_state",
"federation_inbound_events_staging",
]

View File

@@ -60,7 +60,6 @@ from synapse.util import Clock
from tests import unittest
from tests.replication._base import BaseMultiWorkerStreamTestCase
from tests.test_utils import SMALL_PNG
from tests.test_utils.event_injection import inject_event
from tests.unittest import override_config
@@ -5409,64 +5408,6 @@ class UserRedactionTestCase(unittest.HomeserverTestCase):
# we redacted 6 messages
self.assertEqual(len(matches), 6)
def test_redactions_for_remote_user_succeed_with_admin_priv_in_room(self) -> None:
"""
Test that if the admin requester has privileges in a room, redaction requests
succeed for a remote user
"""
# inject some messages from remote user and collect event ids
original_message_ids = []
for i in range(5):
event = self.get_success(
inject_event(
self.hs,
room_id=self.rm1,
type="m.room.message",
sender="@remote:remote_server",
content={"msgtype": "m.text", "body": f"nefarious_chatter{i}"},
)
)
original_message_ids.append(event.event_id)
# send a request to redact a remote user's messages in a room.
# the server admin created this room and has admin privilege in room
channel = self.make_request(
"POST",
"/_synapse/admin/v1/user/@remote:remote_server/redact",
content={"rooms": [self.rm1]},
access_token=self.admin_tok,
)
self.assertEqual(channel.code, 200)
id = channel.json_body.get("redact_id")
# check that there were no failed redactions
channel = self.make_request(
"GET",
f"/_synapse/admin/v1/user/redact_status/{id}",
access_token=self.admin_tok,
)
self.assertEqual(channel.code, 200)
self.assertEqual(channel.json_body.get("status"), "complete")
failed_redactions = channel.json_body.get("failed_redactions")
self.assertEqual(failed_redactions, {})
filter = json.dumps({"types": [EventTypes.Redaction]})
channel = self.make_request(
"GET",
f"rooms/{self.rm1}/messages?filter={filter}&limit=50",
access_token=self.admin_tok,
)
self.assertEqual(channel.code, 200)
for event in channel.json_body["chunk"]:
for event_id in original_message_ids:
if event["type"] == "m.room.redaction" and event["redacts"] == event_id:
original_message_ids.remove(event_id)
break
# we originally sent 5 messages so 5 should be redacted
self.assertEqual(len(original_message_ids), 0)
class UserRedactionBackgroundTaskTestCase(BaseMultiWorkerStreamTestCase):
servlets = [