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