secret: Use oo7 instead of libsecret
This commit is contained in:
parent
b0f51bd1f9
commit
4d5791f817
|
@ -693,6 +693,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"rand_core 0.6.3",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
|
@ -707,9 +708,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "curve25519-dalek"
|
||||
version = "3.2.1"
|
||||
version = "3.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90f9d052967f590a76e62eb387bd0bbb1b000182c3cefe5364db6b7211651bc0"
|
||||
checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"digest 0.9.0",
|
||||
|
@ -1085,7 +1086,6 @@ dependencies = [
|
|||
"image 0.24.4",
|
||||
"indexmap",
|
||||
"libadwaita",
|
||||
"libsecret",
|
||||
"libshumate",
|
||||
"log",
|
||||
"matrix-sdk",
|
||||
|
@ -1093,6 +1093,7 @@ dependencies = [
|
|||
"mime_guess",
|
||||
"num_enum",
|
||||
"once_cell",
|
||||
"oo7",
|
||||
"pulldown-cmark",
|
||||
"qrcode",
|
||||
"rand 0.8.5",
|
||||
|
@ -2352,34 +2353,6 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libsecret"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4af5a2342942fa42d706a424e9f9914287fb8317132750fd73a241140ac38c1"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"gio",
|
||||
"glib",
|
||||
"libc",
|
||||
"libsecret-sys",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libsecret-sys"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b630bef24b542dc1609a14c56b9267c147dbef1ee7ad08fb1a852a07c17d492d"
|
||||
dependencies = [
|
||||
"gio-sys",
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libshumate"
|
||||
version = "0.1.1"
|
||||
|
@ -2890,6 +2863,40 @@ dependencies = [
|
|||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"num-complex",
|
||||
"num-integer",
|
||||
"num-iter",
|
||||
"num-rational 0.4.1",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-complex"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.45"
|
||||
|
@ -2929,6 +2936,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-bigint",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
@ -3008,6 +3016,32 @@ version = "1.15.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1"
|
||||
|
||||
[[package]]
|
||||
name = "oo7"
|
||||
version = "0.1.0-alpha.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e614140b3625a16ddde5880e5c6429d4ef901883256edf496cceb62af1c7b6f8"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"byteorder",
|
||||
"cbc",
|
||||
"cipher 0.4.3",
|
||||
"digest 0.10.3",
|
||||
"dirs",
|
||||
"futures",
|
||||
"hkdf",
|
||||
"hmac",
|
||||
"num",
|
||||
"once_cell",
|
||||
"pbkdf2",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"sha2 0.10.5",
|
||||
"tokio",
|
||||
"zbus",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
version = "0.3.0"
|
||||
|
@ -5040,8 +5074,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "x25519-dalek"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2392b6b94a576b4e2bf3c5b2757d63f10ada8020a2e4d08ac849ebcf6ea8e077"
|
||||
source = "git+https://github.com/A6GibKm/x25519-dalek?rev=9f19028c34107eea87d37bcee2eb2b350ec34cfe#9f19028c34107eea87d37bcee2eb2b350ec34cfe"
|
||||
dependencies = [
|
||||
"curve25519-dalek",
|
||||
"rand_core 0.5.1",
|
||||
|
@ -5126,9 +5159,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.3.0"
|
||||
version = "1.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd"
|
||||
checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f"
|
||||
dependencies = [
|
||||
"zeroize_derive",
|
||||
]
|
||||
|
|
|
@ -28,7 +28,9 @@ serde = "1.0.130"
|
|||
serde_json = "1.0"
|
||||
tokio = { version = "1.15", features = ["rt", "rt-multi-thread", "sync"] }
|
||||
url = "2.2"
|
||||
libsecret = { version = "0.1.4", features = ["v0_19"] }
|
||||
oo7 = { version = "0.1.0-alpha.5", default-features = false, features = [
|
||||
"tokio",
|
||||
] }
|
||||
html2pango = "0.5.0"
|
||||
futures = "0.3"
|
||||
rand = "0.8"
|
||||
|
@ -93,3 +95,7 @@ features = [
|
|||
"unstable-msc3440",
|
||||
"unstable-sanitize",
|
||||
]
|
||||
|
||||
[patch.crates-io.x25519-dalek]
|
||||
git = "https://github.com/A6GibKm/x25519-dalek"
|
||||
rev = "9f19028c34107eea87d37bcee2eb2b350ec34cfe"
|
||||
|
|
|
@ -26,8 +26,6 @@ dependency('gstreamer-1.0', version: '>= 1.18')
|
|||
dependency('gstreamer-base-1.0', version: '>= 1.18')
|
||||
dependency('gstreamer-plugins-base-1.0', version: '>= 1.18')
|
||||
dependency('gstreamer-video-1.0', version: '>= 1.18')
|
||||
dependency('libsecret-1', version: '>= 0.19',
|
||||
default_options: ['gtk_doc=false', 'gir=false', 'vapi=false'])
|
||||
|
||||
glib_compile_resources = find_program('glib-compile-resources', required: true)
|
||||
glib_compile_schemas = find_program('glib-compile-schemas', required: true)
|
||||
|
|
|
@ -3,7 +3,7 @@ use gettextrs::gettext;
|
|||
use gtk::{self, glib, glib::clone, prelude::*, subclass::prelude::*, CompositeTemplate};
|
||||
use log::error;
|
||||
|
||||
use crate::{secret, secret::SecretError, spawn, toast, window::Window};
|
||||
use crate::{spawn, toast, window::Window};
|
||||
|
||||
pub enum ErrorSubpage {
|
||||
SecretErrorSession,
|
||||
|
@ -33,7 +33,7 @@ mod imp {
|
|||
pub page: TemplateChild<adw::StatusPage>,
|
||||
#[template_child]
|
||||
pub stack: TemplateChild<gtk::Stack>,
|
||||
pub secret_error: RefCell<Option<SecretError>>,
|
||||
pub secret_item: RefCell<Option<oo7::Item>>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
|
@ -82,22 +82,24 @@ impl ErrorPage {
|
|||
glib::Object::new(&[]).expect("Failed to create ErrorPage")
|
||||
}
|
||||
|
||||
pub fn display_secret_error(&self, message: &str, error: SecretError) {
|
||||
pub fn display_secret_error(&self, message: &str, item: Option<oo7::Item>) {
|
||||
let priv_ = self.imp();
|
||||
self.action_set_enabled(
|
||||
"error-page.remove-secret-error-session",
|
||||
matches!(error, SecretError::CorruptSession(_)),
|
||||
);
|
||||
self.action_set_enabled("error-page.remove-secret-error-session", item.is_some());
|
||||
priv_.page.set_description(Some(message));
|
||||
priv_
|
||||
.stack
|
||||
.set_visible_child_name(error.error_subpage().as_ref());
|
||||
priv_.secret_error.replace(Some(error));
|
||||
|
||||
let error_subpage = if item.is_some() {
|
||||
ErrorSubpage::SecretErrorSession
|
||||
} else {
|
||||
ErrorSubpage::SecretErrorOther
|
||||
};
|
||||
|
||||
priv_.stack.set_visible_child_name(error_subpage.as_ref());
|
||||
priv_.secret_item.replace(item);
|
||||
}
|
||||
|
||||
async fn remove_secret_error_session(&self) {
|
||||
if let Some(SecretError::CorruptSession((_, item))) = self.imp().secret_error.take() {
|
||||
match secret::remove_item(&item).await {
|
||||
if let Some(item) = self.imp().secret_item.take() {
|
||||
match item.delete().await {
|
||||
Ok(_) => {
|
||||
self.action_set_enabled("error-page.remove-secret-error-session", false);
|
||||
if let Some(window) = self
|
||||
|
|
|
@ -595,12 +595,16 @@ impl Login {
|
|||
let session = Session::new();
|
||||
|
||||
if is_new {
|
||||
if let Err(error) = secret::store_session(&session_info).await {
|
||||
let session_info = session_info.clone();
|
||||
let handle = spawn_tokio!(async move { secret::store_session(&session_info).await });
|
||||
|
||||
if let Err(error) = handle.await.unwrap() {
|
||||
error!("Couldn't store session: {:?}", error);
|
||||
|
||||
let (message, item) = error.into_parts();
|
||||
self.parent_window().switch_to_error_page(
|
||||
&format!("{}\n\n{}", gettext("Unable to store session"), error),
|
||||
error,
|
||||
&format!("{}\n\n{}", gettext("Unable to store session"), message),
|
||||
item,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
|
10
src/main.rs
10
src/main.rs
|
@ -27,14 +27,8 @@ use gtk::{gdk::Display, gio, IconTheme};
|
|||
use once_cell::sync::Lazy;
|
||||
|
||||
use self::{
|
||||
application::Application,
|
||||
error_page::{ErrorPage, ErrorSubpage},
|
||||
greeter::Greeter,
|
||||
i18n::*,
|
||||
login::Login,
|
||||
session::Session,
|
||||
user_facing_error::UserFacingError,
|
||||
window::Window,
|
||||
application::Application, error_page::ErrorPage, greeter::Greeter, i18n::*, login::Login,
|
||||
session::Session, user_facing_error::UserFacingError, window::Window,
|
||||
};
|
||||
|
||||
/// The default tokio runtime to be used for async tasks
|
||||
|
|
280
src/secret.rs
280
src/secret.rs
|
@ -1,68 +1,104 @@
|
|||
use std::{collections::HashMap, ffi::OsStr, fmt, path::PathBuf, string::FromUtf8Error};
|
||||
|
||||
use gettextrs::gettext;
|
||||
use gtk::{gio, glib};
|
||||
use libsecret::{
|
||||
password_clear_future, password_search_sync, password_store_binary_future, prelude::*,
|
||||
Retrievable, Schema, SchemaAttributeType, SchemaFlags, SearchFlags, Value, COLLECTION_DEFAULT,
|
||||
};
|
||||
use log::error;
|
||||
use matrix_sdk::ruma::{DeviceId, OwnedDeviceId, OwnedUserId, UserId};
|
||||
use oo7::{is_sandboxed, Item, Keyring};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::error::Error as JsonError;
|
||||
use thiserror::Error;
|
||||
use url::Url;
|
||||
|
||||
use crate::{config::APP_ID, gettext_f, ErrorSubpage};
|
||||
use crate::{config::APP_ID, gettext_f, user_facing_error::UserFacingError};
|
||||
|
||||
const SCHEMA_ATTRIBUTE: &str = "xdg:schema";
|
||||
|
||||
/// Any error that can happen when interacting with the secret service.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum SecretError {
|
||||
CorruptSession((String, Retrievable)),
|
||||
Libsecret(glib::Error),
|
||||
Unknown,
|
||||
/// A corrupted session was found.
|
||||
#[error("{0}")]
|
||||
CorruptSession(String, Item),
|
||||
|
||||
/// An error occurred interacting with the secret service.
|
||||
#[error(transparent)]
|
||||
Oo7(#[from] oo7::Error),
|
||||
}
|
||||
|
||||
impl SecretError {
|
||||
/// Get the error subpage that matches `self`.
|
||||
pub fn error_subpage(&self) -> ErrorSubpage {
|
||||
/// Split `self` between its message and its optional `Item`.
|
||||
pub fn into_parts(self) -> (String, Option<Item>) {
|
||||
match self {
|
||||
Self::CorruptSession(_) => ErrorSubpage::SecretErrorSession,
|
||||
_ => ErrorSubpage::SecretErrorOther,
|
||||
SecretError::CorruptSession(message, item) => (message, Some(item)),
|
||||
SecretError::Oo7(error) => (error.to_user_facing(), None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<glib::Error> for SecretError {
|
||||
fn from(error: glib::Error) -> Self {
|
||||
Self::Libsecret(error)
|
||||
impl UserFacingError for oo7::Error {
|
||||
fn to_user_facing(self) -> String {
|
||||
match self {
|
||||
oo7::Error::Portal(error) => error.to_user_facing(),
|
||||
oo7::Error::DBus(error) => error.to_user_facing(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for SecretError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
Self::CorruptSession((message, _)) => message.to_owned(),
|
||||
Self::Libsecret(error) if error.is::<libsecret::Error>() => {
|
||||
match error.kind::<libsecret::Error>() {
|
||||
Some(libsecret::Error::Protocol) => error.message().to_owned(),
|
||||
Some(libsecret::Error::IsLocked) => {
|
||||
gettext("Could not unlock the secret storage")
|
||||
}
|
||||
_ => gettext(
|
||||
"An unknown error occurred when interacting with the secret storage",
|
||||
),
|
||||
}
|
||||
}
|
||||
_ => gettext("An unknown error occurred when interacting with the secret storage"),
|
||||
}
|
||||
)
|
||||
impl UserFacingError for oo7::portal::Error {
|
||||
fn to_user_facing(self) -> String {
|
||||
match self {
|
||||
oo7::portal::Error::FileHeaderMismatch(_) |
|
||||
oo7::portal::Error::VersionMismatch(_) |
|
||||
oo7::portal::Error::NoData |
|
||||
oo7::portal::Error::MacError |
|
||||
oo7::portal::Error::HashedAttributeMac(_) |
|
||||
oo7::portal::Error::GVariantDeserialization(_) => gettext(
|
||||
"The secret storage file is corrupted.",
|
||||
),
|
||||
oo7::portal::Error::NoParentDir(_) |
|
||||
oo7::portal::Error::NoDataDir => gettext(
|
||||
"Could not access the secret storage file location.",
|
||||
),
|
||||
oo7::portal::Error::Io(_) => gettext(
|
||||
"An unknown error occurred when accessing the secret storage file.",
|
||||
),
|
||||
oo7::portal::Error::TargetFileChanged(_) => gettext(
|
||||
"The secret storage file has been changed by another process.",
|
||||
),
|
||||
oo7::portal::Error::PortalBus(_) => gettext(
|
||||
"An unknown error occurred when interacting with the D-Bus Secret Service.",
|
||||
),
|
||||
oo7::portal::Error::CancelledPortalRequest => gettext(
|
||||
"The request to the Flatpak Secret Portal was cancelled. Make sure to accept any prompt asking to access it.",
|
||||
),
|
||||
oo7::portal::Error::PortalNotAvailable => gettext(
|
||||
"The Flatpak Secret Portal is not available. Make sure xdg-desktop-portal is installed, and it is at least at version 1.5.0.",
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
impl UserFacingError for oo7::dbus::Error {
|
||||
fn to_user_facing(self) -> String {
|
||||
match self {
|
||||
oo7::dbus::Error::Deleted => gettext(
|
||||
"The item was deleted.",
|
||||
),
|
||||
oo7::dbus::Error::Dismissed => gettext(
|
||||
"The request to the D-Bus Secret Service was cancelled. Make sure to accept any prompt asking to access it.",
|
||||
),
|
||||
oo7::dbus::Error::NotFound(_) => gettext(
|
||||
"Could not access the default collection. Make sure a keyring was created and set as default.",
|
||||
),
|
||||
oo7::dbus::Error::Zbus(_) |
|
||||
oo7::dbus::Error::IO(_) => gettext(
|
||||
"An unknown error occurred when interacting with the D-Bus Secret Service.",
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct StoredSession {
|
||||
pub homeserver: Url,
|
||||
pub user_id: OwnedUserId,
|
||||
|
@ -71,10 +107,21 @@ pub struct StoredSession {
|
|||
pub secret: Secret,
|
||||
}
|
||||
|
||||
impl fmt::Debug for StoredSession {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("StoredSession")
|
||||
.field("homeserver", &self.homeserver)
|
||||
.field("user_id", &self.user_id)
|
||||
.field("device_id", &self.device_id)
|
||||
.field("path", &self.path)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl StoredSession {
|
||||
/// Build self from a secret.
|
||||
pub async fn try_from_secret_item(item: Retrievable) -> Result<Self, SecretError> {
|
||||
let attr = item.attributes();
|
||||
pub async fn try_from_secret_item(item: Item) -> Result<Self, SecretError> {
|
||||
let attr = item.attributes().await?;
|
||||
|
||||
let homeserver = match attr.get("homeserver") {
|
||||
Some(string) => match Url::parse(string) {
|
||||
|
@ -84,17 +131,17 @@ impl StoredSession {
|
|||
"Could not parse 'homeserver' attribute in stored session: {:?}",
|
||||
err
|
||||
);
|
||||
return Err(SecretError::CorruptSession((
|
||||
return Err(SecretError::CorruptSession(
|
||||
gettext("Malformed homeserver in stored session"),
|
||||
item,
|
||||
)));
|
||||
));
|
||||
}
|
||||
},
|
||||
None => {
|
||||
return Err(SecretError::CorruptSession((
|
||||
return Err(SecretError::CorruptSession(
|
||||
gettext("Could not find homeserver in stored session"),
|
||||
item,
|
||||
)));
|
||||
));
|
||||
}
|
||||
};
|
||||
let user_id = match attr.get("user") {
|
||||
|
@ -105,60 +152,54 @@ impl StoredSession {
|
|||
"Could not parse 'user' attribute in stored session: {:?}",
|
||||
err
|
||||
);
|
||||
return Err(SecretError::CorruptSession((
|
||||
return Err(SecretError::CorruptSession(
|
||||
gettext("Malformed user ID in stored session"),
|
||||
item,
|
||||
)));
|
||||
));
|
||||
}
|
||||
},
|
||||
None => {
|
||||
return Err(SecretError::CorruptSession((
|
||||
return Err(SecretError::CorruptSession(
|
||||
gettext("Could not find user ID in stored session"),
|
||||
item,
|
||||
)));
|
||||
));
|
||||
}
|
||||
};
|
||||
let device_id = match attr.get("device-id") {
|
||||
Some(string) => <&DeviceId>::from(string.as_str()).to_owned(),
|
||||
None => {
|
||||
return Err(SecretError::CorruptSession((
|
||||
return Err(SecretError::CorruptSession(
|
||||
gettext("Could not find device ID in stored session"),
|
||||
item,
|
||||
)));
|
||||
));
|
||||
}
|
||||
};
|
||||
let path = match attr.get("db-path") {
|
||||
Some(string) => PathBuf::from(string),
|
||||
None => {
|
||||
return Err(SecretError::CorruptSession((
|
||||
return Err(SecretError::CorruptSession(
|
||||
gettext("Could not find database path in stored session"),
|
||||
item,
|
||||
)));
|
||||
));
|
||||
}
|
||||
};
|
||||
let secret = match item.retrieve_secret_future().await {
|
||||
Ok(Some(value)) => match Secret::from_utf8(value.get()) {
|
||||
let secret = match item.secret().await {
|
||||
Ok(secret) => match Secret::from_utf8(&secret) {
|
||||
Ok(secret) => secret,
|
||||
Err(err) => {
|
||||
error!("Could not parse secret in stored session: {:?}", err);
|
||||
return Err(SecretError::CorruptSession((
|
||||
return Err(SecretError::CorruptSession(
|
||||
gettext("Malformed secret in stored session"),
|
||||
item,
|
||||
)));
|
||||
));
|
||||
}
|
||||
},
|
||||
Ok(None) => {
|
||||
return Err(SecretError::CorruptSession((
|
||||
gettext("No secret in stored session"),
|
||||
item,
|
||||
)));
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Could not get secret in stored session: {:?}", err);
|
||||
return Err(SecretError::CorruptSession((
|
||||
return Err(SecretError::CorruptSession(
|
||||
gettext("Could not get secret in stored session"),
|
||||
item,
|
||||
)));
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -171,20 +212,14 @@ impl StoredSession {
|
|||
})
|
||||
}
|
||||
|
||||
/// Build a secret from `self`.
|
||||
///
|
||||
/// Returns an (attributes, secret) tuple.
|
||||
pub fn to_secret_item(&self) -> (HashMap<&str, &str>, Value) {
|
||||
let attributes = HashMap::from([
|
||||
/// Get the attributes from `self`.
|
||||
pub fn attributes(&self) -> HashMap<&str, &str> {
|
||||
HashMap::from([
|
||||
("homeserver", self.homeserver.as_str()),
|
||||
("user", self.user_id.as_str()),
|
||||
("device-id", self.device_id.as_str()),
|
||||
("db-path", self.path.to_str().unwrap()),
|
||||
]);
|
||||
|
||||
let secret = Value::new(&self.secret.to_string(), "application/json");
|
||||
|
||||
(attributes, secret)
|
||||
])
|
||||
}
|
||||
|
||||
/// Get the unique ID for this `StoredSession`.
|
||||
|
@ -219,7 +254,7 @@ impl From<JsonError> for FromUtf8SecretError {
|
|||
}
|
||||
|
||||
/// A `Secret` that can be stored in the `SecretService`.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
pub struct Secret {
|
||||
pub access_token: String,
|
||||
pub passphrase: String,
|
||||
|
@ -227,38 +262,24 @@ pub struct Secret {
|
|||
|
||||
impl Secret {
|
||||
/// Converts a vector of bytes to a `Secret`.
|
||||
pub fn from_utf8(vec: Vec<u8>) -> Result<Self, FromUtf8SecretError> {
|
||||
let s = String::from_utf8(vec)?;
|
||||
pub fn from_utf8(slice: &[u8]) -> Result<Self, FromUtf8SecretError> {
|
||||
let s = String::from_utf8(slice.to_owned())?;
|
||||
Ok(serde_json::from_str(&s)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Secret {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", serde_json::to_string(self).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
/// The `Schema` of the items in the `SecretService`.
|
||||
fn schema() -> Schema {
|
||||
let attributes = HashMap::from([
|
||||
("homeserver", SchemaAttributeType::String),
|
||||
("user", SchemaAttributeType::String),
|
||||
("device-id", SchemaAttributeType::String),
|
||||
("db-path", SchemaAttributeType::String),
|
||||
]);
|
||||
|
||||
Schema::new(APP_ID, SchemaFlags::NONE, attributes)
|
||||
}
|
||||
|
||||
/// Retrieves all sessions stored to the `SecretService`
|
||||
pub async fn restore_sessions() -> Result<Vec<StoredSession>, SecretError> {
|
||||
let items = password_search_sync(
|
||||
Some(&schema()),
|
||||
HashMap::new(),
|
||||
SearchFlags::ALL | SearchFlags::UNLOCK | SearchFlags::LOAD_SECRETS,
|
||||
gio::Cancellable::NONE,
|
||||
)?;
|
||||
let keyring = Keyring::new().await?;
|
||||
|
||||
let items = if is_sandboxed() {
|
||||
keyring.items().await?
|
||||
} else {
|
||||
keyring
|
||||
.search_items(HashMap::from([(SCHEMA_ATTRIBUTE, APP_ID)]))
|
||||
.await?
|
||||
};
|
||||
|
||||
let mut sessions = Vec::with_capacity(items.len());
|
||||
|
||||
for item in items {
|
||||
|
@ -271,43 +292,40 @@ pub async fn restore_sessions() -> Result<Vec<StoredSession>, SecretError> {
|
|||
/// Writes a session to the `SecretService`, overwriting any previously stored
|
||||
/// session with the same `homeserver`, `username` and `device-id`.
|
||||
pub async fn store_session(session: &StoredSession) -> Result<(), SecretError> {
|
||||
let (attributes, secret) = session.to_secret_item();
|
||||
let keyring = Keyring::new().await?;
|
||||
|
||||
password_store_binary_future(
|
||||
Some(&schema()),
|
||||
attributes,
|
||||
Some(&COLLECTION_DEFAULT),
|
||||
&gettext_f(
|
||||
// Translators: Do NOT translate the content between '{' and '}', this is a variable
|
||||
// name.
|
||||
"Fractal: Matrix credentials for {user_id}",
|
||||
&[("user_id", session.user_id.as_str())],
|
||||
),
|
||||
&secret,
|
||||
)
|
||||
.await?;
|
||||
let mut attributes = session.attributes();
|
||||
|
||||
if !is_sandboxed() {
|
||||
attributes.insert(SCHEMA_ATTRIBUTE, APP_ID);
|
||||
}
|
||||
|
||||
let secret = serde_json::to_string(&session.secret).unwrap();
|
||||
|
||||
keyring
|
||||
.create_item(
|
||||
&gettext_f(
|
||||
// Translators: Do NOT translate the content between '{' and '}', this is a
|
||||
// variable name.
|
||||
"Fractal: Matrix credentials for {user_id}",
|
||||
&[("user_id", session.user_id.as_str())],
|
||||
),
|
||||
attributes,
|
||||
secret,
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Removes a session from the `SecretService`
|
||||
pub async fn remove_session(session: &StoredSession) -> Result<(), SecretError> {
|
||||
let (attributes, _) = session.to_secret_item();
|
||||
let keyring = Keyring::new().await?;
|
||||
|
||||
password_clear_future(Some(&schema()), attributes).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Removes an item from the `SecretService`
|
||||
pub async fn remove_item(item: &Retrievable) -> Result<(), SecretError> {
|
||||
let attributes = item.attributes();
|
||||
let mut attr = HashMap::with_capacity(attributes.len());
|
||||
|
||||
for (key, value) in attributes.iter() {
|
||||
attr.insert(key.as_str(), value.as_str());
|
||||
}
|
||||
password_clear_future(Some(&schema()), attr).await?;
|
||||
let attributes = session.attributes();
|
||||
|
||||
keyring.delete(attributes).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -709,7 +709,9 @@ impl Session {
|
|||
settings.delete_settings();
|
||||
}
|
||||
|
||||
if let Err(error) = secret::remove_session(info).await {
|
||||
let session_info = info.clone();
|
||||
let handle = spawn_tokio!(async move { secret::remove_session(&session_info).await });
|
||||
if let Err(error) = handle.await.unwrap() {
|
||||
error!(
|
||||
"Failed to remove credentials from SecretService after logout: {}",
|
||||
error
|
||||
|
|
|
@ -7,8 +7,7 @@ use log::{info, warn};
|
|||
use crate::{
|
||||
account_switcher::AccountSwitcher,
|
||||
config::{APP_ID, PROFILE},
|
||||
secret::{self, SecretError},
|
||||
spawn, Application, ErrorPage, Greeter, Login, Session,
|
||||
secret, spawn, spawn_tokio, Application, ErrorPage, Greeter, Login, Session,
|
||||
};
|
||||
|
||||
mod imp {
|
||||
|
@ -212,7 +211,8 @@ impl Window {
|
|||
}
|
||||
|
||||
pub async fn restore_sessions(&self) {
|
||||
match secret::restore_sessions().await {
|
||||
let handle = spawn_tokio!(secret::restore_sessions());
|
||||
match handle.await.unwrap() {
|
||||
Ok(sessions) => {
|
||||
if sessions.is_empty() {
|
||||
self.switch_to_greeter_page();
|
||||
|
@ -237,13 +237,15 @@ impl Window {
|
|||
}
|
||||
Err(error) => {
|
||||
warn!("Failed to restore previous sessions: {:?}", error);
|
||||
|
||||
let (message, item) = error.into_parts();
|
||||
self.switch_to_error_page(
|
||||
&format!(
|
||||
"{}\n\n{}",
|
||||
gettext("Failed to restore previous sessions"),
|
||||
error,
|
||||
message,
|
||||
),
|
||||
error,
|
||||
item,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -316,9 +318,9 @@ impl Window {
|
|||
priv_.main_stack.set_visible_child(&*priv_.greeter);
|
||||
}
|
||||
|
||||
pub fn switch_to_error_page(&self, message: &str, error: SecretError) {
|
||||
pub fn switch_to_error_page(&self, message: &str, item: Option<oo7::Item>) {
|
||||
let priv_ = self.imp();
|
||||
priv_.error_page.display_secret_error(message, error);
|
||||
priv_.error_page.display_secret_error(message, item);
|
||||
priv_.main_stack.set_visible_child(&*priv_.error_page);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue