summary refs log tree commit diff
path: root/synapse
diff options
context:
space:
mode:
Diffstat (limited to 'synapse')
-rw-r--r--synapse/api/constants.py2
-rw-r--r--synapse/api/room_versions.py15
-rw-r--r--synapse/event_auth.py45
-rw-r--r--synapse/events/__init__.py39
-rw-r--r--synapse/events/builder.py3
-rw-r--r--synapse/events/utils.py6
-rw-r--r--synapse/federation/federation_base.py46
-rw-r--r--synapse/federation/federation_server.py5
8 files changed, 77 insertions, 84 deletions
diff --git a/synapse/api/constants.py b/synapse/api/constants.py
index dc32553d0c..45fb9cc0de 100644
--- a/synapse/api/constants.py
+++ b/synapse/api/constants.py
@@ -125,6 +125,8 @@ class EventTypes:
 
     Reaction: Final = "m.reaction"
 
+    Hub: Final = "m.room.hub"
+
 
 class ToDeviceEventTypes:
     RoomKeyRequest: Final = "m.room_key_request"
diff --git a/synapse/api/room_versions.py b/synapse/api/room_versions.py
index 21c3efcfde..d9fdda9745 100644
--- a/synapse/api/room_versions.py
+++ b/synapse/api/room_versions.py
@@ -468,8 +468,13 @@ class RoomVersions:
         msc3989_redaction_rules=True,  # Used by MSC3820
         linearized_matrix=False,
     )
+    # Based on room version 11:
+    #
+    # - Enable MSC2176, MSC2175, MSC3989, MSC2174, MSC1767, MSC3821.
+    # - Disable 'restricted' and 'knock_restricted' join rules.
+    # - Mark as linearized.
     LINEARIZED = RoomVersion(
-        "org.matrix.i-d.ralston-mimi-linearized-matrix.00",
+        "org.matrix.i-d.ralston-mimi-linearized-matrix.02",
         RoomDisposition.UNSTABLE,
         EventFormatVersions.LINEARIZED,
         StateResolutionVersions.V2,
@@ -479,13 +484,13 @@ class RoomVersions:
         limit_notifications_power_levels=True,
         msc2175_implicit_room_creator=True,
         msc2176_redaction_rules=True,
-        msc3083_join_rules=True,
-        msc3375_redaction_rules=True,
+        msc3083_join_rules=False,
+        msc3375_redaction_rules=False,
         msc2403_knocking=True,
         msc3389_relation_redactions=False,
-        msc3787_knock_restricted_join_rule=True,
+        msc3787_knock_restricted_join_rule=False,
         msc3667_int_only_power_levels=True,
-        msc3821_redaction_rules=False,
+        msc3821_redaction_rules=True,
         msc3931_push_features=(),
         msc3989_redaction_rules=True,
         linearized_matrix=True,
diff --git a/synapse/event_auth.py b/synapse/event_auth.py
index 84fe9d3c3e..1a7295acfb 100644
--- a/synapse/event_auth.py
+++ b/synapse/event_auth.py
@@ -55,7 +55,7 @@ from synapse.types import (
 
 if typing.TYPE_CHECKING:
     # conditional imports to avoid import cycle
-    from synapse.events import EventBase, FrozenLinearizedEvent
+    from synapse.events import EventBase
     from synapse.events.builder import EventBuilder
 
 logger = logging.getLogger(__name__)
@@ -126,20 +126,13 @@ def validate_event_for_room_version(event: "EventBase") -> None:
             raise AuthError(403, "Event not signed by sending server")
 
     if event.format_version == EventFormatVersions.LINEARIZED:
-        assert isinstance(event, FrozenLinearizedEvent)
-
         # TODO Are these handling DAG-native events properly? Is the null-checks
         # a bypass?
 
-        # CHeck that the authorizing domain signed it.
-        owner_server = event.owner_server
-        if owner_server and not event.signatures.get(owner_server):
-            raise AuthError(403, "Event not signed by owner server")
-
-        # If this is a delegated PDU, check the original domain signed it.
-        delegated_server = event.delegated_server
-        if delegated_server and not event.signatures.get(delegated_server):
-            raise AuthError(403, "Event not signed by delegated server")
+        # Check that the hub server signed it.
+        hub_server = event.hub_server
+        if hub_server and not event.signatures.get(hub_server):
+            raise AuthError(403, "Event not signed by hub server")
 
     is_invite_via_allow_rule = (
         event.room_version.msc3083_join_rules
@@ -293,6 +286,13 @@ def check_state_dependent_auth_rules(
 
     auth_dict = {(e.type, e.state_key): e for e in auth_events}
 
+    # If the event was sent from a hub server it must be the current hub server.
+    if event.room_version.linearized_matrix:
+        if event.hub_server:
+            current_hub_server = get_hub_server(auth_dict)
+            if current_hub_server != event.hub_server:
+                raise AuthError(403, "Event sent from incorrect hub server.")
+
     # additional check for m.federate
     creating_domain = get_domain_from_id(event.room_id)
     originating_domain = get_domain_from_id(event.sender)
@@ -347,6 +347,12 @@ def check_state_dependent_auth_rules(
             logger.debug("Allowing! %s", event)
             return
 
+    # Hub events must be signed by the current hub.
+    if event.type == EventTypes.Hub:
+        current_hub_server = get_hub_server(auth_dict)
+        if not event.signatures.get(current_hub_server):
+            raise AuthError(403, "Unsigned hub event")
+
     _can_send_event(event, auth_dict)
 
     if event.type == EventTypes.PowerLevels:
@@ -1081,6 +1087,16 @@ def _verify_third_party_invite(
     return False
 
 
+def get_hub_server(auth_events: StateMap["EventBase"]) -> str:
+    # The current hub is the sender of the current hub event...
+    hub_event = auth_events.get((EventTypes.Hub, ""), None)
+    if not hub_event:
+        # ...or the room creator if there is no hub event.
+        hub_event = auth_events[(EventTypes.Create, "")]
+
+    return get_domain_from_id(hub_event.sender)
+
+
 def get_public_keys(invite_event: "EventBase") -> List[Dict[str, Any]]:
     public_keys = []
     if "public_key" in invite_event.content:
@@ -1134,4 +1150,9 @@ def auth_types_for_event(
                 )
                 auth_types.add(key)
 
+    # Events sent from a hub server must reference the hub state-event, if one
+    # exists.
+    if event.hub_server:
+        auth_types.add((EventTypes.Hub, ""))
+
     return auth_types
diff --git a/synapse/events/__init__.py b/synapse/events/__init__.py
index 063abb9c93..aa48cd2236 100644
--- a/synapse/events/__init__.py
+++ b/synapse/events/__init__.py
@@ -335,12 +335,7 @@ class EventBase(metaclass=abc.ABCMeta):
     type: DictProperty[str] = DictProperty("type")
     user_id: DictProperty[str] = DictProperty("sender")
     # Should only matter for Linearized Matrix.
-    owner_server: DictProperty[Optional[str]] = DefaultDictProperty(
-        "owner_server", None
-    )
-    delegated_server: DictProperty[Optional[str]] = DefaultDictProperty(
-        "delegated_server", None
-    )
+    hub_server: DictProperty[Optional[str]] = DefaultDictProperty("hub_server", None)
 
     @property
     def event_id(self) -> str:
@@ -615,31 +610,17 @@ class FrozenLinearizedEvent(FrozenEventV3):
         """The domain which added this event to the DAG.
 
         It could be the authorized server or the sender."""
-        if self.delegated_server is not None:
-            return self.delegated_server
+        if self.hub_server is not None:
+            return self.hub_server
         return super().pdu_domain
 
-    def get_linearized_pdu_json(self, /, delegated: bool) -> JsonDict:
-        # if delegated and self.delegated_server is None:
-        #     # TODO Better error.
-        #     raise ValueError("Invalid")
-        # TODO Is this form correct? Are there other fields?
-        result = {
-            "room_id": self.room_id,
-            "type": self.type,
-            # Should state key be left out if it isn't a state event?
-            "state_key": self.state_key,
-            "sender": self.sender,
-            "origin_server_ts": self.origin_server_ts,
-            # If we want the delegated version use the owner_server
-            # if it exists.
-            "owner_server": self.delegated_server,
-            "content": self.content,
-            "hashes": self.hashes,
-        }
-        if delegated and self.delegated_server:
-            result["delegated_server"] = self.delegated_server
-        return result
+    def get_linearized_pdu_json(self) -> JsonDict:
+        # Get the full PDU and then remove fields from it.
+        pdu = self.get_pdu_json()
+        pdu.pop("hashes")
+        pdu.pop("auth_events")
+        pdu.pop("prev_events")
+        return pdu
 
 
 def _event_type_from_format_version(
diff --git a/synapse/events/builder.py b/synapse/events/builder.py
index a254548c6c..65979e89dc 100644
--- a/synapse/events/builder.py
+++ b/synapse/events/builder.py
@@ -87,6 +87,9 @@ class EventBuilder:
     _redacts: Optional[str] = None
     _origin_server_ts: Optional[int] = None
 
+    # TODO(LM) If Synapse is acting as a hub-server this should be itself.
+    hub_server: Optional[str] = None
+
     internal_metadata: _EventInternalMetadata = attr.Factory(
         lambda: _EventInternalMetadata({})
     )
diff --git a/synapse/events/utils.py b/synapse/events/utils.py
index 592e109e22..4f6f075290 100644
--- a/synapse/events/utils.py
+++ b/synapse/events/utils.py
@@ -65,9 +65,6 @@ def prune_event(event: EventBase) -> EventBase:
     type, state_key etc.
     """
 
-    # TODO Check users of prune_event and prune_event_dict to ensure they shouldn't
-    # be doing something with linearized matrix.
-
     pruned_event_dict = prune_event_dict(event.room_version, event.get_dict())
 
     from . import make_event_from_dict
@@ -115,6 +112,9 @@ def prune_event_dict(room_version: RoomVersion, event_dict: JsonDict) -> JsonDic
     # Room versions from before MSC2176 had additional allowed keys.
     if not room_version.msc2176_redaction_rules:
         allowed_keys.extend(["prev_state", "membership"])
+    # The hub server should not be redacted for linear matrix.
+    if room_version.linearized_matrix:
+        allowed_keys.append("hub_server")
 
     # Room versions before MSC3989 kept the origin field.
     if not room_version.msc3989_redaction_rules:
diff --git a/synapse/federation/federation_base.py b/synapse/federation/federation_base.py
index eff887433c..50213a8237 100644
--- a/synapse/federation/federation_base.py
+++ b/synapse/federation/federation_base.py
@@ -21,7 +21,7 @@ from synapse.api.room_versions import EventFormatVersions, RoomVersion
 from synapse.crypto.event_signing import check_event_content_hash
 from synapse.crypto.keyring import Keyring
 from synapse.events import EventBase, FrozenLinearizedEvent, make_event_from_dict
-from synapse.events.utils import prune_event, validate_canonicaljson
+from synapse.events.utils import prune_event, prune_event_dict, validate_canonicaljson
 from synapse.http.servlet import assert_params_in_dict
 from synapse.logging.opentracing import log_kv, trace
 from synapse.types import JsonDict, get_domain_from_id
@@ -174,7 +174,7 @@ async def _check_sigs_on_pdu(
 
     # we want to check that the event is signed by:
     #
-    # (a) the sender's (or the delagated sender's) server
+    # (a) the sender's server or the hub
     #
     #     - except in the case of invites created from a 3pid invite, which are exempt
     #     from this check, because the sender has to match that of the original 3pid
@@ -250,50 +250,26 @@ async def _check_sigs_on_pdu(
                 pdu.event_id,
             ) from None
 
-    # If this is a linearized PDU we may need to check signatures of the owner
+    # If this is a linearized PDU we may need to check signatures of the hub
     # and sender.
     if room_version.event_format == EventFormatVersions.LINEARIZED:
         assert isinstance(pdu, FrozenLinearizedEvent)
 
-        # Check that the fields are not invalid.
-        if pdu.delegated_server and not pdu.owner_server:
-            raise InvalidEventSignatureError(
-                "PDU missing owner_server", pdu.event_id
-            ) from None
-
-        # If the event was sent from a linear server, check the authorized server.
+        # If the event was sent via a hub server, check the signature of the
+        # sender against the Linear PDU. (But only if the sender isn't the hub.)
         #
-        # Note that the signature of the delegated server, if one exists, is
-        # checked against the full PDU above.
-        if pdu.owner_server:
-            # Construct the Linear PDU and check the signature against the sender.
-            #
-            # If the owner & sender are the same, then only a Delegated Linear PDU
-            # is created (the Linear PDU is not signed by itself).
-            if pdu.owner_server != sender_domain:
-                try:
-                    await keyring.verify_json_for_server(
-                        sender_domain,
-                        pdu.get_linearized_pdu_json(delegated=False),
-                        origin_server_ts_for_signing,
-                    )
-                except Exception as e:
-                    raise InvalidEventSignatureError(
-                        f"unable to verify signature for sender {sender_domain}: {e}",
-                        pdu.event_id,
-                    ) from None
-
-            # Construct the Delegated Linear PDU and check the signature
-            # against owner_server.
+        # Note that the signature of the hub server, if one exists, is checked
+        # against the full PDU above.
+        if pdu.hub_server and pdu.hub_server != sender_domain:
             try:
                 await keyring.verify_json_for_server(
-                    pdu.owner_server,
-                    pdu.get_linearized_pdu_json(delegated=True),
+                    sender_domain,
+                    prune_event_dict(pdu.room_version, pdu.get_linearized_pdu_json()),
                     origin_server_ts_for_signing,
                 )
             except Exception as e:
                 raise InvalidEventSignatureError(
-                    f"unable to verify signature for owner_server {pdu.owner_server}: {e}",
+                    f"unable to verify signature for sender {sender_domain}: {e}",
                     pdu.event_id,
                 ) from None
 
diff --git a/synapse/federation/federation_server.py b/synapse/federation/federation_server.py
index 61fa3b30af..e26796b408 100644
--- a/synapse/federation/federation_server.py
+++ b/synapse/federation/federation_server.py
@@ -290,6 +290,11 @@ class FederationServer(FederationBase):
         # accurate as possible.
         request_time = self._clock.time_msec()
 
+        # TODO(LM): If we are the hub server and the destination is a participant
+        #           server then pdus are Linear PDUs.
+        #
+        # TODO(LM): If we are the hub's DAG server we need to linearize and give
+        #           it the results.
         transaction = Transaction(
             transaction_id=transaction_id,
             destination=destination,