742 lines
27 KiB
Rust
742 lines
27 KiB
Rust
use std::{
|
|
collections::{HashMap, HashSet},
|
|
fmt::Debug,
|
|
sync::Arc,
|
|
};
|
|
|
|
use bitflags::bitflags;
|
|
use cosmic_dbus_networkmanager::interface::settings::connection::ConnectionSettingsProxy;
|
|
use futures::{SinkExt, Stream};
|
|
use secure_string::SecureString;
|
|
use tokio::sync::oneshot;
|
|
use zbus::{
|
|
ObjectServer, fdo,
|
|
zvariant::{OwnedValue, Str},
|
|
};
|
|
|
|
pub type SecretSender = Arc<tokio::sync::Mutex<Option<tokio::sync::oneshot::Sender<SecureString>>>>;
|
|
|
|
pub const SECRET_ID: &str = "com.system76.CosmicSettings.NetworkManager";
|
|
pub const DBUS_PATH: &str = "/org/freedesktop/NetworkManager/SecretAgent";
|
|
|
|
bitflags! {
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub struct GetSecretsFlags: u32 {
|
|
/// No special behavior.
|
|
/// By default no user interaction is allowed and secrets must come
|
|
/// from persistent storage, otherwise an error is returned.
|
|
const NONE = 0x0;
|
|
|
|
/// Allows interaction with the user (eg. prompt via UI).
|
|
const ALLOW_INTERACTION = 0x1;
|
|
|
|
/// Explicitly request new secrets from the user.
|
|
/// Implies ALLOW_INTERACTION.
|
|
const REQUEST_NEW = 0x2;
|
|
|
|
/// Request was initiated by a user action (via D-Bus).
|
|
const USER_REQUESTED = 0x4;
|
|
|
|
/// Internal flag, not part of the public D-Bus API.
|
|
const ONLY_SYSTEM = 0x8000_0000;
|
|
|
|
/// Internal flag, not part of the public D-Bus API.
|
|
const NO_ERRORS = 0x4000_0000;
|
|
}
|
|
}
|
|
|
|
#[derive(thiserror::Error, Clone, Debug)]
|
|
pub enum Error {
|
|
#[error("zbus error")]
|
|
Zbus(#[from] zbus::Error),
|
|
#[error("listening for secret agent closed")]
|
|
RecvError(#[from] oneshot::error::RecvError),
|
|
#[error("secret service error")]
|
|
SecretService(#[from] Arc<secret_service::Error>),
|
|
#[error("no password found for identifier: {0}")]
|
|
NoPasswordForIdentifier(String),
|
|
#[error("utf8 error")]
|
|
Utf8Error(#[from] std::string::FromUtf8Error),
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
|
pub enum PasswordFlag {
|
|
/// The system is responsible for providing and storing this secret.
|
|
None = 0,
|
|
/// A user-session secret agent is responsible for providing and storing
|
|
/// this secret; when it is required, agents will be asked to provide it.
|
|
AgentOwned = 1,
|
|
/// This secret should not be saved but should be requested from the user
|
|
/// each time it is required. This flag should be used for One-Time-Pad
|
|
/// secrets, PIN codes from hardware tokens, or if the user simply does not
|
|
/// want to save the secret.
|
|
NotSaved = 2,
|
|
/// in some situations it cannot be automatically determined that a secret is required or not. This flag hints that the secret is not required and should not be requested from the user.
|
|
NotRequired = 4,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct SecretHint {
|
|
pub key: String,
|
|
pub message: Option<String>,
|
|
}
|
|
|
|
fn parse_hints(hints: Vec<String>) -> Vec<SecretHint> {
|
|
hints
|
|
.into_iter()
|
|
// fold message hints into previous hints
|
|
.fold(Vec::new(), |mut acc, hint| {
|
|
if let Some((key, msg)) = hint.split_once(':') {
|
|
if let Some(last) = acc.last_mut() {
|
|
last.message = Some(format!("{}: {}", key, msg));
|
|
}
|
|
} else {
|
|
acc.push(SecretHint {
|
|
key: hint,
|
|
message: None,
|
|
});
|
|
}
|
|
acc
|
|
})
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub enum Event {
|
|
RequestSecret {
|
|
uuid: String,
|
|
name: String,
|
|
description: Option<String>,
|
|
previous: SecureString,
|
|
tx: SecretSender,
|
|
},
|
|
CancelGetSecrets {
|
|
uuid: String,
|
|
name: String,
|
|
},
|
|
Failed(Error),
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum Request {
|
|
SetSecrets {
|
|
setting_name: String,
|
|
uuid: String,
|
|
secrets: HashMap<String, SecureString>,
|
|
applied_tx: oneshot::Sender<()>,
|
|
},
|
|
GetSecrets {
|
|
setting_name: String,
|
|
uuid: String,
|
|
resp_tx: oneshot::Sender<HashMap<String, SecureString>>,
|
|
},
|
|
}
|
|
|
|
pub fn secret_agent_stream(
|
|
identifier: impl AsRef<str>,
|
|
rx: tokio::sync::mpsc::Receiver<Request>,
|
|
) -> impl Stream<Item = Event> {
|
|
iced_futures::stream::channel(
|
|
4,
|
|
move |mut msg_tx: futures::channel::mpsc::Sender<Event>| async move {
|
|
if let Err(e) = secret_agent_stream_impl(identifier.as_ref(), msg_tx.clone(), rx).await
|
|
{
|
|
let _ = msg_tx.send(Event::Failed(e)).await;
|
|
}
|
|
},
|
|
)
|
|
}
|
|
|
|
async fn secret_agent_stream_impl(
|
|
identifier: &str,
|
|
msg_tx: futures::channel::mpsc::Sender<Event>,
|
|
mut rx: tokio::sync::mpsc::Receiver<Request>,
|
|
) -> Result<(), Error> {
|
|
// fail early if we can't connect, closing the channel
|
|
{
|
|
let ss = secret_service::SecretService::connect(secret_service::EncryptionType::Dh)
|
|
.await
|
|
.map_err(Arc::new)?;
|
|
let collection = ss.get_default_collection().await.map_err(Arc::new)?;
|
|
if collection.is_locked().await.map_err(Arc::new)? {
|
|
collection.unlock().await.map_err(Arc::new)?;
|
|
}
|
|
}
|
|
// register the secret agent with NetworkManager
|
|
let proxy =
|
|
nm_secret_agent_manager::AgentManagerProxy::builder(&zbus::Connection::system().await?)
|
|
.path("/org/freedesktop/NetworkManager/AgentManager")?
|
|
.build()
|
|
.await?;
|
|
|
|
let _ = ObjectServer::at(
|
|
proxy.inner().connection().object_server(),
|
|
DBUS_PATH,
|
|
SettingsSecretAgent { tx: msg_tx },
|
|
)
|
|
.await?;
|
|
|
|
proxy.register_with_capabilities(identifier, 1).await?;
|
|
|
|
while let Some(request) = rx.recv().await {
|
|
match request {
|
|
Request::SetSecrets {
|
|
setting_name,
|
|
uuid,
|
|
secrets,
|
|
applied_tx,
|
|
} => {
|
|
let ss = secret_service::SecretService::connect(secret_service::EncryptionType::Dh)
|
|
.await
|
|
.map_err(Arc::new)?;
|
|
let collection = ss.get_default_collection().await.map_err(Arc::new)?;
|
|
|
|
if secrets.is_empty() {
|
|
let mut attributes = std::collections::HashMap::new();
|
|
attributes.insert("application", SECRET_ID);
|
|
attributes.insert("uuid", &uuid);
|
|
let search_items = collection
|
|
.search_items(attributes)
|
|
.await
|
|
.map_err(Arc::new)?;
|
|
|
|
for item in &search_items {
|
|
item.delete().await.map_err(Arc::new)?;
|
|
}
|
|
let _ = applied_tx.send(());
|
|
|
|
continue;
|
|
}
|
|
|
|
for (name, secret) in &secrets {
|
|
let mut attributes = std::collections::HashMap::new();
|
|
attributes.insert("application", SECRET_ID);
|
|
attributes.insert("uuid", &uuid);
|
|
attributes.insert("setting_name", &setting_name);
|
|
attributes.insert("name", name);
|
|
let _item = collection
|
|
.create_item(
|
|
"NetworkManager Secret",
|
|
attributes,
|
|
secret.unsecure().as_bytes(),
|
|
true,
|
|
"text/plain",
|
|
)
|
|
.await
|
|
.map_err(Arc::new)?;
|
|
}
|
|
let _ = applied_tx.send(());
|
|
}
|
|
Request::GetSecrets {
|
|
setting_name,
|
|
uuid,
|
|
resp_tx,
|
|
} => {
|
|
let ss = secret_service::SecretService::connect(secret_service::EncryptionType::Dh)
|
|
.await
|
|
.map_err(Arc::new)?;
|
|
let collection = ss.get_default_collection().await.map_err(Arc::new)?;
|
|
|
|
let mut attributes = std::collections::HashMap::new();
|
|
attributes.insert("application", SECRET_ID);
|
|
attributes.insert("uuid", &uuid);
|
|
attributes.insert("setting_name", &setting_name);
|
|
|
|
let search_items = collection
|
|
.search_items(attributes)
|
|
.await
|
|
.map_err(Arc::new)?;
|
|
|
|
let mut secrets = HashMap::new();
|
|
for item in &search_items {
|
|
let name = item
|
|
.get_attributes()
|
|
.await
|
|
.map_err(Arc::new)?
|
|
.get("name")
|
|
.cloned()
|
|
.unwrap_or_else(|| "unknown".to_string());
|
|
let secret = item.get_secret().await.map_err(Arc::new)?;
|
|
let secret: String = String::from_utf8(secret)?;
|
|
secrets.insert(name, SecureString::from(secret));
|
|
}
|
|
let _ = resp_tx.send(secrets);
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn parse_secret_flag(value: &str) -> PasswordFlag {
|
|
match value {
|
|
"0" => PasswordFlag::None,
|
|
"1" => PasswordFlag::AgentOwned,
|
|
"2" => PasswordFlag::NotSaved,
|
|
"4" => PasswordFlag::NotRequired,
|
|
_ => PasswordFlag::AgentOwned,
|
|
}
|
|
}
|
|
|
|
fn setting_has_always_ask(setting: zbus::zvariant::Dict) -> bool {
|
|
for (key, value) in setting.iter() {
|
|
let Ok(key) = key.downcast_ref::<zbus::zvariant::Str>() else {
|
|
continue;
|
|
};
|
|
let Ok(value) = value.downcast_ref::<zbus::zvariant::Str>() else {
|
|
continue;
|
|
};
|
|
// we only care about "<secret>-flags"
|
|
if !key.ends_with("-flags") {
|
|
continue;
|
|
}
|
|
|
|
if parse_secret_flag(value.as_str()) == PasswordFlag::NotSaved {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
false
|
|
}
|
|
|
|
fn has_always_ask(setting: Option<zbus::zvariant::Dict>) -> bool {
|
|
setting.map(setting_has_always_ask).unwrap_or(false)
|
|
}
|
|
|
|
fn is_connection_always_ask(connection: &HashMap<String, HashMap<String, OwnedValue>>) -> bool {
|
|
let conn_setting = match connection.get("connection") {
|
|
Some(s) => s,
|
|
None => return false,
|
|
};
|
|
|
|
let conn_type = match conn_setting
|
|
.get("type")
|
|
.and_then(|v| v.downcast_ref::<String>().ok())
|
|
{
|
|
Some(t) => t,
|
|
None => return false,
|
|
};
|
|
|
|
// Primary setting (vpn, wifi, ethernet, etc)
|
|
if has_always_ask(
|
|
connection
|
|
.get(&conn_type)
|
|
.and_then(|d| d.get("data"))
|
|
.and_then(|data| data.downcast_ref::<zbus::zvariant::Dict>().ok()),
|
|
) {
|
|
return true;
|
|
}
|
|
|
|
match conn_type.as_str() {
|
|
"802-11-wireless" => {
|
|
if has_always_ask(
|
|
connection
|
|
.get("802-11-wireless-security")
|
|
.and_then(|d| d.get("data"))
|
|
.and_then(|data| data.downcast_ref::<zbus::zvariant::Dict>().ok()),
|
|
) {
|
|
return true;
|
|
}
|
|
if has_always_ask(
|
|
connection
|
|
.get("802-1x")
|
|
.and_then(|d| d.get("data"))
|
|
.and_then(|data| data.downcast_ref::<zbus::zvariant::Dict>().ok()),
|
|
) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
"802-3-ethernet" => {
|
|
if has_always_ask(
|
|
connection
|
|
.get("pppoe")
|
|
.and_then(|d| d.get("data"))
|
|
.and_then(|data| data.downcast_ref::<zbus::zvariant::Dict>().ok()),
|
|
) {
|
|
return true;
|
|
}
|
|
if has_always_ask(
|
|
connection
|
|
.get("802-1x")
|
|
.and_then(|d| d.get("data"))
|
|
.and_then(|data| data.downcast_ref::<zbus::zvariant::Dict>().ok()),
|
|
) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
_ => {}
|
|
}
|
|
|
|
false
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct SettingsSecretAgent {
|
|
tx: futures::channel::mpsc::Sender<Event>,
|
|
}
|
|
|
|
#[zbus::interface(name = "org.freedesktop.NetworkManager.SecretAgent")]
|
|
impl SettingsSecretAgent {
|
|
/// CancelGetSecrets method
|
|
async fn cancel_get_secrets(
|
|
&mut self,
|
|
connection_path: zbus::zvariant::ObjectPath<'_>,
|
|
setting_name: String,
|
|
) -> fdo::Result<()> {
|
|
let conn = ConnectionSettingsProxy::builder(
|
|
&zbus::Connection::system()
|
|
.await
|
|
.map_err(|_| fdo::Error::Failed("failed to get uuid".to_string()))?,
|
|
)
|
|
.path(connection_path)?
|
|
.build()
|
|
.await
|
|
.map_err(|e| fdo::Error::Failed(e.to_string()))?;
|
|
|
|
let uuid = conn
|
|
.get_settings()
|
|
.await
|
|
.map_err(|e| fdo::Error::Failed(e.to_string()))?
|
|
.get("connection")
|
|
.and_then(|m| m.get("uuid"))
|
|
.and_then(|v| v.downcast_ref::<String>().ok())
|
|
.ok_or_else(|| fdo::Error::Failed("failed to get uuid".to_string()))?
|
|
.to_string();
|
|
if let Err(e) = self
|
|
.tx
|
|
.clone()
|
|
.send(Event::CancelGetSecrets {
|
|
uuid,
|
|
name: setting_name,
|
|
})
|
|
.await
|
|
&& e.is_disconnected()
|
|
{
|
|
return Err(fdo::Error::Failed(
|
|
"failed to send cancel message".to_string(),
|
|
));
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// DeleteSecrets method
|
|
async fn delete_secrets(
|
|
&self,
|
|
connection: HashMap<String, HashMap<String, zbus::zvariant::OwnedValue>>,
|
|
connection_path: zbus::zvariant::ObjectPath<'_>,
|
|
) -> fdo::Result<()> {
|
|
match self.delete_secrets_inner(connection, connection_path).await {
|
|
Ok(_) => Ok(()),
|
|
Err(err) => Err(fdo::Error::Failed(err.to_string())),
|
|
}
|
|
}
|
|
|
|
/// GetSecrets method
|
|
async fn get_secrets(
|
|
&mut self,
|
|
connection: HashMap<String, HashMap<String, zbus::zvariant::OwnedValue>>,
|
|
connection_path: zbus::zvariant::ObjectPath<'_>,
|
|
setting_name: String,
|
|
hints: Vec<String>,
|
|
flags: u32,
|
|
) -> HashMap<String, HashMap<String, zbus::zvariant::OwnedValue>> {
|
|
self.get_secrets_inner(connection, connection_path, setting_name, hints, flags)
|
|
.await
|
|
.unwrap_or_default()
|
|
}
|
|
|
|
/// SaveSecrets method
|
|
async fn save_secrets(
|
|
&self,
|
|
connection: HashMap<String, HashMap<String, zbus::zvariant::OwnedValue>>,
|
|
_connection_path: zbus::zvariant::ObjectPath<'_>,
|
|
) -> fdo::Result<()> {
|
|
match self.save_secrets_inner(connection).await {
|
|
Ok(_) => Ok(()),
|
|
Err(err) => Err(fdo::Error::Failed(err.to_string())),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl SettingsSecretAgent {
|
|
pub async fn get_secrets_inner(
|
|
&mut self,
|
|
connection: HashMap<String, HashMap<String, zbus::zvariant::OwnedValue>>,
|
|
connection_path: zbus::zvariant::ObjectPath<'_>,
|
|
setting_name: String,
|
|
hints: Vec<String>,
|
|
flags: u32,
|
|
) -> Result<HashMap<String, HashMap<String, zbus::zvariant::OwnedValue>>, Error> {
|
|
let flags = GetSecretsFlags::from_bits_truncate(flags);
|
|
|
|
let ss = secret_service::SecretService::connect(secret_service::EncryptionType::Dh)
|
|
.await
|
|
.map_err(Arc::new)?;
|
|
|
|
let collection = ss.get_default_collection().await.map_err(Arc::new)?;
|
|
|
|
let conn_uuid = connection
|
|
.get("connection")
|
|
.and_then(|m| m.get("uuid"))
|
|
.and_then(|v| v.downcast_ref::<String>().ok())
|
|
.ok_or_else(|| Error::NoPasswordForIdentifier(setting_name.clone()))?
|
|
.to_string();
|
|
|
|
let conn =
|
|
ConnectionSettingsProxy::builder(&zbus::Connection::system().await.map_err(|_| {
|
|
Error::Zbus(fdo::Error::Failed("failed to get uuid".to_string()).into())
|
|
})?)
|
|
.path(connection_path)?
|
|
.build()
|
|
.await
|
|
.map_err(|e| Error::Zbus(fdo::Error::Failed(e.to_string()).into()))?;
|
|
let settings = conn.get_settings().await?;
|
|
let is_vpn = settings
|
|
.get("connection")
|
|
.and_then(|m| m.get("type"))
|
|
.and_then(|v| v.downcast_ref::<String>().ok())
|
|
.is_some_and(|t| t == "vpn");
|
|
let is_always_ask = is_connection_always_ask(&settings);
|
|
|
|
let mut setting_attributes = std::collections::HashMap::new();
|
|
setting_attributes.insert("application", SECRET_ID);
|
|
setting_attributes.insert("uuid", &conn_uuid);
|
|
setting_attributes.insert("setting_name", &setting_name);
|
|
|
|
let search_items = collection
|
|
.search_items(setting_attributes.clone())
|
|
.await
|
|
.map_err(Arc::new)?;
|
|
let mut result = HashMap::new();
|
|
let mut setting = HashMap::new();
|
|
|
|
if hints.is_empty() {
|
|
for item in &search_items {
|
|
let name = item
|
|
.get_attributes()
|
|
.await
|
|
.map_err(Arc::new)?
|
|
.get("name")
|
|
.cloned()
|
|
.unwrap_or_else(|| "unknown".to_string());
|
|
let secret = item.get_secret().await.map_err(Arc::new)?;
|
|
let secret: String = String::from_utf8(secret)?;
|
|
setting.insert(name, zbus::zvariant::OwnedValue::from(Str::from(secret)));
|
|
}
|
|
result.insert(setting_name, setting);
|
|
Ok(result)
|
|
} else {
|
|
let hints = parse_hints(hints);
|
|
let mut requested = HashSet::new();
|
|
|
|
for SecretHint { key, message } in &hints {
|
|
if requested.contains(key) {
|
|
continue;
|
|
}
|
|
requested.insert(key);
|
|
if flags.contains(GetSecretsFlags::REQUEST_NEW)
|
|
&& flags.contains(GetSecretsFlags::ALLOW_INTERACTION)
|
|
|| is_always_ask
|
|
{
|
|
// request the secret via the message channel
|
|
let (resp_tx, resp_rx) = oneshot::channel();
|
|
// msg begins after ":"
|
|
let actual_hint = message.as_ref().map(|m| {
|
|
m.split_once(":")
|
|
.map(|(_, msg)| msg.trim().to_string())
|
|
.unwrap_or(m.clone())
|
|
});
|
|
if let Err(e) = self
|
|
.tx
|
|
.clone()
|
|
.send(Event::RequestSecret {
|
|
uuid: conn_uuid.clone(),
|
|
name: setting_name.clone(),
|
|
description: actual_hint.clone(),
|
|
previous: String::new().into(),
|
|
tx: Arc::new(tokio::sync::Mutex::new(Some(resp_tx))),
|
|
})
|
|
.await
|
|
&& e.is_disconnected()
|
|
{
|
|
continue;
|
|
} else if let Ok(secret) = resp_rx.await {
|
|
let mut named_attribute = setting_attributes.clone();
|
|
named_attribute.insert("name", key);
|
|
let _item = collection
|
|
.create_item(
|
|
"NetworkManager Secret",
|
|
named_attribute,
|
|
secret.unsecure().as_bytes(),
|
|
true,
|
|
"text/plain",
|
|
)
|
|
.await
|
|
.map_err(Arc::new)?;
|
|
|
|
setting.insert(
|
|
key.clone(),
|
|
zbus::zvariant::OwnedValue::from(Str::from(secret.unsecure())),
|
|
);
|
|
}
|
|
} else if !is_always_ask {
|
|
let mut pos = None;
|
|
let mut pos_with_message = None;
|
|
for item in &search_items {
|
|
let attributes = item.get_attributes().await.map_err(Arc::new)?;
|
|
if let Some(value) = attributes.get("name")
|
|
&& value == key
|
|
{
|
|
if let Some(saved_message) = attributes.get("message") {
|
|
if message.as_ref().is_some_and(|msg| msg == saved_message) {
|
|
pos_with_message = Some(item);
|
|
}
|
|
break;
|
|
} else {
|
|
pos = Some(item);
|
|
}
|
|
}
|
|
}
|
|
|
|
if let Some(item) = pos_with_message.or(pos) {
|
|
let secret = item.get_secret().await.map_err(Arc::new)?;
|
|
let secret: String = String::from_utf8(secret)?;
|
|
if is_vpn {
|
|
// ask anyway, but offer the previous one as a hint
|
|
let (resp_tx, resp_rx) = oneshot::channel();
|
|
let actual_hint = message.as_ref().map(|m| {
|
|
m.split_once(":")
|
|
.map(|(_, msg)| msg.trim().to_string())
|
|
.unwrap_or(m.clone())
|
|
});
|
|
if let Err(e) = self
|
|
.tx
|
|
.clone()
|
|
.send(Event::RequestSecret {
|
|
uuid: conn_uuid.clone(),
|
|
name: setting_name.clone(),
|
|
description: actual_hint.clone(),
|
|
previous: SecureString::from(secret.clone()),
|
|
tx: Arc::new(tokio::sync::Mutex::new(Some(resp_tx))),
|
|
})
|
|
.await
|
|
&& e.is_disconnected()
|
|
{
|
|
continue;
|
|
} else if let Ok(secret) = resp_rx.await {
|
|
let mut named_attribute = setting_attributes.clone();
|
|
named_attribute.insert("name", key);
|
|
let _item = collection
|
|
.create_item(
|
|
"NetworkManager Secret",
|
|
named_attribute,
|
|
secret.unsecure().as_bytes(),
|
|
true,
|
|
"text/plain",
|
|
)
|
|
.await
|
|
.map_err(Arc::new)?;
|
|
|
|
setting.insert(
|
|
key.clone(),
|
|
zbus::zvariant::OwnedValue::from(Str::from(secret.unsecure())),
|
|
);
|
|
}
|
|
} else {
|
|
setting.insert(
|
|
key.clone(),
|
|
zbus::zvariant::OwnedValue::from(Str::from(secret)),
|
|
);
|
|
}
|
|
}
|
|
} else {
|
|
// can't find the secret, and we can't request it, so we just skip it
|
|
continue;
|
|
}
|
|
}
|
|
result.insert(setting_name, setting);
|
|
Ok(result)
|
|
}
|
|
}
|
|
|
|
pub async fn delete_secrets_inner(
|
|
&self,
|
|
connection: HashMap<String, HashMap<String, zbus::zvariant::OwnedValue>>,
|
|
_connection_path: zbus::zvariant::ObjectPath<'_>,
|
|
) -> Result<(), Error> {
|
|
let ss = secret_service::SecretService::connect(secret_service::EncryptionType::Dh)
|
|
.await
|
|
.map_err(Arc::new)?;
|
|
let collection = ss.get_default_collection().await.map_err(Arc::new)?;
|
|
|
|
let conn_uuid = connection
|
|
.get("connection")
|
|
.and_then(|m| m.get("uuid"))
|
|
.and_then(|v| v.downcast_ref::<String>().ok())
|
|
.ok_or_else(|| Error::NoPasswordForIdentifier("unknown".to_string()))?
|
|
.to_string();
|
|
|
|
let mut attributes = std::collections::HashMap::new();
|
|
attributes.insert("application", SECRET_ID);
|
|
attributes.insert("uuid", &conn_uuid);
|
|
|
|
let search_items = collection
|
|
.search_items(attributes)
|
|
.await
|
|
.map_err(Arc::new)?;
|
|
|
|
for item in &search_items {
|
|
item.delete().await.map_err(Arc::new)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn save_secrets_inner(
|
|
&self,
|
|
connection: HashMap<String, HashMap<String, zbus::zvariant::OwnedValue>>,
|
|
) -> Result<(), Error> {
|
|
let ss = secret_service::SecretService::connect(secret_service::EncryptionType::Dh)
|
|
.await
|
|
.map_err(Arc::new)?;
|
|
let collection = ss.get_default_collection().await.map_err(Arc::new)?;
|
|
let conn_uuid = connection
|
|
.get("connection")
|
|
.and_then(|m| m.get("uuid"))
|
|
.and_then(|v| v.downcast_ref::<String>().ok())
|
|
.ok_or_else(|| Error::NoPasswordForIdentifier("unknown".to_string()))?
|
|
.to_string();
|
|
|
|
let secret: Option<(String, String)> = connection
|
|
.get("802-11-wireless-security")
|
|
.and_then(|m| m.get("psk"))
|
|
.and_then(|v| v.downcast_ref::<String>().ok())
|
|
.map(|password| ("psk".to_string(), password.clone()))
|
|
.or_else(|| {
|
|
connection
|
|
.get("802-1x")
|
|
.and_then(|s| s.get("password"))
|
|
.and_then(|v| v.downcast_ref::<String>().ok())
|
|
.map(|password| ("802-1x-password".to_string(), password.clone()))
|
|
});
|
|
if let Some((name, secret)) = secret {
|
|
let mut attributes = std::collections::HashMap::new();
|
|
attributes.insert("application", SECRET_ID);
|
|
attributes.insert("uuid", &conn_uuid);
|
|
attributes.insert("setting_name", &name);
|
|
let _item = collection
|
|
.create_item(
|
|
"NetworkManager Secret",
|
|
attributes,
|
|
secret.as_bytes(),
|
|
true,
|
|
"text/plain",
|
|
)
|
|
.await
|
|
.map_err(Arc::new)?;
|
|
Ok(())
|
|
} else {
|
|
Err(Error::NoPasswordForIdentifier("unknown".to_string()))
|
|
}
|
|
}
|
|
}
|