fix: Apply redactions to room state events in database, too (#917)
fixes #890
This commit is contained in:
parent
f8b02d1a11
commit
b5bd6dfee9
|
@ -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 }
|
|
@ -441,12 +441,11 @@ jobs:
|
|||
with:
|
||||
python-version: 3.8
|
||||
|
||||
- uses: gnunicorn/setup-matrix-synapse@main
|
||||
- uses: michaelkaye/setup-matrix-synapse@main
|
||||
with:
|
||||
uploadLogs: true
|
||||
httpPort: 8228
|
||||
disableRateLimiting: true
|
||||
serverName: "matrix-sdk.rs"
|
||||
|
||||
- name: Test
|
||||
uses: actions-rs/cargo@v1
|
||||
|
|
|
@ -2573,6 +2573,7 @@ dependencies = [
|
|||
"once_cell",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
|
|
|
@ -327,10 +327,7 @@ impl BaseClient {
|
|||
),
|
||||
) => {
|
||||
room_info.handle_redaction(r);
|
||||
// FIXME: Find the event in self.store (needs
|
||||
// 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
|
||||
changes.add_redaction(room_id, r.clone());
|
||||
}
|
||||
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
|
|
|
@ -59,6 +59,7 @@ macro_rules! statestore_integration_tests {
|
|||
},
|
||||
power_levels::RoomPowerLevelsEventContent,
|
||||
MediaSource,
|
||||
topic::{RoomTopicEventContent, OriginalRoomTopicEvent, RedactedRoomTopicEvent},
|
||||
},
|
||||
AnyEphemeralRoomEventContent, AnyGlobalAccountDataEvent,
|
||||
AnyRoomAccountDataEvent, AnyStrippedStateEvent, AnySyncEphemeralRoomEvent,
|
||||
|
@ -78,7 +79,7 @@ macro_rules! statestore_integration_tests {
|
|||
};
|
||||
use $crate::{
|
||||
media::{MediaFormat, MediaRequest, MediaThumbnailSize},
|
||||
store::{Result as StoreResult, StateChanges, StateStore},
|
||||
store::{Result as StoreResult, StateChanges, StateStore, StateStoreExt},
|
||||
RoomInfo, RoomType,
|
||||
};
|
||||
|
||||
|
@ -147,8 +148,8 @@ macro_rules! statestore_integration_tests {
|
|||
|
||||
let topic_json: &JsonValue = &test_json::TOPIC;
|
||||
let topic_raw =
|
||||
serde_json::from_value::<Raw<AnySyncStateEvent>>(topic_json.clone()).unwrap();
|
||||
let topic_event = topic_raw.deserialize().unwrap();
|
||||
serde_json::from_value::<Raw<AnySyncStateEvent>>(topic_json.clone()).expect("can create sync-state-event for topic");
|
||||
let topic_event = topic_raw.deserialize().expect("can deserialize raw topic");
|
||||
room.handle_state_event(&topic_event);
|
||||
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 fn test_populate_store() -> StoreResult<()> {
|
||||
let room_id = room_id();
|
||||
|
|
|
@ -26,15 +26,8 @@ use dashmap::{DashMap, DashSet};
|
|||
use lru::LruCache;
|
||||
#[allow(unused_imports)]
|
||||
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::{
|
||||
canonical_json::redact,
|
||||
events::{
|
||||
presence::PresenceEvent,
|
||||
receipt::{Receipt, ReceiptType},
|
||||
|
@ -43,20 +36,28 @@ use ruma::{
|
|||
AnySyncStateEvent, GlobalAccountDataEventType, RoomAccountDataEventType, StateEventType,
|
||||
},
|
||||
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")]
|
||||
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::{
|
||||
deserialized_responses::MemberEvent,
|
||||
media::{MediaRequest, UniqueKey},
|
||||
MinimalRoomMemberEvent,
|
||||
};
|
||||
#[cfg(feature = "experimental-timeline")]
|
||||
use crate::{deserialized_responses::SyncTimelineEvent, StoreError};
|
||||
|
||||
/// 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")]
|
||||
for (room_id, timeline) in &changes.timeline {
|
||||
use tracing::warn;
|
||||
|
||||
if timeline.sync {
|
||||
info!(%room_id, "Saving new timeline batch from sync response");
|
||||
} else {
|
||||
|
@ -407,16 +416,6 @@ impl MemoryStore {
|
|||
..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 {
|
||||
let mut room_version = None;
|
||||
for event in &timeline.events {
|
||||
|
@ -433,7 +432,8 @@ impl MemoryStore {
|
|||
if let Some(mut full_event) = data.events.get_mut(&position.clone()) {
|
||||
let mut event_json: CanonicalJsonObject =
|
||||
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)
|
||||
.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());
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -46,7 +46,10 @@ use ruma::{
|
|||
events::{
|
||||
presence::PresenceEvent,
|
||||
receipt::{Receipt, ReceiptEventContent, ReceiptType},
|
||||
room::member::{StrippedRoomMemberEvent, SyncRoomMemberEvent},
|
||||
room::{
|
||||
member::{StrippedRoomMemberEvent, SyncRoomMemberEvent},
|
||||
redaction::OriginalSyncRoomRedactionEvent,
|
||||
},
|
||||
AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnyStrippedStateEvent,
|
||||
AnySyncStateEvent, EmptyStateKey, GlobalAccountDataEvent, GlobalAccountDataEventContent,
|
||||
GlobalAccountDataEventType, RedactContent, RedactedStateEventContent, RoomAccountDataEvent,
|
||||
|
@ -686,6 +689,10 @@ pub struct StateChanges {
|
|||
/// A map of `RoomId` to `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
|
||||
/// `AnyStrippedStateEvent`.
|
||||
pub stripped_state: BTreeMap<
|
||||
|
@ -777,6 +784,14 @@ impl StateChanges {
|
|||
.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
|
||||
/// `Notification`.
|
||||
pub fn add_notification(&mut self, room_id: &RoomId, notification: Notification) {
|
||||
|
|
|
@ -36,15 +36,8 @@ use matrix_sdk_base::{
|
|||
#[cfg(feature = "experimental-timeline")]
|
||||
use matrix_sdk_base::{deserialized_responses::SyncTimelineEvent, store::BoxStream};
|
||||
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::{
|
||||
canonical_json::redact,
|
||||
events::{
|
||||
presence::PresenceEvent,
|
||||
receipt::{Receipt, ReceiptType},
|
||||
|
@ -53,11 +46,19 @@ use ruma::{
|
|||
GlobalAccountDataEventType, RoomAccountDataEventType, StateEventType,
|
||||
},
|
||||
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};
|
||||
#[cfg(feature = "experimental-timeline")]
|
||||
use tracing::{info, warn};
|
||||
use tracing::info;
|
||||
use tracing::warn;
|
||||
use wasm_bindgen::JsValue;
|
||||
use web_sys::IdbKeyRange;
|
||||
|
||||
|
@ -582,6 +583,10 @@ impl IndexeddbStateStore {
|
|||
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() {
|
||||
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")]
|
||||
if !changes.timeline.is_empty() {
|
||||
let timeline_store = tx.object_store(KEYS::ROOM_TIMELINE)?;
|
||||
|
|
|
@ -34,15 +34,8 @@ use matrix_sdk_base::{
|
|||
#[cfg(feature = "experimental-timeline")]
|
||||
use matrix_sdk_base::{deserialized_responses::SyncTimelineEvent, store::BoxStream};
|
||||
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::{
|
||||
canonical_json::redact,
|
||||
events::{
|
||||
presence::PresenceEvent,
|
||||
receipt::{Receipt, ReceiptType},
|
||||
|
@ -51,7 +44,15 @@ use ruma::{
|
|||
GlobalAccountDataEventType, RoomAccountDataEventType, StateEventType,
|
||||
},
|
||||
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")]
|
||||
use serde::Deserialize;
|
||||
|
@ -61,7 +62,7 @@ use sled::{
|
|||
Config, Db, Transactional, Tree,
|
||||
};
|
||||
use tokio::task::spawn_blocking;
|
||||
use tracing::{debug, info};
|
||||
use tracing::{debug, info, warn};
|
||||
|
||||
#[cfg(feature = "crypto-store")]
|
||||
use super::OpenStoreError;
|
||||
|
@ -773,6 +774,54 @@ impl SledStateStore {
|
|||
|
||||
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>> =
|
||||
(&self.room_user_receipts, &self.room_event_receipts, &self.presence).transaction(
|
||||
|(room_user_receipts, room_event_receipts, presence)| {
|
||||
|
@ -1414,8 +1463,6 @@ impl SledStateStore {
|
|||
|
||||
#[cfg(feature = "experimental-timeline")]
|
||||
async fn save_room_timeline(&self, changes: &StateChanges) -> Result<()> {
|
||||
use tracing::warn;
|
||||
|
||||
let mut timeline_batch = sled::Batch::default();
|
||||
let mut event_id_to_position_batch = sled::Batch::default();
|
||||
let mut timeline_metadata_batch = sled::Batch::default();
|
||||
|
|
|
@ -1862,6 +1862,7 @@ impl Client {
|
|||
res
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn send_inner<Request>(
|
||||
&self,
|
||||
request: Request,
|
||||
|
|
|
@ -13,4 +13,5 @@ matrix-sdk = { path = "../../crates/matrix-sdk" }
|
|||
once_cell = "1.13.0"
|
||||
tempfile = "3.3.0"
|
||||
tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] }
|
||||
tracing = "0.1.36"
|
||||
tracing-subscriber = { version = "0.3.15", features = ["env-filter"] }
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -1,2 +1,5 @@
|
|||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
#[cfg(test)]
|
||||
mod helpers;
|
||||
|
|
|
@ -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 redaction;
|
||||
|
|
|
@ -4,7 +4,7 @@ use matrix_sdk::{
|
|||
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)]
|
||||
async fn test_invitation_details() -> Result<()> {
|
||||
|
|
|
@ -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(())
|
||||
}
|
|
@ -29,7 +29,7 @@ pub use sync_events::{
|
|||
ALIAS, ALIASES, ENCRYPTION, MEMBER, MEMBER_INVITE, MEMBER_NAME_CHANGE, MEMBER_STRIPPED,
|
||||
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,
|
||||
TOPIC, TYPING,
|
||||
TOPIC, TOPIC_REDACTION, TYPING,
|
||||
};
|
||||
|
||||
/// An empty response.
|
||||
|
|
|
@ -739,6 +739,7 @@ pub static TOPIC: Lazy<JsonValue> = Lazy::new(|| {
|
|||
"origin_server_ts": 151957878,
|
||||
"sender": "@example:localhost",
|
||||
"state_key": "",
|
||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||
"type": "m.room.topic",
|
||||
"prev_content": {
|
||||
"topic": "test"
|
||||
|
@ -763,3 +764,18 @@ pub static TYPING: Lazy<JsonValue> = Lazy::new(|| {
|
|||
"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",
|
||||
}
|
||||
})
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue