mirror of
https://mau.dev/maunium/synapse.git
synced 2024-10-01 01:36:05 -04:00
799001f2c0
... and use it in places where it's trivial to do so. This will make it easier to pass room versions into the FrozenEvent constructors.
285 lines
8.8 KiB
Python
285 lines
8.8 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Copyright 2014-2016 OpenMarket Ltd
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
from typing import Optional
|
|
|
|
import attr
|
|
from nacl.signing import SigningKey
|
|
|
|
from twisted.internet import defer
|
|
|
|
from synapse.api.constants import MAX_DEPTH
|
|
from synapse.api.errors import UnsupportedRoomVersionError
|
|
from synapse.api.room_versions import (
|
|
KNOWN_EVENT_FORMAT_VERSIONS,
|
|
KNOWN_ROOM_VERSIONS,
|
|
EventFormatVersions,
|
|
RoomVersion,
|
|
)
|
|
from synapse.crypto.event_signing import add_hashes_and_signatures
|
|
from synapse.events import EventBase, _EventInternalMetadata, make_event_from_dict
|
|
from synapse.types import EventID, JsonDict
|
|
from synapse.util import Clock
|
|
from synapse.util.stringutils import random_string
|
|
|
|
|
|
@attr.s(slots=True, cmp=False, frozen=True)
|
|
class EventBuilder(object):
|
|
"""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 (str)
|
|
type (str)
|
|
sender (str)
|
|
content (dict)
|
|
unsigned (dict)
|
|
internal_metadata (_EventInternalMetadata)
|
|
|
|
_state (StateHandler)
|
|
_auth (synapse.api.Auth)
|
|
_store (DataStore)
|
|
_clock (Clock)
|
|
_hostname (str): The hostname of the server creating the event
|
|
_signing_key: The signing key to use to sign the event as the server
|
|
"""
|
|
|
|
_state = attr.ib()
|
|
_auth = attr.ib()
|
|
_store = attr.ib()
|
|
_clock = attr.ib()
|
|
_hostname = attr.ib()
|
|
_signing_key = attr.ib()
|
|
|
|
room_version = attr.ib(type=RoomVersion)
|
|
|
|
room_id = attr.ib()
|
|
type = attr.ib()
|
|
sender = attr.ib()
|
|
|
|
content = attr.ib(default=attr.Factory(dict))
|
|
unsigned = attr.ib(default=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 = attr.ib(default=None)
|
|
_redacts = attr.ib(default=None)
|
|
_origin_server_ts = attr.ib(default=None)
|
|
|
|
internal_metadata = attr.ib(
|
|
default=attr.Factory(lambda: _EventInternalMetadata({}))
|
|
)
|
|
|
|
@property
|
|
def state_key(self):
|
|
if self._state_key is not None:
|
|
return self._state_key
|
|
|
|
raise AttributeError("state_key")
|
|
|
|
def is_state(self):
|
|
return self._state_key is not None
|
|
|
|
@defer.inlineCallbacks
|
|
def build(self, prev_event_ids):
|
|
"""Transform into a fully signed and hashed event
|
|
|
|
Args:
|
|
prev_event_ids (list[str]): The event IDs to use as the prev events
|
|
|
|
Returns:
|
|
Deferred[FrozenEvent]
|
|
"""
|
|
|
|
state_ids = yield self._state.get_current_state_ids(
|
|
self.room_id, prev_event_ids
|
|
)
|
|
auth_ids = yield self._auth.compute_auth_events(self, state_ids)
|
|
|
|
format_version = self.room_version.event_format
|
|
if format_version == EventFormatVersions.V1:
|
|
auth_events = yield self._store.add_event_hashes(auth_ids)
|
|
prev_events = yield self._store.add_event_hashes(prev_event_ids)
|
|
else:
|
|
auth_events = auth_ids
|
|
prev_events = prev_event_ids
|
|
|
|
old_depth = yield self._store.get_max_depth_of(prev_event_ids)
|
|
depth = old_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 = {
|
|
"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,
|
|
"prev_state": [],
|
|
}
|
|
|
|
if self.is_state():
|
|
event_dict["state_key"] = self._state_key
|
|
|
|
if self._redacts is not None:
|
|
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(object):
|
|
def __init__(self, hs):
|
|
self.clock = hs.get_clock()
|
|
self.hostname = hs.hostname
|
|
self.signing_key = hs.config.signing_key[0]
|
|
|
|
self.store = hs.get_datastore()
|
|
self.state = hs.get_state_handler()
|
|
self.auth = hs.get_auth()
|
|
|
|
def new(self, room_version, key_values):
|
|
"""Generate an event builder appropriate for the given room version
|
|
|
|
Deprecated: use for_room_version with a RoomVersion object instead
|
|
|
|
Args:
|
|
room_version (str): Version of the room that we're creating an event builder
|
|
for
|
|
key_values (dict): Fields used as the basis of the new event
|
|
|
|
Returns:
|
|
EventBuilder
|
|
"""
|
|
v = KNOWN_ROOM_VERSIONS.get(room_version)
|
|
if not v:
|
|
# this can happen if support is withdrawn for a room version
|
|
raise UnsupportedRoomVersionError()
|
|
return self.for_room_version(v, key_values)
|
|
|
|
def for_room_version(self, room_version, key_values):
|
|
"""Generate an event builder appropriate for the given room version
|
|
|
|
Args:
|
|
room_version (synapse.api.room_versions.RoomVersion):
|
|
Version of the room that we're creating an event builder for
|
|
key_values (dict): Fields used as the basis of the new event
|
|
|
|
Returns:
|
|
EventBuilder
|
|
"""
|
|
return EventBuilder(
|
|
store=self.store,
|
|
state=self.state,
|
|
auth=self.auth,
|
|
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.V1:
|
|
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, hostname):
|
|
"""Create a new event ID
|
|
|
|
Args:
|
|
clock (Clock)
|
|
hostname (str): The server name for the event ID
|
|
|
|
Returns:
|
|
str
|
|
"""
|
|
|
|
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()
|