use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use futures::SinkExt;
use futures::channel::{mpsc, oneshot};
use futures::future::{self, Either};
use log::{debug, warn};
use zvariant::{ObjectPath, OwnedObjectPath};
use crate::types::constants::timeouts;
use super::request::{
CancelReason, ConnectionDict, SecretAgentFlags, SecretReply, SecretRequest, SecretResponder,
SecretStoreEvent, extract_setting_string, parse_secret_setting,
};
#[derive(Debug, zbus::DBusError)]
#[zbus(prefix = "org.freedesktop.NetworkManager.SecretAgent")]
pub(crate) enum SecretAgentDBusError {
#[zbus(error)]
ZBus(zbus::Error),
UserCanceled(String),
NoSecrets(String),
}
type PendingMap = Arc<Mutex<HashMap<(String, String), oneshot::Sender<()>>>>;
pub(crate) struct SecretAgentInterface {
pub(crate) request_tx: mpsc::Sender<SecretRequest>,
pub(crate) cancel_tx: mpsc::UnboundedSender<CancelReason>,
pub(crate) store_tx: mpsc::UnboundedSender<SecretStoreEvent>,
pub(crate) pending: PendingMap,
}
#[zbus::interface(name = "org.freedesktop.NetworkManager.SecretAgent")]
impl SecretAgentInterface {
async fn get_secrets(
&self,
connection: ConnectionDict,
connection_path: ObjectPath<'_>,
setting_name: &str,
hints: Vec<String>,
flags: u32,
) -> Result<ConnectionDict, SecretAgentDBusError> {
let path_owned: OwnedObjectPath = connection_path.into();
let key = (path_owned.to_string(), setting_name.to_owned());
debug!(
"GetSecrets: path={} setting={} flags={:#x}",
path_owned, setting_name, flags
);
let (reply_tx, reply_rx) = oneshot::channel::<SecretReply>();
let (cancel_tx, cancel_rx) = oneshot::channel::<()>();
self.pending
.lock()
.unwrap_or_else(|e| e.into_inner())
.insert(key.clone(), cancel_tx);
let setting = parse_secret_setting(&connection, setting_name);
let request = SecretRequest {
connection_uuid: extract_setting_string(&connection, "connection", "uuid")
.unwrap_or_default(),
connection_id: extract_setting_string(&connection, "connection", "id")
.unwrap_or_default(),
connection_type: extract_setting_string(&connection, "connection", "type")
.unwrap_or_default(),
connection_path: path_owned,
setting,
hints,
flags: SecretAgentFlags::from_bits_truncate(flags),
responder: SecretResponder::new(reply_tx, setting_name.to_owned()),
};
if self.request_tx.clone().send(request).await.is_err() {
self.pending
.lock()
.unwrap_or_else(|e| e.into_inner())
.remove(&key);
return Err(SecretAgentDBusError::NoSecrets(
"agent request channel closed".into(),
));
}
let timeout = futures_timer::Delay::new(timeouts::secret_agent_response_timeout());
let result = future::select(reply_rx, future::select(cancel_rx, timeout)).await;
self.pending
.lock()
.unwrap_or_else(|e| e.into_inner())
.remove(&key);
match result {
Either::Left((Ok(SecretReply::Secrets(map)), _)) => Ok(map),
Either::Left((Ok(SecretReply::UserCanceled), _)) => {
Err(SecretAgentDBusError::UserCanceled("user canceled".into()))
}
Either::Left((Ok(SecretReply::NoSecrets) | Err(_), _)) => Err(
SecretAgentDBusError::NoSecrets("no secrets available".into()),
),
Either::Right((Either::Left(_cancel), _)) => {
debug!("GetSecrets cancelled by NetworkManager for {}", key.1);
Err(SecretAgentDBusError::UserCanceled(
"canceled by NetworkManager".into(),
))
}
Either::Right((Either::Right(_timeout), _)) => {
warn!("GetSecrets timed out for setting {}", key.1);
Err(SecretAgentDBusError::NoSecrets(
"timeout waiting for consumer response".into(),
))
}
}
}
async fn cancel_get_secrets(
&self,
connection_path: ObjectPath<'_>,
setting_name: &str,
) -> Result<(), SecretAgentDBusError> {
let key = (connection_path.to_string(), setting_name.to_owned());
debug!("CancelGetSecrets: path={} setting={}", key.0, key.1);
if let Some(cancel_tx) = self
.pending
.lock()
.unwrap_or_else(|e| e.into_inner())
.remove(&key)
{
let _ = cancel_tx.send(());
}
let _ = self.cancel_tx.unbounded_send(CancelReason {
connection_path: connection_path.into(),
setting_name: setting_name.to_owned(),
});
Ok(())
}
async fn save_secrets(
&self,
_connection: ConnectionDict,
connection_path: ObjectPath<'_>,
) -> Result<(), SecretAgentDBusError> {
debug!("SaveSecrets: path={}", connection_path);
let _ = self.store_tx.unbounded_send(SecretStoreEvent::Save {
connection_path: connection_path.into(),
});
Ok(())
}
async fn delete_secrets(
&self,
_connection: ConnectionDict,
connection_path: ObjectPath<'_>,
) -> Result<(), SecretAgentDBusError> {
debug!("DeleteSecrets: path={}", connection_path);
let _ = self.store_tx.unbounded_send(SecretStoreEvent::Delete {
connection_path: connection_path.into(),
});
Ok(())
}
}