mirror of
https://github.com/matrix-org/matrix-rust-sdk.git
synced 2026-05-16 20:49:05 -04:00
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ć <poljar@termina.org.uk>
This commit is contained in:
@@ -295,7 +295,10 @@ impl WidgetMachine {
|
||||
ReadEventRequest::ReadMessageLikeEvent { event_type, limit } => {
|
||||
let filter_fn = |f: &EventFilter| f.matches_message_like_event_type(&event_type);
|
||||
if !capabilities.read.iter().any(filter_fn) {
|
||||
return Some(self.send_from_widget_error_response(raw_request, "Not allowed"));
|
||||
return Some(self.send_from_widget_error_response(
|
||||
raw_request,
|
||||
"Not allowed to read message like event",
|
||||
));
|
||||
}
|
||||
|
||||
const DEFAULT_EVENT_LIMIT: u32 = 50;
|
||||
@@ -345,7 +348,10 @@ impl WidgetMachine {
|
||||
});
|
||||
action
|
||||
} else {
|
||||
Some(self.send_from_widget_error_response(raw_request, "Not allowed"))
|
||||
Some(self.send_from_widget_error_response(
|
||||
raw_request,
|
||||
"Not allowed to read state event",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -381,7 +387,9 @@ impl WidgetMachine {
|
||||
));
|
||||
}
|
||||
if !capabilities.send.iter().any(|filter| filter.matches(&filter_in)) {
|
||||
return Some(self.send_from_widget_error_response(raw_request, "Not allowed"));
|
||||
return Some(
|
||||
self.send_from_widget_error_response(raw_request, "Not allowed to send event"),
|
||||
);
|
||||
}
|
||||
|
||||
let (request, action) = self.send_matrix_driver_request(request);
|
||||
|
||||
@@ -98,7 +98,10 @@ fn read_request_for_non_allowed_message_like_events() {
|
||||
assert_eq!(request_id, "get-me-some-messages");
|
||||
assert_eq!(msg["api"], "fromWidget");
|
||||
assert_eq!(msg["action"], "org.matrix.msc2876.read_events");
|
||||
assert_eq!(msg["response"]["error"]["message"].as_str().unwrap(), "Not allowed");
|
||||
assert_eq!(
|
||||
msg["response"]["error"]["message"].as_str().unwrap(),
|
||||
"Not allowed to read message like event"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -124,7 +127,10 @@ fn read_request_for_non_allowed_state_events() {
|
||||
assert_eq!(request_id, "get-me-some-messages");
|
||||
assert_eq!(msg["api"], "fromWidget");
|
||||
assert_eq!(msg["action"], "org.matrix.msc2876.read_events");
|
||||
assert_eq!(msg["response"]["error"]["message"].as_str().unwrap(), "Not allowed");
|
||||
assert_eq!(
|
||||
msg["response"]["error"]["message"].as_str().unwrap(),
|
||||
"Not allowed to read state event"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -156,7 +162,7 @@ fn send_request_for_non_allowed_state_events() {
|
||||
assert_eq!(request_id, "send-me-a-message");
|
||||
assert_eq!(msg["api"], "fromWidget");
|
||||
assert_eq!(msg["action"], "send_event");
|
||||
assert_eq!(msg["response"]["error"]["message"].as_str().unwrap(), "Not allowed");
|
||||
assert_eq!(msg["response"]["error"]["message"].as_str().unwrap(), "Not allowed to send event");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -188,7 +194,7 @@ fn send_request_for_non_allowed_message_like_events() {
|
||||
assert_eq!(request_id, "send-me-a-message");
|
||||
assert_eq!(msg["api"], "fromWidget");
|
||||
assert_eq!(msg["action"], "send_event");
|
||||
assert_eq!(msg["response"]["error"]["message"].as_str().unwrap(), "Not allowed");
|
||||
assert_eq!(msg["response"]["error"]["message"].as_str().unwrap(), "Not allowed to send event");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -218,5 +224,8 @@ fn read_request_for_message_like_with_disallowed_msg_type_fails() {
|
||||
assert_eq!(request_id, "get-me-some-messages");
|
||||
assert_eq!(msg["api"], "fromWidget");
|
||||
assert_eq!(msg["action"], "org.matrix.msc2876.read_events");
|
||||
assert_eq!(msg["response"]["error"]["message"].as_str().unwrap(), "Not allowed");
|
||||
assert_eq!(
|
||||
msg["response"]["error"]["message"].as_str().unwrap(),
|
||||
"Not allowed to read message like event"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -29,10 +29,10 @@ use ruma::{
|
||||
AnyMessageLikeEventContent, AnyStateEventContent, AnySyncTimelineEvent, AnyTimelineEvent,
|
||||
MessageLikeEventType, StateEventType, TimelineEventType,
|
||||
},
|
||||
serde::Raw,
|
||||
RoomId, TransactionId,
|
||||
serde::{from_raw_json_value, Raw},
|
||||
EventId, RoomId, TransactionId,
|
||||
};
|
||||
use serde_json::value::RawValue as RawJsonValue;
|
||||
use serde_json::{value::RawValue as RawJsonValue, Value};
|
||||
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver};
|
||||
use tracing::error;
|
||||
|
||||
@@ -107,7 +107,11 @@ impl MatrixDriver {
|
||||
Ok(events)
|
||||
}
|
||||
|
||||
/// Sends a given `event` to the room.
|
||||
/// Sends the given `event` to the room.
|
||||
///
|
||||
/// This method allows the widget machine to handle widget requests by
|
||||
/// providing a unified, high-level widget-specific API for sending events
|
||||
/// to the room.
|
||||
pub(crate) async fn send(
|
||||
&self,
|
||||
event_type: TimelineEventType,
|
||||
@@ -116,6 +120,15 @@ impl MatrixDriver {
|
||||
delayed_event_parameters: Option<delayed_events::DelayParameters>,
|
||||
) -> Result<SendEventResponse> {
|
||||
let type_str = event_type.to_string();
|
||||
|
||||
if let Some(redacts) = from_raw_json_value::<Value, serde_json::Error>(&content)
|
||||
.ok()
|
||||
.and_then(|b| b["redacts"].as_str().and_then(|s| EventId::parse(s).ok()))
|
||||
{
|
||||
return Ok(SendEventResponse::from_event_id(
|
||||
self.room.redact(&redacts, None, None).await?.event_id,
|
||||
));
|
||||
}
|
||||
Ok(match (state_key, delayed_event_parameters) {
|
||||
(None, None) => SendEventResponse::from_event_id(
|
||||
self.room.send_raw(&type_str, content).await?.event_id,
|
||||
|
||||
@@ -19,6 +19,7 @@ use async_trait::async_trait;
|
||||
use futures_util::FutureExt;
|
||||
use matrix_sdk::{
|
||||
config::SyncSettings,
|
||||
test_utils::mocks::MatrixMockServer,
|
||||
widget::{
|
||||
Capabilities, CapabilitiesProvider, WidgetDriver, WidgetDriverHandle, WidgetSettings,
|
||||
},
|
||||
@@ -26,11 +27,11 @@ use matrix_sdk::{
|
||||
};
|
||||
use matrix_sdk_common::{executor::spawn, timeout::timeout};
|
||||
use matrix_sdk_test::{
|
||||
async_test, mocks::mock_encryption_state, EventBuilder, JoinedRoomBuilder, SyncResponseBuilder,
|
||||
ALICE, BOB,
|
||||
async_test, EventBuilder, JoinedRoomBuilder, SyncResponseBuilder, ALICE, BOB,
|
||||
};
|
||||
use once_cell::sync::Lazy;
|
||||
use ruma::{
|
||||
event_id,
|
||||
events::room::{
|
||||
member::{MembershipState, RoomMemberEventContent},
|
||||
message::RoomMessageEventContent,
|
||||
@@ -46,10 +47,10 @@ use serde_json::{json, Value as JsonValue};
|
||||
use tracing::error;
|
||||
use wiremock::{
|
||||
matchers::{header, method, path_regex, query_param},
|
||||
Mock, MockServer, ResponseTemplate,
|
||||
Mock, ResponseTemplate,
|
||||
};
|
||||
|
||||
use crate::{logged_in_client_with_server, mock_sync};
|
||||
use crate::mock_sync;
|
||||
|
||||
/// Create a JSON string from a [`json!`][serde_json::json] "literal".
|
||||
#[macro_export]
|
||||
@@ -60,7 +61,9 @@ macro_rules! json_string {
|
||||
const WIDGET_ID: &str = "test-widget";
|
||||
static ROOM_ID: Lazy<OwnedRoomId> = Lazy::new(|| owned_room_id!("!a98sd12bjh:example.org"));
|
||||
|
||||
async fn run_test_driver(init_on_content_load: bool) -> (Client, MockServer, WidgetDriverHandle) {
|
||||
async fn run_test_driver(
|
||||
init_on_content_load: bool,
|
||||
) -> (Client, MatrixMockServer, WidgetDriverHandle) {
|
||||
struct DummyCapabilitiesProvider;
|
||||
|
||||
#[async_trait]
|
||||
@@ -70,20 +73,11 @@ async fn run_test_driver(init_on_content_load: bool) -> (Client, MockServer, Wid
|
||||
capabilities
|
||||
}
|
||||
}
|
||||
let mock_server = MatrixMockServer::new().await;
|
||||
let client = mock_server.make_client().await;
|
||||
|
||||
let (client, mock_server) = logged_in_client_with_server().await;
|
||||
let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000));
|
||||
|
||||
let mut sync_builder = SyncResponseBuilder::new();
|
||||
sync_builder.add_joined_room(JoinedRoomBuilder::new(&ROOM_ID));
|
||||
|
||||
mock_sync(&mock_server, sync_builder.build_json_sync_response(), None).await;
|
||||
let _response = client.sync_once(sync_settings.clone()).await.unwrap();
|
||||
mock_server.reset().await;
|
||||
|
||||
mock_encryption_state(&mock_server, false).await;
|
||||
|
||||
let room = client.get_room(&ROOM_ID).unwrap();
|
||||
let room = mock_server.sync_joined_room(&client, &ROOM_ID).await;
|
||||
mock_server.mock_room_state_encryption().plain().mount().await;
|
||||
|
||||
let (driver, handle) = WidgetDriver::new(
|
||||
WidgetSettings::new(WIDGET_ID.to_owned(), init_on_content_load, "https://foo.bar/widget")
|
||||
@@ -257,7 +251,7 @@ async fn test_read_messages() {
|
||||
.and(query_param("limit", "2"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(response_json))
|
||||
.expect(1)
|
||||
.mount(&mock_server)
|
||||
.mount(mock_server.server())
|
||||
.await;
|
||||
|
||||
// Ask the driver to read messages
|
||||
@@ -282,8 +276,6 @@ async fn test_read_messages() {
|
||||
let first_event = &events[0];
|
||||
assert_eq!(first_event["content"]["body"], "hello");
|
||||
}
|
||||
|
||||
mock_server.verify().await;
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
@@ -354,7 +346,7 @@ async fn test_read_messages_with_msgtype_capabilities() {
|
||||
.and(query_param("limit", "3"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(response_json))
|
||||
.expect(1)
|
||||
.mount(&mock_server)
|
||||
.mount(mock_server.server())
|
||||
.await;
|
||||
|
||||
// Ask the driver to read messages
|
||||
@@ -379,8 +371,6 @@ async fn test_read_messages_with_msgtype_capabilities() {
|
||||
let first_event = &events[0];
|
||||
assert_eq!(first_event["content"]["body"], "hello");
|
||||
}
|
||||
|
||||
mock_server.verify().await;
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
@@ -488,7 +478,7 @@ async fn test_receive_live_events() {
|
||||
)),
|
||||
);
|
||||
|
||||
mock_sync(&mock_server, sync_builder.build_json_sync_response(), None).await;
|
||||
mock_sync(mock_server.server(), sync_builder.build_json_sync_response(), None).await;
|
||||
let _response =
|
||||
client.sync_once(SyncSettings::new().timeout(Duration::from_millis(3000))).await.unwrap();
|
||||
|
||||
@@ -531,7 +521,7 @@ async fn test_send_room_message() {
|
||||
.and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/m.room.message/.*$"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({ "event_id": "$foobar" })))
|
||||
.expect(1)
|
||||
.mount(&mock_server)
|
||||
.mount(mock_server.server())
|
||||
.await;
|
||||
|
||||
send_request(
|
||||
@@ -556,7 +546,6 @@ async fn test_send_room_message() {
|
||||
assert_eq!(event_id, "$foobar");
|
||||
|
||||
// Make sure the event-sending endpoint was hit exactly once
|
||||
mock_server.verify().await;
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
@@ -573,7 +562,7 @@ async fn test_send_room_name() {
|
||||
.and(path_regex(r"^/_matrix/client/r0/rooms/.*/state/m.room.name/?$"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({ "event_id": "$foobar" })))
|
||||
.expect(1)
|
||||
.mount(&mock_server)
|
||||
.mount(mock_server.server())
|
||||
.await;
|
||||
|
||||
send_request(
|
||||
@@ -598,7 +587,6 @@ async fn test_send_room_name() {
|
||||
assert_eq!(event_id, "$foobar");
|
||||
|
||||
// Make sure the event-sending endpoint was hit exactly once
|
||||
mock_server.verify().await;
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
@@ -620,7 +608,7 @@ async fn test_send_delayed_message_event() {
|
||||
"delay_id": "1234",
|
||||
})))
|
||||
.expect(1)
|
||||
.mount(&mock_server)
|
||||
.mount(mock_server.server())
|
||||
.await;
|
||||
|
||||
send_request(
|
||||
@@ -646,7 +634,6 @@ async fn test_send_delayed_message_event() {
|
||||
assert_eq!(delay_id, "1234");
|
||||
|
||||
// Make sure the event-sending endpoint was hit exactly once
|
||||
mock_server.verify().await;
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
@@ -668,7 +655,7 @@ async fn test_send_delayed_state_event() {
|
||||
"delay_id": "1234",
|
||||
})))
|
||||
.expect(1)
|
||||
.mount(&mock_server)
|
||||
.mount(mock_server.server())
|
||||
.await;
|
||||
|
||||
send_request(
|
||||
@@ -694,7 +681,6 @@ async fn test_send_delayed_state_event() {
|
||||
assert_eq!(delay_id, "1234");
|
||||
|
||||
// Make sure the event-sending endpoint was hit exactly once
|
||||
mock_server.verify().await;
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
@@ -744,7 +730,7 @@ async fn test_update_delayed_event() {
|
||||
.and(path_regex(r"^/_matrix/client/unstable/org.matrix.msc4140/delayed_events/1234"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
|
||||
.expect(1)
|
||||
.mount(&mock_server)
|
||||
.mount(mock_server.server())
|
||||
.await;
|
||||
|
||||
send_request(
|
||||
@@ -764,9 +750,6 @@ async fn test_update_delayed_event() {
|
||||
assert_eq!(response["action"], "org.matrix.msc4157.update_delayed_event");
|
||||
let empty_response = response["response"].clone();
|
||||
assert_eq!(empty_response, serde_json::from_str::<JsonValue>("{}").unwrap());
|
||||
|
||||
// Make sure the event-sending endpoint was hit exactly once
|
||||
mock_server.verify().await;
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
@@ -827,6 +810,45 @@ async fn test_try_update_delayed_event_without_permission_negotiate() {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn test_send_redaction() {
|
||||
let (_, mock_server, driver_handle) = run_test_driver(false).await;
|
||||
|
||||
negotiate_capabilities(
|
||||
&driver_handle,
|
||||
json!([
|
||||
// "org.matrix.msc4157.send.delayed_event",
|
||||
"org.matrix.msc2762.send.event:m.room.redaction"
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
|
||||
mock_server.mock_room_redact().ok(event_id!("$redact_event_id")).mock_once().mount().await;
|
||||
|
||||
send_request(
|
||||
&driver_handle,
|
||||
"send-redact-message",
|
||||
"send_event",
|
||||
json!({
|
||||
"type": "m.room.redaction",
|
||||
"content": {
|
||||
"redacts": "$1234"
|
||||
},
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
// Receive the response
|
||||
let msg = recv_message(&driver_handle).await;
|
||||
assert_eq!(msg["api"], "fromWidget");
|
||||
assert_eq!(msg["action"], "send_event");
|
||||
let redact_event_id = msg["response"]["event_id"].as_str().unwrap();
|
||||
let redact_room_id = msg["response"]["room_id"].as_str().unwrap();
|
||||
|
||||
assert_eq!(redact_event_id, "$redact_event_id");
|
||||
assert_eq!(redact_room_id, "!a98sd12bjh:example.org");
|
||||
}
|
||||
|
||||
async fn negotiate_capabilities(driver_handle: &WidgetDriverHandle, caps: JsonValue) {
|
||||
{
|
||||
// Receive toWidget capabilities request
|
||||
|
||||
Reference in New Issue
Block a user