1# Copyright 2014-2016 OpenMarket Ltd 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14import logging 15from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union 16 17import attr 18from nacl.signing import SigningKey 19 20from synapse.api.constants import MAX_DEPTH 21from synapse.api.room_versions import ( 22 KNOWN_EVENT_FORMAT_VERSIONS, 23 EventFormatVersions, 24 RoomVersion, 25) 26from synapse.crypto.event_signing import add_hashes_and_signatures 27from synapse.events import EventBase, _EventInternalMetadata, make_event_from_dict 28from synapse.state import StateHandler 29from synapse.storage.databases.main import DataStore 30from synapse.types import EventID, JsonDict 31from synapse.util import Clock 32from synapse.util.stringutils import random_string 33 34if TYPE_CHECKING: 35 from synapse.handlers.event_auth import EventAuthHandler 36 from synapse.server import HomeServer 37 38logger = logging.getLogger(__name__) 39 40 41@attr.s(slots=True, cmp=False, frozen=True, auto_attribs=True) 42class EventBuilder: 43 """A format independent event builder used to build up the event content 44 before signing the event. 45 46 (Note that while objects of this class are frozen, the 47 content/unsigned/internal_metadata fields are still mutable) 48 49 Attributes: 50 room_version: Version of the target room 51 room_id 52 type 53 sender 54 content 55 unsigned 56 internal_metadata 57 58 _state 59 _auth 60 _store 61 _clock 62 _hostname: The hostname of the server creating the event 63 _signing_key: The signing key to use to sign the event as the server 64 """ 65 66 _state: StateHandler 67 _event_auth_handler: "EventAuthHandler" 68 _store: DataStore 69 _clock: Clock 70 _hostname: str 71 _signing_key: SigningKey 72 73 room_version: RoomVersion 74 75 room_id: str 76 type: str 77 sender: str 78 79 content: JsonDict = attr.Factory(dict) 80 unsigned: JsonDict = attr.Factory(dict) 81 82 # These only exist on a subset of events, so they raise AttributeError if 83 # someone tries to get them when they don't exist. 84 _state_key: Optional[str] = None 85 _redacts: Optional[str] = None 86 _origin_server_ts: Optional[int] = None 87 88 internal_metadata: _EventInternalMetadata = attr.Factory( 89 lambda: _EventInternalMetadata({}) 90 ) 91 92 @property 93 def state_key(self) -> str: 94 if self._state_key is not None: 95 return self._state_key 96 97 raise AttributeError("state_key") 98 99 def is_state(self) -> bool: 100 return self._state_key is not None 101 102 async def build( 103 self, 104 prev_event_ids: List[str], 105 auth_event_ids: Optional[List[str]], 106 depth: Optional[int] = None, 107 ) -> EventBase: 108 """Transform into a fully signed and hashed event 109 110 Args: 111 prev_event_ids: The event IDs to use as the prev events 112 auth_event_ids: The event IDs to use as the auth events. 113 Should normally be set to None, which will cause them to be calculated 114 based on the room state at the prev_events. 115 depth: Override the depth used to order the event in the DAG. 116 Should normally be set to None, which will cause the depth to be calculated 117 based on the prev_events. 118 119 Returns: 120 The signed and hashed event. 121 """ 122 if auth_event_ids is None: 123 state_ids = await self._state.get_current_state_ids( 124 self.room_id, prev_event_ids 125 ) 126 auth_event_ids = self._event_auth_handler.compute_auth_events( 127 self, state_ids 128 ) 129 130 format_version = self.room_version.event_format 131 # The types of auth/prev events changes between event versions. 132 prev_events: Union[List[str], List[Tuple[str, Dict[str, str]]]] 133 auth_events: Union[List[str], List[Tuple[str, Dict[str, str]]]] 134 if format_version == EventFormatVersions.V1: 135 auth_events = await self._store.add_event_hashes(auth_event_ids) 136 prev_events = await self._store.add_event_hashes(prev_event_ids) 137 else: 138 auth_events = auth_event_ids 139 prev_events = prev_event_ids 140 141 # Otherwise, progress the depth as normal 142 if depth is None: 143 ( 144 _, 145 most_recent_prev_event_depth, 146 ) = await self._store.get_max_depth_of(prev_event_ids) 147 148 depth = most_recent_prev_event_depth + 1 149 150 # we cap depth of generated events, to ensure that they are not 151 # rejected by other servers (and so that they can be persisted in 152 # the db) 153 depth = min(depth, MAX_DEPTH) 154 155 event_dict: Dict[str, Any] = { 156 "auth_events": auth_events, 157 "prev_events": prev_events, 158 "type": self.type, 159 "room_id": self.room_id, 160 "sender": self.sender, 161 "content": self.content, 162 "unsigned": self.unsigned, 163 "depth": depth, 164 "prev_state": [], 165 } 166 167 if self.is_state(): 168 event_dict["state_key"] = self._state_key 169 170 if self._redacts is not None: 171 event_dict["redacts"] = self._redacts 172 173 if self._origin_server_ts is not None: 174 event_dict["origin_server_ts"] = self._origin_server_ts 175 176 return create_local_event_from_event_dict( 177 clock=self._clock, 178 hostname=self._hostname, 179 signing_key=self._signing_key, 180 room_version=self.room_version, 181 event_dict=event_dict, 182 internal_metadata_dict=self.internal_metadata.get_dict(), 183 ) 184 185 186class EventBuilderFactory: 187 def __init__(self, hs: "HomeServer"): 188 self.clock = hs.get_clock() 189 self.hostname = hs.hostname 190 self.signing_key = hs.signing_key 191 192 self.store = hs.get_datastore() 193 self.state = hs.get_state_handler() 194 self._event_auth_handler = hs.get_event_auth_handler() 195 196 def for_room_version( 197 self, room_version: RoomVersion, key_values: dict 198 ) -> EventBuilder: 199 """Generate an event builder appropriate for the given room version 200 201 Args: 202 room_version: 203 Version of the room that we're creating an event builder for 204 key_values: Fields used as the basis of the new event 205 206 Returns: 207 EventBuilder 208 """ 209 return EventBuilder( 210 store=self.store, 211 state=self.state, 212 event_auth_handler=self._event_auth_handler, 213 clock=self.clock, 214 hostname=self.hostname, 215 signing_key=self.signing_key, 216 room_version=room_version, 217 type=key_values["type"], 218 state_key=key_values.get("state_key"), 219 room_id=key_values["room_id"], 220 sender=key_values["sender"], 221 content=key_values.get("content", {}), 222 unsigned=key_values.get("unsigned", {}), 223 redacts=key_values.get("redacts", None), 224 origin_server_ts=key_values.get("origin_server_ts", None), 225 ) 226 227 228def create_local_event_from_event_dict( 229 clock: Clock, 230 hostname: str, 231 signing_key: SigningKey, 232 room_version: RoomVersion, 233 event_dict: JsonDict, 234 internal_metadata_dict: Optional[JsonDict] = None, 235) -> EventBase: 236 """Takes a fully formed event dict, ensuring that fields like `origin` 237 and `origin_server_ts` have correct values for a locally produced event, 238 then signs and hashes it. 239 """ 240 241 format_version = room_version.event_format 242 if format_version not in KNOWN_EVENT_FORMAT_VERSIONS: 243 raise Exception("No event format defined for version %r" % (format_version,)) 244 245 if internal_metadata_dict is None: 246 internal_metadata_dict = {} 247 248 time_now = int(clock.time_msec()) 249 250 if format_version == EventFormatVersions.V1: 251 event_dict["event_id"] = _create_event_id(clock, hostname) 252 253 event_dict["origin"] = hostname 254 event_dict.setdefault("origin_server_ts", time_now) 255 256 event_dict.setdefault("unsigned", {}) 257 age = event_dict["unsigned"].pop("age", 0) 258 event_dict["unsigned"].setdefault("age_ts", time_now - age) 259 260 event_dict.setdefault("signatures", {}) 261 262 add_hashes_and_signatures(room_version, event_dict, hostname, signing_key) 263 return make_event_from_dict( 264 event_dict, room_version, internal_metadata_dict=internal_metadata_dict 265 ) 266 267 268# A counter used when generating new event IDs 269_event_id_counter = 0 270 271 272def _create_event_id(clock: Clock, hostname: str) -> str: 273 """Create a new event ID 274 275 Args: 276 clock 277 hostname: The server name for the event ID 278 279 Returns: 280 The new event ID 281 """ 282 283 global _event_id_counter 284 285 i = str(_event_id_counter) 286 _event_id_counter += 1 287 288 local_part = str(int(clock.time())) + i + random_string(5) 289 290 e_id = EventID(local_part, hostname) 291 292 return e_id.to_string() 293