Skip to main content

matrix_sdk/widget/
capabilities.rs

1// Copyright 2023 The Matrix.org Foundation C.I.C.
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.
14
15//! Types and traits related to the capabilities that a widget can request from
16//! a client.
17
18use std::{fmt, future::Future};
19
20use matrix_sdk_common::{SendOutsideWasm, SyncOutsideWasm};
21use serde::{Deserialize, Deserializer, Serialize, Serializer, ser::SerializeSeq};
22use tracing::{debug, warn};
23
24use super::{
25    MessageLikeEventFilter, StateEventFilter,
26    filter::{Filter, FilterInput, ToDeviceEventFilter},
27};
28
29/// Must be implemented by a component that provides functionality of deciding
30/// whether a widget is allowed to use certain capabilities (typically by
31/// providing a prompt to the user).
32pub trait CapabilitiesProvider: SendOutsideWasm + SyncOutsideWasm + 'static {
33    /// Receives a request for given capabilities and returns the actual
34    /// capabilities that the clients grants to a given widget (usually by
35    /// prompting the user).
36    fn acquire_capabilities(
37        &self,
38        capabilities: Capabilities,
39    ) -> impl Future<Output = Capabilities> + SendOutsideWasm;
40}
41
42/// Capabilities that a widget can request from a client.
43#[derive(Clone, Debug, Default)]
44#[cfg_attr(test, derive(PartialEq))]
45pub struct Capabilities {
46    /// Types of the messages that a widget wants to be able to fetch.
47    pub read: Vec<Filter>,
48    /// Types of the messages that a widget wants to be able to send.
49    pub send: Vec<Filter>,
50    /// If this capability is requested by the widget, it can not operate
51    /// separately from the Matrix client.
52    ///
53    /// This means clients should not offer to open the widget in a separate
54    /// browser/tab/webview that is not connected to the postmessage widget-api.
55    pub requires_client: bool,
56    /// This allows the widget to ask the client to update delayed events.
57    pub update_delayed_event: bool,
58    /// This allows the widget to send events with a delay.
59    pub send_delayed_event: bool,
60
61    /// This allows the widget to download files as per MSC4039.
62    pub download_file: bool,
63
64    /// This allows the widget to discover the RTC transports advertised by the
65    /// homeserver as per MSC4515.
66    pub rtc_transports: bool,
67
68    /// This allows the widget to obtain a token to authenticate with a LiveKit
69    /// SFU from the homeserver as per MSC4533.
70    pub rtc_livekit_get_token: bool,
71
72    /// This allows the widget to hand the delayed leave event of a MatrixRTC
73    /// session over to the homeserver as per MSC4533.
74    pub rtc_livekit_delegate_delayed_leave: bool,
75}
76
77impl Capabilities {
78    /// Checks if a given event is allowed to be forwarded to the widget.
79    ///
80    /// - `event_filter_input` is a minimized event representation that contains
81    ///   only the information needed to check if the widget is allowed to
82    ///   receive the event. (See [`FilterInput`])
83    pub(super) fn allow_reading<'a>(
84        &self,
85        event_filter_input: impl TryInto<FilterInput<'a>>,
86    ) -> bool {
87        match &event_filter_input.try_into() {
88            Err(_) => {
89                warn!("Failed to convert event into filter input for `allow_reading`.");
90                false
91            }
92            Ok(filter_input) => self.read.iter().any(|f| f.matches(filter_input)),
93        }
94    }
95
96    /// Checks if a given event is allowed to be sent by the widget.
97    ///
98    /// - `event_filter_input` is a minimized event representation that contains
99    ///   only the information needed to check if the widget is allowed to send
100    ///   the event to a matrix room. (See [`FilterInput`])
101    pub(super) fn allow_sending<'a>(
102        &self,
103        event_filter_input: impl TryInto<FilterInput<'a>>,
104    ) -> bool {
105        match &event_filter_input.try_into() {
106            Err(_) => {
107                warn!("Failed to convert event into filter input for `allow_sending`.");
108                false
109            }
110            Ok(filter_input) => self.send.iter().any(|f| f.matches(filter_input)),
111        }
112    }
113
114    /// Checks if a filter exists for the given event type, useful for
115    /// optimization. Avoids unnecessary read event requests when no matching
116    /// filter is present.
117    pub(super) fn has_read_filter_for_type(&self, event_type: &str) -> bool {
118        self.read.iter().any(|f| f.filter_event_type() == event_type)
119    }
120}
121
122pub(super) const SEND_EVENT: &str = "org.matrix.msc2762.send.event";
123pub(super) const READ_EVENT: &str = "org.matrix.msc2762.receive.event";
124pub(super) const SEND_STATE: &str = "org.matrix.msc2762.send.state_event";
125pub(super) const READ_STATE: &str = "org.matrix.msc2762.receive.state_event";
126pub(super) const SEND_TODEVICE: &str = "org.matrix.msc3819.send.to_device";
127pub(super) const READ_TODEVICE: &str = "org.matrix.msc3819.receive.to_device";
128pub(super) const REQUIRES_CLIENT: &str = "io.element.requires_client";
129pub(super) const SEND_DELAYED_EVENT: &str = "org.matrix.msc4157.send.delayed_event";
130pub(super) const UPDATE_DELAYED_EVENT: &str = "org.matrix.msc4157.update_delayed_event";
131
132pub(super) const DOWNLOAD_FILE: &str = "org.matrix.msc4039.download_file";
133
134pub(super) const RTC_TRANSPORTS: &str = "org.matrix.msc4515.rtc_transports";
135
136pub(super) const RTC_LIVEKIT_GET_TOKEN: &str = "org.matrix.msc4533.rtc_livekit_get_token";
137pub(super) const RTC_LIVEKIT_DELEGATE_DELAYED_LEAVE: &str =
138    "org.matrix.msc4533.rtc_livekit_delegate_delayed_leave";
139
140impl Serialize for Capabilities {
141    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
142    where
143        S: Serializer,
144    {
145        struct PrintEventFilter<'a>(&'a Filter);
146        impl fmt::Display for PrintEventFilter<'_> {
147            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148                match self.0 {
149                    Filter::MessageLike(filter) => PrintMessageLikeEventFilter(filter).fmt(f),
150                    Filter::State(filter) => PrintStateEventFilter(filter).fmt(f),
151                    Filter::ToDevice(filter) => {
152                        // As per MSC 3819
153                        // https://github.com/matrix-org/matrix-spec-proposals/pull/3819
154                        // ToDevice capabilities is in the form of
155                        // `m.send.to_device:<event type>` or
156                        // `m.receive.to_device:<event type>`
157                        write!(f, "{}", filter.event_type)
158                    }
159                }
160            }
161        }
162
163        struct PrintMessageLikeEventFilter<'a>(&'a MessageLikeEventFilter);
164        impl fmt::Display for PrintMessageLikeEventFilter<'_> {
165            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166                match self.0 {
167                    MessageLikeEventFilter::WithType(event_type) => {
168                        // TODO: escape `#` as `\#` and `\` as `\\` in
169                        // event_type
170                        write!(f, "{event_type}")
171                    }
172                    MessageLikeEventFilter::RoomMessageWithMsgtype(msgtype) => {
173                        write!(f, "m.room.message#{msgtype}")
174                    }
175                }
176            }
177        }
178
179        struct PrintStateEventFilter<'a>(&'a StateEventFilter);
180        impl fmt::Display for PrintStateEventFilter<'_> {
181            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
182                // TODO: escape `#` as `\#` and `\` as `\\` in event_type
183                match self.0 {
184                    StateEventFilter::WithType(event_type) => write!(f, "{event_type}"),
185                    StateEventFilter::WithTypeAndStateKey(event_type, state_key) => {
186                        write!(f, "{event_type}#{state_key}")
187                    }
188                }
189            }
190        }
191
192        let mut seq = serializer.serialize_seq(None)?;
193
194        if self.requires_client {
195            seq.serialize_element(REQUIRES_CLIENT)?;
196        }
197        if self.update_delayed_event {
198            seq.serialize_element(UPDATE_DELAYED_EVENT)?;
199        }
200        if self.send_delayed_event {
201            seq.serialize_element(SEND_DELAYED_EVENT)?;
202        }
203        if self.download_file {
204            seq.serialize_element(DOWNLOAD_FILE)?;
205        }
206        if self.rtc_transports {
207            seq.serialize_element(RTC_TRANSPORTS)?;
208        }
209        if self.rtc_livekit_get_token {
210            seq.serialize_element(RTC_LIVEKIT_GET_TOKEN)?;
211        }
212        if self.rtc_livekit_delegate_delayed_leave {
213            seq.serialize_element(RTC_LIVEKIT_DELEGATE_DELAYED_LEAVE)?;
214        }
215        for filter in &self.read {
216            let name = match filter {
217                Filter::MessageLike(_) => READ_EVENT,
218                Filter::State(_) => READ_STATE,
219                Filter::ToDevice(_) => READ_TODEVICE,
220            };
221            seq.serialize_element(&format!("{name}:{}", PrintEventFilter(filter)))?;
222        }
223        for filter in &self.send {
224            let name = match filter {
225                Filter::MessageLike(_) => SEND_EVENT,
226                Filter::State(_) => SEND_STATE,
227                Filter::ToDevice(_) => SEND_TODEVICE,
228            };
229            seq.serialize_element(&format!("{name}:{}", PrintEventFilter(filter)))?;
230        }
231
232        seq.end()
233    }
234}
235
236impl<'de> Deserialize<'de> for Capabilities {
237    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
238    where
239        D: Deserializer<'de>,
240    {
241        enum Permission {
242            RequiresClient,
243            UpdateDelayedEvent,
244            SendDelayedEvent,
245            DownloadFile,
246            RtcTransports,
247            RtcLivekitGetToken,
248            RtcLivekitDelegateDelayedLeave,
249            Read(Filter),
250            Send(Filter),
251            Unknown,
252        }
253
254        impl<'de> Deserialize<'de> for Permission {
255            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
256            where
257                D: Deserializer<'de>,
258            {
259                let s = ruma::serde::deserialize_cow_str(deserializer)?;
260                if s == REQUIRES_CLIENT {
261                    return Ok(Self::RequiresClient);
262                }
263                if s == UPDATE_DELAYED_EVENT {
264                    return Ok(Self::UpdateDelayedEvent);
265                }
266                if s == SEND_DELAYED_EVENT {
267                    return Ok(Self::SendDelayedEvent);
268                }
269                if s == DOWNLOAD_FILE {
270                    return Ok(Self::DownloadFile);
271                }
272                if s == RTC_TRANSPORTS {
273                    return Ok(Self::RtcTransports);
274                }
275                if s == RTC_LIVEKIT_GET_TOKEN {
276                    return Ok(Self::RtcLivekitGetToken);
277                }
278                if s == RTC_LIVEKIT_DELEGATE_DELAYED_LEAVE {
279                    return Ok(Self::RtcLivekitDelegateDelayedLeave);
280                }
281
282                match s.split_once(':') {
283                    Some((READ_EVENT, filter_s)) => Ok(Permission::Read(Filter::MessageLike(
284                        parse_message_event_filter(filter_s),
285                    ))),
286                    Some((SEND_EVENT, filter_s)) => Ok(Permission::Send(Filter::MessageLike(
287                        parse_message_event_filter(filter_s),
288                    ))),
289                    Some((READ_STATE, filter_s)) => {
290                        Ok(Permission::Read(Filter::State(parse_state_event_filter(filter_s))))
291                    }
292                    Some((SEND_STATE, filter_s)) => {
293                        Ok(Permission::Send(Filter::State(parse_state_event_filter(filter_s))))
294                    }
295                    Some((READ_TODEVICE, filter_s)) => Ok(Permission::Read(Filter::ToDevice(
296                        parse_to_device_event_filter(filter_s),
297                    ))),
298                    Some((SEND_TODEVICE, filter_s)) => Ok(Permission::Send(Filter::ToDevice(
299                        parse_to_device_event_filter(filter_s),
300                    ))),
301                    _ => {
302                        debug!("Unknown capability `{s}`");
303                        Ok(Self::Unknown)
304                    }
305                }
306            }
307        }
308
309        fn parse_message_event_filter(s: &str) -> MessageLikeEventFilter {
310            match s.strip_prefix("m.room.message#") {
311                Some(msgtype) => MessageLikeEventFilter::RoomMessageWithMsgtype(msgtype.to_owned()),
312                // TODO: Replace `\\` by `\` and `\#` by `#`, enforce no unescaped `#`
313                None => MessageLikeEventFilter::WithType(s.into()),
314            }
315        }
316
317        fn parse_state_event_filter(s: &str) -> StateEventFilter {
318            // TODO: Search for un-escaped `#` only, replace `\\` by `\` and
319            // `\#` by `#`
320            match s.split_once('#') {
321                Some((event_type, state_key)) => {
322                    StateEventFilter::WithTypeAndStateKey(event_type.into(), state_key.to_owned())
323                }
324                None => StateEventFilter::WithType(s.into()),
325            }
326        }
327
328        fn parse_to_device_event_filter(s: &str) -> ToDeviceEventFilter {
329            ToDeviceEventFilter::new(s.into())
330        }
331
332        let mut capabilities = Capabilities::default();
333        for capability in Vec::<Permission>::deserialize(deserializer)? {
334            match capability {
335                Permission::RequiresClient => capabilities.requires_client = true,
336                Permission::Read(filter) => capabilities.read.push(filter),
337                Permission::Send(filter) => capabilities.send.push(filter),
338                // ignore unknown capabilities
339                Permission::Unknown => {}
340                Permission::UpdateDelayedEvent => capabilities.update_delayed_event = true,
341                Permission::SendDelayedEvent => capabilities.send_delayed_event = true,
342                Permission::DownloadFile => capabilities.download_file = true,
343                Permission::RtcTransports => capabilities.rtc_transports = true,
344                Permission::RtcLivekitGetToken => capabilities.rtc_livekit_get_token = true,
345                Permission::RtcLivekitDelegateDelayedLeave => {
346                    capabilities.rtc_livekit_delegate_delayed_leave = true
347                }
348            }
349        }
350
351        Ok(capabilities)
352    }
353}
354
355#[cfg(test)]
356mod tests {
357    use ruma::events::StateEventType;
358
359    use super::*;
360    use crate::widget::filter::ToDeviceEventFilter;
361
362    #[test]
363    fn deserialization_of_no_capabilities() {
364        let capabilities_str = r#"[]"#;
365
366        let parsed = serde_json::from_str::<Capabilities>(capabilities_str).unwrap();
367        let expected = Capabilities::default();
368
369        assert_eq!(parsed, expected);
370    }
371
372    #[test]
373    fn deserialization_of_capabilities() {
374        let capabilities_str = r#"[
375            "m.always_on_screen",
376            "io.element.requires_client",
377            "org.matrix.msc2762.receive.event:org.matrix.rageshake_request",
378            "org.matrix.msc2762.receive.state_event:m.room.member",
379            "org.matrix.msc2762.receive.state_event:org.matrix.msc3401.call.member",
380            "org.matrix.msc3819.receive.to_device:io.element.call.encryption_keys",
381            "org.matrix.msc2762.send.event:org.matrix.rageshake_request",
382            "org.matrix.msc2762.send.state_event:org.matrix.msc3401.call.member#@user:matrix.server",
383            "org.matrix.msc3819.send.to_device:io.element.call.encryption_keys",
384            "org.matrix.msc4157.send.delayed_event",
385            "org.matrix.msc4157.update_delayed_event",
386            "org.matrix.msc4039.download_file",
387            "org.matrix.msc4515.rtc_transports",
388            "org.matrix.msc4533.rtc_livekit_get_token",
389            "org.matrix.msc4533.rtc_livekit_delegate_delayed_leave"
390        ]"#;
391
392        let parsed = serde_json::from_str::<Capabilities>(capabilities_str).unwrap();
393        let expected = Capabilities {
394            read: vec![
395                Filter::MessageLike(MessageLikeEventFilter::WithType(
396                    "org.matrix.rageshake_request".into(),
397                )),
398                Filter::State(StateEventFilter::WithType(StateEventType::RoomMember)),
399                Filter::State(StateEventFilter::WithType("org.matrix.msc3401.call.member".into())),
400                Filter::ToDevice(ToDeviceEventFilter::new(
401                    "io.element.call.encryption_keys".into(),
402                )),
403            ],
404            send: vec![
405                Filter::MessageLike(MessageLikeEventFilter::WithType(
406                    "org.matrix.rageshake_request".into(),
407                )),
408                Filter::State(StateEventFilter::WithTypeAndStateKey(
409                    "org.matrix.msc3401.call.member".into(),
410                    "@user:matrix.server".into(),
411                )),
412                Filter::ToDevice(ToDeviceEventFilter::new(
413                    "io.element.call.encryption_keys".into(),
414                )),
415            ],
416            requires_client: true,
417            update_delayed_event: true,
418            send_delayed_event: true,
419            download_file: true,
420            rtc_transports: true,
421            rtc_livekit_get_token: true,
422            rtc_livekit_delegate_delayed_leave: true,
423        };
424
425        assert_eq!(parsed, expected);
426    }
427
428    #[test]
429    fn serialization_and_deserialization_are_symmetrical() {
430        let capabilities = Capabilities {
431            read: vec![
432                Filter::MessageLike(MessageLikeEventFilter::WithType("io.element.custom".into())),
433                Filter::State(StateEventFilter::WithType(StateEventType::RoomMember)),
434                Filter::State(StateEventFilter::WithTypeAndStateKey(
435                    "org.matrix.msc3401.call.member".into(),
436                    "@user:matrix.server".into(),
437                )),
438                Filter::ToDevice(ToDeviceEventFilter::new(
439                    "io.element.call.encryption_keys".into(),
440                )),
441            ],
442            send: vec![
443                Filter::MessageLike(MessageLikeEventFilter::WithType("io.element.custom".into())),
444                Filter::State(StateEventFilter::WithTypeAndStateKey(
445                    "org.matrix.msc3401.call.member".into(),
446                    "@user:matrix.server".into(),
447                )),
448                Filter::ToDevice(ToDeviceEventFilter::new("my.org.other.to_device_event".into())),
449            ],
450            requires_client: true,
451            update_delayed_event: false,
452            send_delayed_event: false,
453            download_file: false,
454            rtc_transports: true,
455            rtc_livekit_get_token: true,
456            rtc_livekit_delegate_delayed_leave: false,
457        };
458
459        let capabilities_str = serde_json::to_string(&capabilities).unwrap();
460        let parsed = serde_json::from_str::<Capabilities>(&capabilities_str).unwrap();
461        assert_eq!(parsed, capabilities);
462    }
463}