use std::{collections::HashMap, sync::Arc};
#[cfg(feature = "async-std")]
use async_std::sync::RwLock;
#[cfg(feature = "tokio")]
use tokio::sync::RwLock;
use zeroize::Zeroizing;
use crate::{
dbus::{self, Algorithm, DEFAULT_COLLECTION},
portal, Result,
};
#[derive(Debug)]
pub enum Keyring {
#[doc(hidden)]
File(Arc<portal::Keyring>),
#[doc(hidden)]
DBus(dbus::Collection<'static>),
}
impl Keyring {
pub async fn new() -> Result<Self> {
let is_sandboxed = crate::is_sandboxed().await;
if is_sandboxed {
#[cfg(feature = "tracing")]
tracing::debug!("Application is sandboxed, using the file backend");
match portal::Keyring::load_default().await {
Ok(portal) => return Ok(Self::File(Arc::new(portal))),
Err(portal::Error::PortalNotAvailable) => {
#[cfg(feature = "tracing")]
tracing::debug!(
"org.freedesktop.portal.Secrets is not available, falling back to the Sercret Service backend"
);
}
Err(e) => return Err(crate::Error::Portal(e)),
};
} else {
#[cfg(feature = "tracing")]
tracing::debug!(
"Application is not sandboxed, falling back to the Sercret Service backend"
);
}
let service = match dbus::Service::new(Algorithm::Encrypted).await {
Ok(service) => Ok(service),
Err(dbus::Error::Zbus(zbus::Error::MethodError(_, _, _))) => {
dbus::Service::new(Algorithm::Plain).await
}
Err(e) => Err(e),
}?;
let collection = match service.default_collection().await {
Ok(c) => Ok(c),
Err(dbus::Error::NotFound(_)) => {
#[cfg(feature = "tracing")]
tracing::debug!("Default collection doesn't exists, trying to create it");
service
.create_collection("Login", Some(DEFAULT_COLLECTION))
.await
}
Err(e) => Err(e),
}?;
Ok(Self::DBus(collection))
}
pub async fn unlock(&self) -> Result<()> {
if let Self::DBus(backend) = self {
backend.unlock().await?;
};
Ok(())
}
pub async fn lock(&self) -> Result<()> {
if let Self::DBus(backend) = self {
backend.lock().await?;
};
Ok(())
}
pub async fn delete(&self, attributes: HashMap<&str, &str>) -> Result<()> {
match self {
Self::DBus(backend) => {
let items = backend.search_items(attributes).await?;
for item in items {
item.delete().await?;
}
}
Self::File(backend) => {
backend.delete(attributes).await?;
}
};
Ok(())
}
pub async fn items(&self) -> Result<Vec<Item>> {
let items = match self {
Self::DBus(backend) => {
let items = backend.items().await?;
items.into_iter().map(Item::for_dbus).collect::<Vec<_>>()
}
Self::File(backend) => {
let items = backend.items().await;
items
.into_iter()
.flatten()
.map(|i| Item::for_file(i, Arc::clone(backend)))
.collect::<Vec<_>>()
}
};
Ok(items)
}
pub async fn create_item(
&self,
label: &str,
attributes: HashMap<&str, &str>,
secret: impl AsRef<[u8]>,
replace: bool,
) -> Result<()> {
match self {
Self::DBus(backend) => {
backend
.create_item(label, attributes, secret, replace, "text/plain")
.await?;
}
Self::File(backend) => {
backend
.create_item(label, attributes, secret, replace)
.await?;
}
};
Ok(())
}
pub async fn search_items(&self, attributes: HashMap<&str, &str>) -> Result<Vec<Item>> {
let items = match self {
Self::DBus(backend) => {
let items = backend.search_items(attributes).await?;
items.into_iter().map(Item::for_dbus).collect::<Vec<_>>()
}
Self::File(backend) => {
let items = backend.search_items(attributes).await?;
items
.into_iter()
.map(|i| Item::for_file(i, Arc::clone(backend)))
.collect::<Vec<_>>()
}
};
Ok(items)
}
}
#[derive(Debug)]
pub enum Item {
#[doc(hidden)]
File(RwLock<portal::Item>, Arc<portal::Keyring>),
#[doc(hidden)]
DBus(dbus::Item<'static>),
}
impl Item {
fn for_file(item: portal::Item, backend: Arc<portal::Keyring>) -> Self {
Self::File(RwLock::new(item), backend)
}
fn for_dbus(item: dbus::Item<'static>) -> Self {
Self::DBus(item)
}
pub async fn label(&self) -> Result<String> {
let label = match self {
Self::File(item, _) => item.read().await.label().to_owned(),
Self::DBus(item) => item.label().await?,
};
Ok(label)
}
pub async fn set_label(&self, label: &str) -> Result<()> {
match self {
Self::File(item, backend) => {
item.write().await.set_label(label);
let item_guard = item.read().await;
let attributes = item_guard
.attributes()
.iter()
.map(|(k, v)| (k.to_owned(), v.to_string()))
.collect::<HashMap<_, _>>();
backend
.create_item(
item_guard.label(),
attributes
.iter()
.map(|(k, v)| (k.as_str(), v.as_str()))
.collect::<HashMap<_, _>>(),
&*item_guard.secret(),
true,
)
.await?;
}
Self::DBus(item) => item.set_label(label).await?,
};
Ok(())
}
pub async fn attributes(&self) -> Result<HashMap<String, String>> {
let attributes = match self {
Self::File(item, _) => item
.read()
.await
.attributes()
.iter()
.map(|(k, v)| (k.to_owned(), v.to_string()))
.collect::<HashMap<_, _>>(),
Self::DBus(item) => item.attributes().await?,
};
Ok(attributes)
}
pub async fn set_attributes(&self, attributes: HashMap<&str, &str>) -> Result<()> {
match self {
Self::File(item, backend) => {
item.write().await.set_attributes(attributes.clone());
let item_guard = item.read().await;
backend
.create_item(item_guard.label(), attributes, &*item_guard.secret(), true)
.await?;
}
Self::DBus(item) => item.set_attributes(attributes).await?,
};
Ok(())
}
pub async fn set_secret(&self, secret: impl AsRef<[u8]>) -> Result<()> {
match self {
Self::File(item, backend) => {
item.write().await.set_secret(secret);
let item_guard = item.read().await;
let attributes = item_guard
.attributes()
.iter()
.map(|(k, v)| (k.to_owned(), v.to_string()))
.collect::<HashMap<_, _>>();
backend
.create_item(
item_guard.label(),
attributes
.iter()
.map(|(k, v)| (k.as_str(), v.as_str()))
.collect::<HashMap<_, _>>(),
&*item_guard.secret(),
true,
)
.await?;
}
Self::DBus(item) => item.set_secret(secret, "text/plain").await?,
};
Ok(())
}
pub async fn secret(&self) -> Result<Zeroizing<Vec<u8>>> {
let secret = match self {
Self::File(item, _) => item.read().await.secret(),
Self::DBus(item) => item.secret().await?,
};
Ok(secret)
}
pub async fn delete(&self) -> Result<()> {
match self {
Self::File(item, backend) => {
let attributes = item
.read()
.await
.attributes()
.iter()
.map(|(k, v)| (k.to_owned(), v.to_string()))
.collect::<HashMap<_, _>>();
backend
.delete(
attributes
.iter()
.map(|(k, v)| (k.as_str(), v.as_str()))
.collect::<HashMap<_, _>>(),
)
.await?;
}
Self::DBus(item) => {
item.delete().await?;
}
};
Ok(())
}
}