|
|
|
@ -13,79 +13,156 @@ |
|
|
|
|
# See the License for the specific language governing permissions and |
|
|
|
|
# limitations under the License. |
|
|
|
|
|
|
|
|
|
import copy |
|
|
|
|
import attr |
|
|
|
|
|
|
|
|
|
from synapse.api.constants import RoomVersions |
|
|
|
|
from twisted.internet import defer |
|
|
|
|
|
|
|
|
|
from synapse.api.constants import ( |
|
|
|
|
KNOWN_EVENT_FORMAT_VERSIONS, |
|
|
|
|
KNOWN_ROOM_VERSIONS, |
|
|
|
|
MAX_DEPTH, |
|
|
|
|
) |
|
|
|
|
from synapse.crypto.event_signing import add_hashes_and_signatures |
|
|
|
|
from synapse.types import EventID |
|
|
|
|
from synapse.util.stringutils import random_string |
|
|
|
|
|
|
|
|
|
from . import EventBase, FrozenEvent, _event_dict_property |
|
|
|
|
from . import ( |
|
|
|
|
_EventInternalMetadata, |
|
|
|
|
event_type_from_format_version, |
|
|
|
|
room_version_to_event_format, |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_event_builder(room_version, key_values={}, internal_metadata_dict={}): |
|
|
|
|
"""Generate an event builder appropriate for the given room version |
|
|
|
|
@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: |
|
|
|
|
format_version (int): Event format version |
|
|
|
|
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() |
|
|
|
|
|
|
|
|
|
format_version = attr.ib() |
|
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
|
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: |
|
|
|
|
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 |
|
|
|
|
internal_metadata_dict (dict): Used to create the `_EventInternalMetadata` |
|
|
|
|
object. |
|
|
|
|
prev_event_ids (list[str]): The event IDs to use as the prev events |
|
|
|
|
|
|
|
|
|
Returns: |
|
|
|
|
EventBuilder |
|
|
|
|
Deferred[FrozenEvent] |
|
|
|
|
""" |
|
|
|
|
if room_version in { |
|
|
|
|
RoomVersions.V1, |
|
|
|
|
RoomVersions.V2, |
|
|
|
|
RoomVersions.VDH_TEST, |
|
|
|
|
RoomVersions.STATE_V2_TEST, |
|
|
|
|
}: |
|
|
|
|
return EventBuilder(key_values, internal_metadata_dict) |
|
|
|
|
else: |
|
|
|
|
raise Exception( |
|
|
|
|
"No event format defined for version %r" % (room_version,) |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
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, |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
class EventBuilder(EventBase): |
|
|
|
|
def __init__(self, key_values={}, internal_metadata_dict={}): |
|
|
|
|
signatures = copy.deepcopy(key_values.pop("signatures", {})) |
|
|
|
|
unsigned = copy.deepcopy(key_values.pop("unsigned", {})) |
|
|
|
|
auth_events = yield self._store.add_event_hashes(auth_ids) |
|
|
|
|
prev_events = yield self._store.add_event_hashes(prev_event_ids) |
|
|
|
|
|
|
|
|
|
super(EventBuilder, self).__init__( |
|
|
|
|
key_values, |
|
|
|
|
signatures=signatures, |
|
|
|
|
unsigned=unsigned, |
|
|
|
|
internal_metadata_dict=internal_metadata_dict, |
|
|
|
|
old_depth = yield self._store.get_max_depth_of( |
|
|
|
|
prev_event_ids, |
|
|
|
|
) |
|
|
|
|
depth = old_depth + 1 |
|
|
|
|
|
|
|
|
|
event_id = _event_dict_property("event_id") |
|
|
|
|
state_key = _event_dict_property("state_key") |
|
|
|
|
type = _event_dict_property("type") |
|
|
|
|
|
|
|
|
|
def build(self): |
|
|
|
|
return FrozenEvent.from_event(self) |
|
|
|
|
# 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": [], |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
class EventBuilderFactory(object): |
|
|
|
|
def __init__(self, clock, hostname): |
|
|
|
|
self.clock = clock |
|
|
|
|
self.hostname = hostname |
|
|
|
|
if self.is_state(): |
|
|
|
|
event_dict["state_key"] = self._state_key |
|
|
|
|
|
|
|
|
|
self.event_id_count = 0 |
|
|
|
|
if self._redacts is not None: |
|
|
|
|
event_dict["redacts"] = self._redacts |
|
|
|
|
|
|
|
|
|
def create_event_id(self): |
|
|
|
|
i = str(self.event_id_count) |
|
|
|
|
self.event_id_count += 1 |
|
|
|
|
defer.returnValue( |
|
|
|
|
create_local_event_from_event_dict( |
|
|
|
|
clock=self._clock, |
|
|
|
|
hostname=self._hostname, |
|
|
|
|
signing_key=self._signing_key, |
|
|
|
|
format_version=self.format_version, |
|
|
|
|
event_dict=event_dict, |
|
|
|
|
internal_metadata_dict=self.internal_metadata.get_dict(), |
|
|
|
|
) |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
local_part = str(int(self.clock.time())) + i + random_string(5) |
|
|
|
|
|
|
|
|
|
e_id = EventID(local_part, self.hostname) |
|
|
|
|
class EventBuilderFactory(object): |
|
|
|
|
def __init__(self, hs): |
|
|
|
|
self.clock = hs.get_clock() |
|
|
|
|
self.hostname = hs.hostname |
|
|
|
|
self.signing_key = hs.config.signing_key[0] |
|
|
|
|
|
|
|
|
|
return e_id.to_string() |
|
|
|
|
self.store = hs.get_datastore() |
|
|
|
|
self.state = hs.get_state_handler() |
|
|
|
|
self.auth = hs.get_auth() |
|
|
|
|
|
|
|
|
|
def new(self, room_version, key_values={}): |
|
|
|
|
def new(self, room_version, key_values): |
|
|
|
|
"""Generate an event builder appropriate for the given room version |
|
|
|
|
|
|
|
|
|
Args: |
|
|
|
@ -98,27 +175,104 @@ class EventBuilderFactory(object): |
|
|
|
|
""" |
|
|
|
|
|
|
|
|
|
# There's currently only the one event version defined |
|
|
|
|
if room_version not in { |
|
|
|
|
RoomVersions.V1, |
|
|
|
|
RoomVersions.V2, |
|
|
|
|
RoomVersions.VDH_TEST, |
|
|
|
|
RoomVersions.STATE_V2_TEST, |
|
|
|
|
}: |
|
|
|
|
if room_version not in KNOWN_ROOM_VERSIONS: |
|
|
|
|
raise Exception( |
|
|
|
|
"No event format defined for version %r" % (room_version,) |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
key_values["event_id"] = self.create_event_id() |
|
|
|
|
key_values["event_id"] = _create_event_id(self.clock, self.hostname) |
|
|
|
|
|
|
|
|
|
time_now = int(self.clock.time_msec()) |
|
|
|
|
return EventBuilder( |
|
|
|
|
store=self.store, |
|
|
|
|
state=self.state, |
|
|
|
|
auth=self.auth, |
|
|
|
|
clock=self.clock, |
|
|
|
|
hostname=self.hostname, |
|
|
|
|
signing_key=self.signing_key, |
|
|
|
|
format_version=room_version_to_event_format(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), |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
key_values.setdefault("origin", self.hostname) |
|
|
|
|
key_values.setdefault("origin_server_ts", time_now) |
|
|
|
|
|
|
|
|
|
key_values.setdefault("unsigned", {}) |
|
|
|
|
age = key_values["unsigned"].pop("age", 0) |
|
|
|
|
key_values["unsigned"].setdefault("age_ts", time_now - age) |
|
|
|
|
def create_local_event_from_event_dict(clock, hostname, signing_key, |
|
|
|
|
format_version, event_dict, |
|
|
|
|
internal_metadata_dict=None): |
|
|
|
|
"""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. |
|
|
|
|
|
|
|
|
|
key_values["signatures"] = {} |
|
|
|
|
Args: |
|
|
|
|
clock (Clock) |
|
|
|
|
hostname (str) |
|
|
|
|
signing_key |
|
|
|
|
format_version (int) |
|
|
|
|
event_dict (dict) |
|
|
|
|
internal_metadata_dict (dict|None) |
|
|
|
|
|
|
|
|
|
Returns: |
|
|
|
|
FrozenEvent |
|
|
|
|
""" |
|
|
|
|
|
|
|
|
|
# There's currently only the one event version defined |
|
|
|
|
if format_version not in KNOWN_EVENT_FORMAT_VERSIONS: |
|
|
|
|
raise Exception( |
|
|
|
|
"No event format defined for version %r" % (format_version,) |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
return EventBuilder(key_values=key_values,) |
|
|
|
|
if internal_metadata_dict is None: |
|
|
|
|
internal_metadata_dict = {} |
|
|
|
|
|
|
|
|
|
time_now = int(clock.time_msec()) |
|
|
|
|
|
|
|
|
|
event_dict["event_id"] = _create_event_id(clock, hostname) |
|
|
|
|
|
|
|
|
|
event_dict["origin"] = hostname |
|
|
|
|
event_dict["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( |
|
|
|
|
event_dict, |
|
|
|
|
hostname, |
|
|
|
|
signing_key, |
|
|
|
|
) |
|
|
|
|
return event_type_from_format_version(format_version)( |
|
|
|
|
event_dict, 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() |
|
|
|
|