mirror of
https://github.com/matrix-org/matrix-rust-sdk.git
synced 2026-05-16 20:49:05 -04:00
Merge branch 'main' into jplatte/server-versions
This commit is contained in:
26
.github/workflows/ci.yml
vendored
26
.github/workflows/ci.yml
vendored
@@ -117,6 +117,32 @@ jobs:
|
||||
command: run
|
||||
args: -p xtask -- ci test-features ${{ matrix.name }}
|
||||
|
||||
test-crypto-features:
|
||||
name: linux / crypto-crate features
|
||||
needs: [style]
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
|
||||
steps:
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v1
|
||||
|
||||
- name: Clippy
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: run
|
||||
args: -p xtask -- ci test-crypto
|
||||
|
||||
test:
|
||||
name: ${{ matrix.name }}
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
|
||||
@@ -90,10 +90,9 @@ pub use matrix_sdk;
|
||||
pub use matrix_sdk::ruma;
|
||||
use matrix_sdk::{
|
||||
bytes::Bytes,
|
||||
config::ClientConfig,
|
||||
event_handler::{EventHandler, EventHandlerResult, SyncEvent},
|
||||
reqwest::Url,
|
||||
Client, Session,
|
||||
Client, ClientBuildError, ClientBuilder, Session,
|
||||
};
|
||||
use regex::Regex;
|
||||
use ruma::{
|
||||
@@ -210,8 +209,8 @@ impl AppService {
|
||||
/// Create new AppService
|
||||
///
|
||||
/// Also creates and caches a [`Client`] for the [`MainUser`].
|
||||
/// The default [`ClientConfig`] is used, if you want to customize it
|
||||
/// use [`Self::new_with_config()`] instead.
|
||||
/// A default [`ClientBuilder`] is used, if you want to customize it
|
||||
/// use [`with_client_builder()`][Self::with_client_builder] instead.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
@@ -227,24 +226,20 @@ impl AppService {
|
||||
server_name: impl TryInto<Box<ServerName>, Error = identifiers::Error>,
|
||||
registration: AppServiceRegistration,
|
||||
) -> Result<Self> {
|
||||
let appservice = Self::new_with_config(
|
||||
homeserver_url,
|
||||
server_name,
|
||||
registration,
|
||||
ClientConfig::default(),
|
||||
)
|
||||
.await?;
|
||||
let appservice =
|
||||
Self::with_client_builder(homeserver_url, server_name, registration, Client::builder())
|
||||
.await?;
|
||||
|
||||
Ok(appservice)
|
||||
}
|
||||
|
||||
/// Same as [`Self::new()`] but lets you provide a [`ClientConfig`] for the
|
||||
/// [`Client`]
|
||||
pub async fn new_with_config(
|
||||
/// Same as [`new()`][Self::new] but lets you provide a [`ClientBuilder`]
|
||||
/// for the [`Client`]
|
||||
pub async fn with_client_builder(
|
||||
homeserver_url: impl TryInto<Url, Error = url::ParseError>,
|
||||
server_name: impl TryInto<Box<ServerName>, Error = identifiers::Error>,
|
||||
registration: AppServiceRegistration,
|
||||
client_config: ClientConfig,
|
||||
builder: ClientBuilder,
|
||||
) -> Result<Self> {
|
||||
let homeserver_url = homeserver_url.try_into()?;
|
||||
let server_name = server_name.try_into()?;
|
||||
@@ -257,7 +252,7 @@ impl AppService {
|
||||
AppService { homeserver_url, server_name, registration, clients, event_handler };
|
||||
|
||||
// we create and cache the [`MainUser`] by default
|
||||
appservice.create_and_cache_client(&sender_localpart, client_config).await?;
|
||||
appservice.create_and_cache_client(&sender_localpart, builder).await?;
|
||||
|
||||
Ok(appservice)
|
||||
}
|
||||
@@ -270,8 +265,9 @@ impl AppService {
|
||||
///
|
||||
/// This method is a singleton that saves the client internally for re-use
|
||||
/// based on the `localpart`. The cached [`Client`] can be retrieved either
|
||||
/// by calling this method again or by calling [`Self::get_cached_client()`]
|
||||
/// which is non-async convenience wrapper.
|
||||
/// by calling this method again or by calling
|
||||
/// [`get_cached_client()`][Self::get_cached_client] which is non-async
|
||||
/// convenience wrapper.
|
||||
///
|
||||
/// Note that if you want to do actions like joining rooms with a virtual
|
||||
/// user it needs to be registered first. `Self::register_virtual_user()`
|
||||
@@ -285,20 +281,20 @@ impl AppService {
|
||||
/// [assert the identity]: https://matrix.org/docs/spec/application_service/r0.1.2#identity-assertion
|
||||
pub async fn virtual_user_client(&self, localpart: impl AsRef<str>) -> Result<Client> {
|
||||
let client =
|
||||
self.virtual_user_client_with_config(localpart, ClientConfig::default()).await?;
|
||||
self.virtual_user_client_with_client_builder(localpart, Client::builder()).await?;
|
||||
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
/// Same as [`Self::virtual_user_client()`] but with the ability to pass in
|
||||
/// a [`ClientConfig`]
|
||||
/// Same as [`virtual_user_client()`][Self::virtual_user_client] but with
|
||||
/// the ability to pass in a [`ClientBuilder`]
|
||||
///
|
||||
/// Since this method is a singleton follow-up calls with different
|
||||
/// [`ClientConfig`]s will be ignored.
|
||||
pub async fn virtual_user_client_with_config(
|
||||
/// [`ClientBuilder`]s will be ignored.
|
||||
pub async fn virtual_user_client_with_client_builder(
|
||||
&self,
|
||||
localpart: impl AsRef<str>,
|
||||
config: ClientConfig,
|
||||
builder: ClientBuilder,
|
||||
) -> Result<Client> {
|
||||
// TODO: check if localpart is covered by namespace?
|
||||
let localpart = localpart.as_ref();
|
||||
@@ -306,7 +302,7 @@ impl AppService {
|
||||
let client = if let Some(client) = self.clients.get(localpart) {
|
||||
client.clone()
|
||||
} else {
|
||||
self.create_and_cache_client(localpart, config).await?
|
||||
self.create_and_cache_client(localpart, builder).await?
|
||||
};
|
||||
|
||||
Ok(client)
|
||||
@@ -315,22 +311,23 @@ impl AppService {
|
||||
async fn create_and_cache_client(
|
||||
&self,
|
||||
localpart: &str,
|
||||
config: ClientConfig,
|
||||
mut builder: ClientBuilder,
|
||||
) -> Result<Client> {
|
||||
let user_id = UserId::parse_with_server_name(localpart, &self.server_name)?;
|
||||
|
||||
// The `as_token` in the `Session` maps to the [`MainUser`]
|
||||
// (`sender_localpart`) by default, so we don't need to assert identity
|
||||
// in that case
|
||||
let config = if localpart != self.registration.sender_localpart {
|
||||
let request_config = config.get_request_config().assert_identity();
|
||||
config.request_config(request_config)
|
||||
} else {
|
||||
config
|
||||
};
|
||||
if localpart != self.registration.sender_localpart {
|
||||
builder = builder.assert_identity();
|
||||
}
|
||||
|
||||
let client =
|
||||
Client::new_with_config(self.homeserver_url.clone(), config.appservice_mode()).await?;
|
||||
let client = builder
|
||||
.homeserver_url(self.homeserver_url.clone())
|
||||
.appservice_mode()
|
||||
.build()
|
||||
.await
|
||||
.map_err(ClientBuildError::assert_valid_builder_args)?;
|
||||
|
||||
let session = Session {
|
||||
access_token: self.registration.as_token.clone(),
|
||||
@@ -348,8 +345,9 @@ impl AppService {
|
||||
/// Get cached [`Client`]
|
||||
///
|
||||
/// Will return the client for the given `localpart` if previously
|
||||
/// constructed with [`Self::virtual_user_client()`] or
|
||||
/// [`Self::virtual_user_client_with_config()`].
|
||||
/// constructed with [`virtual_user_client()`][Self::virtual_user_client] or
|
||||
/// [`virtual_user_client_with_config()`][Self::
|
||||
/// virtual_user_client_with_client_builder].
|
||||
///
|
||||
/// If no `localpart` is given it assumes the [`MainUser`]'s `localpart`. If
|
||||
/// no client for `localpart` is found it will return an Error.
|
||||
|
||||
@@ -4,12 +4,13 @@ use std::{
|
||||
};
|
||||
|
||||
use matrix_sdk::{
|
||||
config::{ClientConfig, RequestConfig},
|
||||
config::RequestConfig,
|
||||
ruma::{api::appservice::Registration, events::room::member::SyncRoomMemberEvent},
|
||||
Client,
|
||||
};
|
||||
use matrix_sdk_appservice::*;
|
||||
use matrix_sdk_test::{appservice::TransactionBuilder, async_test, EventsJson};
|
||||
use ruma::room_id;
|
||||
use ruma::{api::MatrixVersion, room_id};
|
||||
use serde_json::json;
|
||||
use warp::{Filter, Reply};
|
||||
|
||||
@@ -32,11 +33,17 @@ async fn appservice(registration: Option<Registration>) -> Result<AppService> {
|
||||
let homeserver_url = mockito::server_url();
|
||||
let server_name = "localhost";
|
||||
|
||||
let client_config =
|
||||
ClientConfig::default().request_config(RequestConfig::default().disable_retry());
|
||||
let client_builder = Client::builder()
|
||||
.request_config(RequestConfig::default().disable_retry())
|
||||
.server_versions([MatrixVersion::V1_0]);
|
||||
|
||||
AppService::new_with_config(homeserver_url.as_ref(), server_name, registration, client_config)
|
||||
.await
|
||||
AppService::with_client_builder(
|
||||
homeserver_url.as_ref(),
|
||||
server_name,
|
||||
registration,
|
||||
client_builder,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
|
||||
@@ -13,15 +13,15 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#[allow(unused_imports)]
|
||||
#[cfg(feature = "encryption")]
|
||||
use std::ops::Deref;
|
||||
use std::{
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
convert::TryFrom,
|
||||
fmt,
|
||||
sync::Arc,
|
||||
};
|
||||
#[allow(unused_imports)]
|
||||
#[cfg(feature = "encryption")]
|
||||
use std::{ops::Deref, result::Result as StdResult};
|
||||
|
||||
#[cfg(feature = "encryption")]
|
||||
use matrix_sdk_common::locks::Mutex;
|
||||
@@ -54,7 +54,7 @@ use ruma::{
|
||||
events::{
|
||||
room::member::MembershipState, AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent,
|
||||
AnyStrippedStateEvent, AnySyncEphemeralRoomEvent, AnySyncRoomEvent, AnySyncStateEvent,
|
||||
EventContent, EventType,
|
||||
EventContent, GlobalAccountDataEventType, StateEventType,
|
||||
},
|
||||
push::{Action, PushConditionRoomCtx, Ruleset},
|
||||
serde::Raw,
|
||||
@@ -69,7 +69,7 @@ use crate::{
|
||||
rooms::{Room, RoomInfo, RoomType},
|
||||
session::Session,
|
||||
store::{
|
||||
ambiguity_map::AmbiguityCache, Result as StoreResult, StateChanges, StateStore, Store,
|
||||
ambiguity_map::AmbiguityCache, Result as StoreResult, StateChanges, Store, StoreConfig,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -102,52 +102,6 @@ impl fmt::Debug for BaseClient {
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration for the creation of the `BaseClient`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use matrix_sdk_base::BaseClientConfig;
|
||||
///
|
||||
/// let client_config = BaseClientConfig::new();
|
||||
/// ```
|
||||
#[derive(Default)]
|
||||
pub struct BaseClientConfig {
|
||||
#[cfg(feature = "encryption")]
|
||||
crypto_store: Option<Box<dyn CryptoStore>>,
|
||||
state_store: Option<Box<dyn StateStore>>,
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
impl std::fmt::Debug for BaseClientConfig {
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
fmt.debug_struct("BaseClientConfig").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl BaseClientConfig {
|
||||
/// Create a new default `BaseClientConfig`.
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Set a custom implementation of a `CryptoStore`.
|
||||
///
|
||||
/// The crypto store should be opened before being set.
|
||||
#[cfg(feature = "encryption")]
|
||||
pub fn crypto_store(mut self, store: Box<dyn CryptoStore>) -> Self {
|
||||
self.crypto_store = Some(store);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set a custom implementation of a `StateStore`.
|
||||
pub fn state_store(mut self, store: Box<dyn StateStore>) -> Self {
|
||||
self.state_store = Some(store);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "encryption")]
|
||||
enum CryptoHolder {
|
||||
PreSetupStore(Option<Box<dyn CryptoStore>>),
|
||||
@@ -169,7 +123,7 @@ impl CryptoHolder {
|
||||
async fn convert_to_olm(&mut self, session: &Session) -> Result<()> {
|
||||
if let CryptoHolder::PreSetupStore(store) = self {
|
||||
*self = CryptoHolder::Olm(Box::new(
|
||||
OlmMachine::new_with_store(
|
||||
OlmMachine::with_store(
|
||||
session.user_id.to_owned(),
|
||||
session.device_id.as_str().into(),
|
||||
store.take().expect("We always exist"),
|
||||
@@ -193,32 +147,31 @@ impl CryptoHolder {
|
||||
}
|
||||
|
||||
impl BaseClient {
|
||||
/// Create a new default client.
|
||||
pub fn new() -> Self {
|
||||
BaseClient::with_store_config(StoreConfig::default())
|
||||
}
|
||||
|
||||
/// Create a new client.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `config` - An optional session if the user already has one from a
|
||||
/// previous login call.
|
||||
pub async fn new_with_config(config: BaseClientConfig) -> Result<Self> {
|
||||
pub fn with_store_config(config: StoreConfig) -> Self {
|
||||
let store = config.state_store.map(Store::new).unwrap_or_else(Store::open_memory_store);
|
||||
#[cfg(feature = "encryption")]
|
||||
let holder = config.crypto_store.map(CryptoHolder::new).unwrap_or_default();
|
||||
|
||||
Ok(BaseClient {
|
||||
BaseClient {
|
||||
session: store.session.clone(),
|
||||
sync_token: store.sync_token.clone(),
|
||||
store,
|
||||
#[cfg(feature = "encryption")]
|
||||
olm: Mutex::new(holder).into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BaseClient {
|
||||
/// Create a new default client.
|
||||
pub async fn new() -> Result<Self> {
|
||||
BaseClient::new_with_config(BaseClientConfig::default()).await
|
||||
}
|
||||
/// The current client session containing our user id, device id and access
|
||||
/// token.
|
||||
pub fn session(&self) -> &Arc<RwLock<Option<Session>>> {
|
||||
@@ -1107,7 +1060,7 @@ impl BaseClient {
|
||||
/// # use futures::executor::block_on;
|
||||
/// # let alice = user_id!("@alice:example.org").to_owned();
|
||||
/// # block_on(async {
|
||||
/// # let client = BaseClient::new().await.unwrap();
|
||||
/// # let client = BaseClient::new();
|
||||
/// let device = client.get_device(&alice, device_id!("DEVICEID")).await;
|
||||
///
|
||||
/// println!("{:?}", device);
|
||||
@@ -1161,7 +1114,7 @@ impl BaseClient {
|
||||
/// # use futures::executor::block_on;
|
||||
/// # let alice = user_id!("@alice:example.org");
|
||||
/// # block_on(async {
|
||||
/// # let client = BaseClient::new().await.unwrap();
|
||||
/// # let client = BaseClient::new();
|
||||
/// let devices = client.get_user_devices(alice).await.unwrap();
|
||||
///
|
||||
/// for device in devices.devices() {
|
||||
@@ -1197,13 +1150,13 @@ impl BaseClient {
|
||||
pub async fn get_push_rules(&self, changes: &StateChanges) -> Result<Ruleset> {
|
||||
if let Some(AnyGlobalAccountDataEvent::PushRules(event)) = changes
|
||||
.account_data
|
||||
.get(EventType::PushRules.as_str())
|
||||
.get(GlobalAccountDataEventType::PushRules.as_str())
|
||||
.and_then(|e| e.deserialize().ok())
|
||||
{
|
||||
Ok(event.content.global)
|
||||
} else if let Some(AnyGlobalAccountDataEvent::PushRules(event)) = self
|
||||
.store
|
||||
.get_account_data_event(EventType::PushRules)
|
||||
.get_account_data_event(GlobalAccountDataEventType::PushRules)
|
||||
.await?
|
||||
.and_then(|e| e.deserialize().ok())
|
||||
{
|
||||
@@ -1246,14 +1199,14 @@ impl BaseClient {
|
||||
let room_power_levels = if let Some(AnySyncStateEvent::RoomPowerLevels(event)) = changes
|
||||
.state
|
||||
.get(room_id)
|
||||
.and_then(|types| types.get(EventType::RoomPowerLevels.as_str()))
|
||||
.and_then(|types| types.get(StateEventType::RoomPowerLevels.as_str()))
|
||||
.and_then(|events| events.get(""))
|
||||
.and_then(|e| e.deserialize().ok())
|
||||
{
|
||||
event.content
|
||||
} else if let Some(AnySyncStateEvent::RoomPowerLevels(event)) = self
|
||||
.store
|
||||
.get_state_event(room_id, EventType::RoomPowerLevels, "")
|
||||
.get_state_event(room_id, StateEventType::RoomPowerLevels, "")
|
||||
.await?
|
||||
.and_then(|e| e.deserialize().ok())
|
||||
{
|
||||
@@ -1296,7 +1249,7 @@ impl BaseClient {
|
||||
if let Some(AnySyncStateEvent::RoomPowerLevels(event)) = changes
|
||||
.state
|
||||
.get(&**room_id)
|
||||
.and_then(|types| types.get(EventType::RoomPowerLevels.as_str()))
|
||||
.and_then(|types| types.get(StateEventType::RoomPowerLevels.as_str()))
|
||||
.and_then(|events| events.get(""))
|
||||
.and_then(|e| e.deserialize().ok())
|
||||
{
|
||||
@@ -1309,5 +1262,11 @@ impl BaseClient {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for BaseClient {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {}
|
||||
|
||||
@@ -33,7 +33,7 @@ mod session;
|
||||
pub mod store;
|
||||
mod timeline_stream;
|
||||
|
||||
pub use client::{BaseClient, BaseClientConfig};
|
||||
pub use client::BaseClient;
|
||||
#[cfg(any(test, feature = "testing"))]
|
||||
pub use http;
|
||||
#[cfg(feature = "encryption")]
|
||||
|
||||
@@ -32,7 +32,8 @@ use ruma::{
|
||||
tombstone::RoomTombstoneEventContent,
|
||||
},
|
||||
tag::Tags,
|
||||
AnyRoomAccountDataEvent, AnyStateEventContent, AnySyncStateEvent, EventType,
|
||||
AnyRoomAccountDataEvent, AnyStateEventContent, AnySyncStateEvent, RoomAccountDataEventType,
|
||||
StateEventType,
|
||||
},
|
||||
receipt::ReceiptType,
|
||||
EventId, MxcUri, RoomAliasId, RoomId, UserId,
|
||||
@@ -415,7 +416,7 @@ impl Room {
|
||||
|
||||
let power =
|
||||
self.store
|
||||
.get_state_event(self.room_id(), EventType::RoomPowerLevels, "")
|
||||
.get_state_event(self.room_id(), StateEventType::RoomPowerLevels, "")
|
||||
.await?
|
||||
.and_then(|e| e.deserialize().ok())
|
||||
.and_then(|e| {
|
||||
@@ -451,7 +452,7 @@ impl Room {
|
||||
pub async fn tags(&self) -> StoreResult<Option<Tags>> {
|
||||
if let Some(AnyRoomAccountDataEvent::Tag(event)) = self
|
||||
.store
|
||||
.get_room_account_data_event(self.room_id(), EventType::Tag)
|
||||
.get_room_account_data_event(self.room_id(), RoomAccountDataEventType::Tag)
|
||||
.await?
|
||||
.and_then(|r| r.deserialize().ok())
|
||||
{
|
||||
|
||||
@@ -51,9 +51,10 @@ macro_rules! statestore_integration_tests {
|
||||
member::{MembershipState, RoomMemberEventContent},
|
||||
power_levels::RoomPowerLevelsEventContent,
|
||||
},
|
||||
AnyEphemeralRoomEventContent, AnySyncEphemeralRoomEvent, AnyStrippedStateEvent,
|
||||
AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent,
|
||||
AnySyncStateEvent, EventType, Unsigned,
|
||||
AnyEphemeralRoomEventContent, AnySyncEphemeralRoomEvent,
|
||||
AnyStrippedStateEvent, AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent,
|
||||
AnySyncStateEvent, GlobalAccountDataEventType, RoomAccountDataEventType,
|
||||
StateEventType, Unsigned,
|
||||
},
|
||||
mxc_uri,
|
||||
receipt::ReceiptType,
|
||||
@@ -279,10 +280,10 @@ macro_rules! statestore_integration_tests {
|
||||
assert!(store.get_presence_event(user_id).await?.is_some());
|
||||
assert_eq!(store.get_room_infos().await?.len(), 1);
|
||||
assert_eq!(store.get_stripped_room_infos().await?.len(), 1);
|
||||
assert!(store.get_account_data_event(EventType::PushRules).await?.is_some());
|
||||
assert!(store.get_account_data_event(GlobalAccountDataEventType::PushRules).await?.is_some());
|
||||
|
||||
assert!(store.get_state_event(room_id, EventType::RoomName, "").await?.is_some());
|
||||
assert_eq!(store.get_state_events(room_id, EventType::RoomTopic).await?.len(), 1);
|
||||
assert!(store.get_state_event(room_id, StateEventType::RoomName, "").await?.is_some());
|
||||
assert_eq!(store.get_state_events(room_id, StateEventType::RoomTopic).await?.len(), 1);
|
||||
assert!(store.get_profile(room_id, user_id).await?.is_some());
|
||||
assert!(store.get_member_event(room_id, user_id).await?.is_some());
|
||||
assert_eq!(store.get_user_ids(room_id).await?.len(), 2);
|
||||
@@ -290,7 +291,7 @@ macro_rules! statestore_integration_tests {
|
||||
assert_eq!(store.get_joined_user_ids(room_id).await?.len(), 1);
|
||||
assert_eq!(store.get_users_with_display_name(room_id, "example").await?.len(), 2);
|
||||
assert!(store
|
||||
.get_room_account_data_event(room_id, EventType::Tag)
|
||||
.get_room_account_data_event(room_id, RoomAccountDataEventType::Tag)
|
||||
.await?
|
||||
.is_some());
|
||||
assert!(store
|
||||
@@ -337,7 +338,7 @@ macro_rules! statestore_integration_tests {
|
||||
let event = raw_event.deserialize().unwrap();
|
||||
|
||||
assert!(store
|
||||
.get_state_event(room_id, EventType::RoomPowerLevels, "")
|
||||
.get_state_event(room_id, StateEventType::RoomPowerLevels, "")
|
||||
.await
|
||||
.unwrap()
|
||||
.is_none());
|
||||
@@ -346,7 +347,7 @@ macro_rules! statestore_integration_tests {
|
||||
|
||||
store.save_changes(&changes).await.unwrap();
|
||||
assert!(store
|
||||
.get_state_event(room_id, EventType::RoomPowerLevels, "")
|
||||
.get_state_event(room_id, StateEventType::RoomPowerLevels, "")
|
||||
.await
|
||||
.unwrap()
|
||||
.is_some());
|
||||
@@ -527,8 +528,8 @@ macro_rules! statestore_integration_tests {
|
||||
assert_eq!(store.get_room_infos().await?.len(), 0);
|
||||
assert_eq!(store.get_stripped_room_infos().await?.len(), 1);
|
||||
|
||||
assert!(store.get_state_event(room_id, EventType::RoomName, "").await?.is_none());
|
||||
assert_eq!(store.get_state_events(room_id, EventType::RoomTopic).await?.len(), 0);
|
||||
assert!(store.get_state_event(room_id, StateEventType::RoomName, "").await?.is_none());
|
||||
assert_eq!(store.get_state_events(room_id, StateEventType::RoomTopic).await?.len(), 0);
|
||||
assert!(store.get_profile(room_id, user_id).await?.is_none());
|
||||
assert!(store.get_member_event(room_id, user_id).await?.is_none());
|
||||
assert_eq!(store.get_user_ids(room_id).await?.len(), 0);
|
||||
@@ -536,7 +537,7 @@ macro_rules! statestore_integration_tests {
|
||||
assert_eq!(store.get_joined_user_ids(room_id).await?.len(), 0);
|
||||
assert_eq!(store.get_users_with_display_name(room_id, "example").await?.len(), 0);
|
||||
assert!(store
|
||||
.get_room_account_data_event(room_id, EventType::Tag)
|
||||
.get_room_account_data_event(room_id, RoomAccountDataEventType::Tag)
|
||||
.await?
|
||||
.is_none());
|
||||
assert!(store
|
||||
|
||||
@@ -28,7 +28,8 @@ use ruma::{
|
||||
receipt::Receipt,
|
||||
room::member::{MembershipState, RoomMemberEventContent},
|
||||
AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnyStrippedStateEvent,
|
||||
AnySyncMessageEvent, AnySyncRoomEvent, AnySyncStateEvent, EventType,
|
||||
AnySyncMessageEvent, AnySyncRoomEvent, AnySyncStateEvent, GlobalAccountDataEventType,
|
||||
RoomAccountDataEventType, StateEventType,
|
||||
},
|
||||
receipt::ReceiptType,
|
||||
serde::Raw,
|
||||
@@ -408,7 +409,7 @@ impl MemoryStore {
|
||||
async fn get_state_event(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
event_type: EventType,
|
||||
event_type: StateEventType,
|
||||
state_key: &str,
|
||||
) -> Result<Option<Raw<AnySyncStateEvent>>> {
|
||||
Ok(self.room_state.get(room_id).and_then(|e| {
|
||||
@@ -419,7 +420,7 @@ impl MemoryStore {
|
||||
async fn get_state_events(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
event_type: EventType,
|
||||
event_type: StateEventType,
|
||||
) -> Result<Vec<Raw<AnySyncStateEvent>>> {
|
||||
Ok(self
|
||||
.room_state
|
||||
@@ -477,7 +478,7 @@ impl MemoryStore {
|
||||
|
||||
async fn get_account_data_event(
|
||||
&self,
|
||||
event_type: EventType,
|
||||
event_type: GlobalAccountDataEventType,
|
||||
) -> Result<Option<Raw<AnyGlobalAccountDataEvent>>> {
|
||||
Ok(self.account_data.get(event_type.as_ref()).map(|e| e.clone()))
|
||||
}
|
||||
@@ -485,7 +486,7 @@ impl MemoryStore {
|
||||
async fn get_room_account_data_event(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
event_type: EventType,
|
||||
event_type: RoomAccountDataEventType,
|
||||
) -> Result<Option<Raw<AnyRoomAccountDataEvent>>> {
|
||||
Ok(self
|
||||
.room_account_data
|
||||
@@ -631,7 +632,7 @@ impl StateStore for MemoryStore {
|
||||
async fn get_state_event(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
event_type: EventType,
|
||||
event_type: StateEventType,
|
||||
state_key: &str,
|
||||
) -> Result<Option<Raw<AnySyncStateEvent>>> {
|
||||
self.get_state_event(room_id, event_type, state_key).await
|
||||
@@ -640,7 +641,7 @@ impl StateStore for MemoryStore {
|
||||
async fn get_state_events(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
event_type: EventType,
|
||||
event_type: StateEventType,
|
||||
) -> Result<Vec<Raw<AnySyncStateEvent>>> {
|
||||
self.get_state_events(room_id, event_type).await
|
||||
}
|
||||
@@ -695,7 +696,7 @@ impl StateStore for MemoryStore {
|
||||
|
||||
async fn get_account_data_event(
|
||||
&self,
|
||||
event_type: EventType,
|
||||
event_type: GlobalAccountDataEventType,
|
||||
) -> Result<Option<Raw<AnyGlobalAccountDataEvent>>> {
|
||||
self.get_account_data_event(event_type).await
|
||||
}
|
||||
@@ -703,7 +704,7 @@ impl StateStore for MemoryStore {
|
||||
async fn get_room_account_data_event(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
event_type: EventType,
|
||||
event_type: RoomAccountDataEventType,
|
||||
) -> Result<Option<Raw<AnyRoomAccountDataEvent>>> {
|
||||
self.get_room_account_data_event(room_id, event_type).await
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ use std::{
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
ops::Deref,
|
||||
pin::Pin,
|
||||
result::Result as StdResult,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
@@ -33,6 +34,8 @@ pub mod integration_tests;
|
||||
|
||||
use dashmap::DashMap;
|
||||
use matrix_sdk_common::{async_trait, locks::RwLock, AsyncTraitDeps};
|
||||
#[cfg(feature = "encryption")]
|
||||
use matrix_sdk_crypto::store::CryptoStore;
|
||||
use ruma::{
|
||||
api::client::push::get_notifications::v3::Notification,
|
||||
events::{
|
||||
@@ -40,7 +43,8 @@ use ruma::{
|
||||
receipt::{Receipt, ReceiptEventContent},
|
||||
room::member::RoomMemberEventContent,
|
||||
AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnyStrippedStateEvent,
|
||||
AnySyncStateEvent, EventContent, EventType,
|
||||
AnySyncStateEvent, EventContent, GlobalAccountDataEventType, RoomAccountDataEventType,
|
||||
StateEventType,
|
||||
},
|
||||
receipt::ReceiptType,
|
||||
serde::Raw,
|
||||
@@ -145,11 +149,11 @@ pub trait StateStore: AsyncTraitDeps {
|
||||
async fn get_state_event(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
event_type: EventType,
|
||||
event_type: StateEventType,
|
||||
state_key: &str,
|
||||
) -> Result<Option<Raw<AnySyncStateEvent>>>;
|
||||
|
||||
/// Get a list of state events for a given room and `EventType`.
|
||||
/// Get a list of state events for a given room and `StateEventType`.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
@@ -159,7 +163,7 @@ pub trait StateStore: AsyncTraitDeps {
|
||||
async fn get_state_events(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
event_type: EventType,
|
||||
event_type: StateEventType,
|
||||
) -> Result<Vec<Raw<AnySyncStateEvent>>>;
|
||||
|
||||
/// Get the current profile for the given user in the given room.
|
||||
@@ -226,7 +230,7 @@ pub trait StateStore: AsyncTraitDeps {
|
||||
/// * `event_type` - The event type of the account data event.
|
||||
async fn get_account_data_event(
|
||||
&self,
|
||||
event_type: EventType,
|
||||
event_type: GlobalAccountDataEventType,
|
||||
) -> Result<Option<Raw<AnyGlobalAccountDataEvent>>>;
|
||||
|
||||
/// Get an event out of the room account data store.
|
||||
@@ -241,7 +245,7 @@ pub trait StateStore: AsyncTraitDeps {
|
||||
async fn get_room_account_data_event(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
event_type: EventType,
|
||||
event_type: RoomAccountDataEventType,
|
||||
) -> Result<Option<Raw<AnyRoomAccountDataEvent>>>;
|
||||
|
||||
/// Get an event out of the user room receipt store.
|
||||
@@ -605,3 +609,50 @@ impl StateChanges {
|
||||
self.timeline.insert(room_id.to_owned(), timeline);
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration for the state store and, when `encryption` is enabled, for the
|
||||
/// crypto store.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use matrix_sdk_base::store::StoreConfig;
|
||||
///
|
||||
/// let store_config = StoreConfig::new();
|
||||
/// ```
|
||||
#[derive(Default)]
|
||||
pub struct StoreConfig {
|
||||
#[cfg(feature = "encryption")]
|
||||
pub(crate) crypto_store: Option<Box<dyn CryptoStore>>,
|
||||
pub(crate) state_store: Option<Box<dyn StateStore>>,
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
impl std::fmt::Debug for StoreConfig {
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> StdResult<(), std::fmt::Error> {
|
||||
fmt.debug_struct("StoreConfig").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl StoreConfig {
|
||||
/// Create a new default `StoreConfig`.
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Set a custom implementation of a `CryptoStore`.
|
||||
///
|
||||
/// The crypto store must be opened before being set.
|
||||
#[cfg(feature = "encryption")]
|
||||
pub fn crypto_store(mut self, store: Box<dyn CryptoStore>) -> Self {
|
||||
self.crypto_store = Some(store);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set a custom implementation of a `StateStore`.
|
||||
pub fn state_store(mut self, store: Box<dyn StateStore>) -> Self {
|
||||
self.state_store = Some(store);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ pub fn keys_query(c: &mut Criterion) {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let store = Box::new(SledCryptoStore::open_with_passphrase(dir, None).unwrap());
|
||||
let machine = runtime
|
||||
.block_on(OlmMachine::new_with_store(alice_id().into(), alice_device_id().into(), store))
|
||||
.block_on(OlmMachine::with_store(alice_id().into(), alice_device_id().into(), store))
|
||||
.unwrap();
|
||||
|
||||
group.bench_with_input(BenchmarkId::new("sled store", &name), &response, |b, response| {
|
||||
@@ -122,7 +122,7 @@ pub fn keys_claiming(c: &mut Criterion) {
|
||||
let store = Box::new(SledCryptoStore::open_with_passphrase(dir, None).unwrap());
|
||||
|
||||
let machine = runtime
|
||||
.block_on(OlmMachine::new_with_store(
|
||||
.block_on(OlmMachine::with_store(
|
||||
alice_id().into(),
|
||||
alice_device_id().into(),
|
||||
store,
|
||||
@@ -188,7 +188,7 @@ pub fn room_key_sharing(c: &mut Criterion) {
|
||||
let store = Box::new(SledCryptoStore::open_with_passphrase(dir, None).unwrap());
|
||||
|
||||
let machine = runtime
|
||||
.block_on(OlmMachine::new_with_store(alice_id().into(), alice_device_id().into(), store))
|
||||
.block_on(OlmMachine::with_store(alice_id().into(), alice_device_id().into(), store))
|
||||
.unwrap();
|
||||
runtime.block_on(machine.mark_request_as_sent(&txn_id, &keys_query_response)).unwrap();
|
||||
runtime.block_on(machine.mark_request_as_sent(&txn_id, &response)).unwrap();
|
||||
@@ -244,7 +244,7 @@ pub fn devices_missing_sessions_collecting(c: &mut Criterion) {
|
||||
let store = Box::new(SledCryptoStore::open_with_passphrase(dir, None).unwrap());
|
||||
|
||||
let machine = runtime
|
||||
.block_on(OlmMachine::new_with_store(alice_id().into(), alice_device_id().into(), store))
|
||||
.block_on(OlmMachine::with_store(alice_id().into(), alice_device_id().into(), store))
|
||||
.unwrap();
|
||||
|
||||
runtime.block_on(machine.mark_request_as_sent(&txn_id, &response)).unwrap();
|
||||
|
||||
@@ -20,6 +20,7 @@ use std::{
|
||||
use olm_rs::pk::OlmPkEncryption;
|
||||
use ruma::{
|
||||
api::client::backup::{KeyBackupData, KeyBackupDataInit, SessionDataInit},
|
||||
serde::Base64,
|
||||
DeviceKeyId, UserId,
|
||||
};
|
||||
use zeroize::Zeroizing;
|
||||
@@ -127,9 +128,11 @@ impl MegolmV1BackupKey {
|
||||
let message = pk.encrypt(&key);
|
||||
|
||||
let session_data = SessionDataInit {
|
||||
ephemeral: message.ephemeral_key,
|
||||
ciphertext: message.ciphertext,
|
||||
mac: message.mac,
|
||||
ephemeral: Base64::parse(message.ephemeral_key)
|
||||
.expect("Can't decode the base64 encoded ephemeral backup key"),
|
||||
ciphertext: Base64::parse(message.ciphertext)
|
||||
.expect("Can't decode a base64 encoded libolm ciphertext"),
|
||||
mac: Base64::parse(message.mac).expect("Can't decode a base64 encoded MAC"),
|
||||
}
|
||||
.into();
|
||||
|
||||
|
||||
@@ -51,4 +51,4 @@ mod backup;
|
||||
mod recovery;
|
||||
|
||||
pub use backup::MegolmV1BackupKey;
|
||||
pub use recovery::{DecodeError, PickledRecoveryKey, RecoveryKey};
|
||||
pub use recovery::DecodeError;
|
||||
|
||||
@@ -17,25 +17,17 @@ use std::{
|
||||
io::{Cursor, Read},
|
||||
};
|
||||
|
||||
use aes::cipher::generic_array::GenericArray;
|
||||
use aes_gcm::{
|
||||
aead::{Aead, NewAead},
|
||||
Aes256Gcm,
|
||||
};
|
||||
use bs58;
|
||||
use olm_rs::{
|
||||
errors::OlmPkDecryptionError,
|
||||
pk::{OlmPkDecryption, PkMessage},
|
||||
};
|
||||
use rand::{thread_rng, Error as RandomError, Fill};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
use zeroize::{Zeroize, Zeroizing};
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
use super::MegolmV1BackupKey;
|
||||
use crate::utilities::{decode_url_safe, encode, encode_url_safe};
|
||||
|
||||
const NONCE_SIZE: usize = 12;
|
||||
use crate::{store::RecoveryKey, utilities::encode};
|
||||
|
||||
/// Error type for the decoding of a RecoveryKey.
|
||||
#[derive(Debug, Error)]
|
||||
@@ -64,47 +56,12 @@ pub enum DecodeError {
|
||||
pub enum UnpicklingError {
|
||||
#[error(transparent)]
|
||||
Json(#[from] serde_json::Error),
|
||||
#[error("Couldn't decrypt the pickle: {0}")]
|
||||
Decryption(String),
|
||||
// #[error("Couldn't decrypt the pickle: {0}")]
|
||||
// Decryption(String),
|
||||
#[error(transparent)]
|
||||
Decode(#[from] DecodeError),
|
||||
}
|
||||
|
||||
/// The private part of a backup key.
|
||||
#[derive(Zeroize)]
|
||||
pub struct RecoveryKey {
|
||||
inner: [u8; RecoveryKey::KEY_SIZE],
|
||||
}
|
||||
|
||||
impl Drop for RecoveryKey {
|
||||
fn drop(&mut self) {
|
||||
self.inner.zeroize()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for RecoveryKey {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("RecoveryKey").finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// The pickled version of a recovery key.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PickledRecoveryKey(String);
|
||||
|
||||
impl AsRef<str> for PickledRecoveryKey {
|
||||
fn as_ref(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
struct InnerPickle {
|
||||
version: u8,
|
||||
nonce: String,
|
||||
ciphertext: String,
|
||||
}
|
||||
|
||||
impl TryFrom<String> for RecoveryKey {
|
||||
type Error = DecodeError;
|
||||
|
||||
@@ -132,7 +89,6 @@ impl std::fmt::Display for RecoveryKey {
|
||||
}
|
||||
|
||||
impl RecoveryKey {
|
||||
const KEY_SIZE: usize = 32;
|
||||
const PREFIX: [u8; 2] = [0x8b, 0x01];
|
||||
const PREFIX_PARITY: u8 = Self::PREFIX[0] ^ Self::PREFIX[1];
|
||||
const DISPLAY_CHUNK_SIZE: usize = 4;
|
||||
@@ -235,57 +191,6 @@ impl RecoveryKey {
|
||||
public_key
|
||||
}
|
||||
|
||||
/// Export this [`RecoveryKey`] as an encrypted pickle that can be safely
|
||||
/// stored.
|
||||
pub fn pickle(&self, pickle_key: &[u8]) -> PickledRecoveryKey {
|
||||
let key = GenericArray::from_slice(pickle_key);
|
||||
let cipher = Aes256Gcm::new(key);
|
||||
|
||||
let mut nonce = vec![0u8; NONCE_SIZE];
|
||||
let mut rng = thread_rng();
|
||||
|
||||
nonce.try_fill(&mut rng).expect("Can't generate random nocne to pickle the recovery key");
|
||||
let nonce = GenericArray::from_slice(nonce.as_slice());
|
||||
|
||||
let ciphertext =
|
||||
cipher.encrypt(nonce, self.inner.as_ref()).expect("Can't encrypt recovery key");
|
||||
|
||||
let ciphertext = encode_url_safe(ciphertext);
|
||||
|
||||
let pickle =
|
||||
InnerPickle { version: 1, nonce: encode_url_safe(nonce.as_slice()), ciphertext };
|
||||
|
||||
PickledRecoveryKey(serde_json::to_string(&pickle).expect("Can't encode pickled signing"))
|
||||
}
|
||||
|
||||
/// Try to import a `RecoveryKey` from a previously exported pickle.
|
||||
pub fn from_pickle(
|
||||
pickle: PickledRecoveryKey,
|
||||
pickle_key: &[u8],
|
||||
) -> Result<Self, UnpicklingError> {
|
||||
let pickled: InnerPickle = serde_json::from_str(pickle.as_ref())?;
|
||||
|
||||
let key = GenericArray::from_slice(pickle_key);
|
||||
let cipher = Aes256Gcm::new(key);
|
||||
|
||||
let nonce = decode_url_safe(pickled.nonce).map_err(DecodeError::from)?;
|
||||
let nonce = GenericArray::from_slice(&nonce);
|
||||
let ciphertext = &decode_url_safe(pickled.ciphertext).map_err(DecodeError::from)?;
|
||||
|
||||
let decrypted = cipher
|
||||
.decrypt(nonce, ciphertext.as_slice())
|
||||
.map_err(|e| UnpicklingError::Decryption(e.to_string()))?;
|
||||
|
||||
if decrypted.len() != Self::KEY_SIZE {
|
||||
Err(DecodeError::Length(decrypted.len(), Self::KEY_SIZE).into())
|
||||
} else {
|
||||
let mut key = [0u8; Self::KEY_SIZE];
|
||||
key.copy_from_slice(&decrypted);
|
||||
|
||||
Ok(Self { inner: key })
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to decrypt the given ciphertext using this `RecoveryKey`.
|
||||
///
|
||||
/// This will use the [`m.megolm_backup.v1.curve25519-aes-sha2`] algorithm
|
||||
|
||||
@@ -39,13 +39,13 @@ use tracing::{debug, info, instrument, trace, warn};
|
||||
|
||||
use crate::{
|
||||
olm::{Account, InboundGroupSession},
|
||||
store::{BackupKeys, Changes, RoomKeyCounts, Store},
|
||||
store::{BackupKeys, Changes, RecoveryKey, RoomKeyCounts, Store},
|
||||
CryptoStoreError, KeysBackupRequest, OutgoingRequest,
|
||||
};
|
||||
|
||||
mod keys;
|
||||
|
||||
pub use keys::{DecodeError, MegolmV1BackupKey, PickledRecoveryKey, RecoveryKey};
|
||||
pub use keys::{DecodeError, MegolmV1BackupKey};
|
||||
pub use olm_rs::errors::OlmPkDecryptionError;
|
||||
|
||||
/// A state machine that handles backing up room keys.
|
||||
@@ -64,7 +64,7 @@ pub struct BackupMachine {
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct PendingBackup {
|
||||
request_id: &TransactionId,
|
||||
request_id: Box<TransactionId>,
|
||||
request: KeysBackupRequest,
|
||||
sessions: BTreeMap<Box<RoomId>, BTreeMap<String, BTreeSet<String>>>,
|
||||
}
|
||||
@@ -388,8 +388,7 @@ mod test {
|
||||
use matrix_sdk_test::async_test;
|
||||
use ruma::{device_id, room_id, user_id, DeviceId, RoomId, UserId};
|
||||
|
||||
use super::RecoveryKey;
|
||||
use crate::{OlmError, OlmMachine};
|
||||
use crate::{store::RecoveryKey, OlmError, OlmMachine};
|
||||
|
||||
fn alice_id() -> &'static UserId {
|
||||
user_id!("@alice:example.org")
|
||||
@@ -430,12 +429,12 @@ mod test {
|
||||
let request =
|
||||
backup_machine.backup().await?.expect("Created a backup request successfully");
|
||||
assert_eq!(
|
||||
Some(request.request_id),
|
||||
backup_machine.backup().await?.map(|r| r.request_id),
|
||||
Some(&*request.request_id),
|
||||
backup_machine.backup().await?.as_ref().map(|r| &*r.request_id),
|
||||
"Calling backup again without uploading creates the same backup request"
|
||||
);
|
||||
|
||||
backup_machine.mark_request_as_sent(request.request_id).await?;
|
||||
backup_machine.mark_request_as_sent(&request.request_id).await?;
|
||||
|
||||
let counts = backup_machine.store.inbound_group_session_counts().await?;
|
||||
assert_eq!(counts.total, 2);
|
||||
@@ -471,13 +470,9 @@ mod test {
|
||||
use tempfile::tempdir;
|
||||
|
||||
let tmpdir = tempdir().expect("Can't create a temporary dir");
|
||||
let machine = OlmMachine::new_with_default_store(
|
||||
alice_id(),
|
||||
alice_device_id(),
|
||||
tmpdir.as_ref(),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
let machine =
|
||||
OlmMachine::with_default_store(alice_id(), alice_device_id(), tmpdir.as_ref(), None)
|
||||
.await?;
|
||||
|
||||
backup_flow(machine).await
|
||||
}
|
||||
@@ -488,7 +483,7 @@ mod test {
|
||||
use tempfile::tempdir;
|
||||
|
||||
let tmpdir = tempdir().expect("Can't create a temporary dir");
|
||||
let machine = OlmMachine::new_with_default_store(
|
||||
let machine = OlmMachine::with_default_store(
|
||||
alice_id(),
|
||||
alice_device_id(),
|
||||
tmpdir.as_ref(),
|
||||
|
||||
@@ -150,7 +150,7 @@ impl GossipRequest {
|
||||
}
|
||||
};
|
||||
|
||||
let request = ToDeviceRequest::new_with_id(
|
||||
let request = ToDeviceRequest::with_id(
|
||||
&self.request_recipient,
|
||||
DeviceIdOrAllDevices::AllDevices,
|
||||
content,
|
||||
|
||||
@@ -1071,7 +1071,7 @@ pub(crate) mod test {
|
||||
let (_, device) = device(&response);
|
||||
|
||||
let account = ReadOnlyAccount::new(device.user_id(), device.device_id());
|
||||
let (identity, _, _) = PrivateCrossSigningIdentity::new_with_account(&account).await;
|
||||
let (identity, _, _) = PrivateCrossSigningIdentity::with_account(&account).await;
|
||||
|
||||
let id = Arc::new(Mutex::new(identity.clone()));
|
||||
|
||||
|
||||
@@ -220,7 +220,7 @@ impl OlmMachine {
|
||||
/// the encryption keys.
|
||||
///
|
||||
/// [`Cryptostore`]: trait.CryptoStore.html
|
||||
pub async fn new_with_store(
|
||||
pub async fn with_store(
|
||||
user_id: Box<UserId>,
|
||||
device_id: Box<DeviceId>,
|
||||
store: Box<dyn CryptoStore>,
|
||||
@@ -272,7 +272,7 @@ impl OlmMachine {
|
||||
///
|
||||
/// * `device_id` - The unique id of the device that owns this machine.
|
||||
#[cfg(feature = "sled_cryptostore")]
|
||||
pub async fn new_with_default_store(
|
||||
pub async fn with_default_store(
|
||||
user_id: &UserId,
|
||||
device_id: &DeviceId,
|
||||
path: impl AsRef<Path>,
|
||||
@@ -280,7 +280,7 @@ impl OlmMachine {
|
||||
) -> StoreResult<Self> {
|
||||
let store = SledStore::open_with_passphrase(path, passphrase)?;
|
||||
|
||||
OlmMachine::new_with_store(user_id.to_owned(), device_id.into(), Box::new(store)).await
|
||||
OlmMachine::with_store(user_id.to_owned(), device_id.into(), Box::new(store)).await
|
||||
}
|
||||
|
||||
/// The unique user id that owns this `OlmMachine` instance.
|
||||
@@ -376,7 +376,7 @@ impl OlmMachine {
|
||||
}
|
||||
IncomingResponse::KeysBackup(_) => {
|
||||
#[cfg(feature = "backups_v1")]
|
||||
self.backup_machine.mark_request_as_sent(*request_id).await?;
|
||||
self.backup_machine.mark_request_as_sent(request_id).await?;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2036,7 +2036,7 @@ pub(crate) mod test {
|
||||
|
||||
let tmpdir = tempdir().unwrap();
|
||||
|
||||
let machine = OlmMachine::new_with_default_store(
|
||||
let machine = OlmMachine::with_default_store(
|
||||
user_id(),
|
||||
alice_device_id(),
|
||||
tmpdir.as_ref(),
|
||||
@@ -2053,7 +2053,7 @@ pub(crate) mod test {
|
||||
|
||||
drop(machine);
|
||||
|
||||
let machine = OlmMachine::new_with_default_store(
|
||||
let machine = OlmMachine::with_default_store(
|
||||
&user_id,
|
||||
alice_device_id(),
|
||||
tmpdir.as_ref(),
|
||||
|
||||
@@ -696,6 +696,7 @@ impl ReadOnlyAccount {
|
||||
self.inner.lock().await.sign(string)
|
||||
}
|
||||
|
||||
/// Check that the given json value is signed by this account.
|
||||
#[cfg(feature = "backups_v1")]
|
||||
pub fn is_signed(&self, json: &mut Value) -> Result<(), SignatureError> {
|
||||
let signing_key = self.identity_keys.ed25519();
|
||||
@@ -807,7 +808,7 @@ impl ReadOnlyAccount {
|
||||
pub async fn bootstrap_cross_signing(
|
||||
&self,
|
||||
) -> (PrivateCrossSigningIdentity, UploadSigningKeysRequest, SignatureUploadRequest) {
|
||||
PrivateCrossSigningIdentity::new_with_account(self).await
|
||||
PrivateCrossSigningIdentity::with_account(self).await
|
||||
}
|
||||
|
||||
/// Sign the given CrossSigning Key in place
|
||||
|
||||
@@ -238,8 +238,6 @@ impl InboundGroupSession {
|
||||
self.backed_up.store(false, SeqCst)
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "testing"))]
|
||||
#[allow(dead_code)]
|
||||
/// For testing, allow to manually mark this GroupSession to have been
|
||||
/// backed up
|
||||
pub fn mark_as_backed_up(&self) {
|
||||
@@ -342,6 +340,8 @@ impl InboundGroupSession {
|
||||
self.inner.lock().await.decrypt(message)
|
||||
}
|
||||
|
||||
/// Export the inbound group session into a format that can be uploaded to
|
||||
/// the server as a backup.
|
||||
#[cfg(feature = "backups_v1")]
|
||||
pub async fn to_backup(&self) -> BackedUpRoomKey {
|
||||
self.export().await.into()
|
||||
|
||||
@@ -476,7 +476,7 @@ impl PrivateCrossSigningIdentity {
|
||||
/// * `account` - The Olm account that is creating the new identity. The
|
||||
/// account will sign the master key and the self signing key will sign the
|
||||
/// account.
|
||||
pub(crate) async fn new_with_account(
|
||||
pub(crate) async fn with_account(
|
||||
account: &ReadOnlyAccount,
|
||||
) -> (Self, UploadSigningKeysRequest, SignatureUploadRequest) {
|
||||
let master = Signing::new();
|
||||
@@ -739,7 +739,7 @@ mod test {
|
||||
#[async_test]
|
||||
async fn private_identity_signed_by_account() {
|
||||
let account = ReadOnlyAccount::new(user_id(), device_id!("DEVICEID"));
|
||||
let (identity, _, _) = PrivateCrossSigningIdentity::new_with_account(&account).await;
|
||||
let (identity, _, _) = PrivateCrossSigningIdentity::with_account(&account).await;
|
||||
let master = identity.master_key.lock().await;
|
||||
let master = master.as_ref().unwrap();
|
||||
|
||||
@@ -749,7 +749,7 @@ mod test {
|
||||
#[async_test]
|
||||
async fn sign_device() {
|
||||
let account = ReadOnlyAccount::new(user_id(), device_id!("DEVICEID"));
|
||||
let (identity, _, _) = PrivateCrossSigningIdentity::new_with_account(&account).await;
|
||||
let (identity, _, _) = PrivateCrossSigningIdentity::with_account(&account).await;
|
||||
|
||||
let mut device = ReadOnlyDevice::from_account(&account).await;
|
||||
let self_signing = identity.self_signing_key.lock().await;
|
||||
@@ -766,10 +766,10 @@ mod test {
|
||||
#[async_test]
|
||||
async fn sign_user_identity() {
|
||||
let account = ReadOnlyAccount::new(user_id(), device_id!("DEVICEID"));
|
||||
let (identity, _, _) = PrivateCrossSigningIdentity::new_with_account(&account).await;
|
||||
let (identity, _, _) = PrivateCrossSigningIdentity::with_account(&account).await;
|
||||
|
||||
let bob_account = ReadOnlyAccount::new(user_id!("@bob:localhost"), device_id!("DEVICEID"));
|
||||
let (bob_private, _, _) = PrivateCrossSigningIdentity::new_with_account(&bob_account).await;
|
||||
let (bob_private, _, _) = PrivateCrossSigningIdentity::with_account(&bob_account).await;
|
||||
let mut bob_public = ReadOnlyUserIdentity::from_private(&bob_private).await;
|
||||
|
||||
let user_signing = identity.user_signing_key.lock().await;
|
||||
|
||||
@@ -30,7 +30,7 @@ use ruma::{
|
||||
to_device::send_event_to_device::v3::Response as ToDeviceResponse,
|
||||
},
|
||||
encryption::CrossSigningKey,
|
||||
events::{AnyMessageEventContent, AnyToDeviceEventContent, EventContent, EventType},
|
||||
events::{AnyMessageEventContent, AnyToDeviceEventContent, EventContent, ToDeviceEventType},
|
||||
serde::Raw,
|
||||
to_device::DeviceIdOrAllDevices,
|
||||
DeviceId, RoomId, TransactionId, UserId,
|
||||
@@ -42,7 +42,7 @@ use serde::{Deserialize, Serialize};
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct ToDeviceRequest {
|
||||
/// Type of event being sent to each device.
|
||||
pub event_type: EventType,
|
||||
pub event_type: ToDeviceEventType,
|
||||
|
||||
/// A request identifier unique to the access token used to send the
|
||||
/// request.
|
||||
@@ -75,10 +75,10 @@ impl ToDeviceRequest {
|
||||
recipient_device: impl Into<DeviceIdOrAllDevices>,
|
||||
content: AnyToDeviceEventContent,
|
||||
) -> Self {
|
||||
Self::new_with_id(recipient, recipient_device, content, TransactionId::new())
|
||||
Self::with_id(recipient, recipient_device, content, TransactionId::new())
|
||||
}
|
||||
|
||||
pub(crate) fn new_for_recipients(
|
||||
pub(crate) fn for_recipients(
|
||||
recipient: &UserId,
|
||||
recipient_devices: Vec<Box<DeviceId>>,
|
||||
content: AnyToDeviceEventContent,
|
||||
@@ -102,13 +102,13 @@ impl ToDeviceRequest {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new_with_id(
|
||||
pub(crate) fn with_id(
|
||||
recipient: &UserId,
|
||||
recipient_device: impl Into<DeviceIdOrAllDevices>,
|
||||
content: AnyToDeviceEventContent,
|
||||
txn_id: Box<TransactionId>,
|
||||
) -> Self {
|
||||
let event_type = EventType::from(content.event_type());
|
||||
let event_type = ToDeviceEventType::from(content.event_type());
|
||||
let raw_content = Raw::new(&content).expect("Failed to serialize to-device event");
|
||||
|
||||
let user_messages = iter::once((recipient_device.into(), raw_content)).collect();
|
||||
|
||||
@@ -24,7 +24,7 @@ use matrix_sdk_common::executor::spawn;
|
||||
use ruma::{
|
||||
events::{
|
||||
room::{encrypted::RoomEncryptedEventContent, history_visibility::HistoryVisibility},
|
||||
AnyToDeviceEventContent, EventType,
|
||||
AnyToDeviceEventContent, ToDeviceEventType,
|
||||
},
|
||||
serde::Raw,
|
||||
to_device::DeviceIdOrAllDevices,
|
||||
@@ -300,7 +300,7 @@ impl GroupSessionManager {
|
||||
|
||||
let txn_id = TransactionId::new();
|
||||
let request = ToDeviceRequest {
|
||||
event_type: EventType::RoomEncrypted,
|
||||
event_type: ToDeviceEventType::RoomEncrypted,
|
||||
txn_id: txn_id.clone(),
|
||||
messages,
|
||||
};
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
//! # let device_id = device_id!("TEST").to_owned();
|
||||
//! let store = Box::new(MemoryStore::new());
|
||||
//!
|
||||
//! let machine = OlmMachine::new_with_store(user_id, device_id, store);
|
||||
//! let machine = OlmMachine::with_store(user_id, device_id, store);
|
||||
//! ```
|
||||
//!
|
||||
//! [`OlmMachine`]: /matrix_sdk_crypto/struct.OlmMachine.html
|
||||
@@ -65,6 +65,7 @@ use ruma::{
|
||||
events::secret::request::SecretName, identifiers::Error as IdentifierValidationError, DeviceId,
|
||||
DeviceKeyAlgorithm, RoomId, TransactionId, UserId,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Error as SerdeError;
|
||||
use thiserror::Error;
|
||||
use tracing::{info, warn};
|
||||
@@ -108,10 +109,8 @@ pub struct Store {
|
||||
pub struct Changes {
|
||||
pub account: Option<ReadOnlyAccount>,
|
||||
pub private_identity: Option<PrivateCrossSigningIdentity>,
|
||||
#[cfg(feature = "backups_v1")]
|
||||
pub backup_version: Option<String>,
|
||||
#[cfg(feature = "backups_v1")]
|
||||
pub recovery_key: Option<crate::backups::RecoveryKey>,
|
||||
pub recovery_key: Option<RecoveryKey>,
|
||||
pub sessions: Vec<Session>,
|
||||
pub message_hashes: Vec<OlmMessageHash>,
|
||||
pub inbound_group_sessions: Vec<InboundGroupSession>,
|
||||
@@ -157,6 +156,103 @@ pub struct DeviceChanges {
|
||||
pub deleted: Vec<ReadOnlyDevice>,
|
||||
}
|
||||
|
||||
/// The private part of a backup key.
|
||||
#[derive(Zeroize)]
|
||||
#[zeroize(drop)]
|
||||
pub struct RecoveryKey {
|
||||
pub(crate) inner: [u8; RecoveryKey::KEY_SIZE],
|
||||
}
|
||||
|
||||
/// The pickled version of a recovery key.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PickledRecoveryKey(String);
|
||||
|
||||
impl AsRef<str> for PickledRecoveryKey {
|
||||
fn as_ref(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
struct InnerPickle {
|
||||
version: u8,
|
||||
nonce: String,
|
||||
ciphertext: String,
|
||||
}
|
||||
|
||||
impl RecoveryKey {
|
||||
/// The number of bytes the recovery key will hold.
|
||||
pub const KEY_SIZE: usize = 32;
|
||||
const NONCE_SIZE: usize = 12;
|
||||
|
||||
/// Export this [`RecoveryKey`] as an encrypted pickle that can be safely
|
||||
/// stored.
|
||||
pub fn pickle(&self, pickle_key: &[u8]) -> PickledRecoveryKey {
|
||||
use aes::cipher::generic_array::GenericArray;
|
||||
use aes_gcm::aead::{Aead, NewAead};
|
||||
use rand::Fill;
|
||||
|
||||
let key = GenericArray::from_slice(pickle_key);
|
||||
let cipher = aes_gcm::Aes256Gcm::new(key);
|
||||
|
||||
let mut nonce = vec![0u8; Self::NONCE_SIZE];
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
nonce.try_fill(&mut rng).expect("Can't generate random nocne to pickle the recovery key");
|
||||
let nonce = GenericArray::from_slice(nonce.as_slice());
|
||||
|
||||
let ciphertext =
|
||||
cipher.encrypt(nonce, self.inner.as_ref()).expect("Can't encrypt recovery key");
|
||||
|
||||
let ciphertext = crate::utilities::encode_url_safe(ciphertext);
|
||||
|
||||
let pickle = InnerPickle {
|
||||
version: 1,
|
||||
nonce: crate::utilities::encode_url_safe(nonce.as_slice()),
|
||||
ciphertext,
|
||||
};
|
||||
|
||||
PickledRecoveryKey(serde_json::to_string(&pickle).expect("Can't encode pickled signing"))
|
||||
}
|
||||
|
||||
/// Try to import a `RecoveryKey` from a previously exported pickle.
|
||||
pub fn from_pickle(
|
||||
pickle: PickledRecoveryKey,
|
||||
pickle_key: &[u8],
|
||||
) -> Result<Self, CryptoStoreError> {
|
||||
use aes::cipher::generic_array::GenericArray;
|
||||
use aes_gcm::aead::{Aead, NewAead};
|
||||
|
||||
let pickled: InnerPickle = serde_json::from_str(pickle.as_ref())?;
|
||||
|
||||
let key = GenericArray::from_slice(pickle_key);
|
||||
let cipher = aes_gcm::Aes256Gcm::new(key);
|
||||
|
||||
let nonce = crate::utilities::decode_url_safe(pickled.nonce).unwrap();
|
||||
let nonce = GenericArray::from_slice(&nonce);
|
||||
let ciphertext = &crate::utilities::decode_url_safe(pickled.ciphertext).unwrap();
|
||||
|
||||
let decrypted = cipher
|
||||
.decrypt(nonce, ciphertext.as_slice())
|
||||
.map_err(|_| CryptoStoreError::UnpicklingError)?;
|
||||
|
||||
if decrypted.len() != Self::KEY_SIZE {
|
||||
Err(CryptoStoreError::UnpicklingError)
|
||||
} else {
|
||||
let mut key = [0u8; Self::KEY_SIZE];
|
||||
key.copy_from_slice(&decrypted);
|
||||
|
||||
Ok(Self { inner: key })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for RecoveryKey {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("RecoveryKey").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl DeviceChanges {
|
||||
/// Merge the given `DeviceChanges` into this instance of `DeviceChanges`.
|
||||
pub fn extend(&mut self, other: DeviceChanges) {
|
||||
@@ -183,10 +279,8 @@ pub struct RoomKeyCounts {
|
||||
#[derive(Default, Debug)]
|
||||
pub struct BackupKeys {
|
||||
/// The recovery key, the one used to decrypt backed up room keys.
|
||||
#[cfg(feature = "backups_v1")]
|
||||
pub recovery_key: Option<crate::backups::RecoveryKey>,
|
||||
pub recovery_key: Option<RecoveryKey>,
|
||||
/// The version that we are using for backups.
|
||||
#[cfg(feature = "backups_v1")]
|
||||
pub backup_version: Option<String>,
|
||||
}
|
||||
|
||||
|
||||
@@ -727,7 +727,7 @@ impl TryFrom<ToDeviceRequest> for OutgoingContent {
|
||||
type Error = String;
|
||||
|
||||
fn try_from(request: ToDeviceRequest) -> Result<Self, Self::Error> {
|
||||
use ruma::events::EventType;
|
||||
use ruma::events::ToDeviceEventType;
|
||||
use serde_json::Value;
|
||||
|
||||
let json: Value = serde_json::from_str(
|
||||
@@ -742,30 +742,40 @@ impl TryFrom<ToDeviceRequest> for OutgoingContent {
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let content = match request.event_type {
|
||||
EventType::KeyVerificationStart => AnyToDeviceEventContent::KeyVerificationStart(
|
||||
ToDeviceEventType::KeyVerificationStart => {
|
||||
AnyToDeviceEventContent::KeyVerificationStart(
|
||||
serde_json::from_value(json).map_err(|e| e.to_string())?,
|
||||
)
|
||||
}
|
||||
ToDeviceEventType::KeyVerificationKey => AnyToDeviceEventContent::KeyVerificationKey(
|
||||
serde_json::from_value(json).map_err(|e| e.to_string())?,
|
||||
),
|
||||
EventType::KeyVerificationKey => AnyToDeviceEventContent::KeyVerificationKey(
|
||||
ToDeviceEventType::KeyVerificationAccept => {
|
||||
AnyToDeviceEventContent::KeyVerificationAccept(
|
||||
serde_json::from_value(json).map_err(|e| e.to_string())?,
|
||||
)
|
||||
}
|
||||
ToDeviceEventType::KeyVerificationMac => AnyToDeviceEventContent::KeyVerificationMac(
|
||||
serde_json::from_value(json).map_err(|e| e.to_string())?,
|
||||
),
|
||||
EventType::KeyVerificationAccept => AnyToDeviceEventContent::KeyVerificationAccept(
|
||||
serde_json::from_value(json).map_err(|e| e.to_string())?,
|
||||
),
|
||||
EventType::KeyVerificationMac => AnyToDeviceEventContent::KeyVerificationMac(
|
||||
serde_json::from_value(json).map_err(|e| e.to_string())?,
|
||||
),
|
||||
EventType::KeyVerificationCancel => AnyToDeviceEventContent::KeyVerificationCancel(
|
||||
serde_json::from_value(json).map_err(|e| e.to_string())?,
|
||||
),
|
||||
EventType::KeyVerificationReady => AnyToDeviceEventContent::KeyVerificationReady(
|
||||
serde_json::from_value(json).map_err(|e| e.to_string())?,
|
||||
),
|
||||
EventType::KeyVerificationDone => AnyToDeviceEventContent::KeyVerificationDone(
|
||||
serde_json::from_value(json).map_err(|e| e.to_string())?,
|
||||
),
|
||||
EventType::KeyVerificationRequest => AnyToDeviceEventContent::KeyVerificationRequest(
|
||||
ToDeviceEventType::KeyVerificationCancel => {
|
||||
AnyToDeviceEventContent::KeyVerificationCancel(
|
||||
serde_json::from_value(json).map_err(|e| e.to_string())?,
|
||||
)
|
||||
}
|
||||
ToDeviceEventType::KeyVerificationReady => {
|
||||
AnyToDeviceEventContent::KeyVerificationReady(
|
||||
serde_json::from_value(json).map_err(|e| e.to_string())?,
|
||||
)
|
||||
}
|
||||
ToDeviceEventType::KeyVerificationDone => AnyToDeviceEventContent::KeyVerificationDone(
|
||||
serde_json::from_value(json).map_err(|e| e.to_string())?,
|
||||
),
|
||||
ToDeviceEventType::KeyVerificationRequest => {
|
||||
AnyToDeviceEventContent::KeyVerificationRequest(
|
||||
serde_json::from_value(json).map_err(|e| e.to_string())?,
|
||||
)
|
||||
}
|
||||
e => return Err(format!("Unsupported event type {}", e)),
|
||||
};
|
||||
|
||||
|
||||
@@ -164,7 +164,7 @@ impl VerificationRequest {
|
||||
milli_seconds_since_unix_epoch(),
|
||||
);
|
||||
|
||||
ToDeviceRequest::new_for_recipients(
|
||||
ToDeviceRequest::for_recipients(
|
||||
self.other_user(),
|
||||
self.recipient_devices.to_vec(),
|
||||
AnyToDeviceEventContent::KeyVerificationRequest(content),
|
||||
@@ -442,7 +442,7 @@ impl VerificationRequest {
|
||||
let request = content.map(|c| match c {
|
||||
OutgoingContent::ToDevice(content) => {
|
||||
if send_to_everyone {
|
||||
ToDeviceRequest::new_for_recipients(
|
||||
ToDeviceRequest::for_recipients(
|
||||
self.other_user(),
|
||||
self.recipient_devices.to_vec(),
|
||||
content,
|
||||
@@ -537,7 +537,7 @@ impl VerificationRequest {
|
||||
if recipients.is_empty() && filter_device.is_some() {
|
||||
None
|
||||
} else {
|
||||
Some(ToDeviceRequest::new_for_recipients(
|
||||
Some(ToDeviceRequest::for_recipients(
|
||||
self.other_user(),
|
||||
recipients,
|
||||
c,
|
||||
|
||||
@@ -5,7 +5,7 @@ edition = "2021"
|
||||
|
||||
[features]
|
||||
default = ["encryption"]
|
||||
encryption = ["matrix-sdk-crypto"]
|
||||
encryption = ["matrix-sdk-base/encryption", "matrix-sdk-crypto"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
default-target = "wasm32-unknown-unknown"
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use matrix_sdk_base::store::StoreConfig;
|
||||
|
||||
mod safe_encode;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
@@ -12,3 +15,52 @@ mod cryptostore;
|
||||
pub use cryptostore::IndexeddbStore as CryptoStore;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub use state_store::IndexeddbStore as StateStore;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[cfg(feature = "encryption")]
|
||||
/// Create a [`StateStore`] and a [`CryptoStore`] that use the same name and
|
||||
/// passphrase.
|
||||
async fn open_stores_with_name(
|
||||
name: impl Into<String>,
|
||||
passphrase: Option<&str>,
|
||||
) -> Result<(Box<StateStore>, Box<CryptoStore>), anyhow::Error> {
|
||||
let name = name.into();
|
||||
|
||||
if let Some(passphrase) = passphrase {
|
||||
let state_store = StateStore::open_with_passphrase(name.clone(), passphrase).await?;
|
||||
let crypto_store = CryptoStore::open_with_passphrase(name, passphrase).await?;
|
||||
Ok((Box::new(state_store), Box::new(crypto_store)))
|
||||
} else {
|
||||
let state_store = StateStore::open_with_name(name.clone()).await?;
|
||||
let crypto_store = CryptoStore::open_with_name(name).await?;
|
||||
Ok((Box::new(state_store), Box::new(crypto_store)))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
/// Create a [`StoreConfig`] with an opened indexeddb [`StateStore`] that uses
|
||||
/// the given name and passphrase. If `encryption` is enabled, a [`CryptoStore`]
|
||||
/// with the same parameters is also opened.
|
||||
pub async fn make_store_config(
|
||||
name: impl Into<String>,
|
||||
passphrase: Option<&str>,
|
||||
) -> Result<StoreConfig, anyhow::Error> {
|
||||
let name = name.into();
|
||||
|
||||
#[cfg(feature = "encryption")]
|
||||
{
|
||||
let (state_store, crypto_store) = open_stores_with_name(name, passphrase).await?;
|
||||
Ok(StoreConfig::new().state_store(state_store).crypto_store(crypto_store))
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "encryption"))]
|
||||
{
|
||||
let state_store = if let Some(passphrase) = passphrase {
|
||||
StateStore::open_with_passphrase(name, passphrase).await?
|
||||
} else {
|
||||
StateStore::open_with_name(name).await?
|
||||
};
|
||||
|
||||
Ok(StoreConfig::new().state_store(Box::new(state_store)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#![allow(dead_code)]
|
||||
use matrix_sdk_base::ruma::events::StateEventType;
|
||||
use matrix_sdk_common::ruma::{
|
||||
events::EventType, receipt::ReceiptType, DeviceId, EventId, MxcUri, RoomId, TransactionId,
|
||||
UserId,
|
||||
receipt::ReceiptType, DeviceId, EventId, MxcUri, RoomId, TransactionId, UserId,
|
||||
};
|
||||
use wasm_bindgen::JsValue;
|
||||
use web_sys::IdbKeyRange;
|
||||
@@ -135,7 +135,7 @@ impl SafeEncode for TransactionId {
|
||||
}
|
||||
}
|
||||
|
||||
impl SafeEncode for EventType {
|
||||
impl SafeEncode for StateEventType {
|
||||
fn as_encoded_string(&self) -> String {
|
||||
self.as_str().as_encoded_string()
|
||||
}
|
||||
|
||||
@@ -34,7 +34,8 @@ use matrix_sdk_common::{
|
||||
receipt::Receipt,
|
||||
room::member::{MembershipState, RoomMemberEventContent},
|
||||
AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnySyncMessageEvent,
|
||||
AnySyncRoomEvent, AnySyncStateEvent, EventType,
|
||||
AnySyncRoomEvent, AnySyncStateEvent, GlobalAccountDataEventType,
|
||||
RoomAccountDataEventType, StateEventType,
|
||||
},
|
||||
receipt::ReceiptType,
|
||||
serde::Raw,
|
||||
@@ -711,7 +712,7 @@ impl IndexeddbStore {
|
||||
pub async fn get_state_event(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
event_type: EventType,
|
||||
event_type: StateEventType,
|
||||
state_key: &str,
|
||||
) -> Result<Option<Raw<AnySyncStateEvent>>> {
|
||||
self.inner
|
||||
@@ -726,7 +727,7 @@ impl IndexeddbStore {
|
||||
pub async fn get_state_events(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
event_type: EventType,
|
||||
event_type: StateEventType,
|
||||
) -> Result<Vec<Raw<AnySyncStateEvent>>> {
|
||||
let range = (room_id, &event_type).encode_to_range().map_err(StoreError::Codec)?;
|
||||
Ok(self
|
||||
@@ -860,7 +861,7 @@ impl IndexeddbStore {
|
||||
|
||||
pub async fn get_account_data_event(
|
||||
&self,
|
||||
event_type: EventType,
|
||||
event_type: GlobalAccountDataEventType,
|
||||
) -> Result<Option<Raw<AnyGlobalAccountDataEvent>>> {
|
||||
self.inner
|
||||
.transaction_on_one_with_mode(KEYS::ACCOUNT_DATA, IdbTransactionMode::Readonly)?
|
||||
@@ -874,7 +875,7 @@ impl IndexeddbStore {
|
||||
pub async fn get_room_account_data_event(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
event_type: EventType,
|
||||
event_type: RoomAccountDataEventType,
|
||||
) -> Result<Option<Raw<AnyRoomAccountDataEvent>>> {
|
||||
self.inner
|
||||
.transaction_on_one_with_mode(KEYS::ROOM_ACCOUNT_DATA, IdbTransactionMode::Readonly)?
|
||||
@@ -1122,7 +1123,7 @@ impl StateStore for IndexeddbStore {
|
||||
async fn get_state_event(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
event_type: EventType,
|
||||
event_type: StateEventType,
|
||||
state_key: &str,
|
||||
) -> StoreResult<Option<Raw<AnySyncStateEvent>>> {
|
||||
self.get_state_event(room_id, event_type, state_key).await.map_err(|e| e.into())
|
||||
@@ -1131,7 +1132,7 @@ impl StateStore for IndexeddbStore {
|
||||
async fn get_state_events(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
event_type: EventType,
|
||||
event_type: StateEventType,
|
||||
) -> StoreResult<Vec<Raw<AnySyncStateEvent>>> {
|
||||
self.get_state_events(room_id, event_type).await.map_err(|e| e.into())
|
||||
}
|
||||
@@ -1182,7 +1183,7 @@ impl StateStore for IndexeddbStore {
|
||||
|
||||
async fn get_account_data_event(
|
||||
&self,
|
||||
event_type: EventType,
|
||||
event_type: GlobalAccountDataEventType,
|
||||
) -> StoreResult<Option<Raw<AnyGlobalAccountDataEvent>>> {
|
||||
self.get_account_data_event(event_type).await.map_err(|e| e.into())
|
||||
}
|
||||
@@ -1190,7 +1191,7 @@ impl StateStore for IndexeddbStore {
|
||||
async fn get_room_account_data_event(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
event_type: EventType,
|
||||
event_type: RoomAccountDataEventType,
|
||||
) -> StoreResult<Option<Raw<AnyRoomAccountDataEvent>>> {
|
||||
self.get_room_account_data_event(room_id, event_type).await.map_err(|e| e.into())
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ required-features = ["binary-build"]
|
||||
[features]
|
||||
default = ["encryption"]
|
||||
|
||||
encryption = ["matrix-sdk-crypto"]
|
||||
encryption = ["matrix-sdk-base/encryption", "matrix-sdk-crypto"]
|
||||
binary-build = [
|
||||
"atty",
|
||||
"clap",
|
||||
|
||||
@@ -4,7 +4,7 @@ use atty::Stream;
|
||||
use clap::{Arg, ArgMatches, Command as Argparse};
|
||||
use futures::executor::block_on;
|
||||
use matrix_sdk_base::{RoomInfo, Store};
|
||||
use matrix_sdk_common::ruma::{events::EventType, RoomId, UserId};
|
||||
use matrix_sdk_common::ruma::{events::StateEventType, RoomId, UserId};
|
||||
use matrix_sdk_sled::StateStore;
|
||||
use rustyline::{
|
||||
completion::{Completer, Pair},
|
||||
@@ -228,7 +228,8 @@ impl Inspector {
|
||||
}
|
||||
Some(("get-state", args)) => {
|
||||
let room_id = RoomId::parse(args.value_of("room-id").unwrap()).unwrap();
|
||||
let event_type = EventType::try_from(args.value_of("event-type").unwrap()).unwrap();
|
||||
let event_type =
|
||||
StateEventType::try_from(args.value_of("event-type").unwrap()).unwrap();
|
||||
self.get_state(room_id, event_type).await;
|
||||
}
|
||||
_ => unreachable!(),
|
||||
@@ -263,7 +264,7 @@ impl Inspector {
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_state(&self, room_id: Box<RoomId>, event_type: EventType) {
|
||||
async fn get_state(&self, room_id: Box<RoomId>, event_type: StateEventType) {
|
||||
self.printer.pretty_print_struct(
|
||||
&self.store.get_state_event(&room_id, event_type, "").await.unwrap(),
|
||||
);
|
||||
@@ -288,7 +289,9 @@ impl Inspector {
|
||||
RoomId::parse(r).map(|_| ()).map_err(|_| "Invalid room id given".to_owned())
|
||||
}))
|
||||
.arg(Arg::new("event-type").required(true).validator(|e| {
|
||||
EventType::try_from(e).map(|_| ()).map_err(|_| "Invalid event type".to_owned())
|
||||
StateEventType::try_from(e)
|
||||
.map(|_| ())
|
||||
.map_err(|_| "Invalid event type".to_owned())
|
||||
})),
|
||||
]
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ use matrix_sdk_crypto::{
|
||||
},
|
||||
store::{
|
||||
caches::SessionStore, BackupKeys, Changes, CryptoStore, CryptoStoreError, IdentityKeys,
|
||||
PickleKey, PicklingMode, Result, RoomKeyCounts,
|
||||
PickleKey, PicklingMode, RecoveryKey, Result, RoomKeyCounts,
|
||||
},
|
||||
GossipRequest, LocalTrust, ReadOnlyAccount, ReadOnlyDevice, ReadOnlyUserIdentities, SecretInfo,
|
||||
};
|
||||
@@ -518,7 +518,6 @@ impl SledStore {
|
||||
None
|
||||
};
|
||||
|
||||
#[cfg(feature = "backups_v1")]
|
||||
let recovery_key_pickle = changes.recovery_key.map(|r| r.pickle(self.get_pickle_key()));
|
||||
|
||||
let device_changes = changes.devices;
|
||||
@@ -559,7 +558,6 @@ impl SledStore {
|
||||
let identity_changes = changes.identities;
|
||||
let olm_hashes = changes.message_hashes;
|
||||
let key_requests = changes.key_requests;
|
||||
#[cfg(feature = "backups_v1")]
|
||||
let backup_version = changes.backup_version;
|
||||
|
||||
let ret: Result<(), TransactionError<serde_json::Error>> = (
|
||||
@@ -603,7 +601,6 @@ impl SledStore {
|
||||
)?;
|
||||
}
|
||||
|
||||
#[cfg(feature = "backups_v1")]
|
||||
if let Some(r) = &recovery_key_pickle {
|
||||
account.insert(
|
||||
"recovery_key_v1".encode(),
|
||||
@@ -611,7 +608,6 @@ impl SledStore {
|
||||
)?;
|
||||
}
|
||||
|
||||
#[cfg(feature = "backups_v1")]
|
||||
if let Some(b) = &backup_version {
|
||||
account.insert(
|
||||
"backup_version_v1".encode(),
|
||||
@@ -1071,21 +1067,22 @@ impl CryptoStore for SledStore {
|
||||
}
|
||||
|
||||
async fn load_backup_keys(&self) -> Result<BackupKeys> {
|
||||
#[cfg(feature = "backups_v1")]
|
||||
let key = {
|
||||
let backup_version = self
|
||||
.account
|
||||
.get("backup_version_v1".encode())?
|
||||
.get("backup_version_v1".encode())
|
||||
.map_err(|e| CryptoStoreError::Backend(anyhow!(e)))?
|
||||
.map(|v| serde_json::from_slice(&v))
|
||||
.transpose()?;
|
||||
|
||||
let recovery_key = {
|
||||
self.account
|
||||
.get("recovery_key_v1".encode())?
|
||||
.get("recovery_key_v1".encode())
|
||||
.map_err(|e| CryptoStoreError::Backend(anyhow!(e)))?
|
||||
.map(|p| serde_json::from_slice(&p))
|
||||
.transpose()?
|
||||
.map(|p| {
|
||||
crate::backups::RecoveryKey::from_pickle(p, self.get_pickle_key())
|
||||
RecoveryKey::from_pickle(p, self.get_pickle_key())
|
||||
.map_err(|_| CryptoStoreError::UnpicklingError)
|
||||
})
|
||||
.transpose()?
|
||||
@@ -1094,9 +1091,6 @@ impl CryptoStore for SledStore {
|
||||
BackupKeys { backup_version, recovery_key }
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "backups_v1"))]
|
||||
let key = BackupKeys {};
|
||||
|
||||
Ok(key)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
use std::path::Path;
|
||||
|
||||
use matrix_sdk_base::store::StoreConfig;
|
||||
|
||||
#[cfg(feature = "encryption")]
|
||||
mod cryptostore;
|
||||
mod state_store;
|
||||
@@ -5,3 +9,46 @@ mod state_store;
|
||||
#[cfg(feature = "encryption")]
|
||||
pub use cryptostore::SledStore as CryptoStore;
|
||||
pub use state_store::SledStore as StateStore;
|
||||
|
||||
#[cfg(feature = "encryption")]
|
||||
/// Create a [`StateStore`] and a [`CryptoStore`] that use the same database and
|
||||
/// passphrase.
|
||||
fn open_stores_with_path(
|
||||
path: impl AsRef<Path>,
|
||||
passphrase: Option<&str>,
|
||||
) -> Result<(Box<StateStore>, Box<CryptoStore>), anyhow::Error> {
|
||||
if let Some(passphrase) = passphrase {
|
||||
let state_store = StateStore::open_with_passphrase(path, passphrase)?;
|
||||
let crypto_store = state_store.get_crypto_store(Some(passphrase))?;
|
||||
Ok((Box::new(state_store), Box::new(crypto_store)))
|
||||
} else {
|
||||
let state_store = StateStore::open_with_path(path)?;
|
||||
let crypto_store = state_store.get_crypto_store(None)?;
|
||||
Ok((Box::new(state_store), Box::new(crypto_store)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a [`StoreConfig`] with an opened sled [`StateStore`] that uses the
|
||||
/// given path and passphrase. If `encryption` is enabled, a [`CryptoStore`]
|
||||
/// with the same parameters is also opened.
|
||||
pub fn make_store_config(
|
||||
path: impl AsRef<Path>,
|
||||
passphrase: Option<&str>,
|
||||
) -> Result<StoreConfig, anyhow::Error> {
|
||||
#[cfg(feature = "encryption")]
|
||||
{
|
||||
let (state_store, crypto_store) = open_stores_with_path(path, passphrase)?;
|
||||
Ok(StoreConfig::new().state_store(state_store).crypto_store(crypto_store))
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "encryption"))]
|
||||
{
|
||||
let state_store = if let Some(passphrase) = passphrase {
|
||||
StateStore::open_with_passphrase(path, passphrase)?
|
||||
} else {
|
||||
StateStore::open_with_path(path)?
|
||||
};
|
||||
|
||||
Ok(StoreConfig::new().state_store(Box::new(state_store)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,8 @@ use matrix_sdk_common::{
|
||||
receipt::Receipt,
|
||||
room::member::{MembershipState, RoomMemberEventContent},
|
||||
AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnySyncMessageEvent,
|
||||
AnySyncRoomEvent, AnySyncStateEvent, EventType,
|
||||
AnySyncRoomEvent, AnySyncStateEvent, GlobalAccountDataEventType,
|
||||
RoomAccountDataEventType, StateEventType,
|
||||
},
|
||||
receipt::ReceiptType,
|
||||
serde::Raw,
|
||||
@@ -58,6 +59,9 @@ use sled::{
|
||||
use tokio::task::spawn_blocking;
|
||||
use tracing::{info, warn};
|
||||
|
||||
#[cfg(feature = "encryption")]
|
||||
pub use crate::CryptoStore;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum DatabaseType {
|
||||
Unencrypted,
|
||||
@@ -186,12 +190,24 @@ impl EncodeKey for (&str, &str, &str, &str) {
|
||||
}
|
||||
}
|
||||
|
||||
impl EncodeKey for EventType {
|
||||
impl EncodeKey for StateEventType {
|
||||
fn encode(&self) -> Vec<u8> {
|
||||
self.as_str().encode()
|
||||
}
|
||||
}
|
||||
|
||||
impl EncodeKey for GlobalAccountDataEventType {
|
||||
fn encode(&self) -> Vec<u8> {
|
||||
self.as_str().encode()
|
||||
}
|
||||
}
|
||||
|
||||
/* impl EncodeKey for RoomAccountDataEventType {
|
||||
fn encode(&self) -> Vec<u8> {
|
||||
self.as_str().encode()
|
||||
}
|
||||
} */
|
||||
|
||||
impl EncodeKey for EventId {
|
||||
fn encode(&self) -> Vec<u8> {
|
||||
self.as_str().encode()
|
||||
@@ -364,6 +380,14 @@ impl SledStore {
|
||||
SledStore::open_helper(db, Some(path), None)
|
||||
}
|
||||
|
||||
#[cfg(feature = "encryption")]
|
||||
/// Open a `CryptoStore` that uses the same database as this store.
|
||||
///
|
||||
/// The given passphrase will be used to encrypt private data.
|
||||
pub fn get_crypto_store(&self, passphrase: Option<&str>) -> Result<CryptoStore, anyhow::Error> {
|
||||
CryptoStore::open_with_database(self.inner.clone(), passphrase)
|
||||
}
|
||||
|
||||
fn serialize_event(&self, event: &impl Serialize) -> Result<Vec<u8>, SledStoreError> {
|
||||
if let Some(key) = &*self.store_key {
|
||||
let encrypted = key.encrypt(event)?;
|
||||
@@ -649,7 +673,7 @@ impl SledStore {
|
||||
pub async fn get_state_event(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
event_type: EventType,
|
||||
event_type: StateEventType,
|
||||
state_key: &str,
|
||||
) -> Result<Option<Raw<AnySyncStateEvent>>> {
|
||||
let db = self.clone();
|
||||
@@ -663,7 +687,7 @@ impl SledStore {
|
||||
pub async fn get_state_events(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
event_type: EventType,
|
||||
event_type: StateEventType,
|
||||
) -> Result<Vec<Raw<AnySyncStateEvent>>> {
|
||||
let db = self.clone();
|
||||
let key = (room_id.as_str(), event_type.as_str()).encode();
|
||||
@@ -809,7 +833,7 @@ impl SledStore {
|
||||
|
||||
pub async fn get_account_data_event(
|
||||
&self,
|
||||
event_type: EventType,
|
||||
event_type: GlobalAccountDataEventType,
|
||||
) -> Result<Option<Raw<AnyGlobalAccountDataEvent>>> {
|
||||
let db = self.clone();
|
||||
let key = event_type.encode();
|
||||
@@ -822,7 +846,7 @@ impl SledStore {
|
||||
pub async fn get_room_account_data_event(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
event_type: EventType,
|
||||
event_type: RoomAccountDataEventType,
|
||||
) -> Result<Option<Raw<AnyRoomAccountDataEvent>>> {
|
||||
let db = self.clone();
|
||||
let key = (room_id.as_str(), event_type.as_str()).encode();
|
||||
@@ -1300,7 +1324,7 @@ impl StateStore for SledStore {
|
||||
async fn get_state_event(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
event_type: EventType,
|
||||
event_type: StateEventType,
|
||||
state_key: &str,
|
||||
) -> StoreResult<Option<Raw<AnySyncStateEvent>>> {
|
||||
self.get_state_event(room_id, event_type, state_key).await.map_err(Into::into)
|
||||
@@ -1309,7 +1333,7 @@ impl StateStore for SledStore {
|
||||
async fn get_state_events(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
event_type: EventType,
|
||||
event_type: StateEventType,
|
||||
) -> StoreResult<Vec<Raw<AnySyncStateEvent>>> {
|
||||
self.get_state_events(room_id, event_type).await.map_err(Into::into)
|
||||
}
|
||||
@@ -1370,7 +1394,7 @@ impl StateStore for SledStore {
|
||||
|
||||
async fn get_account_data_event(
|
||||
&self,
|
||||
event_type: EventType,
|
||||
event_type: GlobalAccountDataEventType,
|
||||
) -> StoreResult<Option<Raw<AnyGlobalAccountDataEvent>>> {
|
||||
self.get_account_data_event(event_type).await.map_err(Into::into)
|
||||
}
|
||||
@@ -1378,7 +1402,7 @@ impl StateStore for SledStore {
|
||||
async fn get_room_account_data_event(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
event_type: EventType,
|
||||
event_type: RoomAccountDataEventType,
|
||||
) -> StoreResult<Option<Raw<AnyRoomAccountDataEvent>>> {
|
||||
self.get_room_account_data_event(room_id, event_type).await.map_err(Into::into)
|
||||
}
|
||||
@@ -1449,7 +1473,6 @@ struct TimelineMetadata {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
use matrix_sdk_base::statestore_integration_tests;
|
||||
|
||||
use super::{SledStore, StateStore, StoreResult};
|
||||
|
||||
@@ -24,12 +24,13 @@ default = [
|
||||
"native-tls"
|
||||
]
|
||||
|
||||
indexeddb_stores = ["matrix-sdk-indexeddb"]
|
||||
indexeddb_state_store = ["matrix-sdk-indexeddb"]
|
||||
indexeddb_cryptostore = ["matrix-sdk-indexeddb/encryption", "encryption"]
|
||||
encryption = ["matrix-sdk-base/encryption"]
|
||||
qrcode = ["encryption", "matrix-sdk-base/qrcode"]
|
||||
# TODO merge those two sled features
|
||||
sled_state_store = ["matrix-sdk-sled"]
|
||||
sled_cryptostore = ["matrix-sdk-sled", "encryption"]
|
||||
sled_cryptostore = ["matrix-sdk-sled/encryption", "encryption"]
|
||||
markdown = ["ruma/markdown"]
|
||||
native-tls = ["reqwest/native-tls"]
|
||||
rustls-tls = ["reqwest/rustls-tls"]
|
||||
@@ -69,8 +70,8 @@ url = "2.2.2"
|
||||
zeroize = "1.3.0"
|
||||
async-stream = "0.3.2"
|
||||
|
||||
matrix-sdk-sled = { path = "../matrix-sdk-sled", optional = true }
|
||||
matrix-sdk-indexeddb = { path = "../matrix-sdk-indexeddb", optional = true }
|
||||
matrix-sdk-sled = { path = "../matrix-sdk-sled", default-features = false, optional = true }
|
||||
matrix-sdk-indexeddb = { path = "../matrix-sdk-indexeddb", default-features = false, optional = true }
|
||||
|
||||
[dependencies.image]
|
||||
version = "0.24.0"
|
||||
|
||||
@@ -27,14 +27,14 @@ This is demonstrated in the example below.
|
||||
```rust,no_run
|
||||
use std::convert::TryFrom;
|
||||
use matrix_sdk::{
|
||||
Client, config::SyncSettings, Result,
|
||||
Client, config::SyncSettings,
|
||||
ruma::{user_id, events::room::message::SyncRoomMessageEvent},
|
||||
};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
let alice = user_id!("@alice:example.org");
|
||||
let client = Client::new_from_user_id(alice).await?;
|
||||
let client = Client::builder().user_id(alice).build().await?;
|
||||
|
||||
// First we need to log in.
|
||||
client.login(alice, "password", None, None).await?;
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
use std::{env, process::exit};
|
||||
|
||||
use matrix_sdk::{
|
||||
config::{ClientConfig, SyncSettings},
|
||||
room::Room,
|
||||
ruma::events::room::member::StrippedRoomMemberEvent,
|
||||
Client,
|
||||
config::SyncSettings, room::Room, ruma::events::room::member::StrippedRoomMemberEvent, Client,
|
||||
};
|
||||
use tokio::time::{sleep, Duration};
|
||||
use url::Url;
|
||||
|
||||
async fn on_stripped_state_member(
|
||||
room_member: StrippedRoomMemberEvent,
|
||||
@@ -44,16 +40,26 @@ async fn login_and_sync(
|
||||
homeserver_url: String,
|
||||
username: &str,
|
||||
password: &str,
|
||||
) -> Result<(), matrix_sdk::Error> {
|
||||
let mut home = dirs::home_dir().expect("no home directory found");
|
||||
home.push("autojoin_bot");
|
||||
) -> anyhow::Result<()> {
|
||||
#[allow(unused_mut)]
|
||||
let mut client_builder = Client::builder().homeserver_url(homeserver_url);
|
||||
|
||||
let client_config =
|
||||
ClientConfig::with_named_store(home.to_str().expect("home dir path must be utf-8"), None)
|
||||
.await?;
|
||||
#[cfg(feature = "sled_state_store")]
|
||||
{
|
||||
// The location to save files to
|
||||
let mut home = dirs::home_dir().expect("no home directory found");
|
||||
home.push("autojoin_bot");
|
||||
let state_store = matrix_sdk_sled::StateStore::open_with_path(home)?;
|
||||
client_builder = client_builder.state_store(Box::new(state_store));
|
||||
}
|
||||
|
||||
let homeserver_url = Url::parse(&homeserver_url).expect("Couldn't parse the homeserver URL");
|
||||
let client = Client::new_with_config(homeserver_url, client_config).await.unwrap();
|
||||
#[cfg(feature = "indexeddb_state_store")]
|
||||
{
|
||||
let state_store = matrix_sdk_indexeddb::StateStore::open();
|
||||
client_builder = client_builder.state_store(Box::new(state_store));
|
||||
}
|
||||
|
||||
let client = client_builder.build().await?;
|
||||
|
||||
client.login(username, password, None, Some("autojoin bot")).await?;
|
||||
|
||||
@@ -67,7 +73,7 @@ async fn login_and_sync(
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), matrix_sdk::Error> {
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let (homeserver_url, username, password) =
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
use std::{env, process::exit};
|
||||
|
||||
use matrix_sdk::{
|
||||
config::{ClientConfig, SyncSettings},
|
||||
config::SyncSettings,
|
||||
room::Room,
|
||||
ruma::events::room::message::{
|
||||
MessageType, RoomMessageEventContent, SyncRoomMessageEvent, TextMessageEventContent,
|
||||
},
|
||||
Client,
|
||||
};
|
||||
use url::Url;
|
||||
|
||||
async fn on_room_message(event: SyncRoomMessageEvent, room: Room) {
|
||||
if let Room::Joined(room) = room {
|
||||
@@ -36,19 +35,26 @@ async fn login_and_sync(
|
||||
homeserver_url: String,
|
||||
username: String,
|
||||
password: String,
|
||||
) -> Result<(), matrix_sdk::Error> {
|
||||
// the location for `JsonStore` to save files to
|
||||
let mut home = dirs::home_dir().expect("no home directory found");
|
||||
home.push("party_bot");
|
||||
) -> anyhow::Result<()> {
|
||||
#[allow(unused_mut)]
|
||||
let mut client_builder = Client::builder().homeserver_url(homeserver_url);
|
||||
|
||||
let client_config =
|
||||
ClientConfig::with_named_store(home.to_str().expect("home dir path must be utf-8"), None)
|
||||
.await?;
|
||||
#[cfg(feature = "sled_state_store")]
|
||||
{
|
||||
// The location to save files to
|
||||
let mut home = dirs::home_dir().expect("no home directory found");
|
||||
home.push("party_bot");
|
||||
let state_store = matrix_sdk_sled::StateStore::open_with_path(home)?;
|
||||
client_builder = client_builder.state_store(Box::new(state_store));
|
||||
}
|
||||
|
||||
let homeserver_url = Url::parse(&homeserver_url).expect("Couldn't parse the homeserver URL");
|
||||
// create a new Client with the given homeserver url and config
|
||||
let client = Client::new_with_config(homeserver_url, client_config).await.unwrap();
|
||||
#[cfg(feature = "indexeddb_state_store")]
|
||||
{
|
||||
let state_store = matrix_sdk_indexeddb::StateStore::open();
|
||||
client_builder = client_builder.state_store(Box::new(state_store));
|
||||
}
|
||||
|
||||
let client = client_builder.build().await.unwrap();
|
||||
client.login(&username, &password, None, Some("command bot")).await?;
|
||||
|
||||
println!("logged in as {}", username);
|
||||
@@ -72,7 +78,7 @@ async fn login_and_sync(
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), matrix_sdk::Error> {
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let (homeserver_url, username, password) =
|
||||
|
||||
@@ -14,7 +14,7 @@ async fn bootstrap(client: Client, user_id: Box<UserId>, password: String) {
|
||||
|
||||
io::stdin().read_line(&mut input).expect("error: unable to read user input");
|
||||
|
||||
if let Err(e) = client.bootstrap_cross_signing(None).await {
|
||||
if let Err(e) = client.encryption().bootstrap_cross_signing(None).await {
|
||||
use matrix_sdk::ruma::{api::client::uiaa, assign};
|
||||
|
||||
if let Some(response) = e.uiaa_response() {
|
||||
@@ -27,6 +27,7 @@ async fn bootstrap(client: Client, user_id: Box<UserId>, password: String) {
|
||||
));
|
||||
|
||||
client
|
||||
.encryption()
|
||||
.bootstrap_cross_signing(Some(auth_data))
|
||||
.await
|
||||
.expect("Couldn't bootstrap cross signing")
|
||||
|
||||
@@ -54,7 +54,7 @@ fn print_result(sas: &SasVerification) {
|
||||
async fn print_devices(user_id: &UserId, client: &Client) {
|
||||
println!("Devices of user {}", user_id);
|
||||
|
||||
for device in client.get_user_devices(user_id).await.unwrap().devices() {
|
||||
for device in client.encryption().get_user_devices(user_id).await.unwrap().devices() {
|
||||
println!(
|
||||
" {:<10} {:<30} {:<}",
|
||||
device.device_id(),
|
||||
@@ -87,6 +87,7 @@ async fn login(
|
||||
match event {
|
||||
AnyToDeviceEvent::KeyVerificationStart(e) => {
|
||||
if let Some(Verification::SasV1(sas)) = client
|
||||
.encryption()
|
||||
.get_verification(&e.sender, e.content.transaction_id.as_str())
|
||||
.await
|
||||
{
|
||||
@@ -102,6 +103,7 @@ async fn login(
|
||||
|
||||
AnyToDeviceEvent::KeyVerificationKey(e) => {
|
||||
if let Some(Verification::SasV1(sas)) = client
|
||||
.encryption()
|
||||
.get_verification(&e.sender, e.content.transaction_id.as_str())
|
||||
.await
|
||||
{
|
||||
@@ -111,6 +113,7 @@ async fn login(
|
||||
|
||||
AnyToDeviceEvent::KeyVerificationMac(e) => {
|
||||
if let Some(Verification::SasV1(sas)) = client
|
||||
.encryption()
|
||||
.get_verification(&e.sender, e.content.transaction_id.as_str())
|
||||
.await
|
||||
{
|
||||
@@ -136,6 +139,7 @@ async fn login(
|
||||
if let MessageType::VerificationRequest(_) = &m.content.msgtype
|
||||
{
|
||||
let request = client
|
||||
.encryption()
|
||||
.get_verification_request(&m.sender, &m.event_id)
|
||||
.await
|
||||
.expect("Request object wasn't created");
|
||||
@@ -148,6 +152,7 @@ async fn login(
|
||||
}
|
||||
AnySyncMessageEvent::KeyVerificationKey(e) => {
|
||||
if let Some(Verification::SasV1(sas)) = client
|
||||
.encryption()
|
||||
.get_verification(
|
||||
&e.sender,
|
||||
e.content.relates_to.event_id.as_str(),
|
||||
@@ -159,6 +164,7 @@ async fn login(
|
||||
}
|
||||
AnySyncMessageEvent::KeyVerificationMac(e) => {
|
||||
if let Some(Verification::SasV1(sas)) = client
|
||||
.encryption()
|
||||
.get_verification(
|
||||
&e.sender,
|
||||
e.content.relates_to.event_id.as_str(),
|
||||
|
||||
@@ -25,9 +25,9 @@ getrandom = { version = "0.2.4", features = ["js"] }
|
||||
[dependencies.matrix-sdk]
|
||||
path = "../.."
|
||||
default-features = false
|
||||
features = ["native-tls", "encryption", "indexeddb_stores"]
|
||||
features = ["native-tls", "encryption", "indexeddb_state_store", "indexeddb_cryptostore"]
|
||||
|
||||
[workspace]
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.2"
|
||||
wasm-bindgen-test = "0.2"
|
||||
|
||||
433
crates/matrix-sdk/src/client/builder.rs
Normal file
433
crates/matrix-sdk/src/client/builder.rs
Normal file
@@ -0,0 +1,433 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use matrix_sdk_base::{locks::RwLock, store::StoreConfig, BaseClient, StateStore};
|
||||
use ruma::{
|
||||
api::{
|
||||
client::discover::{discover_homeserver, get_supported_versions},
|
||||
MatrixVersion,
|
||||
},
|
||||
ServerName, UserId,
|
||||
};
|
||||
use thiserror::Error;
|
||||
use url::Url;
|
||||
|
||||
use super::{Client, ClientInner};
|
||||
use crate::{
|
||||
config::RequestConfig,
|
||||
http_client::{HttpClient, HttpSend, HttpSettings},
|
||||
HttpError,
|
||||
};
|
||||
|
||||
/// Builder that allows creating and configuring various parts of a [`Client`].
|
||||
///
|
||||
/// When setting the `StateStore` it is up to the user to open/connect
|
||||
/// the storage backend before client creation.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use matrix_sdk::Client;
|
||||
/// // To pass all the request through mitmproxy set the proxy and disable SSL
|
||||
/// // verification
|
||||
///
|
||||
/// let client_builder = Client::builder()
|
||||
/// .proxy("http://localhost:8080")
|
||||
/// .disable_ssl_verification();
|
||||
/// ```
|
||||
///
|
||||
/// # Example for using a custom http client
|
||||
///
|
||||
/// Note: setting a custom http client will ignore `user_agent`, `proxy`, and
|
||||
/// `disable_ssl_verification` - you'd need to set these yourself if you want
|
||||
/// them.
|
||||
///
|
||||
/// ```
|
||||
/// use matrix_sdk::Client;
|
||||
/// use std::sync::Arc;
|
||||
///
|
||||
/// // setting up a custom http client
|
||||
/// let reqwest_builder = reqwest::ClientBuilder::new()
|
||||
/// .https_only(true)
|
||||
/// .no_proxy()
|
||||
/// .user_agent("MyApp/v3.0");
|
||||
///
|
||||
/// let client_builder = Client::builder()
|
||||
/// .http_client(Arc::new(reqwest_builder.build()?));
|
||||
/// # anyhow::Ok(())
|
||||
/// ```
|
||||
#[must_use]
|
||||
#[derive(Debug)]
|
||||
pub struct ClientBuilder {
|
||||
homeserver_cfg: Option<HomeserverConfig>,
|
||||
http_cfg: Option<HttpConfig>,
|
||||
store_config: StoreConfig,
|
||||
request_config: RequestConfig,
|
||||
respect_login_well_known: bool,
|
||||
appservice_mode: bool,
|
||||
server_versions: Option<Arc<[MatrixVersion]>>,
|
||||
}
|
||||
|
||||
impl ClientBuilder {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
homeserver_cfg: None,
|
||||
http_cfg: None,
|
||||
store_config: Default::default(),
|
||||
request_config: Default::default(),
|
||||
respect_login_well_known: true,
|
||||
appservice_mode: false,
|
||||
server_versions: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the homeserver URL to use.
|
||||
///
|
||||
/// This method is mutually exclusive with [`user_id()`][Self::user_id], if
|
||||
/// you set both whatever was set last will be used.
|
||||
pub fn homeserver_url(mut self, url: impl AsRef<str>) -> Self {
|
||||
self.homeserver_cfg = Some(HomeserverConfig::Url(url.as_ref().to_owned()));
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the user ID to discover the homeserver from.
|
||||
///
|
||||
/// `builder.user_id(id)` is a shortcut for
|
||||
/// `builder.server_name(id.server_name())`.
|
||||
///
|
||||
/// This method is mutually exclusive with
|
||||
/// [`homeserver_url()`][Self::homeserver_url], if you set both whatever was
|
||||
/// set last will be used.
|
||||
pub fn user_id(self, user_id: &UserId) -> Self {
|
||||
self.server_name(user_id.server_name())
|
||||
}
|
||||
|
||||
/// Set the server name to discover the homeserver from.
|
||||
///
|
||||
/// This method is mutually exclusive with
|
||||
/// [`homeserver_url()`][Self::homeserver_url], if you set both whatever was
|
||||
/// set last will be used.
|
||||
pub fn server_name(mut self, server_name: &ServerName) -> Self {
|
||||
self.homeserver_cfg = Some(HomeserverConfig::ServerName(server_name.to_owned()));
|
||||
self
|
||||
}
|
||||
|
||||
/// Create a new `ClientConfig` with the given [`StoreConfig`].
|
||||
///
|
||||
/// The easiest way to get a [`StoreConfig`] is to use the
|
||||
/// [`make_store_config`] method from the [`store`] module or directly from
|
||||
/// one of the store crates.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `store_config` - The configuration of the store.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use matrix_sdk_base::store::MemoryStore;
|
||||
/// # let custom_state_store = Box::new(MemoryStore::new());
|
||||
/// use matrix_sdk::{Client, config::StoreConfig};
|
||||
///
|
||||
/// let store_config = StoreConfig::new().state_store(custom_state_store);
|
||||
/// let client_builder = Client::builder().store_config(store_config);
|
||||
/// ```
|
||||
/// [`make_store_config`]: crate::store::make_store_config
|
||||
/// [`store`]: crate::store
|
||||
pub fn store_config(mut self, store_config: StoreConfig) -> Self {
|
||||
self.store_config = store_config;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set a custom implementation of a `StateStore`.
|
||||
///
|
||||
/// The state store should be opened before being set.
|
||||
pub fn state_store(mut self, store: Box<dyn StateStore>) -> Self {
|
||||
self.store_config = self.store_config.state_store(store);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set a custom implementation of a `CryptoStore`.
|
||||
///
|
||||
/// The crypto store should be opened before being set.
|
||||
#[cfg(feature = "encryption")]
|
||||
pub fn crypto_store(
|
||||
mut self,
|
||||
store: Box<dyn matrix_sdk_base::crypto::store::CryptoStore>,
|
||||
) -> Self {
|
||||
self.store_config = self.store_config.crypto_store(store);
|
||||
self
|
||||
}
|
||||
|
||||
/// Update the client's homeserver URL with the discovery information
|
||||
/// present in the login response, if any.
|
||||
pub fn respect_login_well_known(mut self, value: bool) -> Self {
|
||||
self.respect_login_well_known = value;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the default timeout, fail and retry behavior for all HTTP requests.
|
||||
pub fn request_config(mut self, request_config: RequestConfig) -> Self {
|
||||
self.request_config = request_config;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the proxy through which all the HTTP requests should go.
|
||||
///
|
||||
/// Note, only HTTP proxies are supported.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `proxy` - The HTTP URL of the proxy.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # futures::executor::block_on(async {
|
||||
/// use matrix_sdk::Client;
|
||||
///
|
||||
/// let client_config = Client::builder()
|
||||
/// .proxy("http://localhost:8080");
|
||||
///
|
||||
/// # Result::<_, matrix_sdk::Error>::Ok(())
|
||||
/// # });
|
||||
/// ```
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn proxy(mut self, proxy: impl AsRef<str>) -> Self {
|
||||
self.http_settings().proxy = Some(proxy.as_ref().to_owned());
|
||||
self
|
||||
}
|
||||
|
||||
/// Disable SSL verification for the HTTP requests.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn disable_ssl_verification(mut self) -> Self {
|
||||
self.http_settings().disable_ssl_verification = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set a custom HTTP user agent for the client.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn user_agent(mut self, user_agent: impl AsRef<str>) -> Self {
|
||||
self.http_settings().user_agent = Some(user_agent.as_ref().to_owned());
|
||||
self
|
||||
}
|
||||
|
||||
/// Specify an HTTP client to handle sending requests and receiving
|
||||
/// responses.
|
||||
///
|
||||
/// Any type that implements the `HttpSend` trait can be used to send /
|
||||
/// receive `http` types.
|
||||
///
|
||||
/// This method is mutually exclusive with
|
||||
/// [`user_agent()`][Self::user_agent],
|
||||
pub fn http_client(mut self, client: Arc<dyn HttpSend>) -> Self {
|
||||
self.http_cfg = Some(HttpConfig::Custom(client));
|
||||
self
|
||||
}
|
||||
|
||||
/// Puts the client into application service mode
|
||||
///
|
||||
/// This is low-level functionality. For an high-level API check the
|
||||
/// `matrix_sdk_appservice` crate.
|
||||
#[doc(hidden)]
|
||||
#[cfg(feature = "appservice")]
|
||||
pub fn appservice_mode(mut self) -> Self {
|
||||
self.appservice_mode = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// All outgoing http requests will have a GET query key-value appended with
|
||||
/// `user_id` being the key and the `user_id` from the `Session` being
|
||||
/// the value. Will error if there's no `Session`. This is called
|
||||
/// [identity assertion] in the Matrix Application Service Spec
|
||||
///
|
||||
/// [identity assertion]: https://spec.matrix.org/unstable/application-service-api/#identity-assertion
|
||||
#[doc(hidden)]
|
||||
#[cfg(feature = "appservice")]
|
||||
pub fn assert_identity(mut self) -> Self {
|
||||
self.request_config.assert_identity = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Specify the Matrix versions supported by the homeserver manually, rather
|
||||
/// than `build()` doing it using a `get_supported_versions` request.
|
||||
///
|
||||
/// This is helpful for test code that doesn't care to mock that endpoint.
|
||||
pub fn server_versions(mut self, value: impl IntoIterator<Item = MatrixVersion>) -> Self {
|
||||
self.server_versions = Some(value.into_iter().collect());
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn http_settings(&mut self) -> &mut HttpSettings {
|
||||
self.http_cfg.get_or_insert_with(Default::default).settings()
|
||||
}
|
||||
|
||||
/// Create a [`Client`] with the options set on this builder.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This method can fail for two general reasons:
|
||||
///
|
||||
/// * Invalid input: a missing or invalid homeserver URL or invalid proxy
|
||||
/// URL
|
||||
/// * HTTP error: If you supplied a user ID instead of a homeserver URL, a
|
||||
/// server discovery request is made which can fail; if you didn't set
|
||||
/// [`server_versions(false)`][Self::server_versions], that amounts to
|
||||
/// another request that can fail
|
||||
pub async fn build(self) -> Result<Client, ClientBuildError> {
|
||||
let homeserver_cfg = self.homeserver_cfg.ok_or(ClientBuildError::MissingHomeserver)?;
|
||||
|
||||
let inner_http_client = match self.http_cfg.unwrap_or_default() {
|
||||
#[allow(unused_mut)]
|
||||
HttpConfig::Settings(mut settings) => {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
settings.timeout = self.request_config.timeout;
|
||||
}
|
||||
|
||||
Arc::new(settings.make_client()?)
|
||||
}
|
||||
HttpConfig::Custom(c) => c,
|
||||
};
|
||||
|
||||
let base_client = BaseClient::with_store_config(self.store_config);
|
||||
|
||||
let mk_http_client = |homeserver| {
|
||||
HttpClient::new(
|
||||
inner_http_client.clone(),
|
||||
homeserver,
|
||||
base_client.session().clone(),
|
||||
self.request_config,
|
||||
)
|
||||
};
|
||||
|
||||
let homeserver = match homeserver_cfg {
|
||||
HomeserverConfig::Url(url) => url,
|
||||
HomeserverConfig::ServerName(server_name) => {
|
||||
let homeserver = homeserver_from_name(&server_name)?;
|
||||
let http_client = mk_http_client(Arc::new(RwLock::new(homeserver)));
|
||||
let well_known = http_client
|
||||
.send(
|
||||
discover_homeserver::Request::new(),
|
||||
None,
|
||||
[MatrixVersion::V1_0].into_iter().collect(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
well_known.homeserver.base_url
|
||||
}
|
||||
};
|
||||
|
||||
let homeserver = Arc::new(RwLock::new(Url::parse(&homeserver)?));
|
||||
let http_client = mk_http_client(homeserver.clone());
|
||||
|
||||
let server_versions = match self.server_versions {
|
||||
Some(vs) => vs,
|
||||
None => http_client
|
||||
.send(
|
||||
get_supported_versions::Request::new(),
|
||||
None,
|
||||
[MatrixVersion::V1_0].into_iter().collect(),
|
||||
)
|
||||
.await?
|
||||
.known_versions()
|
||||
.collect(),
|
||||
};
|
||||
|
||||
let inner = Arc::new(ClientInner {
|
||||
homeserver,
|
||||
http_client,
|
||||
base_client,
|
||||
server_versions,
|
||||
#[cfg(feature = "encryption")]
|
||||
group_session_locks: Default::default(),
|
||||
#[cfg(feature = "encryption")]
|
||||
key_claim_lock: Default::default(),
|
||||
members_request_locks: Default::default(),
|
||||
typing_notice_times: Default::default(),
|
||||
event_handlers: Default::default(),
|
||||
event_handler_data: Default::default(),
|
||||
notification_handlers: Default::default(),
|
||||
appservice_mode: self.appservice_mode,
|
||||
respect_login_well_known: self.respect_login_well_known,
|
||||
sync_beat: event_listener::Event::new(),
|
||||
});
|
||||
|
||||
Ok(Client { inner })
|
||||
}
|
||||
}
|
||||
|
||||
fn homeserver_from_name(server_name: &ServerName) -> Result<Url, url::ParseError> {
|
||||
#[cfg(not(test))]
|
||||
let homeserver = format!("https://{}", server_name);
|
||||
|
||||
// Mockito only knows how to test http endpoints:
|
||||
// https://github.com/lipanski/mockito/issues/127
|
||||
#[cfg(test)]
|
||||
let homeserver = format!("http://{}", server_name);
|
||||
|
||||
Url::parse(&homeserver)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum HomeserverConfig {
|
||||
Url(String),
|
||||
ServerName(Box<ServerName>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum HttpConfig {
|
||||
Settings(HttpSettings),
|
||||
Custom(Arc<dyn HttpSend>),
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl HttpConfig {
|
||||
fn settings(&mut self) -> &mut HttpSettings {
|
||||
match self {
|
||||
Self::Settings(s) => s,
|
||||
Self::Custom(_) => {
|
||||
*self = Self::default();
|
||||
match self {
|
||||
Self::Settings(s) => s,
|
||||
Self::Custom(_) => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for HttpConfig {
|
||||
fn default() -> Self {
|
||||
Self::Settings(HttpSettings::default())
|
||||
}
|
||||
}
|
||||
|
||||
/// Errors that can happen in [`ClientBuilder::build`].
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ClientBuildError {
|
||||
/// No homeserver or user ID was configured
|
||||
#[error("no homeserver or user ID was configured")]
|
||||
MissingHomeserver,
|
||||
|
||||
/// An error encountered when trying to parse the homeserver url.
|
||||
#[error(transparent)]
|
||||
Url(#[from] url::ParseError),
|
||||
|
||||
/// Error doing an HTTP request.
|
||||
#[error(transparent)]
|
||||
Http(#[from] HttpError),
|
||||
}
|
||||
|
||||
impl ClientBuildError {
|
||||
/// Assert that a valid homeserver URL was given to the builder and no other
|
||||
/// invalid options were specified, which means the only possible error
|
||||
/// case is [`Self::Http`].
|
||||
#[doc(hidden)]
|
||||
pub fn assert_valid_builder_args(self) -> HttpError {
|
||||
match self {
|
||||
ClientBuildError::Http(e) => e,
|
||||
_ => unreachable!("homeserver URL was asserted to be valid"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,7 @@ use ruma::{
|
||||
capabilities::{get_capabilities, Capabilities},
|
||||
device::{delete_devices, get_devices},
|
||||
directory::{get_public_rooms, get_public_rooms_filtered},
|
||||
discover::{discover_homeserver, get_supported_versions},
|
||||
discover::get_supported_versions,
|
||||
filter::{create_filter::v3::Request as FilterUploadRequest, FilterDefinition},
|
||||
media::{create_content, get_content, get_content_thumbnail},
|
||||
membership::{join_room_by_id, join_room_by_id_or_alias},
|
||||
@@ -65,15 +65,21 @@ use serde::de::DeserializeOwned;
|
||||
use tracing::{error, info, instrument, warn};
|
||||
use url::Url;
|
||||
|
||||
#[cfg(feature = "encryption")]
|
||||
use crate::encryption::Encryption;
|
||||
use crate::{
|
||||
attachment::{AttachmentInfo, Thumbnail},
|
||||
config::{ClientConfig, RequestConfig},
|
||||
config::RequestConfig,
|
||||
error::{HttpError, HttpResult},
|
||||
event_handler::{EventHandler, EventHandlerData, EventHandlerResult, EventKind, SyncEvent},
|
||||
http_client::{client_with_config, HttpClient},
|
||||
http_client::HttpClient,
|
||||
room, Account, Error, Result,
|
||||
};
|
||||
|
||||
mod builder;
|
||||
|
||||
pub use self::builder::{ClientBuildError, ClientBuilder};
|
||||
|
||||
/// A conservative upload speed of 1Mbps
|
||||
const DEFAULT_UPLOAD_SPEED: u64 = 125_000;
|
||||
/// 5 min minimal upload request timeout, used to clamp the request timeout.
|
||||
@@ -141,7 +147,7 @@ pub(crate) struct ClientInner {
|
||||
appservice_mode: bool,
|
||||
/// Whether the client should update its homeserver URL with the discovery
|
||||
/// information present in the login response.
|
||||
use_discovery_response: bool,
|
||||
respect_login_well_known: bool,
|
||||
/// An event that can be listened on to wait for a successful sync. The
|
||||
/// event will only be fired if a sync loop is running. Can be used for
|
||||
/// synchronization, e.g. if we send out a request to create a room, we can
|
||||
@@ -163,130 +169,17 @@ impl Client {
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `homeserver_url` - The homeserver that the client should connect to.
|
||||
pub async fn new(homeserver_url: Url) -> Result<Self> {
|
||||
let config = ClientConfig::new().await?;
|
||||
Client::new_with_config(homeserver_url, config).await
|
||||
pub async fn new(homeserver_url: Url) -> Result<Self, HttpError> {
|
||||
Self::builder()
|
||||
.homeserver_url(homeserver_url)
|
||||
.build()
|
||||
.await
|
||||
.map_err(ClientBuildError::assert_valid_builder_args)
|
||||
}
|
||||
|
||||
/// Create a new [`Client`] for the given homeserver and use the given
|
||||
/// configuration.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `homeserver_url` - The homeserver that the client should connect to.
|
||||
///
|
||||
/// * `config` - Configuration for the client.
|
||||
pub async fn new_with_config(homeserver_url: Url, config: ClientConfig) -> Result<Self> {
|
||||
let homeserver = Arc::new(RwLock::new(homeserver_url));
|
||||
|
||||
let client = if let Some(client) = config.client {
|
||||
client
|
||||
} else {
|
||||
Arc::new(client_with_config(&config)?)
|
||||
};
|
||||
|
||||
let base_client = BaseClient::new_with_config(config.base_config).await?;
|
||||
let session = base_client.session().clone();
|
||||
|
||||
let http_client =
|
||||
HttpClient::new(client, homeserver.clone(), session, config.request_config);
|
||||
|
||||
let server_versions = match config.server_versions {
|
||||
Some(vs) => vs,
|
||||
None => http_client
|
||||
.send(
|
||||
get_supported_versions::Request::new(),
|
||||
None,
|
||||
vec![MatrixVersion::V1_0].into(),
|
||||
)
|
||||
.await?
|
||||
.known_versions()
|
||||
.collect(),
|
||||
};
|
||||
|
||||
let inner = Arc::new(ClientInner {
|
||||
homeserver,
|
||||
http_client,
|
||||
base_client,
|
||||
server_versions,
|
||||
#[cfg(feature = "encryption")]
|
||||
group_session_locks: Default::default(),
|
||||
#[cfg(feature = "encryption")]
|
||||
key_claim_lock: Default::default(),
|
||||
members_request_locks: Default::default(),
|
||||
typing_notice_times: Default::default(),
|
||||
event_handlers: Default::default(),
|
||||
event_handler_data: Default::default(),
|
||||
notification_handlers: Default::default(),
|
||||
appservice_mode: config.appservice_mode,
|
||||
use_discovery_response: config.use_discovery_response,
|
||||
sync_beat: event_listener::Event::new(),
|
||||
});
|
||||
|
||||
Ok(Self { inner })
|
||||
}
|
||||
|
||||
/// Create a new [`Client`] using homeserver auto discovery.
|
||||
///
|
||||
/// This method will create a [`Client`] object that will attempt to
|
||||
/// discover and configure the homeserver for the given user. Follows the
|
||||
/// homeserver discovery directions described in the [spec].
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `user_id` - The id of the user whose homeserver the client should
|
||||
/// connect to.
|
||||
///
|
||||
/// # Example
|
||||
/// ```no_run
|
||||
/// # use std::convert::TryFrom;
|
||||
/// # use futures::executor::block_on;
|
||||
/// # block_on(async {
|
||||
/// use matrix_sdk::{Client, ruma::UserId};
|
||||
///
|
||||
/// // First let's try to construct an user id, presumably from user input.
|
||||
/// let alice = UserId::parse("@alice:example.org")?;
|
||||
///
|
||||
/// // Now let's try to discover the homeserver and create a client object.
|
||||
/// let client = Client::new_from_user_id(&alice).await?;
|
||||
///
|
||||
/// // Finally let's try to login.
|
||||
/// client.login(alice, "password", None, None).await?;
|
||||
/// # Result::<_, matrix_sdk::Error>::Ok(()) });
|
||||
/// ```
|
||||
///
|
||||
/// [spec]: https://spec.matrix.org/unstable/client-server-api/#well-known-uri
|
||||
pub async fn new_from_user_id(user_id: &UserId) -> Result<Self> {
|
||||
let config = ClientConfig::new().await?;
|
||||
Client::new_from_user_id_with_config(user_id, config).await
|
||||
}
|
||||
|
||||
/// Create a new [`Client`] using homeserver auto discovery.
|
||||
///
|
||||
/// This method will create a [`Client`] object that will attempt to
|
||||
/// discover and configure the homeserver for the given user. Follows the
|
||||
/// homeserver discovery directions described in the [spec].
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `user_id` - The id of the user whose homeserver the client should
|
||||
/// connect to.
|
||||
///
|
||||
/// * `config` - Configuration for the client.
|
||||
///
|
||||
/// [spec]: https://spec.matrix.org/unstable/client-server-api/#well-known-uri
|
||||
pub async fn new_from_user_id_with_config(
|
||||
user_id: &UserId,
|
||||
config: ClientConfig,
|
||||
) -> Result<Self> {
|
||||
let homeserver = Client::homeserver_from_user_id(user_id)?;
|
||||
let client = Client::new_with_config(homeserver, config).await?;
|
||||
|
||||
let well_known = client.discover_homeserver().await?;
|
||||
let well_known = Url::parse(well_known.homeserver.base_url.as_ref())?;
|
||||
client.set_homeserver(well_known).await;
|
||||
client.get_supported_versions().await?;
|
||||
Ok(client)
|
||||
/// Create a new [`ClientBuilder`].
|
||||
pub fn builder() -> ClientBuilder {
|
||||
ClientBuilder::new()
|
||||
}
|
||||
|
||||
pub(crate) fn base_client(&self) -> &BaseClient {
|
||||
@@ -307,22 +200,6 @@ impl Client {
|
||||
self.base_client().mark_request_as_sent(request_id, response).await
|
||||
}
|
||||
|
||||
fn homeserver_from_user_id(user_id: &UserId) -> Result<Url> {
|
||||
let homeserver = format!("https://{}", user_id.server_name());
|
||||
#[allow(unused_mut)]
|
||||
let mut result = Url::parse(homeserver.as_str())?;
|
||||
// Mockito only knows how to test http endpoints:
|
||||
// https://github.com/lipanski/mockito/issues/127
|
||||
#[cfg(test)]
|
||||
let _ = result.set_scheme("http");
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
async fn discover_homeserver(&self) -> HttpResult<discover_homeserver::Response> {
|
||||
self.send(discover_homeserver::Request::new(), Some(RequestConfig::new().disable_retry()))
|
||||
.await
|
||||
}
|
||||
|
||||
/// Change the homeserver URL used by this client.
|
||||
///
|
||||
/// # Arguments
|
||||
@@ -445,6 +322,12 @@ impl Client {
|
||||
Account::new(self.clone())
|
||||
}
|
||||
|
||||
/// Get the encryption manager of the client.
|
||||
#[cfg(feature = "encryption")]
|
||||
pub fn encryption(&self) -> Encryption {
|
||||
Encryption::new(self.clone())
|
||||
}
|
||||
|
||||
/// Register a handler for a specific event type.
|
||||
///
|
||||
/// The handler is a function or closure with one or more arguments. The
|
||||
@@ -488,7 +371,12 @@ impl Client {
|
||||
/// use serde::{Deserialize, Serialize};
|
||||
///
|
||||
/// # block_on(async {
|
||||
/// # let client = Client::new(homeserver).await.unwrap();
|
||||
/// # let client = matrix_sdk::Client::builder()
|
||||
/// # .homeserver_url(homeserver)
|
||||
/// # .check_supported_versions(false)
|
||||
/// # .build()
|
||||
/// # .await
|
||||
/// # .unwrap();
|
||||
/// client
|
||||
/// .register_event_handler(
|
||||
/// |ev: SyncRoomMessageEvent, room: Room, client: Client| async move {
|
||||
@@ -602,7 +490,12 @@ impl Client {
|
||||
/// # fn obtain_gui_handle() -> SomeType { SomeType }
|
||||
/// # let homeserver = url::Url::parse("http://localhost:8080").unwrap();
|
||||
/// # block_on(async {
|
||||
/// # let client = matrix_sdk::Client::new(homeserver).await.unwrap();
|
||||
/// # let client = matrix_sdk::Client::builder()
|
||||
/// # .homeserver_url(homeserver)
|
||||
/// # .check_supported_versions(false)
|
||||
/// # .build()
|
||||
/// # .await
|
||||
/// # .unwrap();
|
||||
///
|
||||
/// // Handle used to send messages to the UI part of the app
|
||||
/// let my_gui_handle: SomeType = obtain_gui_handle();
|
||||
@@ -1149,7 +1042,7 @@ impl Client {
|
||||
///
|
||||
/// * `response` - A successful login response.
|
||||
async fn receive_login_response(&self, response: &login::v3::Response) -> Result<()> {
|
||||
if self.inner.use_discovery_response {
|
||||
if self.inner.respect_login_well_known {
|
||||
if let Some(well_known) = &response.well_known {
|
||||
if let Ok(homeserver) = Url::parse(&well_known.homeserver.base_url) {
|
||||
self.set_homeserver(homeserver).await;
|
||||
@@ -2344,6 +2237,7 @@ impl Client {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// mockito (the http mocking library) is not supported for wasm32
|
||||
#[cfg(all(test, not(target_arch = "wasm32")))]
|
||||
pub(crate) mod test {
|
||||
@@ -2382,35 +2276,43 @@ pub(crate) mod test {
|
||||
message::{ImageMessageEventContent, RoomMessageEventContent},
|
||||
ImageInfo,
|
||||
},
|
||||
AnySyncStateEvent, EventType,
|
||||
AnySyncStateEvent, StateEventType,
|
||||
},
|
||||
mxc_uri, room_id, thirdparty, uint, user_id, TransactionId, UserId,
|
||||
};
|
||||
use serde_json::json;
|
||||
use url::Url;
|
||||
|
||||
use super::{Client, Session, Url};
|
||||
use super::{Client, ClientBuilder, Session};
|
||||
use crate::{
|
||||
attachment::{
|
||||
AttachmentConfig, AttachmentInfo, BaseImageInfo, BaseThumbnailInfo, BaseVideoInfo,
|
||||
Thumbnail,
|
||||
},
|
||||
config::{ClientConfig, RequestConfig, SyncSettings},
|
||||
config::{RequestConfig, SyncSettings},
|
||||
HttpError, RoomMember,
|
||||
};
|
||||
|
||||
fn test_client_builder() -> ClientBuilder {
|
||||
let homeserver = Url::parse(&mockito::server_url()).unwrap();
|
||||
Client::builder().homeserver_url(homeserver).server_versions([MatrixVersion::V1_0])
|
||||
}
|
||||
|
||||
async fn no_retry_test_client() -> Client {
|
||||
test_client_builder()
|
||||
.request_config(RequestConfig::new().disable_retry())
|
||||
.build()
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub(crate) async fn logged_in_client() -> Client {
|
||||
let session = Session {
|
||||
access_token: "1234".to_owned(),
|
||||
user_id: user_id!("@example:localhost").to_owned(),
|
||||
device_id: device_id!("DEVICEID").to_owned(),
|
||||
};
|
||||
let homeserver = url::Url::parse(&mockito::server_url()).unwrap();
|
||||
let config = ClientConfig::new()
|
||||
.await
|
||||
.unwrap()
|
||||
.request_config(RequestConfig::new().disable_retry())
|
||||
.server_versions([MatrixVersion::V1_0]);
|
||||
let client = Client::new_with_config(homeserver, config).await.unwrap();
|
||||
let client = no_retry_test_client().await;
|
||||
client.restore_login(session).await.unwrap();
|
||||
|
||||
client
|
||||
@@ -2418,12 +2320,8 @@ pub(crate) mod test {
|
||||
|
||||
#[async_test]
|
||||
async fn set_homeserver() {
|
||||
let client = no_retry_test_client().await;
|
||||
let homeserver = Url::from_str("http://example.com/").unwrap();
|
||||
|
||||
let client = Client::new(homeserver).await.unwrap();
|
||||
|
||||
let homeserver = Url::from_str(&mockito::server_url()).unwrap();
|
||||
|
||||
client.set_homeserver(homeserver.clone()).await;
|
||||
|
||||
assert_eq!(client.homeserver().await, homeserver);
|
||||
@@ -2446,7 +2344,7 @@ pub(crate) mod test {
|
||||
.with_status(200)
|
||||
.with_body(test_json::VERSIONS.to_string())
|
||||
.create();
|
||||
let client = Client::new_from_user_id(&alice).await.unwrap();
|
||||
let client = Client::builder().user_id(&alice).build().await.unwrap();
|
||||
|
||||
assert_eq!(client.homeserver().await, Url::parse(server_url.as_ref()).unwrap());
|
||||
}
|
||||
@@ -2465,7 +2363,7 @@ pub(crate) mod test {
|
||||
.create();
|
||||
|
||||
assert!(
|
||||
Client::new_from_user_id(&alice).await.is_err(),
|
||||
Client::builder().user_id(&alice).build().await.is_err(),
|
||||
"Creating a client from a user ID should fail when the \
|
||||
.well-known server returns no version information."
|
||||
);
|
||||
@@ -2474,9 +2372,7 @@ pub(crate) mod test {
|
||||
#[async_test]
|
||||
async fn login() {
|
||||
let homeserver = Url::from_str(&mockito::server_url()).unwrap();
|
||||
|
||||
let config = ClientConfig::new().await.unwrap().server_versions([MatrixVersion::V1_0]);
|
||||
let client = Client::new_with_config(homeserver.clone(), config).await.unwrap();
|
||||
let client = no_retry_test_client().await;
|
||||
|
||||
let _m_types = mock("GET", "/_matrix/client/r0/login")
|
||||
.with_status(200)
|
||||
@@ -2507,14 +2403,7 @@ pub(crate) mod test {
|
||||
|
||||
#[async_test]
|
||||
async fn login_with_discovery() {
|
||||
let homeserver = Url::from_str(&mockito::server_url()).unwrap();
|
||||
let config = ClientConfig::new()
|
||||
.await
|
||||
.unwrap()
|
||||
.use_discovery_response()
|
||||
.server_versions([MatrixVersion::V1_0]);
|
||||
|
||||
let client = Client::new_with_config(homeserver, config).await.unwrap();
|
||||
let client = no_retry_test_client().await;
|
||||
|
||||
let _m_login = mock("POST", "/_matrix/client/r0/login")
|
||||
.with_status(200)
|
||||
@@ -2531,14 +2420,7 @@ pub(crate) mod test {
|
||||
|
||||
#[async_test]
|
||||
async fn login_no_discovery() {
|
||||
let homeserver = Url::from_str(&mockito::server_url()).unwrap();
|
||||
let config = ClientConfig::new()
|
||||
.await
|
||||
.unwrap()
|
||||
.use_discovery_response()
|
||||
.server_versions([MatrixVersion::V1_0]);
|
||||
|
||||
let client = Client::new_with_config(homeserver.clone(), config).await.unwrap();
|
||||
let client = no_retry_test_client().await;
|
||||
|
||||
let _m_login = mock("POST", "/_matrix/client/r0/login")
|
||||
.with_status(200)
|
||||
@@ -2550,7 +2432,7 @@ pub(crate) mod test {
|
||||
let logged_in = client.logged_in().await;
|
||||
assert!(logged_in, "Client should be logged in");
|
||||
|
||||
assert_eq!(client.homeserver().await, homeserver);
|
||||
assert_eq!(client.homeserver().await, Url::parse(&mockito::server_url()).unwrap());
|
||||
}
|
||||
|
||||
#[cfg(feature = "sso_login")]
|
||||
@@ -2562,8 +2444,7 @@ pub(crate) mod test {
|
||||
.create();
|
||||
|
||||
let homeserver = Url::from_str(&mockito::server_url()).unwrap();
|
||||
let config = ClientConfig::new().await.unwrap().server_versions([MatrixVersion::V1_0]);
|
||||
let client = Client::new_with_config(homeserver, config).await.unwrap();
|
||||
let client = no_retry_test_client().await;
|
||||
let idp = crate::client::get_login_types::v3::IdentityProvider::new(
|
||||
"some-id".to_owned(),
|
||||
"idp-name".to_owned(),
|
||||
@@ -2598,10 +2479,7 @@ pub(crate) mod test {
|
||||
|
||||
#[async_test]
|
||||
async fn login_with_sso_token() {
|
||||
let homeserver = Url::from_str(&mockito::server_url()).unwrap();
|
||||
|
||||
let config = ClientConfig::new().await.unwrap().server_versions([MatrixVersion::V1_0]);
|
||||
let client = Client::new_with_config(homeserver, config).await.unwrap();
|
||||
let client = no_retry_test_client().await;
|
||||
|
||||
let _m = mock("GET", "/_matrix/client/r0/login")
|
||||
.with_status(200)
|
||||
@@ -2645,7 +2523,6 @@ pub(crate) mod test {
|
||||
|
||||
#[async_test]
|
||||
async fn test_join_leave_room() {
|
||||
let homeserver = Url::from_str(&mockito::server_url()).unwrap();
|
||||
let room_id = room_id!("!SVkFJHzfwvuaIEawgC:localhost");
|
||||
|
||||
let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned()))
|
||||
@@ -2667,13 +2544,8 @@ pub(crate) mod test {
|
||||
let room = client.get_joined_room(room_id);
|
||||
assert!(room.is_some());
|
||||
|
||||
// test store reloads with correct room state from the sled store
|
||||
let path = tempfile::tempdir().unwrap();
|
||||
let config = ClientConfig::with_named_store(path.into_path().to_str().unwrap(), None)
|
||||
.await
|
||||
.unwrap()
|
||||
.request_config(RequestConfig::new().disable_retry());
|
||||
let joined_client = Client::new_with_config(homeserver, config).await.unwrap();
|
||||
// test store reloads with correct room state from the state store
|
||||
let joined_client = no_retry_test_client().await;
|
||||
joined_client.restore_login(session).await.unwrap();
|
||||
|
||||
// joined room reloaded from state store
|
||||
@@ -2733,9 +2605,7 @@ pub(crate) mod test {
|
||||
|
||||
#[async_test]
|
||||
async fn login_error() {
|
||||
let homeserver = Url::from_str(&mockito::server_url()).unwrap();
|
||||
let config = ClientConfig::default().request_config(RequestConfig::new().disable_retry());
|
||||
let client = Client::new_with_config(homeserver, config).await.unwrap();
|
||||
let client = no_retry_test_client().await;
|
||||
|
||||
let _m = mock("POST", "/_matrix/client/r0/login")
|
||||
.with_status(403)
|
||||
@@ -2763,8 +2633,7 @@ pub(crate) mod test {
|
||||
|
||||
#[async_test]
|
||||
async fn register_error() {
|
||||
let homeserver = Url::from_str(&mockito::server_url()).unwrap();
|
||||
let client = Client::new(homeserver).await.unwrap();
|
||||
let client = no_retry_test_client().await;
|
||||
|
||||
let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/register\?.*$".to_owned()))
|
||||
.with_status(403)
|
||||
@@ -2907,8 +2776,7 @@ pub(crate) mod test {
|
||||
|
||||
#[async_test]
|
||||
async fn room_search_all() {
|
||||
let homeserver = Url::from_str(&mockito::server_url()).unwrap();
|
||||
let client = Client::new(homeserver).await.unwrap();
|
||||
let client = no_retry_test_client().await;
|
||||
|
||||
let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/publicRooms".to_owned()))
|
||||
.with_status(200)
|
||||
@@ -3593,8 +3461,7 @@ pub(crate) mod test {
|
||||
|
||||
#[async_test]
|
||||
async fn delete_devices() {
|
||||
let homeserver = Url::from_str(&mockito::server_url()).unwrap();
|
||||
let client = Client::new(homeserver).await.unwrap();
|
||||
let client = no_retry_test_client().await;
|
||||
|
||||
let _m = mock("POST", "/_matrix/client/r0/delete_devices")
|
||||
.with_status(401)
|
||||
@@ -3649,10 +3516,13 @@ pub(crate) mod test {
|
||||
|
||||
#[async_test]
|
||||
async fn retry_limit_http_requests() {
|
||||
let homeserver = Url::from_str(&mockito::server_url()).unwrap();
|
||||
let config = ClientConfig::default().request_config(RequestConfig::new().retry_limit(3));
|
||||
assert!(config.request_config.retry_limit.unwrap() == 3);
|
||||
let client = Client::new_with_config(homeserver, config).await.unwrap();
|
||||
let client = test_client_builder()
|
||||
.request_config(RequestConfig::new().retry_limit(3))
|
||||
.build()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(client.inner.http_client.request_config.retry_limit.unwrap() == 3);
|
||||
|
||||
let m = mock("POST", "/_matrix/client/r0/login").with_status(501).expect(3).create();
|
||||
|
||||
@@ -3665,13 +3535,15 @@ pub(crate) mod test {
|
||||
|
||||
#[async_test]
|
||||
async fn retry_timeout_http_requests() {
|
||||
let homeserver = Url::from_str(&mockito::server_url()).unwrap();
|
||||
// Keep this timeout small so that the test doesn't take long
|
||||
let retry_timeout = Duration::from_secs(5);
|
||||
let config = ClientConfig::default()
|
||||
.request_config(RequestConfig::new().retry_timeout(retry_timeout));
|
||||
assert!(config.request_config.retry_timeout.unwrap() == retry_timeout);
|
||||
let client = Client::new_with_config(homeserver, config).await.unwrap();
|
||||
let client = test_client_builder()
|
||||
.request_config(RequestConfig::new().retry_timeout(retry_timeout))
|
||||
.build()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(client.inner.http_client.request_config.retry_timeout.unwrap() == retry_timeout);
|
||||
|
||||
let m =
|
||||
mock("POST", "/_matrix/client/r0/login").with_status(501).expect_at_least(2).create();
|
||||
@@ -3685,8 +3557,7 @@ pub(crate) mod test {
|
||||
|
||||
#[async_test]
|
||||
async fn short_retry_initial_http_requests() {
|
||||
let homeserver = Url::from_str(&mockito::server_url()).unwrap();
|
||||
let client = Client::new(homeserver).await.unwrap();
|
||||
let client = test_client_builder().build().await.unwrap();
|
||||
|
||||
let m =
|
||||
mock("POST", "/_matrix/client/r0/login").with_status(501).expect_at_least(3).create();
|
||||
@@ -3798,7 +3669,6 @@ pub(crate) mod test {
|
||||
|
||||
#[async_test]
|
||||
async fn test_state_event_getting() {
|
||||
let homeserver = Url::from_str(&mockito::server_url()).unwrap();
|
||||
let room_id = room_id!("!SVkFJHzfwvuaIEawgC:localhost");
|
||||
|
||||
let session = Session {
|
||||
@@ -3867,8 +3737,11 @@ pub(crate) mod test {
|
||||
.with_body(sync.to_string())
|
||||
.create();
|
||||
|
||||
let config = ClientConfig::default().request_config(RequestConfig::new().retry_limit(3));
|
||||
let client = Client::new_with_config(homeserver.clone(), config).await.unwrap();
|
||||
let client = test_client_builder()
|
||||
.request_config(RequestConfig::new().retry_limit(3))
|
||||
.build()
|
||||
.await
|
||||
.unwrap();
|
||||
client.restore_login(session.clone()).await.unwrap();
|
||||
|
||||
let room = client.get_joined_room(room_id);
|
||||
@@ -3878,14 +3751,14 @@ pub(crate) mod test {
|
||||
|
||||
let room = client.get_joined_room(room_id).unwrap();
|
||||
|
||||
let state_events = room.get_state_events(EventType::RoomEncryption).await.unwrap();
|
||||
let state_events = room.get_state_events(StateEventType::RoomEncryption).await.unwrap();
|
||||
assert_eq!(state_events.len(), 1);
|
||||
|
||||
let state_events = room.get_state_events("m.custom.note".into()).await.unwrap();
|
||||
assert_eq!(state_events.len(), 2);
|
||||
|
||||
let encryption_event = room
|
||||
.get_state_event(EventType::RoomEncryption, "")
|
||||
.get_state_event(StateEventType::RoomEncryption, "")
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
@@ -1,293 +0,0 @@
|
||||
// Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use std::{
|
||||
fmt::{self, Debug},
|
||||
path::Path,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use http::header::InvalidHeaderValue;
|
||||
use matrix_sdk_base::{BaseClientConfig, StateStore};
|
||||
use ruma::api::MatrixVersion;
|
||||
|
||||
use crate::{config::RequestConfig, HttpSend, Result};
|
||||
|
||||
/// Configuration for the creation of the `Client`.
|
||||
///
|
||||
/// When setting the `StateStore` it is up to the user to open/connect
|
||||
/// the storage backend before client creation.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use matrix_sdk::config::ClientConfig;
|
||||
/// // To pass all the request through mitmproxy set the proxy and disable SSL
|
||||
/// // verification
|
||||
///
|
||||
/// # futures::executor::block_on(async {
|
||||
/// let client_config = ClientConfig::new().await?
|
||||
/// .proxy("http://localhost:8080")?
|
||||
/// .disable_ssl_verification();
|
||||
/// # matrix_sdk::Result::<()>::Ok(())
|
||||
/// # });
|
||||
/// ```
|
||||
///
|
||||
/// # Example for using a custom client
|
||||
/// Note: setting a custom client will ignore `user_agent`, `proxy`, and
|
||||
/// `disable_ssl_verification` - you'd need to set these yourself if you
|
||||
/// want them.
|
||||
///
|
||||
/// ```
|
||||
/// use matrix_sdk::config::ClientConfig;
|
||||
/// use reqwest::ClientBuilder;
|
||||
/// use std::sync::Arc;
|
||||
///
|
||||
/// // setting up a custom builder
|
||||
/// let builder = ClientBuilder::new()
|
||||
/// .https_only(true)
|
||||
/// .no_proxy()
|
||||
/// .user_agent("MyApp/v3.0");
|
||||
///
|
||||
/// # futures::executor::block_on(async {
|
||||
/// let client_config = ClientConfig::new().await?
|
||||
/// .client(Arc::new(builder.build()?));
|
||||
/// # matrix_sdk::Result::<()>::Ok(())
|
||||
/// # });
|
||||
/// ```
|
||||
#[derive(Default)]
|
||||
pub struct ClientConfig {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) proxy: Option<reqwest::Proxy>,
|
||||
pub(crate) user_agent: Option<String>,
|
||||
pub(crate) disable_ssl_verification: bool,
|
||||
pub(crate) base_config: BaseClientConfig,
|
||||
pub(crate) request_config: RequestConfig,
|
||||
pub(crate) client: Option<Arc<dyn HttpSend>>,
|
||||
pub(crate) appservice_mode: bool,
|
||||
pub(crate) use_discovery_response: bool,
|
||||
pub(crate) server_versions: Option<Arc<[MatrixVersion]>>,
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
impl Debug for ClientConfig {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut res = fmt.debug_struct("ClientConfig");
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let res = res.field("proxy", &self.proxy);
|
||||
|
||||
res.field("user_agent", &self.user_agent)
|
||||
.field("disable_ssl_verification", &self.disable_ssl_verification)
|
||||
.field("request_config", &self.request_config)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "sled_state_store")]
|
||||
mod store_helpers {
|
||||
use matrix_sdk_sled::StateStore;
|
||||
|
||||
use super::Result;
|
||||
|
||||
/// Build the sled Store with the default settings - as a temporary storage
|
||||
pub async fn default_store() -> Result<Box<StateStore>> {
|
||||
Ok(Box::new(StateStore::open()?))
|
||||
}
|
||||
|
||||
/// Build a sled store at `name` (being a relative or full path), and open
|
||||
/// the store with the given passphrase (if given) for encryption
|
||||
pub async fn default_store_with_name(
|
||||
name: &str,
|
||||
passphrase: Option<&str>,
|
||||
) -> Result<Box<StateStore>> {
|
||||
Ok(Box::new(match passphrase {
|
||||
Some(pass) => StateStore::open_with_passphrase(name, pass)?,
|
||||
_ => StateStore::open_with_path(&name)?,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "indexeddb_stores")]
|
||||
mod store_helpers {
|
||||
use matrix_sdk_indexeddb::StateStore;
|
||||
|
||||
use super::Result;
|
||||
|
||||
/// Open the IndexedDB store with the default name, unencrypted
|
||||
pub async fn default_store() -> Result<Box<StateStore>> {
|
||||
Ok(Box::new(StateStore::open().await?))
|
||||
}
|
||||
|
||||
/// Open the indexeddb store at `name` (IndexedDB Database name), and open
|
||||
/// the store with the given passphrase (if given) for encryption
|
||||
pub async fn default_store_with_name(
|
||||
name: &str,
|
||||
passphrase: Option<&str>,
|
||||
) -> Result<Box<StateStore>> {
|
||||
Ok(Box::new(match passphrase {
|
||||
Some(pass) => StateStore::open_with_passphrase(name.to_owned(), pass).await?,
|
||||
_ => StateStore::open_with_name(name.to_owned()).await?,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "indexeddb_stores", feature = "sled_state_store")))]
|
||||
mod store_helpers {
|
||||
use matrix_sdk_base::store::MemoryStore as StateStore;
|
||||
|
||||
use super::Result;
|
||||
/// Open a new in-memory StateStore
|
||||
pub async fn default_store() -> Result<Box<StateStore>> {
|
||||
Ok(Box::new(StateStore::new()))
|
||||
}
|
||||
|
||||
/// Alias for `default_store` - in Memory Stores are never named
|
||||
pub async fn default_store_with_name(
|
||||
_name: &str,
|
||||
_passphrase: Option<&str>,
|
||||
) -> Result<Box<StateStore>> {
|
||||
Ok(Box::new(StateStore::new()))
|
||||
}
|
||||
}
|
||||
|
||||
pub use store_helpers::{default_store, default_store_with_name};
|
||||
|
||||
impl ClientConfig {
|
||||
/// Create a new default `ClientConfig`.
|
||||
pub async fn new() -> Result<Self> {
|
||||
let mut d = Self::default();
|
||||
d.base_config = d.base_config.state_store(default_store().await?);
|
||||
Ok(d)
|
||||
}
|
||||
|
||||
/// Create a new ClientConfig with a named state store, encrypted with the
|
||||
/// given passphrase (if any)
|
||||
pub async fn with_named_store(name: &str, passphrase: Option<&str>) -> Result<Self> {
|
||||
let mut d = Self::default();
|
||||
d.base_config = d.base_config.state_store(default_store_with_name(name, passphrase).await?);
|
||||
Ok(d)
|
||||
}
|
||||
|
||||
/// Set the proxy through which all the HTTP requests should go.
|
||||
///
|
||||
/// Note, only HTTP proxies are supported.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `proxy` - The HTTP URL of the proxy.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # futures::executor::block_on(async {
|
||||
/// use matrix_sdk::{Client, config::ClientConfig};
|
||||
///
|
||||
/// let client_config = ClientConfig::new().await?
|
||||
/// .proxy("http://localhost:8080")?;
|
||||
///
|
||||
/// # Result::<_, matrix_sdk::Error>::Ok(())
|
||||
/// # });
|
||||
/// ```
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn proxy(mut self, proxy: &str) -> Result<Self> {
|
||||
self.proxy = Some(reqwest::Proxy::all(proxy)?);
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Disable SSL verification for the HTTP requests.
|
||||
#[must_use]
|
||||
pub fn disable_ssl_verification(mut self) -> Self {
|
||||
self.disable_ssl_verification = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set a custom HTTP user agent for the client.
|
||||
pub fn user_agent(mut self, user_agent: &str) -> Result<Self, InvalidHeaderValue> {
|
||||
self.user_agent = Some(user_agent.to_owned());
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Set a custom implementation of a `StateStore`.
|
||||
///
|
||||
/// The state store should be opened before being set.
|
||||
pub fn state_store(mut self, store: Box<dyn StateStore>) -> Self {
|
||||
self.base_config = self.base_config.state_store(store);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the default timeout, fail and retry behavior for all HTTP requests.
|
||||
#[must_use]
|
||||
pub fn request_config(mut self, request_config: RequestConfig) -> Self {
|
||||
self.request_config = request_config;
|
||||
self
|
||||
}
|
||||
|
||||
/// Get the [`RequestConfig`]
|
||||
pub fn get_request_config(&self) -> &RequestConfig {
|
||||
&self.request_config
|
||||
}
|
||||
|
||||
/// Specify a client to handle sending requests and receiving responses.
|
||||
///
|
||||
/// Any type that implements the `HttpSend` trait can be used to
|
||||
/// send/receive `http` types.
|
||||
#[must_use]
|
||||
pub fn client(mut self, client: Arc<dyn HttpSend>) -> Self {
|
||||
self.client = Some(client);
|
||||
self
|
||||
}
|
||||
|
||||
/// Puts the client into application service mode
|
||||
///
|
||||
/// This is low-level functionality. For an high-level API check the
|
||||
/// `matrix_sdk_appservice` crate.
|
||||
#[cfg(feature = "appservice")]
|
||||
#[must_use]
|
||||
pub fn appservice_mode(mut self) -> Self {
|
||||
self.appservice_mode = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set a custom implementation of a `CryptoStore`.
|
||||
///
|
||||
/// The crypto store should be opened before being set.
|
||||
#[cfg(feature = "encryption")]
|
||||
#[must_use]
|
||||
pub fn crypto_store(
|
||||
mut self,
|
||||
store: Box<dyn matrix_sdk_base::crypto::store::CryptoStore>,
|
||||
) -> Self {
|
||||
self.base_config = self.base_config.crypto_store(store);
|
||||
self
|
||||
}
|
||||
|
||||
/// Update the client's homeserver URL with the discovery information
|
||||
/// present in the login response, if any.
|
||||
#[must_use]
|
||||
pub fn use_discovery_response(mut self) -> Self {
|
||||
self.use_discovery_response = true;
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn server_versions(
|
||||
mut self,
|
||||
server_versions: impl IntoIterator<Item = MatrixVersion>,
|
||||
) -> Self {
|
||||
self.server_versions = Some(server_versions.into_iter().collect());
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -12,14 +12,11 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Configuration to change the behaviour of the [`Client`].
|
||||
//!
|
||||
//! [`Client`]: #crate.Client
|
||||
//! Configuration to change the behaviour of the [`Client`][crate::Client].
|
||||
|
||||
mod client;
|
||||
mod request;
|
||||
mod sync;
|
||||
|
||||
pub use client::{default_store, default_store_with_name, ClientConfig};
|
||||
pub use matrix_sdk_base::store::StoreConfig;
|
||||
pub use request::RequestConfig;
|
||||
pub use sync::SyncSettings;
|
||||
|
||||
@@ -17,7 +17,7 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
const DEFAULT_REQUEST_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
use crate::http_client::DEFAULT_REQUEST_TIMEOUT;
|
||||
|
||||
/// Configuration for requests the `Client` makes.
|
||||
///
|
||||
@@ -121,17 +121,4 @@ impl RequestConfig {
|
||||
self.force_auth = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// All outgoing http requests will have a GET query key-value appended with
|
||||
/// `user_id` being the key and the `user_id` from the `Session` being
|
||||
/// the value. Will error if there's no `Session`. This is called
|
||||
/// [identity assertion] in the Matrix Application Service Spec
|
||||
///
|
||||
/// [identity assertion]: https://spec.matrix.org/unstable/application-service-api/#identity-assertion
|
||||
#[cfg(feature = "appservice")]
|
||||
#[must_use]
|
||||
pub fn assert_identity(mut self) -> Self {
|
||||
self.assert_identity = true;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ stored, otherwise we won't be able to decrypt historical messages. The SDK
|
||||
stores all room keys locally in a encrypted manner.
|
||||
|
||||
Besides storing them as part of the SDK store, users can export room keys
|
||||
using the [`Client::export_keys`] method.
|
||||
using the [`Encryption::export_keys`] method.
|
||||
|
||||
# Verification
|
||||
|
||||
@@ -165,8 +165,10 @@ unverified devices, verifying devices is **not** necessary for encryption
|
||||
to work.
|
||||
|
||||
1. Make sure the `encryption` feature is enabled.
|
||||
2. Ensure you have a persistent storage backend, either by activating the
|
||||
`sled_state_store`-feature or providing one via [`ClientConfig.state_store`]
|
||||
2. To persist the encryption keys, you can use one of the provided backend
|
||||
constructors as described in the documentation of the [`store`] module or you
|
||||
can provide your own backend that implements [`CryptoStore`] in a
|
||||
[`StoreConfig`] or via [`ClientBuilder::crypto_store()`].
|
||||
|
||||
## Restoring a client
|
||||
|
||||
@@ -228,3 +230,7 @@ is **not** supported using the default store.
|
||||
[Restoring a Client]: #restoring-a-client
|
||||
[spec]: https://spec.matrix.org/unstable/client-server-api/#relationship-between-access-tokens-and-devices
|
||||
[device keys]: https://spec.matrix.org/unstable/client-server-api/#device-keys
|
||||
[`store`]: crate::store
|
||||
[`CryptoStore`]: matrix_sdk_base::crypto::store::CryptoStore
|
||||
[`StoreConfig`]: crate::config::StoreConfig
|
||||
[`ClientBuilder::crypto_store()`]: crate::ClientBuilder::crypto_store()
|
||||
|
||||
@@ -89,7 +89,7 @@ impl Device {
|
||||
/// # let alice = user_id!("@alice:example.org");
|
||||
/// # let homeserver = Url::parse("http://example.com")?;
|
||||
/// # let client = Client::new(homeserver).await?;
|
||||
/// let device = client.get_device(alice, device_id!("DEVICEID")).await?;
|
||||
/// let device = client.encryption().get_device(alice, device_id!("DEVICEID")).await?;
|
||||
///
|
||||
/// if let Some(device) = device {
|
||||
/// let verification = device.request_verification().await?;
|
||||
@@ -137,7 +137,7 @@ impl Device {
|
||||
/// # let alice = user_id!("@alice:example.org");
|
||||
/// # let homeserver = Url::parse("http://example.com")?;
|
||||
/// # let client = Client::new(homeserver).await?;
|
||||
/// let device = client.get_device(alice, device_id!("DEVICEID")).await?;
|
||||
/// let device = client.encryption().get_device(alice, device_id!("DEVICEID")).await?;
|
||||
///
|
||||
/// // We don't want to support showing a QR code, we only support SAS
|
||||
/// // verification
|
||||
@@ -179,7 +179,7 @@ impl Device {
|
||||
/// # let alice = user_id!("@alice:example.org");
|
||||
/// # let homeserver = Url::parse("http://example.com")?;
|
||||
/// # let client = Client::new(homeserver).await?;
|
||||
/// let device = client.get_device(alice, device_id!("DEVICEID")).await?;
|
||||
/// let device = client.encryption().get_device(alice, device_id!("DEVICEID")).await?;
|
||||
///
|
||||
/// if let Some(device) = device {
|
||||
/// let verification = device.start_verification().await?;
|
||||
@@ -212,9 +212,9 @@ impl Device {
|
||||
/// key.
|
||||
///
|
||||
/// The state of our private cross signing keys can be inspected using the
|
||||
/// [`Client::cross_signing_status()`] method.
|
||||
/// [`Encryption::cross_signing_status()`] method.
|
||||
///
|
||||
/// [`Client::cross_signing_status()`]: crate::Client::cross_signing_status
|
||||
/// [`Encryption::cross_signing_status()`]: crate::encryption::Encryption::cross_signing_status
|
||||
///
|
||||
/// ### Problems of manual verification
|
||||
///
|
||||
@@ -243,7 +243,7 @@ impl Device {
|
||||
/// # let alice = user_id!("@alice:example.org");
|
||||
/// # let homeserver = Url::parse("http://example.com")?;
|
||||
/// # let client = Client::new(homeserver).await?;
|
||||
/// let device = client.get_device(alice, device_id!("DEVICEID")).await?;
|
||||
/// let device = client.encryption().get_device(alice, device_id!("DEVICEID")).await?;
|
||||
///
|
||||
/// if let Some(device) = device {
|
||||
/// device.verify().await?;
|
||||
@@ -358,7 +358,7 @@ impl Device {
|
||||
/// # let alice = user_id!("@alice:example.org");
|
||||
/// # let homeserver = Url::parse("http://example.com")?;
|
||||
/// # let client = Client::new(homeserver).await?;
|
||||
/// let device = client.get_device(alice, device_id!("DEVICEID")).await?;
|
||||
/// let device = client.encryption().get_device(alice, device_id!("DEVICEID")).await?;
|
||||
///
|
||||
/// if let Some(device) = device {
|
||||
/// if device.verified() {
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
//! # let homeserver = Url::parse("http://example.com").unwrap();
|
||||
//! # block_on(async {
|
||||
//! # let client = Client::new(homeserver).await.unwrap();
|
||||
//! let device = client.get_device(alice, device_id!("DEVICEID")).await?;
|
||||
//! let device = client.encryption().get_device(alice, device_id!("DEVICEID")).await?;
|
||||
//!
|
||||
//! if let Some(device) = device {
|
||||
//! // Let's request the device to be verified.
|
||||
@@ -69,7 +69,7 @@
|
||||
//! # let homeserver = Url::parse("http://example.com").unwrap();
|
||||
//! # block_on(async {
|
||||
//! # let client = Client::new(homeserver).await.unwrap();
|
||||
//! let user = client.get_user_identity(alice).await?;
|
||||
//! let user = client.encryption().get_user_identity(alice).await?;
|
||||
//!
|
||||
//! if let Some(user) = user {
|
||||
//! // Let's request the user to be verified.
|
||||
|
||||
@@ -35,8 +35,8 @@ use crate::{encryption::verification::VerificationRequest, room::Joined, Client}
|
||||
///
|
||||
/// The identity is backed by public [cross signing] keys that users upload. If
|
||||
/// our own user doesn't yet have such an identity, a new one can be created and
|
||||
/// uploaded to the server using [`Client::bootstrap_cross_signing()`]. The user
|
||||
/// identity can be also reset using the same method.
|
||||
/// uploaded to the server using [`Encryption::bootstrap_cross_signing()`]. The
|
||||
/// user identity can be also reset using the same method.
|
||||
///
|
||||
/// The user identity consists of three separate `Ed25519` keypairs:
|
||||
///
|
||||
@@ -63,6 +63,7 @@ use crate::{encryption::verification::VerificationRequest, room::Joined, Client}
|
||||
/// let us know whom the user verified.
|
||||
///
|
||||
/// [cross signing]: https://spec.matrix.org/unstable/client-server-api/#cross-signing
|
||||
/// [`Encryption::bootstrap_cross_signing()`]: crate::encryption::Encryption::bootstrap_cross_signing
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UserIdentity {
|
||||
inner: UserIdentities,
|
||||
@@ -97,7 +98,7 @@ impl UserIdentity {
|
||||
/// # let homeserver = Url::parse("http://example.com").unwrap();
|
||||
/// # futures::executor::block_on(async {
|
||||
/// # let client = Client::new(homeserver).await.unwrap();
|
||||
/// let user = client.get_user_identity(alice).await?;
|
||||
/// let user = client.encryption().get_user_identity(alice).await?;
|
||||
///
|
||||
/// if let Some(user) = user {
|
||||
/// println!("This user identity belongs to {}", user.user_id().as_str());
|
||||
@@ -149,7 +150,7 @@ impl UserIdentity {
|
||||
/// # let homeserver = Url::parse("http://example.com").unwrap();
|
||||
/// # futures::executor::block_on(async {
|
||||
/// # let client = Client::new(homeserver).await.unwrap();
|
||||
/// let user = client.get_user_identity(alice).await?;
|
||||
/// let user = client.encryption().get_user_identity(alice).await?;
|
||||
///
|
||||
/// if let Some(user) = user {
|
||||
/// let verification = user.request_verification().await?;
|
||||
@@ -208,7 +209,7 @@ impl UserIdentity {
|
||||
/// # let homeserver = Url::parse("http://example.com").unwrap();
|
||||
/// # block_on(async {
|
||||
/// # let client = Client::new(homeserver).await.unwrap();
|
||||
/// let user = client.get_user_identity(alice).await?;
|
||||
/// let user = client.encryption().get_user_identity(alice).await?;
|
||||
///
|
||||
/// // We don't want to support showing a QR code, we only support SAS
|
||||
/// // verification
|
||||
@@ -252,7 +253,7 @@ impl UserIdentity {
|
||||
/// course fail if the private part of the User-signing key isn't available.
|
||||
///
|
||||
/// The availability of the User-signing key can be checked using the
|
||||
/// [`Client::cross_signing_status()`] method.
|
||||
/// [`Encryption::cross_signing_status()`] method.
|
||||
///
|
||||
/// ### Manually verifying our own user
|
||||
///
|
||||
@@ -287,13 +288,14 @@ impl UserIdentity {
|
||||
/// # let homeserver = Url::parse("http://example.com").unwrap();
|
||||
/// # block_on(async {
|
||||
/// # let client = Client::new(homeserver).await.unwrap();
|
||||
/// let user = client.get_user_identity(alice).await?;
|
||||
/// let user = client.encryption().get_user_identity(alice).await?;
|
||||
///
|
||||
/// if let Some(user) = user {
|
||||
/// user.verify().await?;
|
||||
/// }
|
||||
/// # anyhow::Result::<()>::Ok(()) });
|
||||
/// ```
|
||||
/// [`Encryption::cross_signing_status()`]: crate::encryption::Encryption::cross_signing_status
|
||||
pub async fn verify(&self) -> Result<(), ManualVerifyError> {
|
||||
match &self.inner {
|
||||
UserIdentities::Own(i) => i.verify().await,
|
||||
@@ -330,7 +332,7 @@ impl UserIdentity {
|
||||
/// # let homeserver = Url::parse("http://example.com").unwrap();
|
||||
/// # block_on(async {
|
||||
/// # let client = Client::new(homeserver).await.unwrap();
|
||||
/// let user = client.get_user_identity(alice).await?;
|
||||
/// let user = client.encryption().get_user_identity(alice).await?;
|
||||
///
|
||||
/// if let Some(user) = user {
|
||||
/// if user.verified() {
|
||||
@@ -370,7 +372,7 @@ impl UserIdentity {
|
||||
/// # let homeserver = Url::parse("http://example.com").unwrap();
|
||||
/// # block_on(async {
|
||||
/// # let client = Client::new(homeserver).await.unwrap();
|
||||
/// let user = client.get_user_identity(alice).await?;
|
||||
/// let user = client.encryption().get_user_identity(alice).await?;
|
||||
///
|
||||
/// if let Some(user) = user {
|
||||
/// // Let's verify the user after we confirm that the master key
|
||||
|
||||
@@ -48,7 +48,7 @@ use ruma::{
|
||||
uiaa::AuthData,
|
||||
},
|
||||
assign,
|
||||
events::{AnyMessageEvent, AnyRoomEvent, AnySyncMessageEvent, EventType},
|
||||
events::{AnyMessageEvent, AnyRoomEvent, AnySyncMessageEvent, GlobalAccountDataEventType},
|
||||
serde::Raw,
|
||||
DeviceId, TransactionId, UserId,
|
||||
};
|
||||
@@ -65,395 +65,6 @@ use crate::{
|
||||
};
|
||||
|
||||
impl Client {
|
||||
/// Get the public ed25519 key of our own device. This is usually what is
|
||||
/// called the fingerprint of the device.
|
||||
#[cfg(feature = "encryption")]
|
||||
pub async fn ed25519_key(&self) -> Option<String> {
|
||||
self.olm_machine().await.map(|o| o.identity_keys().ed25519().to_owned())
|
||||
}
|
||||
|
||||
/// Get the status of the private cross signing keys.
|
||||
///
|
||||
/// This can be used to check which private cross signing keys we have
|
||||
/// stored locally.
|
||||
#[cfg(feature = "encryption")]
|
||||
pub async fn cross_signing_status(&self) -> Option<CrossSigningStatus> {
|
||||
if let Some(machine) = self.olm_machine().await {
|
||||
Some(machine.cross_signing_status().await)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Get all the tracked users we know about
|
||||
///
|
||||
/// Tracked users are users for which we keep the device list of E2EE
|
||||
/// capable devices up to date.
|
||||
#[cfg(feature = "encryption")]
|
||||
pub async fn tracked_users(&self) -> HashSet<Box<UserId>> {
|
||||
self.olm_machine().await.map(|o| o.tracked_users()).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Get a verification object with the given flow id.
|
||||
#[cfg(feature = "encryption")]
|
||||
pub async fn get_verification(&self, user_id: &UserId, flow_id: &str) -> Option<Verification> {
|
||||
let olm = self.olm_machine().await?;
|
||||
olm.get_verification(user_id, flow_id).map(|v| match v {
|
||||
matrix_sdk_base::crypto::Verification::SasV1(s) => {
|
||||
SasVerification { inner: s, client: self.clone() }.into()
|
||||
}
|
||||
#[cfg(feature = "qrcode")]
|
||||
matrix_sdk_base::crypto::Verification::QrV1(qr) => {
|
||||
verification::QrVerification { inner: qr, client: self.clone() }.into()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Get a `VerificationRequest` object for the given user with the given
|
||||
/// flow id.
|
||||
#[cfg(feature = "encryption")]
|
||||
pub async fn get_verification_request(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
flow_id: impl AsRef<str>,
|
||||
) -> Option<VerificationRequest> {
|
||||
let olm = self.olm_machine().await?;
|
||||
|
||||
olm.get_verification_request(user_id, flow_id)
|
||||
.map(|r| VerificationRequest { inner: r, client: self.clone() })
|
||||
}
|
||||
|
||||
/// Get a specific device of a user.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `user_id` - The unique id of the user that the device belongs to.
|
||||
///
|
||||
/// * `device_id` - The unique id of the device.
|
||||
///
|
||||
/// Returns a `Device` if one is found and the crypto store didn't throw an
|
||||
/// error.
|
||||
///
|
||||
/// This will always return None if the client hasn't been logged in.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use std::convert::TryFrom;
|
||||
/// # use matrix_sdk::{Client, ruma::{device_id, user_id}};
|
||||
/// # use url::Url;
|
||||
/// # use futures::executor::block_on;
|
||||
/// # block_on(async {
|
||||
/// # let alice = user_id!("@alice:example.org");
|
||||
/// # let homeserver = Url::parse("http://example.com")?;
|
||||
/// # let client = Client::new(homeserver).await?;
|
||||
/// if let Some(device) = client.get_device(alice, device_id!("DEVICEID")).await? {
|
||||
/// println!("{:?}", device.verified());
|
||||
///
|
||||
/// if !device.verified() {
|
||||
/// let verification = device.request_verification().await?;
|
||||
/// }
|
||||
/// }
|
||||
/// # anyhow::Result::<()>::Ok(()) });
|
||||
/// ```
|
||||
#[cfg(feature = "encryption")]
|
||||
pub async fn get_device(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
device_id: &DeviceId,
|
||||
) -> Result<Option<Device>, CryptoStoreError> {
|
||||
let device = self.base_client().get_device(user_id, device_id).await?;
|
||||
|
||||
Ok(device.map(|d| Device { inner: d, client: self.clone() }))
|
||||
}
|
||||
|
||||
/// Get a map holding all the devices of an user.
|
||||
///
|
||||
/// This will always return an empty map if the client hasn't been logged
|
||||
/// in.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `user_id` - The unique id of the user that the devices belong to.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use std::convert::TryFrom;
|
||||
/// # use matrix_sdk::{Client, ruma::user_id};
|
||||
/// # use url::Url;
|
||||
/// # use futures::executor::block_on;
|
||||
/// # block_on(async {
|
||||
/// # let alice = user_id!("@alice:example.org");
|
||||
/// # let homeserver = Url::parse("http://example.com")?;
|
||||
/// # let client = Client::new(homeserver).await?;
|
||||
/// let devices = client.get_user_devices(alice).await?;
|
||||
///
|
||||
/// for device in devices.devices() {
|
||||
/// println!("{:?}", device);
|
||||
/// }
|
||||
/// # anyhow::Result::<()>::Ok(()) });
|
||||
/// ```
|
||||
#[cfg(feature = "encryption")]
|
||||
pub async fn get_user_devices(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
) -> Result<UserDevices, CryptoStoreError> {
|
||||
let devices = self.base_client().get_user_devices(user_id).await?;
|
||||
|
||||
Ok(UserDevices { inner: devices, client: self.clone() })
|
||||
}
|
||||
|
||||
/// Get a E2EE identity of an user.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `user_id` - The unique id of the user that the identity belongs to.
|
||||
///
|
||||
/// Returns a `UserIdentity` if one is found and the crypto store
|
||||
/// didn't throw an error.
|
||||
///
|
||||
/// This will always return None if the client hasn't been logged in.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use std::convert::TryFrom;
|
||||
/// # use matrix_sdk::{Client, ruma::user_id};
|
||||
/// # use url::Url;
|
||||
/// # use futures::executor::block_on;
|
||||
/// # block_on(async {
|
||||
/// # let alice = user_id!("@alice:example.org");
|
||||
/// # let homeserver = Url::parse("http://example.com")?;
|
||||
/// # let client = Client::new(homeserver).await?;
|
||||
/// let user = client.get_user_identity(alice).await?;
|
||||
///
|
||||
/// if let Some(user) = user {
|
||||
/// println!("{:?}", user.verified());
|
||||
///
|
||||
/// let verification = user.request_verification().await?;
|
||||
/// }
|
||||
/// # anyhow::Result::<()>::Ok(()) });
|
||||
/// ```
|
||||
#[cfg(feature = "encryption")]
|
||||
pub async fn get_user_identity(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
) -> Result<Option<crate::encryption::identities::UserIdentity>, CryptoStoreError> {
|
||||
use crate::encryption::identities::UserIdentity;
|
||||
|
||||
if let Some(olm) = self.olm_machine().await {
|
||||
let identity = olm.get_identity(user_id).await?;
|
||||
|
||||
Ok(identity.map(|i| match i {
|
||||
matrix_sdk_base::crypto::UserIdentities::Own(i) => {
|
||||
UserIdentity::new_own(self.clone(), i)
|
||||
}
|
||||
matrix_sdk_base::crypto::UserIdentities::Other(i) => {
|
||||
UserIdentity::new(self.clone(), i, self.get_dm_room(user_id))
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Create and upload a new cross signing identity.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `auth_data` - This request requires user interactive auth, the first
|
||||
/// request needs to set this to `None` and will always fail with an
|
||||
/// `UiaaResponse`. The response will contain information for the
|
||||
/// interactive auth and the same request needs to be made but this time
|
||||
/// with some `auth_data` provided.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```no_run
|
||||
/// # use std::{convert::TryFrom, collections::BTreeMap};
|
||||
/// # use matrix_sdk::{
|
||||
/// # ruma::{api::client::uiaa, assign},
|
||||
/// # Client,
|
||||
/// # };
|
||||
/// # use url::Url;
|
||||
/// # use futures::executor::block_on;
|
||||
/// # use serde_json::json;
|
||||
/// # block_on(async {
|
||||
/// # let homeserver = Url::parse("http://example.com")?;
|
||||
/// # let client = Client::new(homeserver).await?;
|
||||
/// if let Err(e) = client.bootstrap_cross_signing(None).await {
|
||||
/// if let Some(response) = e.uiaa_response() {
|
||||
/// let auth_data = uiaa::AuthData::Password(assign!(
|
||||
/// uiaa::Password::new(
|
||||
/// uiaa::UserIdentifier::UserIdOrLocalpart("example"),
|
||||
/// "wordpass",
|
||||
/// ), {
|
||||
/// session: response.session.as_deref(),
|
||||
/// }
|
||||
/// ));
|
||||
///
|
||||
/// client
|
||||
/// .bootstrap_cross_signing(Some(auth_data))
|
||||
/// .await
|
||||
/// .expect("Couldn't bootstrap cross signing")
|
||||
/// } else {
|
||||
/// panic!("Error durign cross signing bootstrap {:#?}", e);
|
||||
/// }
|
||||
/// }
|
||||
/// # anyhow::Result::<()>::Ok(()) });
|
||||
#[cfg(feature = "encryption")]
|
||||
pub async fn bootstrap_cross_signing(&self, auth_data: Option<AuthData<'_>>) -> Result<()> {
|
||||
let olm = self.olm_machine().await.ok_or(Error::AuthenticationRequired)?;
|
||||
|
||||
let (request, signature_request) = olm.bootstrap_cross_signing(false).await?;
|
||||
|
||||
let to_raw = |k| Raw::new(&k).expect("Can't serialize newly created cross signing keys");
|
||||
|
||||
let request = assign!(UploadSigningKeysRequest::new(), {
|
||||
auth: auth_data,
|
||||
master_key: request.master_key.map(to_raw),
|
||||
self_signing_key: request.self_signing_key.map(to_raw),
|
||||
user_signing_key: request.user_signing_key.map(to_raw),
|
||||
});
|
||||
|
||||
self.send(request, None).await?;
|
||||
self.send(signature_request, None).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Export E2EE keys that match the given predicate encrypting them with the
|
||||
/// given passphrase.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `path` - The file path where the exported key file will be saved.
|
||||
///
|
||||
/// * `passphrase` - The passphrase that will be used to encrypt the
|
||||
/// exported
|
||||
/// room keys.
|
||||
///
|
||||
/// * `predicate` - A closure that will be called for every known
|
||||
/// `InboundGroupSession`, which represents a room key. If the closure
|
||||
/// returns `true` the `InboundGroupSessoin` will be included in the export,
|
||||
/// if the closure returns `false` it will not be included.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This method will panic if it isn't run on a Tokio runtime.
|
||||
///
|
||||
/// This method will panic if it can't get enough randomness from the OS to
|
||||
/// encrypt the exported keys securely.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use std::{path::PathBuf, time::Duration};
|
||||
/// # use matrix_sdk::{
|
||||
/// # Client, config::SyncSettings,
|
||||
/// # ruma::room_id,
|
||||
/// # };
|
||||
/// # use futures::executor::block_on;
|
||||
/// # use url::Url;
|
||||
/// # block_on(async {
|
||||
/// # let homeserver = Url::parse("http://localhost:8080")?;
|
||||
/// # let mut client = Client::new(homeserver).await?;
|
||||
/// let path = PathBuf::from("/home/example/e2e-keys.txt");
|
||||
/// // Export all room keys.
|
||||
/// client
|
||||
/// .export_keys(path, "secret-passphrase", |_| true)
|
||||
/// .await?;
|
||||
///
|
||||
/// // Export only the room keys for a certain room.
|
||||
/// let path = PathBuf::from("/home/example/e2e-room-keys.txt");
|
||||
/// let room_id = room_id!("!test:localhost");
|
||||
///
|
||||
/// client
|
||||
/// .export_keys(path, "secret-passphrase", |s| s.room_id() == room_id)
|
||||
/// .await?;
|
||||
/// # anyhow::Result::<()>::Ok(()) });
|
||||
/// ```
|
||||
#[cfg(all(feature = "encryption", not(target_arch = "wasm32")))]
|
||||
pub async fn export_keys(
|
||||
&self,
|
||||
path: PathBuf,
|
||||
passphrase: &str,
|
||||
predicate: impl FnMut(&matrix_sdk_base::crypto::olm::InboundGroupSession) -> bool,
|
||||
) -> Result<()> {
|
||||
let olm = self.olm_machine().await.ok_or(Error::AuthenticationRequired)?;
|
||||
|
||||
let keys = olm.export_keys(predicate).await?;
|
||||
let passphrase = zeroize::Zeroizing::new(passphrase.to_owned());
|
||||
|
||||
let encrypt = move || -> Result<()> {
|
||||
let export: String =
|
||||
matrix_sdk_base::crypto::encrypt_key_export(&keys, &passphrase, 500_000)?;
|
||||
let mut file = std::fs::File::create(path)?;
|
||||
file.write_all(&export.into_bytes())?;
|
||||
Ok(())
|
||||
};
|
||||
|
||||
let task = tokio::task::spawn_blocking(encrypt);
|
||||
task.await.expect("Task join error")
|
||||
}
|
||||
|
||||
/// Import E2EE keys from the given file path.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `path` - The file path where the exported key file will can be found.
|
||||
///
|
||||
/// * `passphrase` - The passphrase that should be used to decrypt the
|
||||
/// exported room keys.
|
||||
///
|
||||
/// Returns a tuple of numbers that represent the number of sessions that
|
||||
/// were imported and the total number of sessions that were found in the
|
||||
/// key export.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This method will panic if it isn't run on a Tokio runtime.
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use std::{path::PathBuf, time::Duration};
|
||||
/// # use matrix_sdk::{
|
||||
/// # Client, config::SyncSettings,
|
||||
/// # ruma::room_id,
|
||||
/// # };
|
||||
/// # use futures::executor::block_on;
|
||||
/// # use url::Url;
|
||||
/// # block_on(async {
|
||||
/// # let homeserver = Url::parse("http://localhost:8080")?;
|
||||
/// # let mut client = Client::new(homeserver).await?;
|
||||
/// let path = PathBuf::from("/home/example/e2e-keys.txt");
|
||||
/// let result = client.import_keys(path, "secret-passphrase").await?;
|
||||
///
|
||||
/// println!(
|
||||
/// "Imported {} room keys out of {}",
|
||||
/// result.imported_count, result.total_count
|
||||
/// );
|
||||
/// # anyhow::Result::<()>::Ok(()) });
|
||||
/// ```
|
||||
#[cfg(all(feature = "encryption", not(target_arch = "wasm32")))]
|
||||
pub async fn import_keys(
|
||||
&self,
|
||||
path: PathBuf,
|
||||
passphrase: &str,
|
||||
) -> Result<RoomKeyImportResult, RoomKeyImportError> {
|
||||
let olm = self.olm_machine().await.ok_or(RoomKeyImportError::StoreClosed)?;
|
||||
let passphrase = zeroize::Zeroizing::new(passphrase.to_owned());
|
||||
|
||||
let decrypt = move || {
|
||||
let file = std::fs::File::open(path)?;
|
||||
matrix_sdk_base::crypto::decrypt_key_export(file, &passphrase)
|
||||
};
|
||||
|
||||
let task = tokio::task::spawn_blocking(decrypt);
|
||||
let import = task.await.expect("Task join error")?;
|
||||
|
||||
Ok(olm.import_keys(import, false, |_, _| {}).await?)
|
||||
}
|
||||
|
||||
/// Tries to decrypt a `AnyRoomEvent`. Returns undecrypted room event when
|
||||
/// decryption fails.
|
||||
#[cfg(feature = "encryption")]
|
||||
@@ -666,7 +277,7 @@ impl Client {
|
||||
// have with this user.
|
||||
let mut content = self
|
||||
.store()
|
||||
.get_account_data_event(EventType::Direct)
|
||||
.get_account_data_event(GlobalAccountDataEventType::Direct)
|
||||
.await?
|
||||
.map(|e| e.deserialize())
|
||||
.transpose()?
|
||||
@@ -877,3 +488,406 @@ impl Client {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A high-level API to manage the client's encryption.
|
||||
///
|
||||
/// To get this, use [`Client::encryption()`].
|
||||
#[cfg(feature = "encryption")]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Encryption {
|
||||
/// The underlying client.
|
||||
client: Client,
|
||||
}
|
||||
|
||||
#[cfg(feature = "encryption")]
|
||||
impl Encryption {
|
||||
pub(crate) fn new(client: Client) -> Self {
|
||||
Self { client }
|
||||
}
|
||||
|
||||
/// Get the public ed25519 key of our own device. This is usually what is
|
||||
/// called the fingerprint of the device.
|
||||
pub async fn ed25519_key(&self) -> Option<String> {
|
||||
self.client.olm_machine().await.map(|o| o.identity_keys().ed25519().to_owned())
|
||||
}
|
||||
|
||||
/// Get the status of the private cross signing keys.
|
||||
///
|
||||
/// This can be used to check which private cross signing keys we have
|
||||
/// stored locally.
|
||||
pub async fn cross_signing_status(&self) -> Option<CrossSigningStatus> {
|
||||
if let Some(machine) = self.client.olm_machine().await {
|
||||
Some(machine.cross_signing_status().await)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Get all the tracked users we know about
|
||||
///
|
||||
/// Tracked users are users for which we keep the device list of E2EE
|
||||
/// capable devices up to date.
|
||||
pub async fn tracked_users(&self) -> HashSet<Box<UserId>> {
|
||||
self.client.olm_machine().await.map(|o| o.tracked_users()).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Get a verification object with the given flow id.
|
||||
pub async fn get_verification(&self, user_id: &UserId, flow_id: &str) -> Option<Verification> {
|
||||
let olm = self.client.olm_machine().await?;
|
||||
olm.get_verification(user_id, flow_id).map(|v| match v {
|
||||
matrix_sdk_base::crypto::Verification::SasV1(s) => {
|
||||
SasVerification { inner: s, client: self.client.clone() }.into()
|
||||
}
|
||||
#[cfg(feature = "qrcode")]
|
||||
matrix_sdk_base::crypto::Verification::QrV1(qr) => {
|
||||
verification::QrVerification { inner: qr, client: self.client.clone() }.into()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Get a `VerificationRequest` object for the given user with the given
|
||||
/// flow id.
|
||||
pub async fn get_verification_request(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
flow_id: impl AsRef<str>,
|
||||
) -> Option<VerificationRequest> {
|
||||
let olm = self.client.olm_machine().await?;
|
||||
|
||||
olm.get_verification_request(user_id, flow_id)
|
||||
.map(|r| VerificationRequest { inner: r, client: self.client.clone() })
|
||||
}
|
||||
|
||||
/// Get a specific device of a user.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `user_id` - The unique id of the user that the device belongs to.
|
||||
///
|
||||
/// * `device_id` - The unique id of the device.
|
||||
///
|
||||
/// Returns a `Device` if one is found and the crypto store didn't throw an
|
||||
/// error.
|
||||
///
|
||||
/// This will always return None if the client hasn't been logged in.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use std::convert::TryFrom;
|
||||
/// # use matrix_sdk::{Client, ruma::{device_id, user_id}};
|
||||
/// # use url::Url;
|
||||
/// # use futures::executor::block_on;
|
||||
/// # block_on(async {
|
||||
/// # let alice = user_id!("@alice:example.org");
|
||||
/// # let homeserver = Url::parse("http://example.com")?;
|
||||
/// # let client = Client::new(homeserver).await?;
|
||||
/// if let Some(device) = client
|
||||
/// .encryption()
|
||||
/// .get_device(alice, device_id!("DEVICEID"))
|
||||
/// .await? {
|
||||
/// println!("{:?}", device.verified());
|
||||
///
|
||||
/// if !device.verified() {
|
||||
/// let verification = device.request_verification().await?;
|
||||
/// }
|
||||
/// }
|
||||
/// # anyhow::Result::<()>::Ok(()) });
|
||||
/// ```
|
||||
pub async fn get_device(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
device_id: &DeviceId,
|
||||
) -> Result<Option<Device>, CryptoStoreError> {
|
||||
let device = self.client.base_client().get_device(user_id, device_id).await?;
|
||||
|
||||
Ok(device.map(|d| Device { inner: d, client: self.client.clone() }))
|
||||
}
|
||||
|
||||
/// Get a map holding all the devices of an user.
|
||||
///
|
||||
/// This will always return an empty map if the client hasn't been logged
|
||||
/// in.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `user_id` - The unique id of the user that the devices belong to.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use std::convert::TryFrom;
|
||||
/// # use matrix_sdk::{Client, ruma::user_id};
|
||||
/// # use url::Url;
|
||||
/// # use futures::executor::block_on;
|
||||
/// # block_on(async {
|
||||
/// # let alice = user_id!("@alice:example.org");
|
||||
/// # let homeserver = Url::parse("http://example.com")?;
|
||||
/// # let client = Client::new(homeserver).await?;
|
||||
/// let devices = client.encryption().get_user_devices(alice).await?;
|
||||
///
|
||||
/// for device in devices.devices() {
|
||||
/// println!("{:?}", device);
|
||||
/// }
|
||||
/// # anyhow::Result::<()>::Ok(()) });
|
||||
/// ```
|
||||
pub async fn get_user_devices(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
) -> Result<UserDevices, CryptoStoreError> {
|
||||
let devices = self.client.base_client().get_user_devices(user_id).await?;
|
||||
|
||||
Ok(UserDevices { inner: devices, client: self.client.clone() })
|
||||
}
|
||||
|
||||
/// Get a E2EE identity of an user.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `user_id` - The unique id of the user that the identity belongs to.
|
||||
///
|
||||
/// Returns a `UserIdentity` if one is found and the crypto store
|
||||
/// didn't throw an error.
|
||||
///
|
||||
/// This will always return None if the client hasn't been logged in.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use std::convert::TryFrom;
|
||||
/// # use matrix_sdk::{Client, ruma::user_id};
|
||||
/// # use url::Url;
|
||||
/// # use futures::executor::block_on;
|
||||
/// # block_on(async {
|
||||
/// # let alice = user_id!("@alice:example.org");
|
||||
/// # let homeserver = Url::parse("http://example.com")?;
|
||||
/// # let client = Client::new(homeserver).await?;
|
||||
/// let user = client.encryption().get_user_identity(alice).await?;
|
||||
///
|
||||
/// if let Some(user) = user {
|
||||
/// println!("{:?}", user.verified());
|
||||
///
|
||||
/// let verification = user.request_verification().await?;
|
||||
/// }
|
||||
/// # anyhow::Result::<()>::Ok(()) });
|
||||
/// ```
|
||||
pub async fn get_user_identity(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
) -> Result<Option<crate::encryption::identities::UserIdentity>, CryptoStoreError> {
|
||||
use crate::encryption::identities::UserIdentity;
|
||||
|
||||
if let Some(olm) = self.client.olm_machine().await {
|
||||
let identity = olm.get_identity(user_id).await?;
|
||||
|
||||
Ok(identity.map(|i| match i {
|
||||
matrix_sdk_base::crypto::UserIdentities::Own(i) => {
|
||||
UserIdentity::new_own(self.client.clone(), i)
|
||||
}
|
||||
matrix_sdk_base::crypto::UserIdentities::Other(i) => {
|
||||
UserIdentity::new(self.client.clone(), i, self.client.get_dm_room(user_id))
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Create and upload a new cross signing identity.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `auth_data` - This request requires user interactive auth, the first
|
||||
/// request needs to set this to `None` and will always fail with an
|
||||
/// `UiaaResponse`. The response will contain information for the
|
||||
/// interactive auth and the same request needs to be made but this time
|
||||
/// with some `auth_data` provided.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```no_run
|
||||
/// # use std::{convert::TryFrom, collections::BTreeMap};
|
||||
/// # use matrix_sdk::{
|
||||
/// # ruma::{api::client::uiaa, assign},
|
||||
/// # Client,
|
||||
/// # };
|
||||
/// # use url::Url;
|
||||
/// # use futures::executor::block_on;
|
||||
/// # use serde_json::json;
|
||||
/// # block_on(async {
|
||||
/// # let homeserver = Url::parse("http://example.com")?;
|
||||
/// # let client = Client::new(homeserver).await?;
|
||||
/// if let Err(e) = client.encryption().bootstrap_cross_signing(None).await {
|
||||
/// if let Some(response) = e.uiaa_response() {
|
||||
/// let auth_data = uiaa::AuthData::Password(assign!(
|
||||
/// uiaa::Password::new(
|
||||
/// uiaa::UserIdentifier::UserIdOrLocalpart("example"),
|
||||
/// "wordpass",
|
||||
/// ), {
|
||||
/// session: response.session.as_deref(),
|
||||
/// }
|
||||
/// ));
|
||||
///
|
||||
/// client
|
||||
/// .encryption()
|
||||
/// .bootstrap_cross_signing(Some(auth_data))
|
||||
/// .await
|
||||
/// .expect("Couldn't bootstrap cross signing")
|
||||
/// } else {
|
||||
/// panic!("Error durign cross signing bootstrap {:#?}", e);
|
||||
/// }
|
||||
/// }
|
||||
/// # anyhow::Result::<()>::Ok(()) });
|
||||
pub async fn bootstrap_cross_signing(&self, auth_data: Option<AuthData<'_>>) -> Result<()> {
|
||||
let olm = self.client.olm_machine().await.ok_or(Error::AuthenticationRequired)?;
|
||||
|
||||
let (request, signature_request) = olm.bootstrap_cross_signing(false).await?;
|
||||
|
||||
let to_raw = |k| Raw::new(&k).expect("Can't serialize newly created cross signing keys");
|
||||
|
||||
let request = assign!(UploadSigningKeysRequest::new(), {
|
||||
auth: auth_data,
|
||||
master_key: request.master_key.map(to_raw),
|
||||
self_signing_key: request.self_signing_key.map(to_raw),
|
||||
user_signing_key: request.user_signing_key.map(to_raw),
|
||||
});
|
||||
|
||||
self.client.send(request, None).await?;
|
||||
self.client.send(signature_request, None).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Export E2EE keys that match the given predicate encrypting them with the
|
||||
/// given passphrase.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `path` - The file path where the exported key file will be saved.
|
||||
///
|
||||
/// * `passphrase` - The passphrase that will be used to encrypt the
|
||||
/// exported
|
||||
/// room keys.
|
||||
///
|
||||
/// * `predicate` - A closure that will be called for every known
|
||||
/// `InboundGroupSession`, which represents a room key. If the closure
|
||||
/// returns `true` the `InboundGroupSessoin` will be included in the export,
|
||||
/// if the closure returns `false` it will not be included.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This method will panic if it isn't run on a Tokio runtime.
|
||||
///
|
||||
/// This method will panic if it can't get enough randomness from the OS to
|
||||
/// encrypt the exported keys securely.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use std::{path::PathBuf, time::Duration};
|
||||
/// # use matrix_sdk::{
|
||||
/// # Client, config::SyncSettings,
|
||||
/// # ruma::room_id,
|
||||
/// # };
|
||||
/// # use futures::executor::block_on;
|
||||
/// # use url::Url;
|
||||
/// # block_on(async {
|
||||
/// # let homeserver = Url::parse("http://localhost:8080")?;
|
||||
/// # let mut client = Client::new(homeserver).await?;
|
||||
/// let path = PathBuf::from("/home/example/e2e-keys.txt");
|
||||
/// // Export all room keys.
|
||||
/// client
|
||||
/// .encryption()
|
||||
/// .export_keys(path, "secret-passphrase", |_| true)
|
||||
/// .await?;
|
||||
///
|
||||
/// // Export only the room keys for a certain room.
|
||||
/// let path = PathBuf::from("/home/example/e2e-room-keys.txt");
|
||||
/// let room_id = room_id!("!test:localhost");
|
||||
///
|
||||
/// client
|
||||
/// .encryption()
|
||||
/// .export_keys(path, "secret-passphrase", |s| s.room_id() == room_id)
|
||||
/// .await?;
|
||||
/// # anyhow::Result::<()>::Ok(()) });
|
||||
/// ```
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub async fn export_keys(
|
||||
&self,
|
||||
path: PathBuf,
|
||||
passphrase: &str,
|
||||
predicate: impl FnMut(&matrix_sdk_base::crypto::olm::InboundGroupSession) -> bool,
|
||||
) -> Result<()> {
|
||||
let olm = self.client.olm_machine().await.ok_or(Error::AuthenticationRequired)?;
|
||||
|
||||
let keys = olm.export_keys(predicate).await?;
|
||||
let passphrase = zeroize::Zeroizing::new(passphrase.to_owned());
|
||||
|
||||
let encrypt = move || -> Result<()> {
|
||||
let export: String =
|
||||
matrix_sdk_base::crypto::encrypt_key_export(&keys, &passphrase, 500_000)?;
|
||||
let mut file = std::fs::File::create(path)?;
|
||||
file.write_all(&export.into_bytes())?;
|
||||
Ok(())
|
||||
};
|
||||
|
||||
let task = tokio::task::spawn_blocking(encrypt);
|
||||
task.await.expect("Task join error")
|
||||
}
|
||||
|
||||
/// Import E2EE keys from the given file path.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `path` - The file path where the exported key file will can be found.
|
||||
///
|
||||
/// * `passphrase` - The passphrase that should be used to decrypt the
|
||||
/// exported room keys.
|
||||
///
|
||||
/// Returns a tuple of numbers that represent the number of sessions that
|
||||
/// were imported and the total number of sessions that were found in the
|
||||
/// key export.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This method will panic if it isn't run on a Tokio runtime.
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use std::{path::PathBuf, time::Duration};
|
||||
/// # use matrix_sdk::{
|
||||
/// # Client, config::SyncSettings,
|
||||
/// # ruma::room_id,
|
||||
/// # };
|
||||
/// # use futures::executor::block_on;
|
||||
/// # use url::Url;
|
||||
/// # block_on(async {
|
||||
/// # let homeserver = Url::parse("http://localhost:8080")?;
|
||||
/// # let mut client = Client::new(homeserver).await?;
|
||||
/// let path = PathBuf::from("/home/example/e2e-keys.txt");
|
||||
/// let result = client.encryption().import_keys(path, "secret-passphrase").await?;
|
||||
///
|
||||
/// println!(
|
||||
/// "Imported {} room keys out of {}",
|
||||
/// result.imported_count, result.total_count
|
||||
/// );
|
||||
/// # anyhow::Result::<()>::Ok(()) });
|
||||
/// ```
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub async fn import_keys(
|
||||
&self,
|
||||
path: PathBuf,
|
||||
passphrase: &str,
|
||||
) -> Result<RoomKeyImportResult, RoomKeyImportError> {
|
||||
let olm = self.client.olm_machine().await.ok_or(RoomKeyImportError::StoreClosed)?;
|
||||
let passphrase = zeroize::Zeroizing::new(passphrase.to_owned());
|
||||
|
||||
let decrypt = move || {
|
||||
let file = std::fs::File::open(path)?;
|
||||
matrix_sdk_base::crypto::decrypt_key_export(file, &passphrase)
|
||||
};
|
||||
|
||||
let task = tokio::task::spawn_blocking(decrypt);
|
||||
let import = task.await.expect("Task join error")?;
|
||||
|
||||
Ok(olm.import_keys(import, false, |_, _| {}).await?)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ impl SasVerification {
|
||||
/// # let homeserver = Url::parse("http://example.com")?;
|
||||
/// # let client = Client::new(homeserver).await?;
|
||||
/// let sas = client
|
||||
/// .encryption()
|
||||
/// .get_verification(&user_id, flow_id)
|
||||
/// .await
|
||||
/// .and_then(|v| v.sas());
|
||||
@@ -128,6 +129,7 @@ impl SasVerification {
|
||||
/// # let homeserver = Url::parse("http://example.com")?;
|
||||
/// # let client = Client::new(homeserver).await?;
|
||||
/// let sas_verification = client
|
||||
/// .encryption()
|
||||
/// .get_verification(&user_id, flow_id)
|
||||
/// .await
|
||||
/// .and_then(|v| v.sas());
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::{convert::TryFrom, fmt::Debug, sync::Arc};
|
||||
use std::{convert::TryFrom, fmt::Debug, sync::Arc, time::Duration};
|
||||
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use http::Response as HttpResponse;
|
||||
@@ -25,11 +25,9 @@ use ruma::api::{
|
||||
use tracing::trace;
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
config::{ClientConfig, RequestConfig},
|
||||
error::HttpError,
|
||||
Session,
|
||||
};
|
||||
use crate::{config::RequestConfig, error::HttpError, Session};
|
||||
|
||||
pub(crate) const DEFAULT_REQUEST_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
|
||||
/// Abstraction around the http layer. The allows implementors to use different
|
||||
/// http libraries.
|
||||
@@ -206,33 +204,58 @@ impl HttpClient {
|
||||
}
|
||||
}
|
||||
|
||||
/// Build a client with the specified configuration.
|
||||
pub(crate) fn client_with_config(config: &ClientConfig) -> Result<Client, HttpError> {
|
||||
let http_client = reqwest::Client::builder();
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct HttpSettings {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let http_client = {
|
||||
let http_client = if config.disable_ssl_verification {
|
||||
http_client.danger_accept_invalid_certs(true)
|
||||
} else {
|
||||
http_client
|
||||
pub(crate) disable_ssl_verification: bool,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) proxy: Option<String>,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) user_agent: Option<String>,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) timeout: Duration,
|
||||
}
|
||||
|
||||
#[allow(clippy::derivable_impls)]
|
||||
impl Default for HttpSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
disable_ssl_verification: false,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
proxy: None,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
user_agent: None,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
timeout: DEFAULT_REQUEST_TIMEOUT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HttpSettings {
|
||||
/// Build a client with the specified configuration.
|
||||
pub(crate) fn make_client(&self) -> Result<Client, HttpError> {
|
||||
#[allow(unused_mut)]
|
||||
let mut http_client = reqwest::Client::builder();
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
if self.disable_ssl_verification {
|
||||
http_client = http_client.danger_accept_invalid_certs(true)
|
||||
}
|
||||
|
||||
if let Some(p) = &self.proxy {
|
||||
http_client = http_client.proxy(reqwest::Proxy::all(p.as_str())?);
|
||||
}
|
||||
|
||||
let user_agent =
|
||||
self.user_agent.clone().unwrap_or_else(|| "matrix-rust-sdk".to_owned());
|
||||
|
||||
http_client = http_client.user_agent(user_agent).timeout(self.timeout);
|
||||
};
|
||||
|
||||
let http_client = match &config.proxy {
|
||||
Some(p) => http_client.proxy(p.clone()),
|
||||
None => http_client,
|
||||
};
|
||||
|
||||
let user_agent = config.user_agent.clone().unwrap_or_else(|| "matrix-rust-sdk".to_owned());
|
||||
|
||||
http_client.user_agent(user_agent).timeout(config.request_config.timeout)
|
||||
};
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[allow(unused)]
|
||||
let _ = config;
|
||||
|
||||
Ok(http_client.build()?)
|
||||
Ok(http_client.build()?)
|
||||
}
|
||||
}
|
||||
|
||||
async fn response_to_http_response(
|
||||
|
||||
@@ -50,13 +50,14 @@ mod http_client;
|
||||
/// High-level room API
|
||||
pub mod room;
|
||||
mod room_member;
|
||||
pub mod store;
|
||||
mod sync;
|
||||
|
||||
#[cfg(feature = "encryption")]
|
||||
pub mod encryption;
|
||||
|
||||
pub use account::Account;
|
||||
pub use client::{Client, LoopCtrl};
|
||||
pub use client::{Client, ClientBuildError, ClientBuilder, LoopCtrl};
|
||||
#[cfg(feature = "image_proc")]
|
||||
pub use error::ImageError;
|
||||
pub use error::{Error, HttpError, HttpResult, Result};
|
||||
|
||||
@@ -18,7 +18,7 @@ use ruma::{
|
||||
events::{
|
||||
room::history_visibility::HistoryVisibility,
|
||||
tag::{TagInfo, TagName},
|
||||
AnyStateEvent, AnySyncStateEvent, EventType,
|
||||
AnyStateEvent, AnySyncStateEvent, StateEventType,
|
||||
},
|
||||
serde::Raw,
|
||||
uint, EventId, RoomId, UInt, UserId,
|
||||
@@ -640,7 +640,7 @@ impl Common {
|
||||
/// Get all state events of a given type in this room.
|
||||
pub async fn get_state_events(
|
||||
&self,
|
||||
event_type: EventType,
|
||||
event_type: StateEventType,
|
||||
) -> Result<Vec<Raw<AnySyncStateEvent>>> {
|
||||
self.client.store().get_state_events(self.room_id(), event_type).await.map_err(Into::into)
|
||||
}
|
||||
@@ -648,7 +648,7 @@ impl Common {
|
||||
/// Get a specific state event in this room.
|
||||
pub async fn get_state_event(
|
||||
&self,
|
||||
event_type: EventType,
|
||||
event_type: StateEventType,
|
||||
state_key: &str,
|
||||
) -> Result<Option<Raw<AnySyncStateEvent>>> {
|
||||
self.client
|
||||
@@ -667,7 +667,7 @@ impl Common {
|
||||
let user_ids = self.client.store().get_user_ids(self.room_id()).await?;
|
||||
|
||||
for user_id in user_ids {
|
||||
let devices = self.client.get_user_devices(&user_id).await?;
|
||||
let devices = self.client.encryption().get_user_devices(&user_id).await?;
|
||||
let any_unverified = devices.devices().any(|d| !d.verified());
|
||||
|
||||
if any_unverified {
|
||||
|
||||
35
crates/matrix-sdk/src/store.rs
Normal file
35
crates/matrix-sdk/src/store.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright 2022 Kévin Commaille
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Functions and types to initialize a store.
|
||||
//!
|
||||
//! The re-exports present here depend on the store-related features that are
|
||||
//! enabled:
|
||||
//!
|
||||
//! 1. `sled_state_store` provides a `StateStore`, while
|
||||
//! `sled_cryptostore` provides also a `CryptoStore` for encryption data. This
|
||||
//! is the default persistent store implementation for non-WebAssembly.
|
||||
//! 2. `indexeddb_store` provides both a `StateStore` and a `CryptoStore` if
|
||||
//! `encryption` is also enabled. This is the default persistent store
|
||||
//! implementation for WebAssembly.
|
||||
//!
|
||||
//! Both options provide a `make_store_config` convenience method to create a
|
||||
//! [`StoreConfig`] for [`ClientBuilder::store_config()`].
|
||||
//!
|
||||
//! [`StoreConfig`]: crate::config::StoreConfig
|
||||
//! [`ClientBuilder::store_config()`]: crate::ClientBuilder::store_config
|
||||
|
||||
#[cfg(any(feature = "indexeddb_state_store", feature = "indexeddb_cryptostore"))]
|
||||
pub use matrix_sdk_indexeddb::*;
|
||||
#[cfg(any(feature = "sled_state_store", feature = "sled_cryptostore"))]
|
||||
pub use matrix_sdk_sled::*;
|
||||
@@ -36,6 +36,8 @@ enum CiCommand {
|
||||
#[clap(subcommand)]
|
||||
cmd: Option<WasmFeatureSet>,
|
||||
},
|
||||
/// Run tests for the different crypto crate features
|
||||
TestCrypto,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, PartialEq, Eq, PartialOrd, Ord)]
|
||||
@@ -76,6 +78,7 @@ impl CiArgs {
|
||||
CiCommand::TestFeatures { cmd } => run_feature_tests(cmd),
|
||||
CiCommand::TestAppservice => run_appservice_tests(),
|
||||
CiCommand::Wasm { cmd } => run_wasm_checks(cmd),
|
||||
CiCommand::TestCrypto => run_crypto_tests(),
|
||||
},
|
||||
None => {
|
||||
check_style()?;
|
||||
@@ -86,6 +89,7 @@ impl CiArgs {
|
||||
run_feature_tests(None)?;
|
||||
run_appservice_tests()?;
|
||||
run_wasm_checks(None)?;
|
||||
run_crypto_tests()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -108,11 +112,17 @@ fn check_typos() -> Result<()> {
|
||||
fn check_clippy() -> Result<()> {
|
||||
cmd!("rustup run nightly cargo clippy --all-targets -- -D warnings").run()?;
|
||||
cmd!(
|
||||
"rustup run nightly cargo clippy --all-targets
|
||||
"rustup run nightly cargo clippy --workspace --all-targets
|
||||
--exclude matrix-sdk-crypto --exclude xtask
|
||||
--no-default-features --features native-tls,warp
|
||||
-- -D warnings"
|
||||
)
|
||||
.run()?;
|
||||
cmd!(
|
||||
"rustup run nightly cargo clippy --all-targets -p matrix-sdk-crypto
|
||||
--no-default-features -- -D warnings"
|
||||
)
|
||||
.run()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -159,6 +169,16 @@ fn run_feature_tests(cmd: Option<FeatureSet>) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_crypto_tests() -> Result<()> {
|
||||
cmd!(
|
||||
"rustup run stable cargo clippy -p matrix-sdk-crypto --features=backups_v1 -- -D warnings"
|
||||
)
|
||||
.run()?;
|
||||
cmd!("rustup run stable cargo test -p matrix-sdk-crypto --features=backups_v1").run()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_appservice_tests() -> Result<()> {
|
||||
cmd!("rustup run stable cargo clippy -p matrix-sdk-appservice -- -D warnings").run()?;
|
||||
cmd!("rustup run stable cargo test -p matrix-sdk-appservice").run()?;
|
||||
@@ -173,14 +193,14 @@ fn run_wasm_checks(cmd: Option<WasmFeatureSet>) -> Result<()> {
|
||||
WasmFeatureSet::MatrixSdkNoDefault,
|
||||
"-p matrix-sdk \
|
||||
--no-default-features \
|
||||
--features qrcode,encryption,indexeddb_stores,rustls-tls",
|
||||
--features qrcode,encryption,indexeddb_state_store,indexeddb_cryptostore,rustls-tls",
|
||||
),
|
||||
(WasmFeatureSet::MatrixSdkBase, "-p matrix-sdk-base"),
|
||||
(WasmFeatureSet::MatrixSdkCommon, "-p matrix-sdk-common"),
|
||||
(WasmFeatureSet::MatrixSdkCrypto, "-p matrix-sdk-crypto"),
|
||||
(
|
||||
WasmFeatureSet::MatrixSdkIndexeddbStores,
|
||||
"-p matrix-sdk --no-default-features --features indexeddb_stores,encryption,rustls-tls",
|
||||
"-p matrix-sdk --no-default-features --features indexeddb_state_store,indexeddb_cryptostore,encryption,rustls-tls",
|
||||
),
|
||||
]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user