Skip to content

Commit b8a61cf

Browse files
toger5poljar
andauthored
feat(WidgetDriver): Support widget redacts (#3987)
Changelog: Implement proper redact handling in the widget driver. This allows the Rust SDK widget driver to support widgets that rely on redacting. Co-authored-by: Damir Jelić <[email protected]>
1 parent ab61077 commit b8a61cf

File tree

4 files changed

+101
-49
lines changed

4 files changed

+101
-49
lines changed

crates/matrix-sdk/src/widget/machine/mod.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,10 @@ impl WidgetMachine {
295295
ReadEventRequest::ReadMessageLikeEvent { event_type, limit } => {
296296
let filter_fn = |f: &EventFilter| f.matches_message_like_event_type(&event_type);
297297
if !capabilities.read.iter().any(filter_fn) {
298-
return Some(self.send_from_widget_error_response(raw_request, "Not allowed"));
298+
return Some(self.send_from_widget_error_response(
299+
raw_request,
300+
"Not allowed to read message like event",
301+
));
299302
}
300303

301304
const DEFAULT_EVENT_LIMIT: u32 = 50;
@@ -345,7 +348,10 @@ impl WidgetMachine {
345348
});
346349
action
347350
} else {
348-
Some(self.send_from_widget_error_response(raw_request, "Not allowed"))
351+
Some(self.send_from_widget_error_response(
352+
raw_request,
353+
"Not allowed to read state event",
354+
))
349355
}
350356
}
351357
}
@@ -381,7 +387,9 @@ impl WidgetMachine {
381387
));
382388
}
383389
if !capabilities.send.iter().any(|filter| filter.matches(&filter_in)) {
384-
return Some(self.send_from_widget_error_response(raw_request, "Not allowed"));
390+
return Some(
391+
self.send_from_widget_error_response(raw_request, "Not allowed to send event"),
392+
);
385393
}
386394

387395
let (request, action) = self.send_matrix_driver_request(request);

crates/matrix-sdk/src/widget/machine/tests/error.rs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,10 @@ fn read_request_for_non_allowed_message_like_events() {
9898
assert_eq!(request_id, "get-me-some-messages");
9999
assert_eq!(msg["api"], "fromWidget");
100100
assert_eq!(msg["action"], "org.matrix.msc2876.read_events");
101-
assert_eq!(msg["response"]["error"]["message"].as_str().unwrap(), "Not allowed");
101+
assert_eq!(
102+
msg["response"]["error"]["message"].as_str().unwrap(),
103+
"Not allowed to read message like event"
104+
);
102105
}
103106

104107
#[test]
@@ -124,7 +127,10 @@ fn read_request_for_non_allowed_state_events() {
124127
assert_eq!(request_id, "get-me-some-messages");
125128
assert_eq!(msg["api"], "fromWidget");
126129
assert_eq!(msg["action"], "org.matrix.msc2876.read_events");
127-
assert_eq!(msg["response"]["error"]["message"].as_str().unwrap(), "Not allowed");
130+
assert_eq!(
131+
msg["response"]["error"]["message"].as_str().unwrap(),
132+
"Not allowed to read state event"
133+
);
128134
}
129135

130136
#[test]
@@ -156,7 +162,7 @@ fn send_request_for_non_allowed_state_events() {
156162
assert_eq!(request_id, "send-me-a-message");
157163
assert_eq!(msg["api"], "fromWidget");
158164
assert_eq!(msg["action"], "send_event");
159-
assert_eq!(msg["response"]["error"]["message"].as_str().unwrap(), "Not allowed");
165+
assert_eq!(msg["response"]["error"]["message"].as_str().unwrap(), "Not allowed to send event");
160166
}
161167

162168
#[test]
@@ -188,7 +194,7 @@ fn send_request_for_non_allowed_message_like_events() {
188194
assert_eq!(request_id, "send-me-a-message");
189195
assert_eq!(msg["api"], "fromWidget");
190196
assert_eq!(msg["action"], "send_event");
191-
assert_eq!(msg["response"]["error"]["message"].as_str().unwrap(), "Not allowed");
197+
assert_eq!(msg["response"]["error"]["message"].as_str().unwrap(), "Not allowed to send event");
192198
}
193199

194200
#[test]
@@ -218,5 +224,8 @@ fn read_request_for_message_like_with_disallowed_msg_type_fails() {
218224
assert_eq!(request_id, "get-me-some-messages");
219225
assert_eq!(msg["api"], "fromWidget");
220226
assert_eq!(msg["action"], "org.matrix.msc2876.read_events");
221-
assert_eq!(msg["response"]["error"]["message"].as_str().unwrap(), "Not allowed");
227+
assert_eq!(
228+
msg["response"]["error"]["message"].as_str().unwrap(),
229+
"Not allowed to read message like event"
230+
);
222231
}

crates/matrix-sdk/src/widget/matrix.rs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ use ruma::{
2929
AnyMessageLikeEventContent, AnyStateEventContent, AnySyncTimelineEvent, AnyTimelineEvent,
3030
MessageLikeEventType, StateEventType, TimelineEventType,
3131
},
32-
serde::Raw,
33-
RoomId, TransactionId,
32+
serde::{from_raw_json_value, Raw},
33+
EventId, RoomId, TransactionId,
3434
};
35-
use serde_json::value::RawValue as RawJsonValue;
35+
use serde_json::{value::RawValue as RawJsonValue, Value};
3636
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver};
3737
use tracing::error;
3838

@@ -107,7 +107,11 @@ impl MatrixDriver {
107107
Ok(events)
108108
}
109109

110-
/// Sends a given `event` to the room.
110+
/// Sends the given `event` to the room.
111+
///
112+
/// This method allows the widget machine to handle widget requests by
113+
/// providing a unified, high-level widget-specific API for sending events
114+
/// to the room.
111115
pub(crate) async fn send(
112116
&self,
113117
event_type: TimelineEventType,
@@ -116,6 +120,15 @@ impl MatrixDriver {
116120
delayed_event_parameters: Option<delayed_events::DelayParameters>,
117121
) -> Result<SendEventResponse> {
118122
let type_str = event_type.to_string();
123+
124+
if let Some(redacts) = from_raw_json_value::<Value, serde_json::Error>(&content)
125+
.ok()
126+
.and_then(|b| b["redacts"].as_str().and_then(|s| EventId::parse(s).ok()))
127+
{
128+
return Ok(SendEventResponse::from_event_id(
129+
self.room.redact(&redacts, None, None).await?.event_id,
130+
));
131+
}
119132
Ok(match (state_key, delayed_event_parameters) {
120133
(None, None) => SendEventResponse::from_event_id(
121134
self.room.send_raw(&type_str, content).await?.event_id,

crates/matrix-sdk/tests/integration/widget.rs

Lines changed: 59 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,19 @@ use async_trait::async_trait;
1919
use futures_util::FutureExt;
2020
use matrix_sdk::{
2121
config::SyncSettings,
22+
test_utils::mocks::MatrixMockServer,
2223
widget::{
2324
Capabilities, CapabilitiesProvider, WidgetDriver, WidgetDriverHandle, WidgetSettings,
2425
},
2526
Client,
2627
};
2728
use matrix_sdk_common::{executor::spawn, timeout::timeout};
2829
use matrix_sdk_test::{
29-
async_test, mocks::mock_encryption_state, EventBuilder, JoinedRoomBuilder, SyncResponseBuilder,
30-
ALICE, BOB,
30+
async_test, EventBuilder, JoinedRoomBuilder, SyncResponseBuilder, ALICE, BOB,
3131
};
3232
use once_cell::sync::Lazy;
3333
use ruma::{
34+
event_id,
3435
events::room::{
3536
member::{MembershipState, RoomMemberEventContent},
3637
message::RoomMessageEventContent,
@@ -46,10 +47,10 @@ use serde_json::{json, Value as JsonValue};
4647
use tracing::error;
4748
use wiremock::{
4849
matchers::{header, method, path_regex, query_param},
49-
Mock, MockServer, ResponseTemplate,
50+
Mock, ResponseTemplate,
5051
};
5152

52-
use crate::{logged_in_client_with_server, mock_sync};
53+
use crate::mock_sync;
5354

5455
/// Create a JSON string from a [`json!`][serde_json::json] "literal".
5556
#[macro_export]
@@ -60,7 +61,9 @@ macro_rules! json_string {
6061
const WIDGET_ID: &str = "test-widget";
6162
static ROOM_ID: Lazy<OwnedRoomId> = Lazy::new(|| owned_room_id!("!a98sd12bjh:example.org"));
6263

63-
async fn run_test_driver(init_on_content_load: bool) -> (Client, MockServer, WidgetDriverHandle) {
64+
async fn run_test_driver(
65+
init_on_content_load: bool,
66+
) -> (Client, MatrixMockServer, WidgetDriverHandle) {
6467
struct DummyCapabilitiesProvider;
6568

6669
#[async_trait]
@@ -70,20 +73,11 @@ async fn run_test_driver(init_on_content_load: bool) -> (Client, MockServer, Wid
7073
capabilities
7174
}
7275
}
76+
let mock_server = MatrixMockServer::new().await;
77+
let client = mock_server.make_client().await;
7378

74-
let (client, mock_server) = logged_in_client_with_server().await;
75-
let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000));
76-
77-
let mut sync_builder = SyncResponseBuilder::new();
78-
sync_builder.add_joined_room(JoinedRoomBuilder::new(&ROOM_ID));
79-
80-
mock_sync(&mock_server, sync_builder.build_json_sync_response(), None).await;
81-
let _response = client.sync_once(sync_settings.clone()).await.unwrap();
82-
mock_server.reset().await;
83-
84-
mock_encryption_state(&mock_server, false).await;
85-
86-
let room = client.get_room(&ROOM_ID).unwrap();
79+
let room = mock_server.sync_joined_room(&client, &ROOM_ID).await;
80+
mock_server.mock_room_state_encryption().plain().mount().await;
8781

8882
let (driver, handle) = WidgetDriver::new(
8983
WidgetSettings::new(WIDGET_ID.to_owned(), init_on_content_load, "https://foo.bar/widget")
@@ -257,7 +251,7 @@ async fn test_read_messages() {
257251
.and(query_param("limit", "2"))
258252
.respond_with(ResponseTemplate::new(200).set_body_json(response_json))
259253
.expect(1)
260-
.mount(&mock_server)
254+
.mount(mock_server.server())
261255
.await;
262256

263257
// Ask the driver to read messages
@@ -282,8 +276,6 @@ async fn test_read_messages() {
282276
let first_event = &events[0];
283277
assert_eq!(first_event["content"]["body"], "hello");
284278
}
285-
286-
mock_server.verify().await;
287279
}
288280

289281
#[async_test]
@@ -354,7 +346,7 @@ async fn test_read_messages_with_msgtype_capabilities() {
354346
.and(query_param("limit", "3"))
355347
.respond_with(ResponseTemplate::new(200).set_body_json(response_json))
356348
.expect(1)
357-
.mount(&mock_server)
349+
.mount(mock_server.server())
358350
.await;
359351

360352
// Ask the driver to read messages
@@ -379,8 +371,6 @@ async fn test_read_messages_with_msgtype_capabilities() {
379371
let first_event = &events[0];
380372
assert_eq!(first_event["content"]["body"], "hello");
381373
}
382-
383-
mock_server.verify().await;
384374
}
385375

386376
#[async_test]
@@ -488,7 +478,7 @@ async fn test_receive_live_events() {
488478
)),
489479
);
490480

491-
mock_sync(&mock_server, sync_builder.build_json_sync_response(), None).await;
481+
mock_sync(mock_server.server(), sync_builder.build_json_sync_response(), None).await;
492482
let _response =
493483
client.sync_once(SyncSettings::new().timeout(Duration::from_millis(3000))).await.unwrap();
494484

@@ -531,7 +521,7 @@ async fn test_send_room_message() {
531521
.and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/m.room.message/.*$"))
532522
.respond_with(ResponseTemplate::new(200).set_body_json(json!({ "event_id": "$foobar" })))
533523
.expect(1)
534-
.mount(&mock_server)
524+
.mount(mock_server.server())
535525
.await;
536526

537527
send_request(
@@ -556,7 +546,6 @@ async fn test_send_room_message() {
556546
assert_eq!(event_id, "$foobar");
557547

558548
// Make sure the event-sending endpoint was hit exactly once
559-
mock_server.verify().await;
560549
}
561550

562551
#[async_test]
@@ -573,7 +562,7 @@ async fn test_send_room_name() {
573562
.and(path_regex(r"^/_matrix/client/r0/rooms/.*/state/m.room.name/?$"))
574563
.respond_with(ResponseTemplate::new(200).set_body_json(json!({ "event_id": "$foobar" })))
575564
.expect(1)
576-
.mount(&mock_server)
565+
.mount(mock_server.server())
577566
.await;
578567

579568
send_request(
@@ -598,7 +587,6 @@ async fn test_send_room_name() {
598587
assert_eq!(event_id, "$foobar");
599588

600589
// Make sure the event-sending endpoint was hit exactly once
601-
mock_server.verify().await;
602590
}
603591

604592
#[async_test]
@@ -620,7 +608,7 @@ async fn test_send_delayed_message_event() {
620608
"delay_id": "1234",
621609
})))
622610
.expect(1)
623-
.mount(&mock_server)
611+
.mount(mock_server.server())
624612
.await;
625613

626614
send_request(
@@ -646,7 +634,6 @@ async fn test_send_delayed_message_event() {
646634
assert_eq!(delay_id, "1234");
647635

648636
// Make sure the event-sending endpoint was hit exactly once
649-
mock_server.verify().await;
650637
}
651638

652639
#[async_test]
@@ -668,7 +655,7 @@ async fn test_send_delayed_state_event() {
668655
"delay_id": "1234",
669656
})))
670657
.expect(1)
671-
.mount(&mock_server)
658+
.mount(mock_server.server())
672659
.await;
673660

674661
send_request(
@@ -694,7 +681,6 @@ async fn test_send_delayed_state_event() {
694681
assert_eq!(delay_id, "1234");
695682

696683
// Make sure the event-sending endpoint was hit exactly once
697-
mock_server.verify().await;
698684
}
699685

700686
#[async_test]
@@ -744,7 +730,7 @@ async fn test_update_delayed_event() {
744730
.and(path_regex(r"^/_matrix/client/unstable/org.matrix.msc4140/delayed_events/1234"))
745731
.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
746732
.expect(1)
747-
.mount(&mock_server)
733+
.mount(mock_server.server())
748734
.await;
749735

750736
send_request(
@@ -764,9 +750,6 @@ async fn test_update_delayed_event() {
764750
assert_eq!(response["action"], "org.matrix.msc4157.update_delayed_event");
765751
let empty_response = response["response"].clone();
766752
assert_eq!(empty_response, serde_json::from_str::<JsonValue>("{}").unwrap());
767-
768-
// Make sure the event-sending endpoint was hit exactly once
769-
mock_server.verify().await;
770753
}
771754

772755
#[async_test]
@@ -827,6 +810,45 @@ async fn test_try_update_delayed_event_without_permission_negotiate() {
827810
}
828811
}
829812

813+
#[async_test]
814+
async fn test_send_redaction() {
815+
let (_, mock_server, driver_handle) = run_test_driver(false).await;
816+
817+
negotiate_capabilities(
818+
&driver_handle,
819+
json!([
820+
// "org.matrix.msc4157.send.delayed_event",
821+
"org.matrix.msc2762.send.event:m.room.redaction"
822+
]),
823+
)
824+
.await;
825+
826+
mock_server.mock_room_redact().ok(event_id!("$redact_event_id")).mock_once().mount().await;
827+
828+
send_request(
829+
&driver_handle,
830+
"send-redact-message",
831+
"send_event",
832+
json!({
833+
"type": "m.room.redaction",
834+
"content": {
835+
"redacts": "$1234"
836+
},
837+
}),
838+
)
839+
.await;
840+
841+
// Receive the response
842+
let msg = recv_message(&driver_handle).await;
843+
assert_eq!(msg["api"], "fromWidget");
844+
assert_eq!(msg["action"], "send_event");
845+
let redact_event_id = msg["response"]["event_id"].as_str().unwrap();
846+
let redact_room_id = msg["response"]["room_id"].as_str().unwrap();
847+
848+
assert_eq!(redact_event_id, "$redact_event_id");
849+
assert_eq!(redact_room_id, "!a98sd12bjh:example.org");
850+
}
851+
830852
async fn negotiate_capabilities(driver_handle: &WidgetDriverHandle, caps: JsonValue) {
831853
{
832854
// Receive toWidget capabilities request

0 commit comments

Comments
 (0)