use std::{
io::{self, IsTerminal, Write},
sync::{Arc, Mutex},
};
use rho_providers::credentials::{CredentialResult, CredentialStore, CredentialStoreBackend};
static NOTICE: Mutex<NoticeState> = Mutex::new(NoticeState::Disabled);
enum NoticeState {
Disabled,
Pending,
Shown,
}
impl NoticeState {
fn before_access(&mut self, backend: CredentialStoreBackend, output: &mut impl Write) {
if backend == CredentialStoreBackend::Os && matches!(self, Self::Pending) {
let _ = writeln!(
output,
"accessing keyring; check your desktop for an unlock prompt"
);
let _ = output.flush();
*self = Self::Shown;
}
}
}
pub(crate) struct StartupKeyringNotice;
impl StartupKeyringNotice {
pub(crate) fn begin(interactive: bool) -> Self {
let mut state = NOTICE.lock().unwrap_or_else(|error| error.into_inner());
*state = if interactive && io::stdin().is_terminal() && io::stderr().is_terminal() {
NoticeState::Pending
} else {
NoticeState::Disabled
};
Self
}
}
impl Drop for StartupKeyringNotice {
fn drop(&mut self) {
*NOTICE.lock().unwrap_or_else(|error| error.into_inner()) = NoticeState::Disabled;
}
}
pub(super) struct NotifyingCredentialStore {
pub(super) inner: Arc<dyn CredentialStore>,
pub(super) backend: CredentialStoreBackend,
}
impl NotifyingCredentialStore {
fn before_access(&self) {
NOTICE
.lock()
.unwrap_or_else(|error| error.into_inner())
.before_access(self.backend, &mut io::stderr());
}
}
impl CredentialStore for NotifyingCredentialStore {
fn get_secret(&self, account: &str) -> CredentialResult<Option<String>> {
self.before_access();
self.inner.get_secret(account)
}
fn set_secret(&self, account: &str, secret: &str) -> CredentialResult<()> {
self.before_access();
self.inner.set_secret(account, secret)
}
fn delete_secret(&self, account: &str) -> CredentialResult<bool> {
self.before_access();
self.inner.delete_secret(account)
}
}
#[cfg(test)]
#[path = "startup_notice_tests.rs"]
mod tests;