fix: Apply redactions to room state events in database, too (#917)

fixes #890
This commit is contained in:
Benjamin Kampmann 2022-09-27 09:24:53 +02:00 committed by GitHub
parent f8b02d1a11
commit b5bd6dfee9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 494 additions and 129 deletions

5
.config/nextest.toml Normal file
View File

@ -0,0 +1,5 @@
[profile.default]
retries = 2
# kill the slow tests if they still aren't up after 180s
slow-timeout = { period = "60s", terminate-after = 3 }

View File

@ -441,12 +441,11 @@ jobs:
with: with:
python-version: 3.8 python-version: 3.8
- uses: gnunicorn/setup-matrix-synapse@main - uses: michaelkaye/setup-matrix-synapse@main
with: with:
uploadLogs: true uploadLogs: true
httpPort: 8228 httpPort: 8228
disableRateLimiting: true disableRateLimiting: true
serverName: "matrix-sdk.rs"
- name: Test - name: Test
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1

1
Cargo.lock generated
View File

@ -2573,6 +2573,7 @@ dependencies = [
"once_cell", "once_cell",
"tempfile", "tempfile",
"tokio", "tokio",
"tracing",
"tracing-subscriber", "tracing-subscriber",
] ]

View File

@ -327,10 +327,7 @@ impl BaseClient {
), ),
) => { ) => {
room_info.handle_redaction(r); room_info.handle_redaction(r);
// FIXME: Find the event in self.store (needs changes.add_redaction(room_id, r.clone());
// something like a get_event_by_id), redact it and
// put it back via StateChanges. See
// https://github.com/matrix-org/matrix-rust-sdk/issues/607
} }
#[cfg(feature = "e2e-encryption")] #[cfg(feature = "e2e-encryption")]

View File

@ -59,6 +59,7 @@ macro_rules! statestore_integration_tests {
}, },
power_levels::RoomPowerLevelsEventContent, power_levels::RoomPowerLevelsEventContent,
MediaSource, MediaSource,
topic::{RoomTopicEventContent, OriginalRoomTopicEvent, RedactedRoomTopicEvent},
}, },
AnyEphemeralRoomEventContent, AnyGlobalAccountDataEvent, AnyEphemeralRoomEventContent, AnyGlobalAccountDataEvent,
AnyRoomAccountDataEvent, AnyStrippedStateEvent, AnySyncEphemeralRoomEvent, AnyRoomAccountDataEvent, AnyStrippedStateEvent, AnySyncEphemeralRoomEvent,
@ -78,7 +79,7 @@ macro_rules! statestore_integration_tests {
}; };
use $crate::{ use $crate::{
media::{MediaFormat, MediaRequest, MediaThumbnailSize}, media::{MediaFormat, MediaRequest, MediaThumbnailSize},
store::{Result as StoreResult, StateChanges, StateStore}, store::{Result as StoreResult, StateChanges, StateStore, StateStoreExt},
RoomInfo, RoomType, RoomInfo, RoomType,
}; };
@ -147,8 +148,8 @@ macro_rules! statestore_integration_tests {
let topic_json: &JsonValue = &test_json::TOPIC; let topic_json: &JsonValue = &test_json::TOPIC;
let topic_raw = let topic_raw =
serde_json::from_value::<Raw<AnySyncStateEvent>>(topic_json.clone()).unwrap(); serde_json::from_value::<Raw<AnySyncStateEvent>>(topic_json.clone()).expect("can create sync-state-event for topic");
let topic_event = topic_raw.deserialize().unwrap(); let topic_event = topic_raw.deserialize().expect("can deserialize raw topic");
room.handle_state_event(&topic_event); room.handle_state_event(&topic_event);
changes.add_state_event(room_id, topic_event, topic_raw); changes.add_state_event(room_id, topic_event, topic_raw);
@ -284,6 +285,55 @@ macro_rules! statestore_integration_tests {
}) })
} }
#[async_test]
async fn test_topic_redaction() -> StoreResult<()> {
let room_id = room_id();
let inner_store = get_store().await?;
let store = Arc::new(inner_store);
populate_store(store.clone()).await?;
assert!(store.get_sync_token().await?.is_some());
assert_eq!(
store
.get_state_event_static::<RoomTopicEventContent>(room_id)
.await?
.expect("room topic found before redaction")
.deserialize_as::<OriginalRoomTopicEvent>()
.expect("can deserialize room topic before redaction")
.content
.topic,
"😀"
);
let mut changes = StateChanges::default();
let redaction_json: &JsonValue = &test_json::TOPIC_REDACTION;
let redaction_evt = serde_json::from_value(redaction_json.clone()).expect("topic redaction event making works");
changes.add_redaction(room_id, redaction_evt);
store.save_changes(&changes).await?;
match store
.get_state_event_static::<RoomTopicEventContent>(room_id)
.await?
.expect("room topic found before redaction")
.deserialize_as::<OriginalRoomTopicEvent>()
{
Err(_) => { } // as expected
Ok(_) => panic!("Topic has not been redacted")
}
let _ = store
.get_state_event_static::<RoomTopicEventContent>(room_id)
.await?
.expect("room topic found after redaction")
.deserialize_as::<RedactedRoomTopicEvent>()
.expect("can deserialize room topic after redaction");
Ok(())
}
#[async_test] #[async_test]
async fn test_populate_store() -> StoreResult<()> { async fn test_populate_store() -> StoreResult<()> {
let room_id = room_id(); let room_id = room_id();

View File

@ -26,15 +26,8 @@ use dashmap::{DashMap, DashSet};
use lru::LruCache; use lru::LruCache;
#[allow(unused_imports)] #[allow(unused_imports)]
use matrix_sdk_common::{instant::Instant, locks::Mutex}; use matrix_sdk_common::{instant::Instant, locks::Mutex};
#[cfg(feature = "experimental-timeline")]
use ruma::{
canonical_json::redact_in_place,
events::{
room::redaction::SyncRoomRedactionEvent, AnySyncMessageLikeEvent, AnySyncTimelineEvent,
},
CanonicalJsonObject, RoomVersionId,
};
use ruma::{ use ruma::{
canonical_json::redact,
events::{ events::{
presence::PresenceEvent, presence::PresenceEvent,
receipt::{Receipt, ReceiptType}, receipt::{Receipt, ReceiptType},
@ -43,20 +36,28 @@ use ruma::{
AnySyncStateEvent, GlobalAccountDataEventType, RoomAccountDataEventType, StateEventType, AnySyncStateEvent, GlobalAccountDataEventType, RoomAccountDataEventType, StateEventType,
}, },
serde::Raw, serde::Raw,
EventId, MxcUri, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, UserId, CanonicalJsonObject, EventId, MxcUri, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId,
RoomVersionId, UserId,
}; };
use tracing::info; #[cfg(feature = "experimental-timeline")]
use ruma::{
canonical_json::redact_in_place,
events::{
room::redaction::SyncRoomRedactionEvent, AnySyncMessageLikeEvent, AnySyncTimelineEvent,
},
};
use tracing::{info, warn};
#[cfg(feature = "experimental-timeline")] #[cfg(feature = "experimental-timeline")]
use super::BoxStream; use super::BoxStream;
use super::{Result, RoomInfo, StateChanges, StateStore}; use super::{Result, RoomInfo, StateChanges, StateStore, StoreError};
#[cfg(feature = "experimental-timeline")]
use crate::deserialized_responses::SyncTimelineEvent;
use crate::{ use crate::{
deserialized_responses::MemberEvent, deserialized_responses::MemberEvent,
media::{MediaRequest, UniqueKey}, media::{MediaRequest, UniqueKey},
MinimalRoomMemberEvent, MinimalRoomMemberEvent,
}; };
#[cfg(feature = "experimental-timeline")]
use crate::{deserialized_responses::SyncTimelineEvent, StoreError};
/// In-Memory, non-persistent implementation of the `StateStore` /// In-Memory, non-persistent implementation of the `StateStore`
/// ///
@ -353,10 +354,18 @@ impl MemoryStore {
} }
} }
let make_room_version = |room_id| {
self.room_info
.get(room_id)
.and_then(|info| info.room_version().cloned())
.unwrap_or_else(|| {
warn!(%room_id, "Unable to find the room version, assuming version 9");
RoomVersionId::V9
})
};
#[cfg(feature = "experimental-timeline")] #[cfg(feature = "experimental-timeline")]
for (room_id, timeline) in &changes.timeline { for (room_id, timeline) in &changes.timeline {
use tracing::warn;
if timeline.sync { if timeline.sync {
info!(%room_id, "Saving new timeline batch from sync response"); info!(%room_id, "Saving new timeline batch from sync response");
} else { } else {
@ -407,16 +416,6 @@ impl MemoryStore {
..Default::default() ..Default::default()
}); });
let make_room_version = || {
self.room_info
.get(room_id)
.and_then(|info| info.room_version().cloned())
.unwrap_or_else(|| {
warn!(%room_id, "Unable to find the room version, assuming version 9");
RoomVersionId::V9
})
};
if timeline.sync { if timeline.sync {
let mut room_version = None; let mut room_version = None;
for event in &timeline.events { for event in &timeline.events {
@ -433,7 +432,8 @@ impl MemoryStore {
if let Some(mut full_event) = data.events.get_mut(&position.clone()) { if let Some(mut full_event) = data.events.get_mut(&position.clone()) {
let mut event_json: CanonicalJsonObject = let mut event_json: CanonicalJsonObject =
full_event.event.deserialize_as()?; full_event.event.deserialize_as()?;
let v = room_version.get_or_insert_with(make_room_version); let v =
room_version.get_or_insert_with(|| make_room_version(room_id));
redact_in_place(&mut event_json, v) redact_in_place(&mut event_json, v)
.map_err(StoreError::Redaction)?; .map_err(StoreError::Redaction)?;
@ -463,6 +463,27 @@ impl MemoryStore {
} }
} }
for (room_id, redactions) in &changes.redactions {
let mut room_version = None;
if let Some(room) = self.room_state.get_mut(room_id) {
for mut ref_room_mu in room.iter_mut() {
for mut ref_evt_mu in ref_room_mu.value_mut().iter_mut() {
let raw_evt = ref_evt_mu.value_mut();
if let Ok(Some(event_id)) = raw_evt.get_field::<OwnedEventId>("event_id") {
if redactions.get(&event_id).is_some() {
let redacted = redact(
&raw_evt.deserialize_as::<CanonicalJsonObject>()?,
room_version.get_or_insert_with(|| make_room_version(room_id)),
)
.map_err(StoreError::Redaction)?;
*raw_evt = Raw::new(&redacted)?.cast();
}
}
}
}
}
}
info!("Saved changes in {:?}", now.elapsed()); info!("Saved changes in {:?}", now.elapsed());
Ok(()) Ok(())

View File

@ -46,7 +46,10 @@ use ruma::{
events::{ events::{
presence::PresenceEvent, presence::PresenceEvent,
receipt::{Receipt, ReceiptEventContent, ReceiptType}, receipt::{Receipt, ReceiptEventContent, ReceiptType},
room::member::{StrippedRoomMemberEvent, SyncRoomMemberEvent}, room::{
member::{StrippedRoomMemberEvent, SyncRoomMemberEvent},
redaction::OriginalSyncRoomRedactionEvent,
},
AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnyStrippedStateEvent, AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnyStrippedStateEvent,
AnySyncStateEvent, EmptyStateKey, GlobalAccountDataEvent, GlobalAccountDataEventContent, AnySyncStateEvent, EmptyStateKey, GlobalAccountDataEvent, GlobalAccountDataEventContent,
GlobalAccountDataEventType, RedactContent, RedactedStateEventContent, RoomAccountDataEvent, GlobalAccountDataEventType, RedactContent, RedactedStateEventContent, RoomAccountDataEvent,
@ -686,6 +689,10 @@ pub struct StateChanges {
/// A map of `RoomId` to `ReceiptEventContent`. /// A map of `RoomId` to `ReceiptEventContent`.
pub receipts: BTreeMap<OwnedRoomId, ReceiptEventContent>, pub receipts: BTreeMap<OwnedRoomId, ReceiptEventContent>,
/// A map of `RoomId` to maps of `OwnedEventId` to be redacted by
/// `SyncRoomRedactionEvent`.
pub redactions: BTreeMap<OwnedRoomId, BTreeMap<OwnedEventId, OriginalSyncRoomRedactionEvent>>,
/// A mapping of `RoomId` to a map of event type to a map of state key to /// A mapping of `RoomId` to a map of event type to a map of state key to
/// `AnyStrippedStateEvent`. /// `AnyStrippedStateEvent`.
pub stripped_state: BTreeMap< pub stripped_state: BTreeMap<
@ -777,6 +784,14 @@ impl StateChanges {
.insert(event.state_key().to_owned(), raw_event); .insert(event.state_key().to_owned(), raw_event);
} }
/// Redact an event in the room
pub fn add_redaction(&mut self, room_id: &RoomId, redaction: OriginalSyncRoomRedactionEvent) {
self.redactions
.entry(room_id.to_owned())
.or_default()
.insert(redaction.redacts.clone(), redaction);
}
/// Update the `StateChanges` struct with the given room with a new /// Update the `StateChanges` struct with the given room with a new
/// `Notification`. /// `Notification`.
pub fn add_notification(&mut self, room_id: &RoomId, notification: Notification) { pub fn add_notification(&mut self, room_id: &RoomId, notification: Notification) {

View File

@ -36,15 +36,8 @@ use matrix_sdk_base::{
#[cfg(feature = "experimental-timeline")] #[cfg(feature = "experimental-timeline")]
use matrix_sdk_base::{deserialized_responses::SyncTimelineEvent, store::BoxStream}; use matrix_sdk_base::{deserialized_responses::SyncTimelineEvent, store::BoxStream};
use matrix_sdk_store_encryption::{Error as EncryptionError, StoreCipher}; use matrix_sdk_store_encryption::{Error as EncryptionError, StoreCipher};
#[cfg(feature = "experimental-timeline")]
use ruma::{
canonical_json::redact_in_place,
events::{
room::redaction::SyncRoomRedactionEvent, AnySyncMessageLikeEvent, AnySyncTimelineEvent,
},
CanonicalJsonObject, RoomVersionId,
};
use ruma::{ use ruma::{
canonical_json::redact,
events::{ events::{
presence::PresenceEvent, presence::PresenceEvent,
receipt::{Receipt, ReceiptType}, receipt::{Receipt, ReceiptType},
@ -53,11 +46,19 @@ use ruma::{
GlobalAccountDataEventType, RoomAccountDataEventType, StateEventType, GlobalAccountDataEventType, RoomAccountDataEventType, StateEventType,
}, },
serde::Raw, serde::Raw,
EventId, MxcUri, OwnedEventId, OwnedUserId, RoomId, UserId, CanonicalJsonObject, EventId, MxcUri, OwnedEventId, OwnedUserId, RoomId, RoomVersionId, UserId,
};
#[cfg(feature = "experimental-timeline")]
use ruma::{
canonical_json::redact_in_place,
events::{
room::redaction::SyncRoomRedactionEvent, AnySyncMessageLikeEvent, AnySyncTimelineEvent,
},
}; };
use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde::{de::DeserializeOwned, Deserialize, Serialize};
#[cfg(feature = "experimental-timeline")] #[cfg(feature = "experimental-timeline")]
use tracing::{info, warn}; use tracing::info;
use tracing::warn;
use wasm_bindgen::JsValue; use wasm_bindgen::JsValue;
use web_sys::IdbKeyRange; use web_sys::IdbKeyRange;
@ -582,6 +583,10 @@ impl IndexeddbStateStore {
stores.extend([KEYS::ROOM_STATE, KEYS::STRIPPED_ROOM_STATE]); stores.extend([KEYS::ROOM_STATE, KEYS::STRIPPED_ROOM_STATE]);
} }
if !changes.redactions.is_empty() {
stores.extend([KEYS::ROOM_STATE, KEYS::ROOM_INFOS]);
}
if !changes.room_infos.is_empty() || !changes.stripped_room_infos.is_empty() { if !changes.room_infos.is_empty() || !changes.stripped_room_infos.is_empty() {
stores.extend([KEYS::ROOM_INFOS, KEYS::STRIPPED_ROOM_INFOS]); stores.extend([KEYS::ROOM_INFOS, KEYS::STRIPPED_ROOM_INFOS]);
} }
@ -862,6 +867,53 @@ impl IndexeddbStateStore {
} }
} }
if !changes.redactions.is_empty() {
let state = tx.object_store(KEYS::ROOM_STATE)?;
let room_info = tx.object_store(KEYS::ROOM_INFOS)?;
for (room_id, redactions) in &changes.redactions {
let range = self.encode_to_range(KEYS::ROOM_STATE, room_id)?;
let cursor = match state.open_cursor_with_range(&range)?.await? {
Some(c) => c,
_ => continue,
};
let mut room_version = None;
while let Some(key) = cursor.key() {
let raw_evt =
self.deserialize_event::<Raw<AnySyncStateEvent>>(cursor.value())?;
if let Ok(Some(event_id)) = raw_evt.get_field::<OwnedEventId>("event_id") {
if redactions.contains_key(&event_id) {
let version = {
if room_version.is_none() {
room_version.replace(room_info
.get(&self.encode_key(KEYS::ROOM_INFOS, room_id))?
.await?
.and_then(|f| self.deserialize_event::<RoomInfo>(f).ok())
.and_then(|info| info.room_version().cloned())
.unwrap_or_else(|| {
warn!(%room_id, "Unable to find the room version, assume version 9");
RoomVersionId::V9
})
);
}
room_version.as_ref().unwrap()
};
let redacted =
redact(&raw_evt.deserialize_as::<CanonicalJsonObject>()?, version)
.map_err(StoreError::Redaction)?;
state.put_key_val(&key, &self.serialize_event(&redacted)?)?;
}
}
// move forward.
cursor.advance(1)?.await?;
}
}
}
#[cfg(feature = "experimental-timeline")] #[cfg(feature = "experimental-timeline")]
if !changes.timeline.is_empty() { if !changes.timeline.is_empty() {
let timeline_store = tx.object_store(KEYS::ROOM_TIMELINE)?; let timeline_store = tx.object_store(KEYS::ROOM_TIMELINE)?;

View File

@ -34,15 +34,8 @@ use matrix_sdk_base::{
#[cfg(feature = "experimental-timeline")] #[cfg(feature = "experimental-timeline")]
use matrix_sdk_base::{deserialized_responses::SyncTimelineEvent, store::BoxStream}; use matrix_sdk_base::{deserialized_responses::SyncTimelineEvent, store::BoxStream};
use matrix_sdk_store_encryption::{Error as KeyEncryptionError, StoreCipher}; use matrix_sdk_store_encryption::{Error as KeyEncryptionError, StoreCipher};
#[cfg(feature = "experimental-timeline")]
use ruma::{
canonical_json::redact_in_place,
events::{
room::redaction::SyncRoomRedactionEvent, AnySyncMessageLikeEvent, AnySyncTimelineEvent,
},
CanonicalJsonObject, RoomVersionId,
};
use ruma::{ use ruma::{
canonical_json::redact,
events::{ events::{
presence::PresenceEvent, presence::PresenceEvent,
receipt::{Receipt, ReceiptType}, receipt::{Receipt, ReceiptType},
@ -51,7 +44,15 @@ use ruma::{
GlobalAccountDataEventType, RoomAccountDataEventType, StateEventType, GlobalAccountDataEventType, RoomAccountDataEventType, StateEventType,
}, },
serde::Raw, serde::Raw,
EventId, IdParseError, MxcUri, OwnedEventId, OwnedUserId, RoomId, UserId, CanonicalJsonObject, EventId, IdParseError, MxcUri, OwnedEventId, OwnedUserId, RoomId,
RoomVersionId, UserId,
};
#[cfg(feature = "experimental-timeline")]
use ruma::{
canonical_json::redact_in_place,
events::{
room::redaction::SyncRoomRedactionEvent, AnySyncMessageLikeEvent, AnySyncTimelineEvent,
},
}; };
#[cfg(feature = "experimental-timeline")] #[cfg(feature = "experimental-timeline")]
use serde::Deserialize; use serde::Deserialize;
@ -61,7 +62,7 @@ use sled::{
Config, Db, Transactional, Tree, Config, Db, Transactional, Tree,
}; };
use tokio::task::spawn_blocking; use tokio::task::spawn_blocking;
use tracing::{debug, info}; use tracing::{debug, info, warn};
#[cfg(feature = "crypto-store")] #[cfg(feature = "crypto-store")]
use super::OpenStoreError; use super::OpenStoreError;
@ -773,6 +774,54 @@ impl SledStateStore {
ret?; ret?;
if !changes.redactions.is_empty() {
let mut redactions_found = false;
let mut redactions_batch = sled::Batch::default();
let make_room_version = |room_id| {
self.room_info
.get(&self.encode_key(ROOM_INFO, room_id))
.ok()
.flatten()
.map(|r| self.deserialize_value::<RoomInfo>(&r))
.transpose()
.ok()
.flatten()
.and_then(|info| info.room_version().cloned())
.unwrap_or_else(|| {
warn!(%room_id, "Unable to find the room version, assume version 9");
RoomVersionId::V9
})
};
for (room_id, redactions) in &changes.redactions {
let key_prefix = self.encode_key(ROOM_STATE, room_id);
let mut room_version = None;
// iterate through all saved state events and check whether they are among the
// redacted, if so apply redaction and save that to the batch
// applied all at once after.
for (key, evt) in self.room_state.scan_prefix(key_prefix).filter_map(|r| r.ok()) {
let raw_evt = self.deserialize_value::<Raw<AnySyncStateEvent>>(&evt)?;
if let Ok(Some(event_id)) = raw_evt.get_field::<OwnedEventId>("event_id") {
if redactions.contains_key(&event_id) {
let redacted = redact(
&raw_evt.deserialize_as::<CanonicalJsonObject>()?,
room_version.get_or_insert_with(|| make_room_version(room_id)),
)
.map_err(StoreError::Redaction)?;
redactions_found = true;
redactions_batch.insert(key, self.serialize_value(&redacted)?);
}
}
}
}
if redactions_found {
self.room_state.apply_batch(redactions_batch)?;
}
}
let ret: Result<(), TransactionError<SledStoreError>> = let ret: Result<(), TransactionError<SledStoreError>> =
(&self.room_user_receipts, &self.room_event_receipts, &self.presence).transaction( (&self.room_user_receipts, &self.room_event_receipts, &self.presence).transaction(
|(room_user_receipts, room_event_receipts, presence)| { |(room_user_receipts, room_event_receipts, presence)| {
@ -1414,8 +1463,6 @@ impl SledStateStore {
#[cfg(feature = "experimental-timeline")] #[cfg(feature = "experimental-timeline")]
async fn save_room_timeline(&self, changes: &StateChanges) -> Result<()> { async fn save_room_timeline(&self, changes: &StateChanges) -> Result<()> {
use tracing::warn;
let mut timeline_batch = sled::Batch::default(); let mut timeline_batch = sled::Batch::default();
let mut event_id_to_position_batch = sled::Batch::default(); let mut event_id_to_position_batch = sled::Batch::default();
let mut timeline_metadata_batch = sled::Batch::default(); let mut timeline_metadata_batch = sled::Batch::default();

View File

@ -1862,6 +1862,7 @@ impl Client {
res res
} }
#[tracing::instrument(skip(self))]
async fn send_inner<Request>( async fn send_inner<Request>(
&self, &self,
request: Request, request: Request,

View File

@ -13,4 +13,5 @@ matrix-sdk = { path = "../../crates/matrix-sdk" }
once_cell = "1.13.0" once_cell = "1.13.0"
tempfile = "3.3.0" tempfile = "3.3.0"
tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] } tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] }
tracing = "0.1.36"
tracing-subscriber = { version = "0.3.15", features = ["env-filter"] } tracing-subscriber = { version = "0.3.15", features = ["env-filter"] }

View File

@ -0,0 +1,68 @@
use std::{collections::HashMap, option_env};
use anyhow::Result;
use assign::assign;
use matrix_sdk::{
ruma::api::client::{account::register::v3::Request as RegistrationRequest, uiaa},
store::make_store_config,
Client,
};
use once_cell::sync::Lazy;
use tempfile::{tempdir, TempDir};
use tokio::sync::Mutex;
static USERS: Lazy<Mutex<HashMap<String, (Client, TempDir)>>> = Lazy::new(Mutex::default);
#[ctor::ctor]
fn init_logging() {
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
tracing_subscriber::registry()
.with(tracing_subscriber::EnvFilter::from_default_env())
.with(tracing_subscriber::fmt::layer().with_test_writer())
.init();
}
/// read the test configuration from the environment
pub fn test_server_conf() -> (String, String) {
(
option_env!("HOMESERVER_URL").unwrap_or("http://localhost:8228").to_owned(),
option_env!("HOMESERVER_DOMAIN").unwrap_or("matrix-sdk.rs").to_owned(),
)
}
pub async fn get_client_for_user(username: String) -> Result<Client> {
let mut users = USERS.lock().await;
if let Some((client, _)) = users.get(&username) {
return Ok(client.clone());
}
let (homeserver_url, _domain_name) = test_server_conf();
let tmp_dir = tempdir()?;
let client = Client::builder()
.user_agent("matrix-sdk-integation-tests")
.store_config(make_store_config(tmp_dir.path(), None)?)
.homeserver_url(homeserver_url)
.build()
.await?;
// safe to assume we have not registered this user yet, but ignore if we did
if let Err(resp) = client.register(RegistrationRequest::new()).await {
// FIXME: do actually check the registration types...
if let Some(_response) = resp.uiaa_response() {
let request = assign!(RegistrationRequest::new(), {
username: Some(username.as_ref()),
password: Some(username.as_ref()),
auth: Some(uiaa::AuthData::Dummy(uiaa::Dummy::new())),
});
// we don't care if this failed, then we just try to login anyways
let _ = client.register(request).await;
}
}
client.login_username(&username, &username).send().await?;
users.insert(username, (client.clone(), tmp_dir)); // keeping temp dir around so it doesn't get destroyed yet
Ok(client)
}

View File

@ -1,2 +1,5 @@
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
#[cfg(test)]
mod helpers;

View File

@ -1,70 +1,2 @@
use std::{collections::HashMap, option_env};
use anyhow::Result;
use assign::assign;
use matrix_sdk::{
ruma::api::client::{account::register::v3::Request as RegistrationRequest, uiaa},
store::make_store_config,
Client,
};
use once_cell::sync::Lazy;
use tempfile::{tempdir, TempDir};
use tokio::sync::Mutex;
static USERS: Lazy<Mutex<HashMap<String, (Client, TempDir)>>> = Lazy::new(Mutex::default);
#[ctor::ctor]
fn init_logging() {
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
tracing_subscriber::registry()
.with(tracing_subscriber::EnvFilter::from_default_env())
.with(tracing_subscriber::fmt::layer().with_test_writer())
.init();
}
/// read the test configuration from the environment
pub fn test_server_conf() -> (String, String) {
(
option_env!("HOMESERVER_URL").unwrap_or("http://localhost:8228").to_owned(),
option_env!("HOMESERVER_DOMAIN").unwrap_or("matrix-sdk.rs").to_owned(),
)
}
pub async fn get_client_for_user(username: String) -> Result<Client> {
let mut users = USERS.lock().await;
if let Some((client, _)) = users.get(&username) {
return Ok(client.clone());
}
let (homeserver_url, _domain_name) = test_server_conf();
let tmp_dir = tempdir()?;
let client = Client::builder()
.user_agent("matrix-sdk-integation-tests")
.store_config(make_store_config(tmp_dir.path(), None)?)
.homeserver_url(homeserver_url)
.build()
.await?;
// safe to assume we have not registered this user yet, but ignore if we did
if let Err(resp) = client.register(RegistrationRequest::new()).await {
// FIXME: do actually check the registration types...
if let Some(_response) = resp.uiaa_response() {
let request = assign!(RegistrationRequest::new(), {
username: Some(username.as_ref()),
password: Some(username.as_ref()),
auth: Some(uiaa::AuthData::Dummy(uiaa::Dummy::new())),
});
// we don't care if this failed, then we just try to login anyways
let _ = client.register(request).await;
}
}
client.login_username(&username, &username).send().await?;
users.insert(username, (client.clone(), tmp_dir)); // keeping temp dir around so it doesn't get destroyed yet
Ok(client)
}
mod invitations; mod invitations;
mod redaction;

View File

@ -4,7 +4,7 @@ use matrix_sdk::{
room::Room, ruma::api::client::room::create_room::v3::Request as CreateRoomRequest, room::Room, ruma::api::client::room::create_room::v3::Request as CreateRoomRequest,
}; };
use super::get_client_for_user; use crate::helpers::get_client_for_user;
#[tokio::test(flavor = "multi_thread", worker_threads = 4)] #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn test_invitation_details() -> Result<()> { async fn test_invitation_details() -> Result<()> {

View File

@ -0,0 +1,157 @@
use anyhow::Result;
use assign::assign;
use matrix_sdk::{
config::SyncSettings,
ruma::{
api::client::room::create_room::v3::Request as CreateRoomRequest,
events::{
room::name::{RoomNameEventContent, SyncRoomNameEvent},
StateEventType,
},
},
Client,
};
use crate::helpers::get_client_for_user;
async fn sync_once(client: &Client) -> Result<()> {
let settings = match client.sync_token().await {
Some(token) => SyncSettings::default().token(token),
None => SyncSettings::default(),
};
client.sync_once(settings).await?;
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn test_redacting_name() -> Result<()> {
let tamatoa = get_client_for_user("tamatoa".to_owned()).await?;
// create a room
let request = assign!(CreateRoomRequest::new(), {
is_direct: true,
});
let room_id = tamatoa.create_room(request).await?.room_id;
for _ in 0..=10 {
sync_once(&tamatoa).await?;
if tamatoa.get_joined_room(&room_id).is_some() {
break;
}
}
let room = tamatoa.get_joined_room(&room_id).unwrap();
// let's send a specific state event
let content = RoomNameEventContent::new(Some("Inappropriate text".to_owned()));
room.send_state_event(content).await?;
// sync up.
for _ in 0..=10 {
// we call sync up to ten times to give the server time to flush other
// messages over and send us the new state event
sync_once(&tamatoa).await?;
if room.name().is_some() {
break;
}
}
assert_eq!(room.name(), Some("Inappropriate text".to_owned()));
// check state event.
let raw_event =
room.get_state_event(StateEventType::RoomName, "").await?.expect("Room Name not found");
let room_name_event: SyncRoomNameEvent = raw_event.deserialize_as()?;
assert!(
room_name_event.as_original().expect("event exists").content.name.is_some(),
"Event not found"
);
room.redact(room_name_event.event_id(), None, None).await?;
// sync up.
for _ in 0..=10 {
// we call sync up to ten times to give the server time to flush other
// messages over and send us the new state ev
sync_once(&tamatoa).await?;
if room.name().is_none() {
break;
}
}
let raw_event =
room.get_state_event(StateEventType::RoomName, "").await?.expect("Room Name not found");
let room_name_event: SyncRoomNameEvent = raw_event.deserialize_as()?;
// Name content has been redacted
assert!(
room_name_event.as_original().expect("event exists").content.name.is_none(),
"Event hasn't been redacted"
);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn test_redacting_name_static() -> Result<()> {
let tamatoa = get_client_for_user("tamatoa".to_owned()).await?;
// create a room
let request = assign!(CreateRoomRequest::new(), {
is_direct: true,
});
let room_id = tamatoa.create_room(request).await?.room_id;
for _ in 0..=10 {
sync_once(&tamatoa).await?;
if tamatoa.get_joined_room(&room_id).is_some() {
break;
}
}
let room = tamatoa.get_joined_room(&room_id).unwrap();
// let's send a specific state event
let content = RoomNameEventContent::new(Some("Inappropriate text".to_owned()));
room.send_state_event(content).await?;
// sync up.
for _ in 0..=10 {
// we call sync up to ten times to give the server time to flush other
// messages over and send us the new state event
sync_once(&tamatoa).await?;
if room.name().is_some() {
break;
}
}
// check state event.
let room_name_event: SyncRoomNameEvent =
room.get_state_event_static().await?.expect("Room Name not found").deserialize()?;
assert!(
room_name_event.as_original().expect("event exists").content.name.is_some(),
"Event not found"
);
room.redact(room_name_event.event_id(), None, None).await?;
// we sync up.
for _ in 0..=10 {
// we call sync up to ten times to give the server time to flush other
// messages over and send us the new state ev
sync_once(&tamatoa).await?;
if room.name().is_none() {
break;
}
}
let room_name_event: SyncRoomNameEvent =
room.get_state_event_static().await?.expect("Room Name not found").deserialize()?;
// Name content has been redacted
assert!(
room_name_event.as_original().expect("event exists").content.name.is_none(),
"Event hasn't been redacted"
);
Ok(())
}

View File

@ -29,7 +29,7 @@ pub use sync_events::{
ALIAS, ALIASES, ENCRYPTION, MEMBER, MEMBER_INVITE, MEMBER_NAME_CHANGE, MEMBER_STRIPPED, ALIAS, ALIASES, ENCRYPTION, MEMBER, MEMBER_INVITE, MEMBER_NAME_CHANGE, MEMBER_STRIPPED,
MESSAGE_EDIT, MESSAGE_TEXT, NAME, NAME_STRIPPED, POWER_LEVELS, PRESENCE, PUSH_RULES, REACTION, MESSAGE_EDIT, MESSAGE_TEXT, NAME, NAME_STRIPPED, POWER_LEVELS, PRESENCE, PUSH_RULES, REACTION,
READ_RECEIPT, READ_RECEIPT_OTHER, REDACTED, REDACTED_INVALID, REDACTED_STATE, REDACTION, TAG, READ_RECEIPT, READ_RECEIPT_OTHER, REDACTED, REDACTED_INVALID, REDACTED_STATE, REDACTION, TAG,
TOPIC, TYPING, TOPIC, TOPIC_REDACTION, TYPING,
}; };
/// An empty response. /// An empty response.

View File

@ -739,6 +739,7 @@ pub static TOPIC: Lazy<JsonValue> = Lazy::new(|| {
"origin_server_ts": 151957878, "origin_server_ts": 151957878,
"sender": "@example:localhost", "sender": "@example:localhost",
"state_key": "", "state_key": "",
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"type": "m.room.topic", "type": "m.room.topic",
"prev_content": { "prev_content": {
"topic": "test" "topic": "test"
@ -763,3 +764,18 @@ pub static TYPING: Lazy<JsonValue> = Lazy::new(|| {
"type": "m.typing" "type": "m.typing"
}) })
}); });
pub static TOPIC_REDACTION: Lazy<JsonValue> = Lazy::new(|| {
json!({
"content": {},
"redacts": "$151957878228ssqrJ:localhost",
"event_id": "$151957878228ssqrJ_REDACTION:localhost",
"origin_server_ts": 151957879,
"sender": "@example:localhost",
"type": "m.room.redaction",
"unsigned": {
"age": 1392990,
"prev_sender": "@example:localhost",
}
})
});