[fractal/fractal-next] Actually make state store presistent
- From: Julian Sparber <jsparber src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [fractal/fractal-next] Actually make state store presistent
- Date: Tue, 11 May 2021 08:17:44 +0000 (UTC)
commit 601ddee7c351dffd6078956db9d602eb2894be4f
Author: Julian Sparber <julian sparber net>
Date: Fri May 7 19:01:01 2021 +0200
Actually make state store presistent
This sets a passphrase and path for the state store created by the sdk.
The additional information is stored in via the SecretService.
This sets opt-level for deps to 3, because otherwise the statestore
would be really slow because of the passphrase.
Cargo.lock | 1 +
Cargo.toml | 4 ++
src/login.rs | 12 +++--
src/secret.rs | 105 ++++++++++++++++++++++++++++++----------
src/session/mod.rs | 137 ++++++++++++++++++++++++++++-------------------------
5 files changed, 165 insertions(+), 94 deletions(-)
---
diff --git a/Cargo.lock b/Cargo.lock
index e51b2629..ae63e4d9 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -684,6 +684,7 @@ dependencies = [
"log",
"matrix-sdk",
"once_cell",
+ "rand 0.8.3",
"secret-service",
"serde_json",
"sourceview5",
diff --git a/Cargo.toml b/Cargo.toml
index c10aa5c1..80df81b2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -4,6 +4,9 @@ version = "0.1.0"
authors = ["Daniel GarcĂa Moreno <danigm wadobo com>"]
edition = "2018"
+[profile.dev.package."*"]
+opt-level = 3
+
[dependencies]
log = "0.4"
tracing-subscriber = "0.2"
@@ -18,6 +21,7 @@ html2pango = "0.4"
chrono = "0.4"
futures = "0.3"
comrak = "0.10"
+rand = "0.8"
[dependencies.sourceview]
branch = "main"
diff --git a/src/login.rs b/src/login.rs
index 50e8eba6..dcc45626 100644
--- a/src/login.rs
+++ b/src/login.rs
@@ -121,16 +121,20 @@ impl Login {
self.freeze();
- let session = Session::new(homeserver);
+ let session = Session::new();
self.setup_session(&session);
- session.login_with_password(username, password);
+ session.login_with_password(
+ url::Url::parse(homeserver.as_str()).unwrap(),
+ username,
+ password,
+ );
}
pub fn restore_sessions(&self) -> Result<(), secret_service::Error> {
let sessions = secret::restore_sessions()?;
- for (homeserver, stored_session) in sessions {
- let session = Session::new(homeserver.to_string());
+ for stored_session in sessions {
+ let session = Session::new();
self.setup_session(&session);
session.login_with_previous_session(stored_session);
}
diff --git a/src/secret.rs b/src/secret.rs
index 68ca4877..3feb854e 100644
--- a/src/secret.rs
+++ b/src/secret.rs
@@ -1,38 +1,74 @@
+use matrix_sdk::identifiers::{DeviceIdBox, UserId};
use secret_service::EncryptionType;
use secret_service::SecretService;
use std::collections::HashMap;
+use std::convert::TryFrom;
+use std::path::PathBuf;
+use url::Url;
-/// Retrives all sessions stored to the `SecretService`
-pub fn restore_sessions() -> Result<Vec<(String, matrix_sdk::Session)>, secret_service::Error> {
- use std::convert::TryInto;
+pub struct StoredSession {
+ pub homeserver: Url,
+ pub path: PathBuf,
+ pub passphrase: String,
+ pub user_id: UserId,
+ pub access_token: String,
+ pub device_id: DeviceIdBox,
+}
+/// Retrives all sessions stored to the `SecretService`
+pub fn restore_sessions() -> Result<Vec<StoredSession>, secret_service::Error> {
let ss = SecretService::new(EncryptionType::Dh)?;
let collection = ss.get_default_collection()?;
// Sessions that contain or produce errors are ignored.
// TODO: Return error for corrupt sessions
+
let res = collection
.get_all_items()?
.iter()
- .filter_map(|item| {
- let attr = item.get_attributes().ok()?;
- if let (Some(homeserver), Some(access_token), Some(user_id), Some(device_id)) = (
- attr.get("homeserver"),
- String::from_utf8(item.get_secret().ok()?).ok(),
- attr.get("user-id")
- .and_then(|s| s.to_string().try_into().ok()),
- attr.get("device-id")
- .and_then(|s| Some(s.to_string().into())),
- ) {
- let session = matrix_sdk::Session {
- access_token,
- user_id,
- device_id,
- };
- Some((homeserver.to_string(), session))
- } else {
- None
+ .fold(HashMap::new(), |mut acc, item| {
+ let finder = move || -> Option<((String, String, String), (String, Option<String>))> {
+ let attr = item.get_attributes().ok()?;
+
+ let homeserver = attr.get("homeserver")?.to_string();
+ let user_id = attr.get("user-id")?.to_string();
+ let device_id = attr.get("device-id")?.to_string();
+ let secret = String::from_utf8(item.get_secret().ok()?).ok()?;
+ let path = attr.get("path").map(|s| s.to_string());
+ Some(((homeserver, user_id, device_id), (secret, path)))
+ };
+
+ if let Some((key, value)) = finder() {
+ acc.entry(key).or_insert(vec![]).push(value);
}
+
+ acc
+ })
+ .into_iter()
+ .filter_map(|((homeserver, user_id, device_id), mut items)| {
+ if items.len() != 2 {
+ return None;
+ }
+ let (access_token, passphrase, path) = match items.pop()? {
+ (secret, Some(path)) => (items.pop()?.0, secret, PathBuf::from(path)),
+ (secret, None) => {
+ let item = items.pop()?;
+ (secret, item.0, PathBuf::from(item.1?))
+ }
+ };
+
+ let homeserver = Url::parse(&homeserver).ok()?;
+ let user_id = UserId::try_from(user_id).ok()?;
+ let device_id = DeviceIdBox::try_from(device_id).ok()?;
+
+ Some(StoredSession {
+ homeserver,
+ path,
+ passphrase,
+ user_id,
+ access_token,
+ device_id,
+ })
})
.collect();
@@ -41,16 +77,14 @@ pub fn restore_sessions() -> Result<Vec<(String, matrix_sdk::Session)>, secret_s
/// Writes a sessions to the `SecretService`, overwriting any previously stored session with the
/// same `homeserver`, `username` and `device-id`.
-pub fn store_session(
- homeserver: &str,
- session: matrix_sdk::Session,
-) -> Result<(), secret_service::Error> {
+pub fn store_session(session: StoredSession) -> Result<(), secret_service::Error> {
let ss = SecretService::new(EncryptionType::Dh)?;
let collection = ss.get_default_collection()?;
+ // Store the infromation for the login
let attributes: HashMap<&str, &str> = [
("user-id", session.user_id.as_str()),
- ("homeserver", homeserver),
+ ("homeserver", session.homeserver.as_str()),
("device-id", session.device_id.as_str()),
]
.iter()
@@ -65,5 +99,24 @@ pub fn store_session(
"text/plain",
)?;
+ // Store the infromation for the crypto store
+ let attributes: HashMap<&str, &str> = [
+ ("path", session.path.to_str().unwrap()),
+ ("user-id", session.user_id.as_str()),
+ ("homeserver", session.homeserver.as_str()),
+ ("device-id", session.device_id.as_str()),
+ ]
+ .iter()
+ .cloned()
+ .collect();
+
+ collection.create_item(
+ "Fractal (Encrypted local database)",
+ attributes,
+ session.passphrase.as_bytes(),
+ true,
+ "text/plain",
+ )?;
+
Ok(())
}
diff --git a/src/session/mod.rs b/src/session/mod.rs
index ab8b303e..c76282c8 100644
--- a/src/session/mod.rs
+++ b/src/session/mod.rs
@@ -11,9 +11,11 @@ use self::user::User;
use crate::event_from_sync_event;
use crate::secret;
+use crate::secret::StoredSession;
use crate::utils::do_async;
use crate::RUNTIME;
+use crate::session::categories::Categories;
use adw;
use adw::subclass::prelude::BinImpl;
use gtk::subclass::prelude::*;
@@ -27,13 +29,14 @@ use matrix_sdk::{
deserialized_responses::SyncResponse,
events::{AnyRoomEvent, AnySyncRoomEvent},
identifiers::RoomId,
+ uuid::Uuid,
Client, ClientConfig, RequestConfig, SyncSettings,
};
+use rand::{distributions::Alphanumeric, thread_rng, Rng};
+use std::fs;
use std::time::Duration;
use url::Url;
-use crate::session::categories::Categories;
-
mod imp {
use super::*;
use glib::subclass::{InitializingObject, Signal};
@@ -50,7 +53,6 @@ mod imp {
pub sidebar: TemplateChild<Sidebar>,
#[template_child]
pub content: TemplateChild<Content>,
- pub homeserver: OnceCell<String>,
/// Contains the error if something went wrong
pub error: RefCell<Option<matrix_sdk::Error>>,
pub client: OnceCell<Client>,
@@ -79,13 +81,6 @@ mod imp {
fn properties() -> &'static [glib::ParamSpec] {
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
vec![
- glib::ParamSpec::new_string(
- "homeserver",
- "Homeserver",
- "The matrix homeserver of this session",
- None,
- glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY,
- ),
glib::ParamSpec::new_object(
"categories",
"Categories",
@@ -114,10 +109,6 @@ mod imp {
pspec: &glib::ParamSpec,
) {
match pspec.name() {
- "homeserver" => {
- let homeserver = value.get().unwrap();
- let _ = obj.set_homeserver(homeserver);
- }
"selected-room" => {
let selected_room = value.get().unwrap();
obj.set_selected_room(selected_room);
@@ -128,7 +119,6 @@ mod imp {
fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
match pspec.name() {
- "homeserver" => self.homeserver.get().to_value(),
"categories" => self.categories.to_value(),
"selected-room" => obj.selected_room().to_value(),
_ => unimplemented!(),
@@ -156,8 +146,8 @@ glib::wrapper! {
}
impl Session {
- pub fn new(homeserver: String) -> Self {
- glib::Object::new(&[("homeserver", &homeserver)]).expect("Failed to create Session")
+ pub fn new() -> Self {
+ glib::Object::new(&[]).expect("Failed to create Session")
}
pub fn selected_room(&self) -> Option<Room> {
@@ -184,42 +174,51 @@ impl Session {
self.notify("selected-room");
}
- fn set_homeserver(&self, homeserver: String) {
- let priv_ = imp::Session::from_instance(self);
-
- priv_.homeserver.set(homeserver.clone()).unwrap();
-
- let config = ClientConfig::new().request_config(RequestConfig::new().retry_limit(2));
- let homeserver = match Url::parse(homeserver.as_str()) {
- Ok(homeserver) => homeserver,
- Err(_error) => {
- // TODO: hanlde parse error
- panic!();
- }
- };
-
- let client = Client::new_with_config(homeserver, config).unwrap();
- priv_.client.set(client).unwrap();
- }
-
- fn client(&self) -> Client {
- let priv_ = imp::Session::from_instance(self);
- priv_.client.get().unwrap().clone()
- }
-
- pub fn login_with_password(&self, username: String, password: String) {
- let client = self.client();
+ pub fn login_with_password(&self, homeserver: Url, username: String, password: String) {
+ let mut path = glib::user_data_dir();
+ path.push(
+ &Uuid::new_v4()
+ .to_hyphenated()
+ .encode_lower(&mut Uuid::encode_buffer()),
+ );
do_async(
async move {
- client
+ let passphrase: String = {
+ let mut rng = thread_rng();
+ (&mut rng)
+ .sample_iter(Alphanumeric)
+ .take(30)
+ .map(char::from)
+ .collect()
+ };
+ let config = ClientConfig::new()
+ .request_config(RequestConfig::new().retry_limit(2))
+ .passphrase(passphrase.clone())
+ .store_path(path.clone());
+
+ let client = Client::new_with_config(homeserver.clone(), config).unwrap();
+ let response = client
.login(&username, &password, None, Some("Fractal Next"))
- .await
- .map(|response| matrix_sdk::Session {
- access_token: response.access_token,
- user_id: response.user_id,
- device_id: response.device_id,
- })
+ .await;
+ match response {
+ Ok(response) => Ok((
+ client,
+ StoredSession {
+ homeserver: homeserver,
+ path: path,
+ passphrase: passphrase,
+ access_token: response.access_token,
+ user_id: response.user_id,
+ device_id: response.device_id,
+ },
+ )),
+ Err(error) => {
+ // Remove the store created by Client::new()
+ fs::remove_dir_all(path).unwrap();
+ Err(error)
+ }
+ }
},
clone!(@weak self as obj => move |result| async move {
obj.handle_login_result(result, true);
@@ -227,11 +226,24 @@ impl Session {
);
}
- pub fn login_with_previous_session(&self, session: matrix_sdk::Session) {
- let client = self.client();
-
+ pub fn login_with_previous_session(&self, session: StoredSession) {
do_async(
- async move { client.restore_login(session.clone()).await.map(|_| session) },
+ async move {
+ let config = ClientConfig::new()
+ .request_config(RequestConfig::new().retry_limit(2))
+ .passphrase(session.passphrase.clone())
+ .store_path(session.path.clone());
+
+ let client = Client::new_with_config(session.homeserver.clone(), config).unwrap();
+ client
+ .restore_login(matrix_sdk::Session {
+ user_id: session.user_id.clone(),
+ device_id: session.device_id.clone(),
+ access_token: session.access_token.clone(),
+ })
+ .await
+ .map(|_| (client, session))
+ },
clone!(@weak self as obj => move |result| async move {
obj.handle_login_result(result, false);
}),
@@ -240,15 +252,17 @@ impl Session {
fn handle_login_result(
&self,
- result: Result<matrix_sdk::Session, matrix_sdk::Error>,
+ result: Result<(Client, StoredSession), matrix_sdk::Error>,
store_session: bool,
) {
- let priv_ = &imp::Session::from_instance(self);
+ let priv_ = imp::Session::from_instance(self);
match result {
- Ok(session) => {
+ Ok((client, session)) => {
+ priv_.client.set(client).unwrap();
self.set_user(User::new(&session.user_id));
if store_session {
- self.store_session(session).unwrap();
+ // TODO: report secret service errors
+ secret::store_session(session).unwrap();
}
self.load();
self.sync();
@@ -261,8 +275,9 @@ impl Session {
}
fn sync(&self) {
+ let priv_ = imp::Session::from_instance(self);
let sender = self.create_new_sync_response_sender();
- let client = self.client();
+ let client = priv_.client.get().unwrap().clone();
RUNTIME.spawn(async move {
// TODO: only create the filter once and reuse it in the future
let room_event_filter = assign!(RoomEventFilter::default(), {
@@ -344,12 +359,6 @@ impl Session {
.unwrap()
}
- fn store_session(&self, session: matrix_sdk::Session) -> Result<(), secret_service::Error> {
- let priv_ = &imp::Session::from_instance(self);
- let homeserver = priv_.homeserver.get().unwrap();
- secret::store_session(homeserver, session)
- }
-
fn handle_sync_response(&self, response: SyncResponse) {
let priv_ = imp::Session::from_instance(self);
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]