use log::debug;
use super::credential::{
Credential, CredentialApi, CredentialBuilder, CredentialBuilderApi, CredentialPersistence,
};
use super::error::{Error, Result};
use super::keyutils::KeyutilsCredential;
use super::secret_service::{SsCredential, SsCredentialBuilder};
#[derive(Debug, Clone)]
pub struct KeyutilsPersistentCredential {
keyutils: KeyutilsCredential,
ss: SsCredential,
}
impl CredentialApi for KeyutilsPersistentCredential {
fn set_password(&self, password: &str) -> Result<()> {
self.set_secret(password.as_bytes())
}
fn set_secret(&self, secret: &[u8]) -> Result<()> {
let prev_secret = self.keyutils.get_secret();
self.keyutils.set_secret(secret)?;
if let Err(err) = self.ss.set_secret(secret) {
debug!("Failed set of secret-service: {err}; reverting keyutils");
match prev_secret {
Ok(ref secret) => self.keyutils.set_secret(secret),
Err(Error::NoEntry) => self.keyutils.delete_credential(),
Err(err) => Err(err),
}?;
return Err(err);
}
Ok(())
}
fn get_password(&self) -> Result<String> {
match self.keyutils.get_password() {
Ok(password) => {
return Ok(password);
}
Err(err) => {
debug!("Failed get from keyutils: {err}; trying secret service")
}
}
let password = self.ss.get_password().map_err(ambiguous_to_no_entry)?;
self.keyutils.set_password(&password)?;
Ok(password)
}
fn get_secret(&self) -> Result<Vec<u8>> {
match self.keyutils.get_secret() {
Ok(secret) => {
return Ok(secret);
}
Err(err) => {
debug!("Failed get from keyutils: {err}; trying secret service")
}
}
let secret = self.ss.get_secret().map_err(ambiguous_to_no_entry)?;
self.keyutils.set_secret(&secret)?;
Ok(secret)
}
fn delete_credential(&self) -> Result<()> {
if let Err(err) = self.keyutils.delete_credential() {
debug!("cannot delete keyutils credential: {err}");
}
self.ss.delete_credential()
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn debug_fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Debug::fmt(self, f)
}
}
impl KeyutilsPersistentCredential {
pub fn new_with_target(target: Option<&str>, service: &str, user: &str) -> Result<Self> {
let ss = SsCredential::new_with_target(target, service, user)?;
let keyutils = KeyutilsCredential::new_with_target(target, service, user)?;
Ok(Self { keyutils, ss })
}
}
#[derive(Debug, Default)]
pub struct KeyutilsPersistentCredentialBuilder {}
pub fn default_credential_builder() -> Box<CredentialBuilder> {
Box::new(KeyutilsPersistentCredentialBuilder {})
}
impl CredentialBuilderApi for KeyutilsPersistentCredentialBuilder {
fn build(&self, target: Option<&str>, service: &str, user: &str) -> Result<Box<Credential>> {
Ok(Box::new(SsCredential::new_with_target(
target, service, user,
)?))
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn persistence(&self) -> CredentialPersistence {
SsCredentialBuilder {}.persistence()
}
}
fn ambiguous_to_no_entry(err: Error) -> Error {
if let Error::Ambiguous(_) = err {
return Error::NoEntry;
};
err
}
#[cfg(test)]
mod tests {
use crate::{Entry, Error};
use super::KeyutilsPersistentCredential;
fn entry_new(service: &str, user: &str) -> Entry {
crate::tests::entry_from_constructor(
KeyutilsPersistentCredential::new_with_target,
service,
user,
)
}
#[test]
fn test_invalid_parameter() {
let credential = KeyutilsPersistentCredential::new_with_target(Some(""), "service", "user");
assert!(
matches!(credential, Err(Error::Invalid(_, _))),
"Created entry with empty target"
);
}
#[test]
fn test_empty_service_and_user() {
crate::tests::test_empty_service_and_user(entry_new);
}
#[test]
fn test_missing_entry() {
crate::tests::test_missing_entry(entry_new);
}
#[test]
fn test_empty_password() {
let entry = entry_new("empty password service", "empty password user");
assert!(
matches!(entry.set_password(""), Err(Error::Invalid(_, _))),
"Able to set empty password"
);
}
#[test]
fn test_round_trip_ascii_password() {
crate::tests::test_round_trip_ascii_password(entry_new);
}
#[test]
fn test_round_trip_non_ascii_password() {
crate::tests::test_round_trip_non_ascii_password(entry_new);
}
#[test]
fn test_round_trip_random_secret() {
crate::tests::test_round_trip_random_secret(entry_new);
}
#[test]
fn test_update() {
crate::tests::test_update(entry_new);
}
#[test]
fn test_noop_get_update_attributes() {
crate::tests::test_noop_get_update_attributes(entry_new);
}
}