use crate::error::{KeyringError, Result};
use crate::Keyring;
use security_framework::base::Error as SfError;
use security_framework::os::macos::keychain::SecKeychain;
use security_framework::os::macos::passwords::find_generic_password;
#[cfg(feature = "macos-specify-keychain")]
use std::path::Path;
#[allow(non_upper_case_globals)]
const errSecItemNotFound: i32 = -25300;
pub struct MacosKeyringManager {
application: String,
keychain: SecKeychain,
}
impl MacosKeyringManager {
pub fn new(application: &str) -> Result<Self> {
Ok(MacosKeyringManager {
application: application.to_owned(),
keychain: SecKeychain::default()?,
})
}
#[cfg(feature = "macos-specify-keychain")]
pub fn with_path(application: &str, path: &Path) -> Result<Self> {
Ok(MacosKeyringManager {
application: application.to_owned(),
keychain: SecKeychain::open(path)?,
})
}
pub fn with_keyring<F, T>(&self, service: &str, key: &str, func: F) -> Result<T>
where
F: FnOnce(&mut dyn Keyring) -> Result<T>,
{
let mut kr = MacosKeyring::new(self, service, key)?;
func(&mut kr)
}
}
pub struct MacosKeyring<'a> {
manager: &'a MacosKeyringManager,
service: &'a str,
key: &'a str,
}
impl<'a> MacosKeyring<'a> {
fn new(
manager: &'a MacosKeyringManager,
service: &'a str,
key: &'a str,
) -> Result<MacosKeyring<'a>> {
Ok(MacosKeyring {
manager,
service,
key,
})
}
fn make_service_name(&self) -> String {
[
crate::escape(&self.manager.application),
crate::escape(self.service),
]
.join("\t")
}
}
impl Keyring for MacosKeyring<'_> {
fn set_value(&mut self, value: &str) -> Result<()> {
let service_name = self.make_service_name();
self.manager
.keychain
.set_generic_password(&service_name, self.key, value.as_bytes())?;
Ok(())
}
fn get_value(&self) -> Result<String> {
let service_name = self.make_service_name();
let (password_bytes, _) = match find_generic_password(
Some(std::slice::from_ref(&self.manager.keychain)),
&service_name,
self.key,
) {
Ok(v) => v,
Err(e) => {
if e.code() == errSecItemNotFound {
return Err(KeyringError::NoPasswordFound);
} else {
return Err(KeyringError::MacOsKeychainError(SfError::from_code(
e.code(),
)));
}
}
};
let password = String::from_utf8(password_bytes.to_vec())?;
Ok(password)
}
fn delete_value(&mut self) -> Result<()> {
let service_name = self.make_service_name();
let (_, item) = match find_generic_password(
Some(std::slice::from_ref(&self.manager.keychain)),
&service_name,
self.key,
) {
Ok(v) => v,
Err(e) => {
if e.code() == errSecItemNotFound {
return Err(KeyringError::NoPasswordFound);
} else {
return Err(KeyringError::MacOsKeychainError(SfError::from_code(
e.code(),
)));
}
}
};
item.delete();
Ok(())
}
}