mirror of
https://mau.dev/maunium/synapse.git
synced 2024-10-01 01:36:05 -04:00
23740eaa3d
During the migration the automated script to update the copyright headers accidentally got rid of some of the existing copyright lines. Reinstate them.
309 lines
9.9 KiB
Python
309 lines
9.9 KiB
Python
#
|
|
# This file is licensed under the Affero General Public License (AGPL) version 3.
|
|
#
|
|
# Copyright 2014-2016 OpenMarket Ltd
|
|
# Copyright (C) 2023 New Vector, Ltd
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU Affero General Public License as
|
|
# published by the Free Software Foundation, either version 3 of the
|
|
# License, or (at your option) any later version.
|
|
#
|
|
# See the GNU Affero General Public License for more details:
|
|
# <https://www.gnu.org/licenses/agpl-3.0.html>.
|
|
#
|
|
# Originally licensed under the Apache License, Version 2.0:
|
|
# <http://www.apache.org/licenses/LICENSE-2.0>.
|
|
#
|
|
# [This file includes modifications made by New Vector Limited]
|
|
#
|
|
#
|
|
import logging
|
|
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union
|
|
|
|
import attr
|
|
from signedjson.types import SigningKey
|
|
|
|
from synapse.api.constants import MAX_DEPTH
|
|
from synapse.api.room_versions import (
|
|
KNOWN_EVENT_FORMAT_VERSIONS,
|
|
EventFormatVersions,
|
|
RoomVersion,
|
|
)
|
|
from synapse.crypto.event_signing import add_hashes_and_signatures
|
|
from synapse.event_auth import auth_types_for_event
|
|
from synapse.events import EventBase, make_event_from_dict
|
|
from synapse.state import StateHandler
|
|
from synapse.storage.databases.main import DataStore
|
|
from synapse.synapse_rust.events import EventInternalMetadata
|
|
from synapse.types import EventID, JsonDict, StrCollection
|
|
from synapse.types.state import StateFilter
|
|
from synapse.util import Clock
|
|
from synapse.util.stringutils import random_string
|
|
|
|
if TYPE_CHECKING:
|
|
from synapse.handlers.event_auth import EventAuthHandler
|
|
from synapse.server import HomeServer
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@attr.s(slots=True, cmp=False, frozen=True, auto_attribs=True)
|
|
class EventBuilder:
|
|
"""A format independent event builder used to build up the event content
|
|
before signing the event.
|
|
|
|
(Note that while objects of this class are frozen, the
|
|
content/unsigned/internal_metadata fields are still mutable)
|
|
|
|
Attributes:
|
|
room_version: Version of the target room
|
|
room_id
|
|
type
|
|
sender
|
|
content
|
|
unsigned
|
|
internal_metadata
|
|
|
|
_state
|
|
_auth
|
|
_store
|
|
_clock
|
|
_hostname: The hostname of the server creating the event
|
|
_signing_key: The signing key to use to sign the event as the server
|
|
"""
|
|
|
|
_state: StateHandler
|
|
_event_auth_handler: "EventAuthHandler"
|
|
_store: DataStore
|
|
_clock: Clock
|
|
_hostname: str
|
|
_signing_key: SigningKey
|
|
|
|
room_version: RoomVersion
|
|
|
|
room_id: str
|
|
type: str
|
|
sender: str
|
|
|
|
content: JsonDict = attr.Factory(dict)
|
|
unsigned: JsonDict = attr.Factory(dict)
|
|
|
|
# These only exist on a subset of events, so they raise AttributeError if
|
|
# someone tries to get them when they don't exist.
|
|
_state_key: Optional[str] = None
|
|
_redacts: Optional[str] = None
|
|
_origin_server_ts: Optional[int] = None
|
|
|
|
internal_metadata: EventInternalMetadata = attr.Factory(
|
|
lambda: EventInternalMetadata({})
|
|
)
|
|
|
|
@property
|
|
def state_key(self) -> str:
|
|
if self._state_key is not None:
|
|
return self._state_key
|
|
|
|
raise AttributeError("state_key")
|
|
|
|
def is_state(self) -> bool:
|
|
return self._state_key is not None
|
|
|
|
async def build(
|
|
self,
|
|
prev_event_ids: List[str],
|
|
auth_event_ids: Optional[List[str]],
|
|
depth: Optional[int] = None,
|
|
) -> EventBase:
|
|
"""Transform into a fully signed and hashed event
|
|
|
|
Args:
|
|
prev_event_ids: The event IDs to use as the prev events
|
|
auth_event_ids: The event IDs to use as the auth events.
|
|
Should normally be set to None, which will cause them to be calculated
|
|
based on the room state at the prev_events.
|
|
depth: Override the depth used to order the event in the DAG.
|
|
Should normally be set to None, which will cause the depth to be calculated
|
|
based on the prev_events.
|
|
|
|
Returns:
|
|
The signed and hashed event.
|
|
"""
|
|
if auth_event_ids is None:
|
|
state_ids = await self._state.compute_state_after_events(
|
|
self.room_id,
|
|
prev_event_ids,
|
|
state_filter=StateFilter.from_types(
|
|
auth_types_for_event(self.room_version, self)
|
|
),
|
|
await_full_state=False,
|
|
)
|
|
auth_event_ids = self._event_auth_handler.compute_auth_events(
|
|
self, state_ids
|
|
)
|
|
|
|
format_version = self.room_version.event_format
|
|
# The types of auth/prev events changes between event versions.
|
|
prev_events: Union[StrCollection, List[Tuple[str, Dict[str, str]]]]
|
|
auth_events: Union[List[str], List[Tuple[str, Dict[str, str]]]]
|
|
if format_version == EventFormatVersions.ROOM_V1_V2:
|
|
auth_events = await self._store.add_event_hashes(auth_event_ids)
|
|
prev_events = await self._store.add_event_hashes(prev_event_ids)
|
|
else:
|
|
auth_events = auth_event_ids
|
|
prev_events = prev_event_ids
|
|
|
|
# Otherwise, progress the depth as normal
|
|
if depth is None:
|
|
(
|
|
_,
|
|
most_recent_prev_event_depth,
|
|
) = await self._store.get_max_depth_of(prev_event_ids)
|
|
|
|
depth = most_recent_prev_event_depth + 1
|
|
|
|
# we cap depth of generated events, to ensure that they are not
|
|
# rejected by other servers (and so that they can be persisted in
|
|
# the db)
|
|
depth = min(depth, MAX_DEPTH)
|
|
|
|
event_dict: Dict[str, Any] = {
|
|
"auth_events": auth_events,
|
|
"prev_events": prev_events,
|
|
"type": self.type,
|
|
"room_id": self.room_id,
|
|
"sender": self.sender,
|
|
"content": self.content,
|
|
"unsigned": self.unsigned,
|
|
"depth": depth,
|
|
}
|
|
|
|
if self.is_state():
|
|
event_dict["state_key"] = self._state_key
|
|
|
|
# MSC2174 moves the redacts property to the content, it is invalid to
|
|
# provide it as a top-level property.
|
|
if self._redacts is not None and not self.room_version.updated_redaction_rules:
|
|
event_dict["redacts"] = self._redacts
|
|
|
|
if self._origin_server_ts is not None:
|
|
event_dict["origin_server_ts"] = self._origin_server_ts
|
|
|
|
return create_local_event_from_event_dict(
|
|
clock=self._clock,
|
|
hostname=self._hostname,
|
|
signing_key=self._signing_key,
|
|
room_version=self.room_version,
|
|
event_dict=event_dict,
|
|
internal_metadata_dict=self.internal_metadata.get_dict(),
|
|
)
|
|
|
|
|
|
class EventBuilderFactory:
|
|
def __init__(self, hs: "HomeServer"):
|
|
self.clock = hs.get_clock()
|
|
self.hostname = hs.hostname
|
|
self.signing_key = hs.signing_key
|
|
|
|
self.store = hs.get_datastores().main
|
|
self.state = hs.get_state_handler()
|
|
self._event_auth_handler = hs.get_event_auth_handler()
|
|
|
|
def for_room_version(
|
|
self, room_version: RoomVersion, key_values: dict
|
|
) -> EventBuilder:
|
|
"""Generate an event builder appropriate for the given room version
|
|
|
|
Args:
|
|
room_version:
|
|
Version of the room that we're creating an event builder for
|
|
key_values: Fields used as the basis of the new event
|
|
|
|
Returns:
|
|
EventBuilder
|
|
"""
|
|
return EventBuilder(
|
|
store=self.store,
|
|
state=self.state,
|
|
event_auth_handler=self._event_auth_handler,
|
|
clock=self.clock,
|
|
hostname=self.hostname,
|
|
signing_key=self.signing_key,
|
|
room_version=room_version,
|
|
type=key_values["type"],
|
|
state_key=key_values.get("state_key"),
|
|
room_id=key_values["room_id"],
|
|
sender=key_values["sender"],
|
|
content=key_values.get("content", {}),
|
|
unsigned=key_values.get("unsigned", {}),
|
|
redacts=key_values.get("redacts", None),
|
|
origin_server_ts=key_values.get("origin_server_ts", None),
|
|
)
|
|
|
|
|
|
def create_local_event_from_event_dict(
|
|
clock: Clock,
|
|
hostname: str,
|
|
signing_key: SigningKey,
|
|
room_version: RoomVersion,
|
|
event_dict: JsonDict,
|
|
internal_metadata_dict: Optional[JsonDict] = None,
|
|
) -> EventBase:
|
|
"""Takes a fully formed event dict, ensuring that fields like `origin`
|
|
and `origin_server_ts` have correct values for a locally produced event,
|
|
then signs and hashes it.
|
|
"""
|
|
|
|
format_version = room_version.event_format
|
|
if format_version not in KNOWN_EVENT_FORMAT_VERSIONS:
|
|
raise Exception("No event format defined for version %r" % (format_version,))
|
|
|
|
if internal_metadata_dict is None:
|
|
internal_metadata_dict = {}
|
|
|
|
time_now = int(clock.time_msec())
|
|
|
|
if format_version == EventFormatVersions.ROOM_V1_V2:
|
|
event_dict["event_id"] = _create_event_id(clock, hostname)
|
|
|
|
event_dict["origin"] = hostname
|
|
event_dict.setdefault("origin_server_ts", time_now)
|
|
|
|
event_dict.setdefault("unsigned", {})
|
|
age = event_dict["unsigned"].pop("age", 0)
|
|
event_dict["unsigned"].setdefault("age_ts", time_now - age)
|
|
|
|
event_dict.setdefault("signatures", {})
|
|
|
|
add_hashes_and_signatures(room_version, event_dict, hostname, signing_key)
|
|
return make_event_from_dict(
|
|
event_dict, room_version, internal_metadata_dict=internal_metadata_dict
|
|
)
|
|
|
|
|
|
# A counter used when generating new event IDs
|
|
_event_id_counter = 0
|
|
|
|
|
|
def _create_event_id(clock: Clock, hostname: str) -> str:
|
|
"""Create a new event ID
|
|
|
|
Args:
|
|
clock
|
|
hostname: The server name for the event ID
|
|
|
|
Returns:
|
|
The new event ID
|
|
"""
|
|
|
|
global _event_id_counter
|
|
|
|
i = str(_event_id_counter)
|
|
_event_id_counter += 1
|
|
|
|
local_part = str(int(clock.time())) + i + random_string(5)
|
|
|
|
e_id = EventID(local_part, hostname)
|
|
|
|
return e_id.to_string()
|