Browse Source

session-list: Only restore sessions with a data directory on the system

This is necessary to avoid issues for users sharing their secrets
between devices.
fractal-9
Kévin Commaille 1 year ago
parent
commit
3400931fb3
No known key found for this signature in database
GPG Key ID: C971D9DBC9D678D
  1. 39
      src/secret/mod.rs
  2. 147
      src/session_list/mod.rs
  3. 29
      src/utils/mod.rs

39
src/secret/mod.rs

@ -1,6 +1,6 @@
//! API to store the data of a session in a secret store on the system.
use std::{borrow::Cow, fmt, path::PathBuf};
use std::{fmt, path::PathBuf};
use gtk::glib;
use matrix_sdk::{
@ -28,7 +28,11 @@ pub use self::linux::{restore_sessions, store_session};
use self::unimplemented::delete_session;
#[cfg(not(target_os = "linux"))]
pub use self::unimplemented::{restore_sessions, store_session};
use crate::{application::AppProfile, prelude::*, spawn_tokio, GETTEXT_PACKAGE, PROFILE};
use crate::{
prelude::*,
spawn_tokio,
utils::{data_dir_path, DataType},
};
/// The length of a session ID, in chars or bytes as the string is ASCII.
pub const SESSION_ID_LENGTH: usize = 8;
@ -93,7 +97,7 @@ impl StoredSession {
// Generate a unique random session ID.
let mut id = None;
let data_path = db_dir_path(DbContentType::Data);
let data_path = data_dir_path(DataType::Persistent);
// Try 10 times, so we do not have an infinite loop.
for _ in 0..10 {
@ -137,14 +141,14 @@ impl StoredSession {
/// The path where the persistent data of this session lives.
pub fn data_path(&self) -> PathBuf {
let mut path = db_dir_path(DbContentType::Data);
let mut path = data_dir_path(DataType::Persistent);
path.push(&self.id);
path
}
/// The path where the cached data of this session lives.
pub fn cache_path(&self) -> PathBuf {
let mut path = db_dir_path(DbContentType::Cache);
let mut path = data_dir_path(DataType::Cache);
path.push(&self.id);
path
}
@ -179,28 +183,3 @@ pub struct Secret {
/// The passphrase used to encrypt the local databases.
pub passphrase: String,
}
/// The path of the directory where a database should be stored, depending on
/// the type of content.
fn db_dir_path(content_type: DbContentType) -> PathBuf {
let dir_name = match PROFILE {
AppProfile::Stable => Cow::Borrowed(GETTEXT_PACKAGE),
_ => Cow::Owned(format!("{GETTEXT_PACKAGE}-{PROFILE}")),
};
let mut path = match content_type {
DbContentType::Data => glib::user_data_dir(),
DbContentType::Cache => glib::user_cache_dir(),
};
path.push(dir_name.as_ref());
path
}
/// The type of content of a database.
enum DbContentType {
/// Data that should not be deleted.
Data,
/// Cache that can be deleted freely.
Cache,
}

147
src/session_list/mod.rs

@ -1,4 +1,4 @@
use std::cmp::Ordering;
use std::{cmp::Ordering, ffi::OsString};
use gettextrs::gettext;
use gtk::{gio, glib, glib::clone, prelude::*, subclass::prelude::*};
@ -16,7 +16,7 @@ use crate::{
secret::{self, StoredSession},
session::model::{Session, SessionState},
spawn, spawn_tokio,
utils::LoadingState,
utils::{data_dir_path, DataType, LoadingState},
};
mod imp {
@ -222,58 +222,121 @@ impl SessionList {
self.set_state(LoadingState::Loading);
let handle = spawn_tokio!(secret::restore_sessions());
match handle.await.unwrap() {
Ok(mut sessions) => {
let settings = self.settings();
settings.load();
let session_ids = settings.session_ids();
// Keep the order from the settings.
sessions.sort_by(|a, b| {
let pos_a = session_ids.get_index_of(&a.id);
let pos_b = session_ids.get_index_of(&b.id);
match (pos_a, pos_b) {
(Some(pos_a), Some(pos_b)) => pos_a.cmp(&pos_b),
// Keep unknown sessions at the end.
(Some(_), None) => Ordering::Greater,
(None, Some(_)) => Ordering::Less,
_ => Ordering::Equal,
}
});
for stored_session in sessions {
info!(
"Restoring previous session {} for user {}",
stored_session.id, stored_session.user_id,
);
self.insert(NewSession::new(stored_session.clone()));
spawn!(
glib::Priority::DEFAULT_IDLE,
clone!(
#[weak(rename_to = obj)]
self,
async move {
obj.restore_stored_session(stored_session).await;
}
)
);
}
let mut sessions = match handle.await.unwrap() {
Ok(sessions) => sessions,
Err(error) => {
let message = format!(
"{}\n\n{}",
gettext("Could not restore previous sessions"),
error.to_user_facing(),
);
self.set_state(LoadingState::Ready)
self.set_error(message);
self.set_state(LoadingState::Error);
return;
}
};
let settings = self.settings();
settings.load();
let session_ids = settings.session_ids();
// Keep the order from the settings.
sessions.sort_by(|a, b| {
let pos_a = session_ids.get_index_of(&a.id);
let pos_b = session_ids.get_index_of(&b.id);
match (pos_a, pos_b) {
(Some(pos_a), Some(pos_b)) => pos_a.cmp(&pos_b),
// Keep unknown sessions at the end.
(Some(_), None) => Ordering::Greater,
(None, Some(_)) => Ordering::Less,
_ => Ordering::Equal,
}
});
// Get the directories present in the data path to only restore sessions with
// data on the system. This is necessary for users sharing their secrets between
// devices.
let mut directories = match self.data_directories(sessions.len()).await {
Ok(directories) => directories,
Err(error) => {
error!("Could not access data directory: {error}");
let message = format!(
"{}\n\n{}",
gettext("Could not restore previous sessions"),
error.to_user_facing(),
gettext("An unexpected error happened while accessing the data directory"),
);
self.set_error(message);
self.set_state(LoadingState::Error);
return;
}
};
for stored_session in sessions {
if let Some(pos) = directories
.iter()
.position(|dir_name| dir_name == stored_session.id.as_str())
{
directories.swap_remove(pos);
info!(
"Restoring previous session {} for user {}",
stored_session.id, stored_session.user_id,
);
self.insert(NewSession::new(stored_session.clone()));
spawn!(
glib::Priority::DEFAULT_IDLE,
clone!(
#[weak(rename_to = obj)]
self,
async move {
obj.restore_stored_session(stored_session).await;
}
)
);
} else {
info!(
"Ignoring session {} for user {}: no data directory",
stored_session.id, stored_session.user_id,
);
}
}
self.set_state(LoadingState::Ready)
}
/// The list of directories in the data directory.
async fn data_directories(&self, capacity: usize) -> std::io::Result<Vec<OsString>> {
let data_path = data_dir_path(DataType::Persistent);
if !data_path.try_exists()? {
return Ok(Vec::new());
}
spawn_tokio!(async move {
let mut read_dir = tokio::fs::read_dir(data_path).await?;
let mut directories = Vec::with_capacity(capacity);
loop {
let Some(entry) = read_dir.next_entry().await? else {
// We are at the end of the list.
break;
};
if !entry.file_type().await?.is_dir() {
// We are only interested in directories.
continue;
}
directories.push(entry.file_name());
}
std::io::Result::Ok(directories)
})
.await
.expect("task was not aborted")
}
/// Restore a stored session.

29
src/utils/mod.rs

@ -14,8 +14,10 @@ pub mod string;
pub mod template_callbacks;
use std::{
borrow::Cow,
cell::{Cell, OnceCell, RefCell},
fmt,
path::PathBuf,
rc::{Rc, Weak},
};
@ -33,7 +35,32 @@ pub use self::{
location::{Location, LocationError, LocationExt},
single_item_list_model::SingleItemListModel,
};
use crate::RUNTIME;
use crate::{AppProfile, GETTEXT_PACKAGE, PROFILE, RUNTIME};
/// The path of the directory where data should be stored, depending on its
/// type.
pub fn data_dir_path(data_type: DataType) -> PathBuf {
let dir_name = match PROFILE {
AppProfile::Stable => Cow::Borrowed(GETTEXT_PACKAGE),
_ => Cow::Owned(format!("{GETTEXT_PACKAGE}-{PROFILE}")),
};
let mut path = match data_type {
DataType::Persistent => glib::user_data_dir(),
DataType::Cache => glib::user_cache_dir(),
};
path.push(dir_name.as_ref());
path
}
/// The type of data.
pub enum DataType {
/// Data that should not be deleted.
Persistent,
/// Cache that can be deleted freely.
Cache,
}
pub enum TimeoutFuture {
Timeout,

Loading…
Cancel
Save