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
69impl Capabilities {
70    /// Checks if a given event is allowed to be forwarded to the widget.
71    ///
72    /// - `event_filter_input` is a minimized event representation that contains
73    ///   only the information needed to check if the widget is allowed to
74    ///   receive the event. (See [`FilterInput`])
75    pub(super) fn allow_reading<'a>(
76        &self,
77        event_filter_input: impl TryInto<FilterInput<'a>>,
78    ) -> bool {
79        match &event_filter_input.try_into() {
80            Err(_) => {
81                warn!("Failed to convert event into filter input for `allow_reading`.");
82                false
83            }
84            Ok(filter_input) => self.read.iter().any(|f| f.matches(filter_input)),
85        }
86    }
87
88    /// Checks if a given event is allowed to be sent by the widget.
89    ///
90    /// - `event_filter_input` is a minimized event representation that contains
91    ///   only the information needed to check if the widget is allowed to send
92    ///   the event to a matrix room. (See [`FilterInput`])
93    pub(super) fn allow_sending<'a>(
94        &self,
95        event_filter_input: impl TryInto<FilterInput<'a>>,
96    ) -> bool {
97        match &event_filter_input.try_into() {
98            Err(_) => {
99                warn!("Failed to convert event into filter input for `allow_sending`.");
100                false
101            }
102            Ok(filter_input) => self.send.iter().any(|f| f.matches(filter_input)),
103        }
104    }
105
106    /// Checks if a filter exists for the given event type, useful for
107    /// optimization. Avoids unnecessary read event requests when no matching
108    /// filter is present.
109    pub(super) fn has_read_filter_for_type(&self, event_type: &str) -> bool {
110        self.read.iter().any(|f| f.filter_event_type() == event_type)
111    }
112}
113
114pub(super) const SEND_EVENT: &str = "org.matrix.msc2762.send.event";
115pub(super) const READ_EVENT: &str = "org.matrix.msc2762.receive.event";
116pub(super) const SEND_STATE: &str = "org.matrix.msc2762.send.state_event";
117pub(super) const READ_STATE: &str = "org.matrix.msc2762.receive.state_event";
118pub(super) const SEND_TODEVICE: &str = "org.matrix.msc3819.send.to_device";
119pub(super) const READ_TODEVICE: &str = "org.matrix.msc3819.receive.to_device";
120pub(super) const REQUIRES_CLIENT: &str = "io.element.requires_client";
121pub(super) const SEND_DELAYED_EVENT: &str = "org.matrix.msc4157.send.delayed_event";
122pub(super) const UPDATE_DELAYED_EVENT: &str = "org.matrix.msc4157.update_delayed_event";
123
124pub(super) const DOWNLOAD_FILE: &str = "org.matrix.msc4039.download_file";
125
126pub(super) const RTC_TRANSPORTS: &str = "org.matrix.msc4515.rtc_transports";
127
128impl Serialize for Capabilities {
129    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
130    where
131        S: Serializer,
132    {
133        struct PrintEventFilter<'a>(&'a Filter);
134        impl fmt::Display for PrintEventFilter<'_> {
135            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136                match self.0 {
137                    Filter::MessageLike(filter) => PrintMessageLikeEventFilter(filter).fmt(f),
138                    Filter::State(filter) => PrintStateEventFilter(filter).fmt(f),
139                    Filter::ToDevice(filter) => {
140                        // As per MSC 3819
141                        // https://github.com/matrix-org/matrix-spec-proposals/pull/3819
142                        // ToDevice capabilities is in the form of
143                        // `m.send.to_device:<event type>` or
144                        // `m.receive.to_device:<event type>`
145                        write!(f, "{}", filter.event_type)
146                    }
147                }
148            }
149        }
150
151        struct PrintMessageLikeEventFilter<'a>(&'a MessageLikeEventFilter);
152        impl fmt::Display for PrintMessageLikeEventFilter<'_> {
153            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154                match self.0 {
155                    MessageLikeEventFilter::WithType(event_type) => {
156                        // TODO: escape `#` as `\#` and `\` as `\\` in
157                        // event_type
158                        write!(f, "{event_type}")
159                    }
160                    MessageLikeEventFilter::RoomMessageWithMsgtype(msgtype) => {
161                        write!(f, "m.room.message#{msgtype}")
162                    }
163                }
164            }
165        }
166
167        struct PrintStateEventFilter<'a>(&'a StateEventFilter);
168        impl fmt::Display for PrintStateEventFilter<'_> {
169            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
170                // TODO: escape `#` as `\#` and `\` as `\\` in event_type
171                match self.0 {
172                    StateEventFilter::WithType(event_type) => write!(f, "{event_type}"),
173                    StateEventFilter::WithTypeAndStateKey(event_type, state_key) => {
174                        write!(f, "{event_type}#{state_key}")
175                    }
176                }
177            }
178        }
179
180        let mut seq = serializer.serialize_seq(None)?;
181
182        if self.requires_client {
183            seq.serialize_element(REQUIRES_CLIENT)?;
184        }
185        if self.update_delayed_event {
186            seq.serialize_element(UPDATE_DELAYED_EVENT)?;
187        }
188        if self.send_delayed_event {
189            seq.serialize_element(SEND_DELAYED_EVENT)?;
190        }
191        if self.download_file {
192            seq.serialize_element(DOWNLOAD_FILE)?;
193        }
194        if self.rtc_transports {
195            seq.serialize_element(RTC_TRANSPORTS)?;
196        }
197        for filter in &self.read {
198            let name = match filter {
199                Filter::MessageLike(_) => READ_EVENT,
200                Filter::State(_) => READ_STATE,
201                Filter::ToDevice(_) => READ_TODEVICE,
202            };
203            seq.serialize_element(&format!("{name}:{}", PrintEventFilter(filter)))?;
204        }
205        for filter in &self.send {
206            let name = match filter {
207                Filter::MessageLike(_) => SEND_EVENT,
208                Filter::State(_) => SEND_STATE,
209                Filter::ToDevice(_) => SEND_TODEVICE,
210            };
211            seq.serialize_element(&format!("{name}:{}", PrintEventFilter(filter)))?;
212        }
213
214        seq.end()
215    }
216}
217
218impl<'de> Deserialize<'de> for Capabilities {
219    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
220    where
221        D: Deserializer<'de>,
222    {
223        enum Permission {
224            RequiresClient,
225            UpdateDelayedEvent,
226            SendDelayedEvent,
227            DownloadFile,
228            RtcTransports,
229            Read(Filter),
230            Send(Filter),
231            Unknown,
232        }
233
234        impl<'de> Deserialize<'de> for Permission {
235            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
236            where
237                D: Deserializer<'de>,
238            {
239                let s = ruma::serde::deserialize_cow_str(deserializer)?;
240                if s == REQUIRES_CLIENT {
241                    return Ok(Self::RequiresClient);
242                }
243                if s == UPDATE_DELAYED_EVENT {
244                    return Ok(Self::UpdateDelayedEvent);
245                }
246                if s == SEND_DELAYED_EVENT {
247                    return Ok(Self::SendDelayedEvent);
248                }
249                if s == DOWNLOAD_FILE {
250                    return Ok(Self::DownloadFile);
251                }
252                if s == RTC_TRANSPORTS {
253                    return Ok(Self::RtcTransports);
254                }
255
256                match s.split_once(':') {
257                    Some((READ_EVENT, filter_s)) => Ok(Permission::Read(Filter::MessageLike(
258                        parse_message_event_filter(filter_s),
259                    ))),
260                    Some((SEND_EVENT, filter_s)) => Ok(Permission::Send(Filter::MessageLike(
261                        parse_message_event_filter(filter_s),
262                    ))),
263                    Some((READ_STATE, filter_s)) => {
264                        Ok(Permission::Read(Filter::State(parse_state_event_filter(filter_s))))
265                    }
266                    Some((SEND_STATE, filter_s)) => {
267                        Ok(Permission::Send(Filter::State(parse_state_event_filter(filter_s))))
268                    }
269                    Some((READ_TODEVICE, filter_s)) => Ok(Permission::Read(Filter::ToDevice(
270                        parse_to_device_event_filter(filter_s),
271                    ))),
272                    Some((SEND_TODEVICE, filter_s)) => Ok(Permission::Send(Filter::ToDevice(
273                        parse_to_device_event_filter(filter_s),
274                    ))),
275                    _ => {
276                        debug!("Unknown capability `{s}`");
277                        Ok(Self::Unknown)
278                    }
279                }
280            }
281        }
282
283        fn parse_message_event_filter(s: &str) -> MessageLikeEventFilter {
284            match s.strip_prefix("m.room.message#") {
285                Some(msgtype) => MessageLikeEventFilter::RoomMessageWithMsgtype(msgtype.to_owned()),
286                // TODO: Replace `\\` by `\` and `\#` by `#`, enforce no unescaped `#`
287                None => MessageLikeEventFilter::WithType(s.into()),
288            }
289        }
290
291        fn parse_state_event_filter(s: &str) -> StateEventFilter {
292            // TODO: Search for un-escaped `#` only, replace `\\` by `\` and
293            // `\#` by `#`
294            match s.split_once('#') {
295                Some((event_type, state_key)) => {
296                    StateEventFilter::WithTypeAndStateKey(event_type.into(), state_key.to_owned())
297                }
298                None => StateEventFilter::WithType(s.into()),
299            }
300        }
301
302        fn parse_to_device_event_filter(s: &str) -> ToDeviceEventFilter {
303            ToDeviceEventFilter::new(s.into())
304        }
305
306        let mut capabilities = Capabilities::default();
307        for capability in Vec::<Permission>::deserialize(deserializer)? {
308            match capability {
309                Permission::RequiresClient => capabilities.requires_client = true,
310                Permission::Read(filter) => capabilities.read.push(filter),
311                Permission::Send(filter) => capabilities.send.push(filter),
312                // ignore unknown capabilities
313                Permission::Unknown => {}
314                Permission::UpdateDelayedEvent => capabilities.update_delayed_event = true,
315                Permission::SendDelayedEvent => capabilities.send_delayed_event = true,
316                Permission::DownloadFile => capabilities.download_file = true,
317                Permission::RtcTransports => capabilities.rtc_transports = true,
318            }
319        }
320
321        Ok(capabilities)
322    }
323}
324
325#[cfg(test)]
326mod tests {
327    use ruma::events::StateEventType;
328
329    use super::*;
330    use crate::widget::filter::ToDeviceEventFilter;
331
332    #[test]
333    fn deserialization_of_no_capabilities() {
334        let capabilities_str = r#"[]"#;
335
336        let parsed = serde_json::from_str::<Capabilities>(capabilities_str).unwrap();
337        let expected = Capabilities::default();
338
339        assert_eq!(parsed, expected);
340    }
341
342    #[test]
343    fn deserialization_of_capabilities() {
344        let capabilities_str = r#"[
345            "m.always_on_screen",
346            "io.element.requires_client",
347            "org.matrix.msc2762.receive.event:org.matrix.rageshake_request",
348            "org.matrix.msc2762.receive.state_event:m.room.member",
349            "org.matrix.msc2762.receive.state_event:org.matrix.msc3401.call.member",
350            "org.matrix.msc3819.receive.to_device:io.element.call.encryption_keys",
351            "org.matrix.msc2762.send.event:org.matrix.rageshake_request",
352            "org.matrix.msc2762.send.state_event:org.matrix.msc3401.call.member#@user:matrix.server",
353            "org.matrix.msc3819.send.to_device:io.element.call.encryption_keys",
354            "org.matrix.msc4157.send.delayed_event",
355            "org.matrix.msc4157.update_delayed_event",
356            "org.matrix.msc4039.download_file",
357            "org.matrix.msc4515.rtc_transports"
358        ]"#;
359
360        let parsed = serde_json::from_str::<Capabilities>(capabilities_str).unwrap();
361        let expected = Capabilities {
362            read: vec![
363                Filter::MessageLike(MessageLikeEventFilter::WithType(
364                    "org.matrix.rageshake_request".into(),
365                )),
366                Filter::State(StateEventFilter::WithType(StateEventType::RoomMember)),
367                Filter::State(StateEventFilter::WithType("org.matrix.msc3401.call.member".into())),
368                Filter::ToDevice(ToDeviceEventFilter::new(
369                    "io.element.call.encryption_keys".into(),
370                )),
371            ],
372            send: vec![
373                Filter::MessageLike(MessageLikeEventFilter::WithType(
374                    "org.matrix.rageshake_request".into(),
375                )),
376                Filter::State(StateEventFilter::WithTypeAndStateKey(
377                    "org.matrix.msc3401.call.member".into(),
378                    "@user:matrix.server".into(),
379                )),
380                Filter::ToDevice(ToDeviceEventFilter::new(
381                    "io.element.call.encryption_keys".into(),
382                )),
383            ],
384            requires_client: true,
385            update_delayed_event: true,
386            send_delayed_event: true,
387            download_file: true,
388            rtc_transports: true,
389        };
390
391        assert_eq!(parsed, expected);
392    }
393
394    #[test]
395    fn serialization_and_deserialization_are_symmetrical() {
396        let capabilities = Capabilities {
397            read: vec![
398                Filter::MessageLike(MessageLikeEventFilter::WithType("io.element.custom".into())),
399                Filter::State(StateEventFilter::WithType(StateEventType::RoomMember)),
400                Filter::State(StateEventFilter::WithTypeAndStateKey(
401                    "org.matrix.msc3401.call.member".into(),
402                    "@user:matrix.server".into(),
403                )),
404                Filter::ToDevice(ToDeviceEventFilter::new(
405                    "io.element.call.encryption_keys".into(),
406                )),
407            ],
408            send: vec![
409                Filter::MessageLike(MessageLikeEventFilter::WithType("io.element.custom".into())),
410                Filter::State(StateEventFilter::WithTypeAndStateKey(
411                    "org.matrix.msc3401.call.member".into(),
412                    "@user:matrix.server".into(),
413                )),
414                Filter::ToDevice(ToDeviceEventFilter::new("my.org.other.to_device_event".into())),
415            ],
416            requires_client: true,
417            update_delayed_event: false,
418            send_delayed_event: false,
419            download_file: false,
420            rtc_transports: true,
421        };
422
423        let capabilities_str = serde_json::to_string(&capabilities).unwrap();
424        let parsed = serde_json::from_str::<Capabilities>(&capabilities_str).unwrap();
425        assert_eq!(parsed, capabilities);
426    }
427}