Skip to content

Commit 40d699b

Browse files
turt2liveCopilotMadLittleMods
authored
Stable support for MSC4284 policy servers (#19503)
Fixes #19494 MSC4284 policy servers This: * removes the old `/check` (recommendation) support because it's from an older design. Policy servers should have updated to `/sign` by now. We also remove optionality around the policy server's public key because it was only optional to support `/check`. * supports the stable `m.room.policy` state event and `/sign` endpoints, falling back to unstable if required. Note the changes between unstable and stable: * Stable `/sign` uses errors instead of an empty signatures block to indicate refusal. * Stable `m.room.policy` nests the public key in an object with explicit key algorithm (always ed25519 for now) * does *not* introduce tests that the above fallback to unstable works. If it breaks, we're not going to be sad about an early transition. Tests can be added upon request, though. * fixes a bug where the policy server was asked to sign policy server state events (the events were correctly skipped in `is_event_allowed`, but `ask_policy_server_to_sign_event` didn't do the same). * fixes a bug where the original event sender's signature can be deleted if the sending server is the same as the policy server. * proxies Matrix-shaped errors from the policy server to the Client-Server API as `SynapseError`s (a new capability of the stable API). Membership event handling (from the issue) is expected to be a different PR due to the size of changes involved (tracked by #19587). ### Pull Request Checklist <!-- Please read https://element-hq.github.io/synapse/latest/development/contributing_guide.html before submitting your pull request --> * [x] Pull request is based on the develop branch * [x] Pull request includes a [changelog file](https://element-hq.github.io/synapse/latest/development/contributing_guide.html#changelog). The entry should: - Be a short description of your change which makes sense to users. "Fixed a bug that prevented receiving messages from other servers." instead of "Moved X method from `EventStore` to `EventWorkerStore`.". - Use markdown where necessary, mostly for `code blocks`. - End with either a period (.) or an exclamation mark (!). - Start with a capital letter. - Feel free to credit yourself, by adding a sentence "Contributed by @github_username." or "Contributed by [Your Name]." to the end of the entry. * [x] [Code style](https://element-hq.github.io/synapse/latest/code_style.html) is correct (run the [linters](https://element-hq.github.io/synapse/latest/development/contributing_guide.html#run-the-linters)) --------- Co-authored-by: turt2live <1190097+turt2live@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Eric Eastwood <madlittlemods@gmail.com>
1 parent b4282b8 commit 40d699b

9 files changed

Lines changed: 249 additions & 249 deletions

File tree

changelog.d/19503.bugfix.1

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix [MSC4284](https://github.com/matrix-org/matrix-spec-proposals/pull/4284) Policy Servers implementation to skip signing `org.matrix.msc4284.policy` and `m.room.policy` state events.

changelog.d/19503.bugfix.2

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Correctly apply [MSC4284](https://github.com/matrix-org/matrix-spec-proposals/pull/4284) Policy Server signatures to events when the sender and policy server have the same server name.

changelog.d/19503.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add stable support for [MSC4284](https://github.com/matrix-org/matrix-spec-proposals/pull/4284) Policy Servers.

synapse/api/constants.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,8 @@ class EventTypes:
158158

159159
PollStart: Final = "m.poll.start"
160160

161+
RoomPolicy: Final = "m.room.policy"
162+
161163

162164
class ToDeviceEventTypes:
163165
RoomKeyRequest: Final = "m.room_key_request"

synapse/federation/federation_client.py

Lines changed: 5 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@
7272
from synapse.logging.opentracing import SynapseTags, log_kv, set_tag, tag_args, trace
7373
from synapse.metrics import SERVER_NAME_LABEL
7474
from synapse.types import JsonDict, StrCollection, UserID, get_domain_from_id
75-
from synapse.types.handlers.policy_server import RECOMMENDATION_OK, RECOMMENDATION_SPAM
7675
from synapse.util.async_helpers import concurrently_execute
7776
from synapse.util.caches.expiringcache import ExpiringCache
7877
from synapse.util.duration import Duration
@@ -438,72 +437,16 @@ async def _record_failure_callback(
438437

439438
return None
440439

441-
@trace
442-
@tag_args
443-
async def get_pdu_policy_recommendation(
444-
self, destination: str, pdu: EventBase, timeout: int | None = None
445-
) -> str:
446-
"""Requests that the destination server (typically a policy server)
447-
check the event and return its recommendation on how to handle the
448-
event.
449-
450-
If the policy server could not be contacted or the policy server
451-
returned an unknown recommendation, this returns an OK recommendation.
452-
This type fixing behaviour is done because the typical caller will be
453-
in a critical call path and would generally interpret a `None` or similar
454-
response as "weird value; don't care; move on without taking action". We
455-
just frontload that logic here.
456-
457-
458-
Args:
459-
destination: The remote homeserver to ask (a policy server)
460-
pdu: The event to check
461-
timeout: How long to try (in ms) the destination for before
462-
giving up. None indicates no timeout.
463-
464-
Returns:
465-
The policy recommendation, or RECOMMENDATION_OK if the policy server was
466-
uncontactable or returned an unknown recommendation.
467-
"""
468-
469-
logger.debug(
470-
"get_pdu_policy_recommendation for event_id=%s from %s",
471-
pdu.event_id,
472-
destination,
473-
)
474-
475-
try:
476-
res = await self.transport_layer.get_policy_recommendation_for_pdu(
477-
destination, pdu, timeout=timeout
478-
)
479-
recommendation = res.get("recommendation")
480-
if not isinstance(recommendation, str):
481-
raise InvalidResponseError("recommendation is not a string")
482-
if recommendation not in (RECOMMENDATION_OK, RECOMMENDATION_SPAM):
483-
logger.warning(
484-
"get_pdu_policy_recommendation: unknown recommendation: %s",
485-
recommendation,
486-
)
487-
return RECOMMENDATION_OK
488-
return recommendation
489-
except Exception as e:
490-
logger.warning(
491-
"get_pdu_policy_recommendation: server %s responded with error, assuming OK recommendation: %s",
492-
destination,
493-
e,
494-
)
495-
return RECOMMENDATION_OK
496-
497440
@trace
498441
@tag_args
499442
async def ask_policy_server_to_sign_event(
500443
self, destination: str, pdu: EventBase, timeout: int | None = None
501-
) -> JsonDict | None:
444+
) -> JsonDict:
502445
"""Requests that the destination server (typically a policy server)
503446
sign the event as not spam.
504447
505448
If the policy server could not be contacted or the policy server
506-
returned an error, this returns no signature.
449+
returned an error, that error is raised.
507450
508451
Args:
509452
destination: The remote homeserver to ask (a policy server)
@@ -519,17 +462,9 @@ async def ask_policy_server_to_sign_event(
519462
pdu.event_id,
520463
destination,
521464
)
522-
try:
523-
return await self.transport_layer.ask_policy_server_to_sign_event(
524-
destination, pdu, timeout=timeout
525-
)
526-
except Exception as e:
527-
logger.warning(
528-
"ask_policy_server_to_sign_event: server %s responded with error: %s",
529-
destination,
530-
e,
531-
)
532-
return None
465+
return await self.transport_layer.ask_policy_server_to_sign_event(
466+
destination, pdu, timeout=timeout
467+
)
533468

534469
@trace
535470
@tag_args

synapse/federation/transport/client.py

Lines changed: 23 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
)
4848
from synapse.events import EventBase, make_event_from_dict
4949
from synapse.federation.units import Transaction
50+
from synapse.http.client import is_unknown_endpoint
5051
from synapse.http.matrixfederationclient import ByteParser, LegacyJsonSendParser
5152
from synapse.http.types import QueryParams
5253
from synapse.types import JsonDict, UserID
@@ -141,33 +142,6 @@ async def get_event(
141142
destination, path=path, timeout=timeout, try_trailing_slash_on_400=True
142143
)
143144

144-
async def get_policy_recommendation_for_pdu(
145-
self, destination: str, event: EventBase, timeout: int | None = None
146-
) -> JsonDict:
147-
"""Requests the policy recommendation for the given pdu from the given policy server.
148-
149-
Args:
150-
destination: The host name of the remote homeserver checking the event.
151-
event: The event to check.
152-
timeout: How long to try (in ms) the destination for before giving up.
153-
None indicates no timeout.
154-
155-
Returns:
156-
The full recommendation object from the remote server.
157-
"""
158-
logger.debug(
159-
"get_policy_recommendation_for_pdu dest=%s, event_id=%s",
160-
destination,
161-
event.event_id,
162-
)
163-
return await self.client.post_json(
164-
destination=destination,
165-
path=f"/_matrix/policy/unstable/org.matrix.msc4284/event/{event.event_id}/check",
166-
data=event.get_pdu_json(),
167-
ignore_backoff=True,
168-
timeout=timeout,
169-
)
170-
171145
async def ask_policy_server_to_sign_event(
172146
self, destination: str, event: EventBase, timeout: int | None = None
173147
) -> JsonDict:
@@ -186,13 +160,28 @@ async def ask_policy_server_to_sign_event(
186160
The signature from the policy server, structured in the same was as the 'signatures'
187161
JSON in the event e.g { "$policy_server_via_domain" : { "ed25519:policy_server": "signature_base64" }}
188162
"""
189-
return await self.client.post_json(
190-
destination=destination,
191-
path="/_matrix/policy/unstable/org.matrix.msc4284/sign",
192-
data=event.get_pdu_json(),
193-
ignore_backoff=True,
194-
timeout=timeout,
195-
)
163+
# Try stable first, then fall back to unstable if unsupported. All other errors
164+
# are just errors.
165+
try:
166+
return await self.client.post_json(
167+
destination=destination,
168+
path="/_matrix/policy/v1/sign",
169+
data=event.get_pdu_json(),
170+
ignore_backoff=True,
171+
timeout=timeout,
172+
)
173+
except HttpResponseException as ex:
174+
if is_unknown_endpoint(ex):
175+
# TODO: Remove unstable MSC4284 support
176+
# https://github.com/element-hq/synapse/issues/19502
177+
return await self.client.post_json(
178+
destination=destination,
179+
path="/_matrix/policy/unstable/org.matrix.msc4284/sign",
180+
data=event.get_pdu_json(),
181+
ignore_backoff=True,
182+
timeout=timeout,
183+
)
184+
raise
196185

197186
async def backfill(
198187
self, destination: str, room_id: str, event_tuples: Collection[str], limit: int

0 commit comments

Comments
 (0)