Switch to oo7 for secret service handling

Also switch to tracing while we are at it. Might need to refactor the
whole keyring module in a
later commit
This commit is contained in:
Bilal Elmoussaoui 2022-04-27 15:27:28 +02:00 committed by Bilal Elmoussaoui
parent 68029e5e42
commit 3c3eb02fff
26 changed files with 547 additions and 590 deletions

633
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -17,15 +17,15 @@ gtk = {package = "gtk4", version = "0.4"}
gtk-macros = "0.3"
search-provider = "0.3"
hex = { version = "0.4.3", features = [ "serde" ] }
log = "0.4"
tracing = "0.1"
tracing-subscriber = "0.3"
once_cell = "1.9"
percent-encoding = "2.1"
pretty_env_logger = "0.4"
qrcode = {version = "0.12", features = ["image"]}
rand = "0.8"
ring = "0.16"
rust-argon2 = "1.0"
secret-service = "2.0"
oo7 = {version = "0.1.0-alpha.3", default-features = false, features = ["tokio_runtime", "tracing"]}
serde = "1.0"
serde_json = "1.0"
unicase = "2.6"

View file

@ -15,7 +15,7 @@
"--socket=wayland",
"--device=dri",
"--talk-name=org.freedesktop.secrets",
"--env=RUST_LOG=authenticator=debug,ashpd=debug",
"--env=RUST_LOG=authenticator=debug,ashpd=debug,oo7=debug",
"--env=G_MESSAGES_DEBUG=none",
"--own-name=com.belmoussaoui.Authenticator.Devel.SearchProvider"
],

View file

@ -31,5 +31,8 @@
<summary>Auto lock timeout</summary>
<description>Lock the application on idle after X minutes</description>
</key>
<key name="keyrings-migrated" type="b">
<default>false</default>
</key>
</schema>
</schemalist>

View file

@ -13,7 +13,7 @@ quick-xml = "0.22"
image = {version = "0.24", features = ["ico", "png"], default-features = false}
url = "2.2"
once_cell = "1.9"
log = "0.4"
tracing = "0.1"
base64 = "0.13.0"
svg_metadata = "0.4"
tokio = { version = "1.0", features = ["rt-multi-thread", "fs", "io-util", "macros"] }

View file

@ -77,16 +77,16 @@ impl Favicon {
/// Save the favicon into `destination` and convert it to a [`Format::Png`]
/// if it is original format is [`Format::Ico`].
pub async fn save(&self, destination: PathBuf) -> Result<(), Error> {
log::debug!("Caching the icon into {:#?}", destination);
tracing::debug!("Caching the icon into {:#?}", destination);
let format = *self.metadata().format();
let body = self.data().await?;
if format.is_ico() {
log::debug!("Found a ICO favicon, converting to PNG");
tracing::debug!("Found a ICO favicon, converting to PNG");
if let Ok(ico) = image::load_from_memory_with_format(&body, image::ImageFormat::Ico) {
ico.save_with_format(destination, image::ImageFormat::Png)?;
return Ok(());
} else {
log::debug!("It seems to not be a ICO favicon, fallback to PNG");
tracing::debug!("It seems to not be a ICO favicon, fallback to PNG");
};
}
let mut dest = tokio::fs::File::create(destination).await?;

View file

@ -1,7 +1,7 @@
use log::debug;
use percent_encoding::percent_decode_str;
use quick_xml::events::{attributes::Attribute, BytesStart, Event};
use std::{fmt, path::PathBuf};
use tracing::debug;
use url::Url;
use crate::{Error, Favicon, Format, Metadata, CLIENT};

View file

@ -1,6 +1,8 @@
use crate::{
config,
models::{Account, Keyring, OTPUri, Provider, ProvidersModel, FAVICONS_PATH},
models::{
keyring, Account, OTPUri, Provider, ProvidersModel, FAVICONS_PATH, RUNTIME, SECRET_SERVICE,
},
widgets::{PreferencesWindow, ProvidersDialog, Window},
};
use adw::prelude::*;
@ -9,12 +11,15 @@ use glib::clone;
use gtk::{gio, glib, subclass::prelude::*};
use gtk_macros::{action, get_action};
use search_provider::{ResultID, ResultMeta, SearchProvider, SearchProviderImpl};
use std::str::FromStr;
use std::{collections::HashMap, str::FromStr};
mod imp {
use crate::utils::spawn_tokio_blocking;
use super::*;
use adw::subclass::prelude::*;
use glib::{ParamSpec, ParamSpecBoolean, Value, WeakRef};
use once_cell::sync::OnceCell;
use std::cell::{Cell, RefCell};
// The basic struct that holds our state and widgets
@ -25,7 +30,7 @@ mod imp {
pub locked: Cell<bool>,
pub lock_timeout_id: RefCell<Option<glib::SourceId>>,
pub can_be_locked: Cell<bool>,
pub settings: gio::Settings,
pub settings: OnceCell<gio::Settings>,
pub search_provider: RefCell<Option<SearchProvider<super::Application>>>,
}
@ -39,12 +44,11 @@ mod imp {
// Initialize with default values
fn new() -> Self {
let model = ProvidersModel::new();
let settings = gio::Settings::new(config::APP_ID);
Self {
window: RefCell::new(None),
model,
settings,
settings: OnceCell::default(),
can_be_locked: Cell::new(false),
lock_timeout_id: RefCell::default(),
locked: Cell::new(false),
@ -188,7 +192,7 @@ mod imp {
}
});
self.settings.connect_changed(
self.settings.get().unwrap().connect_changed(
None,
clone!(@weak app => move |settings, key| {
match key {
@ -217,7 +221,7 @@ mod imp {
Ok(search_provider) => {
imp.search_provider.replace(Some(search_provider));
},
Err(err) => log::debug!("Could not start search provider: {}", err),
Err(err) => tracing::debug!("Could not start search provider: {}", err),
};
}));
}
@ -233,7 +237,8 @@ mod imp {
window.present();
self.window.replace(Some(window.downgrade()));
let has_set_password = Keyring::has_set_password().unwrap_or(false);
let has_set_password =
spawn_tokio_blocking(async { keyring::has_set_password().await.unwrap_or(false) });
app.set_accels_for_action("app.quit", &["<primary>q"]);
app.set_accels_for_action("app.lock", &["<primary>l"]);
app.set_accels_for_action("app.providers", &["<primary>p"]);
@ -283,13 +288,50 @@ glib::wrapper! {
impl Application {
pub fn run() {
info!("Authenticator ({})", config::APP_ID);
info!("Version: {} ({})", config::VERSION, config::PROFILE);
info!("Datadir: {}", config::PKGDATADIR);
tracing::info!("Authenticator ({})", config::APP_ID);
tracing::info!("Version: {} ({})", config::VERSION, config::PROFILE);
tracing::info!("Datadir: {}", config::PKGDATADIR);
std::fs::create_dir_all(&*FAVICONS_PATH.clone()).ok();
Keyring::ensure_unlocked()
.expect("Authenticator couldn't reach a secret service provider or unlock it");
// To be removed in the upcoming release
let settings = gio::Settings::new(config::APP_ID);
if !settings.boolean("keyrings-migrated") {
tracing::info!("Migrating the secrets to the file backend");
let output: oo7::Result<()> = RUNTIME.block_on(async {
oo7::migrate(
vec![
HashMap::from([("application", config::APP_ID), ("type", "token")]),
HashMap::from([("application", config::APP_ID), ("type", "password")]),
],
false,
)
.await?;
Ok(())
});
match output {
Ok(_) => {
settings
.set_boolean("keyrings-migrated", true)
.expect("Failed to update settings");
tracing::info!("Secrets were migrated successfully");
}
Err(err) => {
tracing::error!("Failed to migrate your data {err}");
}
}
}
RUNTIME.block_on(async {
let keyring = oo7::Keyring::new()
.await
.expect("Failed to start a location service");
keyring
.unlock()
.await
.expect("Failed to unlock the default collection");
SECRET_SERVICE.set(keyring).unwrap()
});
let app = glib::Object::new::<Application>(&[
("application-id", &Some(config::APP_ID)),
@ -297,6 +339,8 @@ impl Application {
("resource-base-path", &"/com/belmoussaoui/Authenticator"),
])
.unwrap();
let imp = app.imp();
imp.settings.set(settings).unwrap();
ApplicationExtManual::run(&app);
}
@ -334,8 +378,8 @@ impl Application {
/// Starts or restarts the lock timeout.
pub fn restart_lock_timeout(&self) {
let imp = self.imp();
let auto_lock = imp.settings.boolean("auto-lock");
let timeout = imp.settings.uint("auto-lock-timeout") * 60;
let auto_lock = imp.settings.get().unwrap().boolean("auto-lock");
let timeout = imp.settings.get().unwrap().uint("auto-lock-timeout") * 60;
if !auto_lock {
return;
@ -364,7 +408,7 @@ impl Application {
fn update_color_scheme(&self) {
let manager = self.style_manager();
if !manager.system_supports_color_schemes() {
let color_scheme = if self.imp().settings.boolean("dark-theme") {
let color_scheme = if self.imp().settings.get().unwrap().boolean("dark-theme") {
adw::ColorScheme::PreferDark
} else {
adw::ColorScheme::PreferLight

View file

@ -157,7 +157,7 @@ impl Aegis {
// Check whether file is encrypted or in plaintext
match aegis_root {
Aegis::Plaintext(plain_text) => {
log::info!(
tracing::info!(
"Found unencrypted aegis vault with version {} and database version {}.",
plain_text.version,
plain_text.db.version
@ -180,7 +180,7 @@ impl Aegis {
}
}
Aegis::Encrypted(encrypted) => {
log::info!(
tracing::info!(
"Found encrypted aegis vault with version {}.",
encrypted.version
);
@ -212,7 +212,7 @@ impl Aegis {
.iter()
.filter(|slot| slot.type_ == 1) // We don't handle biometric slots for now
.map(|slot| -> Result<Vec<u8>> {
log::info!("Found possible master key with UUID {}.", slot.uuid);
tracing::info!("Found possible master key with UUID {}.", slot.uuid);
// Create parameters for scrypt function and derive decryption key for master key
//
@ -250,20 +250,20 @@ impl Aegis {
.filter_map(|x| match x {
Ok(x) => Some(x),
Err(e) => {
log::error!("Decrypting master key failed: {:?}", e);
tracing::error!("Decrypting master key failed: {:?}", e);
None
}
})
.collect();
// Choose the first valid master key. I don't think there are aegis installations with two valid password slots.
log::info!(
tracing::info!(
"Found {} valid password slots / master keys.",
master_keys.len()
);
let master_key = match master_keys.first() {
Some(x) => {
log::info!("Using only the first valid key slot / master key.");
tracing::info!("Using only the first valid key slot / master key.");
x
}
None => anyhow::bail!(
@ -288,7 +288,7 @@ impl Aegis {
.context("Deserialize decrypted database failed")?;
// Check version of the database
log::info!("Found aegis database with version {}.", db.version);
tracing::info!("Found aegis database with version {}.", db.version);
if encrypted.version > 2 {
anyhow::bail!(
"Aegis database version expected to be 1 or 2. Found {} instead.",

View file

@ -1,11 +1,10 @@
#[macro_use]
extern crate log;
#[macro_use]
extern crate diesel;
#[macro_use]
extern crate diesel_migrations;
use gtk::{gio, glib};
mod utils;
use gettextrs::*;
mod application;
mod backup;
@ -25,13 +24,13 @@ fn init_i18n() -> anyhow::Result<()> {
}
fn main() {
pretty_env_logger::init();
tracing_subscriber::fmt::init();
gtk::init().expect("failed to init gtk");
gst::init().expect("failed to init gstreamer");
gst4gtk::plugin_register_static().expect("Failed to register gstgtk4 plugin");
if let Err(err) = init_i18n() {
error!("Failed to initialize i18n {}", err);
tracing::error!("Failed to initialize i18n {}", err);
}
let res = gio::Resource::load(config::PKGDATADIR.to_owned() + "/authenticator.gresource")

View file

@ -1,10 +1,11 @@
use super::{
provider::{DiProvider, Provider},
OTPMethod, OTPUri,
OTPMethod, OTPUri, RUNTIME,
};
use crate::{
models::{database, otp, Keyring},
models::{database, keyring, otp},
schema::accounts,
utils::spawn_tokio_blocking,
widgets::QRCodeData,
};
use anyhow::{Context, Result};
@ -177,8 +178,13 @@ impl Account {
let db = database::connection();
let conn = db.get()?;
let token_id = Keyring::store(&format!("{} - {}", provider.name(), name), token)
.context("Failed to save token")?;
let label = format!("{} - {}", provider.name(), name);
let token_send = token.to_owned();
let token_id = spawn_tokio_blocking(async move {
keyring::store(&label, &token_send)
.await
.context("Failed to save token")
})?;
diesel::insert_into(accounts::table)
.values(NewAccount {
@ -255,7 +261,12 @@ impl Account {
let token = if let Some(t) = token {
t.to_string()
} else {
Keyring::token(token_id)?.context("Could not get item from keyring")?
let token_id = token_id.to_owned();
spawn_tokio_blocking(async move {
keyring::token(&token_id)
.await?
.context("Could not get item from keyring")
})?
};
account.imp().token.set(token).unwrap();
account.generate_otp();
@ -288,7 +299,7 @@ impl Account {
let label = match otp_password {
Ok(password) => password,
Err(err) => {
warn!("Failed to generate the OTP {}", err);
tracing::warn!("Failed to generate the OTP {}", err);
"Error".to_string()
}
};
@ -402,9 +413,9 @@ impl Account {
pub fn delete(&self) -> Result<()> {
let token_id = self.token_id();
std::thread::spawn(move || {
if let Err(err) = Keyring::remove_token(&token_id) {
error!("Failed to remove the token from secret service {}", err);
RUNTIME.spawn(async move {
if let Err(err) = keyring::remove_token(&token_id).await {
tracing::error!("Failed to remove the token from secret service {}", err);
}
});
let db = database::connection();

View file

@ -15,7 +15,7 @@ pub(crate) fn connection() -> Pool {
}
fn run_migration_on(connection: &SqliteConnection) -> Result<()> {
info!("Running DB Migrations...");
tracing::info!("Running DB Migrations...");
embedded_migrations::run_with_output(connection, &mut std::io::stdout()).map_err(From::from)
}
@ -33,6 +33,6 @@ fn init_pool() -> Result<Pool> {
let db = pool.get()?;
run_migration_on(&*db)?;
}
info!("Database pool initialized.");
tracing::info!("Database pool initialized.");
Ok(pool)
}

View file

@ -1,14 +1,9 @@
use crate::config;
use once_cell::sync::Lazy;
use secret_service::{Collection, EncryptionType, Error, SecretService};
use once_cell::sync::OnceCell;
use rand::RngCore;
use std::collections::HashMap;
static SECRET_SERVICE: Lazy<SecretService<'static>> = Lazy::new(|| {
SecretService::new(EncryptionType::Dh)
.expect("A running secret-service is required for Authenticator")
});
pub struct Keyring;
pub static SECRET_SERVICE: OnceCell<oo7::Keyring> = OnceCell::new();
fn token_attributes(token_id: &str) -> HashMap<&str, &str> {
let mut attributes = HashMap::new();
@ -25,135 +20,94 @@ fn password_attributes() -> HashMap<&'static str, &'static str> {
attributes
}
fn random_salt() -> [u8; 64] {
use rand::RngCore;
fn encode_argon2(secret: &str) -> anyhow::Result<String> {
let password = secret.as_bytes();
let mut salt = [0u8; 64];
rand::thread_rng().fill_bytes(&mut salt);
salt
}
fn encode_argon2(secret: &str) -> anyhow::Result<String> {
use argon2::Config;
let password = secret.as_bytes();
let salt = &random_salt();
let config = Config::default();
let hash = argon2::hash_encoded(password, salt, &config)?;
let config = argon2::Config::default();
let hash = argon2::hash_encoded(password, &salt, &config)?;
Ok(hash)
}
/// Verifies that the hash generated by `password` corresponds
/// to `hash`.
fn verify_argon2(hash: &str, password: &str) -> anyhow::Result<bool> {
Ok(argon2::verify_encoded(hash, password.as_bytes())?)
pub async fn store(label: &str, token: &str) -> anyhow::Result<String> {
let token_id = encode_argon2(token)?;
let attributes = token_attributes(&token_id);
let base64_encoded_token = hex::encode(token.as_bytes());
SECRET_SERVICE
.get()
.unwrap()
.create_item(label, attributes, base64_encoded_token.as_bytes(), true)
.await?;
Ok(token_id)
}
impl Keyring {
pub fn get_default_collection<'a>() -> Result<Collection<'a>, Error> {
let collection = match SECRET_SERVICE.get_default_collection() {
Err(Error::NoResult) => SECRET_SERVICE.create_collection("default", "default"),
e => e,
}?;
pub async fn token(token_id: &str) -> anyhow::Result<Option<String>> {
let attributes = token_attributes(token_id);
let items = SECRET_SERVICE
.get()
.unwrap()
.search_items(attributes)
.await?;
Ok(match items.get(0) {
Some(e) => Some(String::from_utf8(hex::decode(&*e.secret().await?).unwrap()).unwrap()),
_ => None,
})
}
Ok(collection)
pub async fn remove_token(token_id: &str) -> anyhow::Result<()> {
let attributes = token_attributes(token_id);
SECRET_SERVICE.get().unwrap().delete(attributes).await?;
Ok(())
}
pub async fn has_set_password() -> anyhow::Result<bool> {
let attributes = password_attributes();
match SECRET_SERVICE.get().unwrap().search_items(attributes).await {
Ok(items) => Ok(matches!(items.get(0), Some(_))),
_ => Ok(false),
}
}
pub fn ensure_unlocked() -> Result<(), Error> {
let collection = Self::get_default_collection()?;
collection.unlock()?;
Ok(())
}
pub fn store(label: &str, token: &str) -> anyhow::Result<String> {
let col = Self::get_default_collection()?;
let token_id = encode_argon2(token)?;
let attributes = token_attributes(&token_id);
let base64_encoded_token = hex::encode(token.as_bytes());
col.create_item(
label,
attributes,
base64_encoded_token.as_bytes(),
true,
"text/plain",
)?;
Ok(token_id)
}
pub fn token(token_id: &str) -> Result<Option<String>, Error> {
let col = Self::get_default_collection()?;
let attributes = token_attributes(token_id);
let items = col.search_items(attributes)?;
Ok(match items.get(0) {
Some(e) => Some(String::from_utf8(hex::decode(e.get_secret()?).unwrap()).unwrap()),
_ => None,
})
}
pub fn remove_token(token_id: &str) -> Result<(), Error> {
let col = Self::get_default_collection()?;
let attributes = token_attributes(token_id);
let items = col.search_items(attributes)?;
match items.get(0) {
Some(e) => e.delete(),
_ => Err(Error::NoResult),
}
}
pub fn has_set_password() -> Result<bool, Error> {
let col = Self::get_default_collection()?;
let attributes = password_attributes();
match col.search_items(attributes) {
Ok(items) => Ok(matches!(items.get(0), Some(_))),
_ => Ok(false),
}
}
/// Stores password using the Argon2 algorithm with a random 128bit salt.
pub fn set_password(password: &str) -> anyhow::Result<()> {
let col = Self::get_default_collection()?;
let encoded_password = encode_argon2(password)?;
let attributes = password_attributes();
col.create_item(
/// Stores password using the Argon2 algorithm with a random 128bit salt.
pub async fn set_password(password: &str) -> anyhow::Result<()> {
let encoded_password = encode_argon2(password)?;
let attributes = password_attributes();
SECRET_SERVICE
.get()
.unwrap()
.create_item(
"Authenticator password",
attributes,
encoded_password.as_bytes(),
true,
"plain",
)?;
Ok(())
}
pub fn reset_password() -> Result<(), Error> {
let col = Self::get_default_collection()?;
let attributes = password_attributes();
let items = col.search_items(attributes)?;
match items.get(0) {
Some(i) => i.delete(),
None => Err(Error::NoResult),
}
}
pub fn is_current_password(password: &str) -> anyhow::Result<bool> {
let col = Self::get_default_collection()?;
let attributes = password_attributes();
let items = col.search_items(attributes)?;
Ok(match items.get(0) {
Some(i) => {
let secret = &i.get_secret()?;
let stored_pass = std::str::from_utf8(secret)?;
verify_argon2(stored_pass, password)?
}
None => false,
})
}
)
.await?;
Ok(())
}
pub async fn reset_password() -> anyhow::Result<()> {
let attributes = password_attributes();
SECRET_SERVICE.get().unwrap().delete(attributes).await?;
Ok(())
}
pub async fn is_current_password(password: &str) -> anyhow::Result<bool> {
let attributes = password_attributes();
let items = SECRET_SERVICE
.get()
.unwrap()
.search_items(attributes)
.await?;
Ok(match items.get(0) {
Some(i) => {
// Verifies that the hash generated by `password` corresponds
// to `hash`.
argon2::verify_encoded(
&String::from_utf8_lossy(&*i.secret().await?),
password.as_bytes(),
)?
}
None => false,
})
}

View file

@ -5,7 +5,7 @@ mod accounts;
mod algorithm;
pub mod database;
pub mod i18n;
mod keyring;
pub mod keyring;
pub mod otp;
mod otp_uri;
mod provider;
@ -25,7 +25,7 @@ pub use self::{
account_sorter::AccountSorter,
accounts::AccountsModel,
algorithm::{Algorithm, OTPMethod},
keyring::Keyring,
keyring::SECRET_SERVICE,
otp_uri::OTPUri,
provider::{Provider, ProviderPatch},
provider_sorter::ProviderSorter,

View file

@ -381,7 +381,7 @@ impl Provider {
) -> Result<String, Box<dyn std::error::Error>> {
let website_url = Url::parse(&website)?;
let favicon = favicon_scrapper::Scrapper::from_url(website_url).await?;
log::debug!("Found the following icons {:#?} for {}", favicon, name);
tracing::debug!("Found the following icons {:#?} for {}", favicon, name);
let icon_name = format!("{}_{}", id, name.replace(' ', "_"));
let icon_name = glib::base64_encode(icon_name.as_bytes());
@ -392,15 +392,15 @@ impl Provider {
// - 32x32 for the accounts lists
// - 96x96 elsewhere
if let Some(best_favicon) = favicon.find_best().await {
log::debug!("Largest favicon found is {:#?}", best_favicon);
tracing::debug!("Largest favicon found is {:#?}", best_favicon);
let cache_path = FAVICONS_PATH.join(&*icon_name);
best_favicon.save(cache_path.clone()).await?;
// Don't try to scale down svg variants
if !best_favicon.metadata().format().is_svg() {
log::debug!("Creating scaled down variants for {:#?}", cache_path);
tracing::debug!("Creating scaled down variants for {:#?}", cache_path);
{
let pixbuf = gdk_pixbuf::Pixbuf::from_file(cache_path.clone())?;
log::debug!("Creating a 32x32 variant of the favicon");
tracing::debug!("Creating a 32x32 variant of the favicon");
let small_pixbuf = pixbuf
.scale_simple(32, 32, gdk_pixbuf::InterpType::Bilinear)
.unwrap();
@ -409,7 +409,7 @@ impl Provider {
small_cache.set_file_name(small_icon_name);
small_pixbuf.savev(small_cache.clone(), "png", &[])?;
log::debug!("Creating a 96x96 variant of the favicon");
tracing::debug!("Creating a 96x96 variant of the favicon");
let large_pixbuf = pixbuf
.scale_simple(96, 96, gdk_pixbuf::InterpType::Bilinear)
.unwrap();

29
src/utils.rs Normal file
View file

@ -0,0 +1,29 @@
use crate::models::RUNTIME;
pub fn spawn_tokio_blocking<F>(fut: F) -> F::Output
where
F: std::future::Future + Send + 'static,
F::Output: Send + 'static,
{
let (sender, receiver) = tokio::sync::oneshot::channel();
RUNTIME.spawn(async {
let response = fut.await;
sender.send(response)
});
receiver.blocking_recv().unwrap()
}
pub async fn spawn_tokio<F>(fut: F) -> F::Output
where
F: std::future::Future + Send + 'static,
F::Output: Send + 'static,
{
let (sender, receiver) = tokio::sync::oneshot::channel();
RUNTIME.spawn(async {
let response = fut.await;
sender.send(response)
});
receiver.await.unwrap()
}

View file

@ -154,7 +154,7 @@ impl AccountAddDialog {
fn scan_from_screenshot(&self) {
spawn!(clone!(@weak self as page => async move {
if let Err(err) = page.imp().camera.from_screenshot().await {
log::error!("Failed to scan from screenshot {}", err);
tracing::error!("Failed to scan from screenshot {}", err);
}
}));
}

View file

@ -84,7 +84,7 @@ mod imp {
});
klass.install_action("account.save", None, move |page, _, _| {
if let Err(err) = page.save() {
log::error!("Failed to save account details {}", err);
tracing::error!("Failed to save account details {}", err);
}
});

View file

@ -47,7 +47,7 @@ mod imp {
klass.install_action("account.increment-counter", None, move |row, _, _| {
match row.account().increment_counter() {
Ok(_) => row.account().generate_otp(),
Err(err) => log::error!("Failed to increment the counter {err}"),
Err(err) => tracing::error!("Failed to increment the counter {err}"),
};
});
}

View file

@ -159,7 +159,7 @@ impl Camera {
fn set_state(&self, state: CameraState) {
let imp = self.imp();
info!("The camera state changed to {:#?}", state);
tracing::info!("The camera state changed to {:#?}", state);
match state {
CameraState::NotFound => {
imp.stack.set_visible_child_name("not-found");
@ -199,13 +199,13 @@ impl Camera {
Ok(Some((stream_fd, node_id))) => {
match camera.imp().paintable.set_pipewire_node_id(stream_fd, node_id) {
Ok(_) => camera.start(),
Err(err) => log::error!("Failed to start the camera stream {err}"),
Err(err) => tracing::error!("Failed to start the camera stream {err}"),
};
},
Ok(None) => {
camera.set_state(CameraState::NotFound);
}
Err(e) => log::error!("Failed to stream {}", e),
Err(e) => tracing::error!("Failed to stream {}", e),
}
}));
}

View file

@ -11,6 +11,7 @@ use gtk::{
};
use gtk_macros::send;
use once_cell::sync::Lazy;
use tracing::error;
static PIPELINE_NAME: Lazy<glib::GString> = Lazy::new(|| glib::GString::from("camera"));
/// Fancy Camera with QR code detection using ZBar
///
@ -125,16 +126,16 @@ impl CameraPaintable {
pipewire_element.set_property("fd", &raw_fd);
if let Some(node_id) = node_id {
pipewire_element.set_property("path", &node_id.to_string());
log::debug!("Loading PipeWire Node ID: {} with FD: {}", node_id, raw_fd);
tracing::debug!("Loading PipeWire Node ID: {} with FD: {}", node_id, raw_fd);
} else {
log::debug!("Loading PipeWire with FD: {}", raw_fd);
tracing::debug!("Loading PipeWire with FD: {}", raw_fd);
}
self.init_pipeline(pipewire_element)?;
Ok(())
}
fn init_pipeline(&self, pipewire_src: gst::Element) -> anyhow::Result<()> {
log::debug!("Init pipeline");
tracing::debug!("Init pipeline");
let imp = self.imp();
let pipeline = gst::Pipeline::new(None);
@ -187,7 +188,7 @@ impl CameraPaintable {
let sender = paintable.imp().sender.borrow().as_ref().unwrap().clone();
match msg.view() {
MessageView::Error(err) => {
log::error!(
tracing::error!(
"Error from {:?}: {} ({:?})",
err.src().map(|s| s.path_string()),
err.error(),
@ -222,10 +223,10 @@ impl CameraPaintable {
}
pub fn close_pipeline(&self) {
log::debug!("Closing pipeline");
tracing::debug!("Closing pipeline");
if let Some(pipeline) = self.imp().pipeline.borrow_mut().take() {
if let Err(err) = pipeline.set_state(gst::State::Null) {
log::error!("Failed to close the pipeline: {err}");
tracing::error!("Failed to close the pipeline: {err}");
}
}
}
@ -233,7 +234,7 @@ impl CameraPaintable {
pub fn start(&self) {
if let Some(pipeline) = &*self.imp().pipeline.borrow() {
if let Err(err) = pipeline.set_state(gst::State::Playing) {
log::error!("Failed to start the camera stream: {err}");
tracing::error!("Failed to start the camera stream: {err}");
}
}
}
@ -241,7 +242,7 @@ impl CameraPaintable {
pub fn stop(&self) {
if let Some(pipeline) = &*self.imp().pipeline.borrow() {
if let Err(err) = pipeline.set_state(gst::State::Null) {
log::error!("Failed to stop the camera stream: {err}");
tracing::error!("Failed to stop the camera stream: {err}");
}
}
}

View file

@ -1,4 +1,4 @@
use crate::{config, models::Keyring, widgets::ErrorRevealer};
use crate::{config, models::keyring, utils::spawn_tokio, widgets::ErrorRevealer};
use gettextrs::gettext;
use glib::clone;
use gtk::{gio, glib, prelude::*, subclass::prelude::*, CompositeTemplate};
@ -178,7 +178,10 @@ impl PasswordPage {
actions,
"save_password",
clone!(@weak self as page => move |_, _| {
page.save();
let ctx = glib::MainContext::default();
ctx.spawn_local(clone!(@weak page => async move {
page.save().await;
}));
})
);
@ -186,7 +189,10 @@ impl PasswordPage {
actions,
"reset_password",
clone!(@weak self as page => move |_,_| {
page.reset_password();
let ctx = glib::MainContext::default();
ctx.spawn_local(clone!(@weak page => async move {
page.reset_password().await;
}));
})
);
get_action!(actions, @save_password).set_enabled(false);
@ -200,18 +206,23 @@ impl PasswordPage {
.build();
}
fn reset_password(&self) {
async fn reset_password(&self) {
let imp = self.imp();
let current_password = imp.current_password_entry.text();
if self.has_set_password()
&& !Keyring::is_current_password(&current_password).unwrap_or(false)
{
let is_current_password =
spawn_tokio(async move { keyring::is_current_password(&current_password).await })
.await
.unwrap_or(false);
if self.has_set_password() && !is_current_password {
imp.error_revealer.popup(&gettext("Wrong Passphrase"));
return;
}
if Keyring::reset_password().is_ok() {
let password_was_reset =
spawn_tokio(async move { keyring::reset_password().await.is_ok() }).await;
if password_was_reset {
let imp = self.imp();
let actions = imp.actions.get().unwrap();
@ -229,21 +240,26 @@ impl PasswordPage {
imp.confirm_password_entry.get().set_text("");
}
fn save(&self) {
async fn save(&self) {
let imp = self.imp();
let actions = imp.actions.get().unwrap();
let current_password = imp.current_password_entry.text();
let password = imp.password_entry.text();
let is_current_password = spawn_tokio(async move {
keyring::is_current_password(&current_password)
.await
.unwrap_or(false)
})
.await;
if self.has_set_password()
&& !Keyring::is_current_password(&current_password).unwrap_or(false)
{
if self.has_set_password() && is_current_password {
imp.error_revealer.popup(&gettext("Wrong Passphrase"));
return;
}
if Keyring::set_password(&password).is_ok() {
let password_was_set =
spawn_tokio(async move { keyring::set_password(&password).await.is_ok() }).await;
if password_was_set {
self.reset();
get_action!(actions, @save_password).set_enabled(false);
self.set_has_set_password(true);

View file

@ -256,7 +256,7 @@ impl PreferencesWindow {
win.encyption_key(Operation::Backup, &T::identifier())
}).flatten();
if let Err(err) = T::backup(&model, &d.file().unwrap(), key.as_deref()) {
warn!("Failed to create a backup {}", err);
tracing::warn!("Failed to create a backup {}", err);
}
}
d.destroy();
@ -325,7 +325,7 @@ impl PreferencesWindow {
win.restore_items::<T, T::Item>(items);
},
Err(err) => {
warn!("Failed to parse the selected file {}", err);
tracing::warn!("Failed to parse the selected file {}", err);
}
}
}
@ -355,7 +355,7 @@ impl PreferencesWindow {
.filter(Result::is_ok)
.for_each(|item| {
if let Err(err) = item {
warn!("Failed to restore item {}", err);
tracing::warn!("Failed to restore item {}", err);
}
});
self.emit_by_name::<()>("restore-completed", &[]);

View file

@ -3,6 +3,7 @@ use crate::models::{Provider, FAVICONS_PATH};
use glib::{clone, Receiver, Sender};
use gtk::{gio, glib, prelude::*, subclass::prelude::*, CompositeTemplate};
use gtk_macros::send;
use tracing::error;
pub enum ImageAction {
Ready(String),
@ -205,7 +206,7 @@ impl ProviderImage {
sender.send(Some(cache_name)).unwrap();
}
Err(err) => {
log::error!("Failed to load favicon {}", err);
tracing::error!("Failed to load favicon {}", err);
sender.send(None).unwrap();
}
};
@ -223,7 +224,7 @@ impl ProviderImage {
send!(imp.sender.clone(), ImageAction::Failed);
},
Err(_) => {
log::debug!("Provider image fetching aborted");
tracing::debug!("Provider image fetching aborted");
}
};
}));
@ -283,7 +284,7 @@ impl ProviderImage {
if let Some(provider) = self.provider() {
let guard = provider.freeze_notify();
if let Err(err) = provider.set_image_uri(&image_path) {
warn!("Failed to update provider image {}", err);
tracing::warn!("Failed to update provider image {}", err);
}
drop(guard);
}

View file

@ -103,12 +103,12 @@ mod imp {
klass.install_action("providers.save", None, move |page, _, _| {
if let Err(err) = page.save() {
warn!("Failed to save provider {}", err);
tracing::warn!("Failed to save provider {}", err);
}
});
klass.install_action("providers.delete", None, move |page, _, _| {
if let Err(err) = page.delete_provider() {
warn!("Failed to delete the provider {}", err);
tracing::warn!("Failed to delete the provider {}", err);
}
});
@ -268,13 +268,13 @@ impl ProviderPage {
// Create a 96x96 & 32x32 variants
let stream = file.read(gio::Cancellable::NONE)?;
let pixbuf = gdk_pixbuf::Pixbuf::from_stream(&stream, gio::Cancellable::NONE)?;
log::debug!("Creating a 32x32 variant of the selected favicon");
tracing::debug!("Creating a 32x32 variant of the selected favicon");
let small_pixbuf = pixbuf
.scale_simple(32, 32, gdk_pixbuf::InterpType::Bilinear)
.unwrap();
small_pixbuf.savev(FAVICONS_PATH.join(small_icon_name), "png", &[])?;
log::debug!("Creating a 96x96 variant of the selected favicon");
tracing::debug!("Creating a 96x96 variant of the selected favicon");
let large_pixbuf = pixbuf
.scale_simple(96, 96, gdk_pixbuf::InterpType::Bilinear)
.unwrap();

View file

@ -1,7 +1,8 @@
use crate::{
application::Application,
config,
models::{Account, Keyring, OTPUri, ProvidersModel},
models::{keyring, Account, OTPUri, ProvidersModel},
utils::spawn_tokio_blocking,
widgets::{
accounts::AccountDetailsPage,
providers::{ProvidersList, ProvidersListView},
@ -247,7 +248,7 @@ impl Window {
// save window state on delete event
self.connect_close_request(move |window| {
if let Err(err) = window.save_window_state() {
warn!("Failed to save window state {:#?}", err);
tracing::warn!("Failed to save window state {:#?}", err);
}
Inhibit(false)
});
@ -326,10 +327,11 @@ impl Window {
"unlock",
clone!(@weak self as win, @weak password_entry, @weak app => move |_, _| {
let password = password_entry.text();
let is_current_password = Keyring::is_current_password(&password).unwrap_or_else(|err| {
debug!("Could not verify password: {:?}", err);
let is_current_password = spawn_tokio_blocking(async move {
keyring::is_current_password(&password).await.unwrap_or_else(|err| {
tracing::debug!("Could not verify password: {:?}", err);
false
});
})});
if is_current_password {
password_entry.set_text("");
app.set_locked(false);