diff options
-rw-r--r-- | synapse/api/constants.py | 2 | ||||
-rw-r--r-- | synapse/api/room_versions.py | 15 | ||||
-rw-r--r-- | synapse/event_auth.py | 45 | ||||
-rw-r--r-- | synapse/events/__init__.py | 39 | ||||
-rw-r--r-- | synapse/events/builder.py | 3 | ||||
-rw-r--r-- | synapse/events/utils.py | 6 | ||||
-rw-r--r-- | synapse/federation/federation_base.py | 46 | ||||
-rw-r--r-- | synapse/federation/federation_server.py | 5 | ||||
-rw-r--r-- | tests/state/test_v2.py | 1 |
9 files changed, 78 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, diff --git a/tests/state/test_v2.py b/tests/state/test_v2.py index 2e3f2318d9..dd4e9593ad 100644 --- a/tests/state/test_v2.py +++ b/tests/state/test_v2.py @@ -85,6 +85,7 @@ class FakeEvent: self.state_key = state_key self.content = content self.room_id = ROOM_ID + self.hub_server = None def to_event(self, auth_events: List[str], prev_events: List[str]) -> EventBase: """Given the auth_events and prev_events, convert to a Frozen Event |