feat: password resolution via OS keychain and password_cmd
SecretStore trait with a keyring-backed implementation (macOS Keychain, Windows Credential Manager, Linux Secret Service) under service soulseek-rs. Precedence: --password/env > keychain > password_cmd from config.toml. Keychain failures degrade to not-found so headless boxes fall through to password_cmd.
This commit is contained in:
Generated
+223
-9
@@ -68,7 +68,7 @@ version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -79,7 +79,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell_polyfill",
|
||||
"windows-sys",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -199,6 +199,12 @@ version = "1.25.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6aedf8ae72766347502cf3cb4f41cf5e9cc37d28bee90f1fdaaae15f9cf9424"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.12.1"
|
||||
@@ -334,6 +340,32 @@ dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.17"
|
||||
@@ -439,6 +471,27 @@ dependencies = [
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dbus"
|
||||
version = "0.9.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ab69f03cc8c4340c9c8e315114e1658e6775a9b16a04357973aa21cec22b32e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"libdbus-sys",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dbus-secret-service"
|
||||
version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "708b509edf7889e53d7efb0ffadd994cc6c2345ccb62f55cfd6b0682165e4fa6"
|
||||
dependencies = [
|
||||
"dbus",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deltae"
|
||||
version = "0.3.2"
|
||||
@@ -501,7 +554,7 @@ dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users",
|
||||
"windows-sys",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -549,7 +602,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -959,6 +1012,21 @@ dependencies = [
|
||||
"thiserror 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "keyring"
|
||||
version = "3.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eebcc3aff044e5944a8fbaf69eb277d11986064cba30c468730e8b9909fb551c"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"dbus-secret-service",
|
||||
"log",
|
||||
"security-framework 2.11.1",
|
||||
"security-framework 3.7.0",
|
||||
"windows-sys 0.60.2",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lab"
|
||||
version = "0.11.0"
|
||||
@@ -977,6 +1045,15 @@ version = "0.2.186"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
||||
|
||||
[[package]]
|
||||
name = "libdbus-sys"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043"
|
||||
dependencies = [
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.16"
|
||||
@@ -1098,7 +1175,7 @@ dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi",
|
||||
"windows-sys",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1354,6 +1431,12 @@ version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.14.0"
|
||||
@@ -1612,7 +1695,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1633,6 +1716,42 @@ version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "2.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
|
||||
dependencies = [
|
||||
"bitflags 2.13.1",
|
||||
"core-foundation 0.9.4",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
"security-framework-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "3.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
|
||||
dependencies = [
|
||||
"bitflags 2.13.1",
|
||||
"core-foundation 0.10.1",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
"security-framework-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework-sys"
|
||||
version = "2.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.28"
|
||||
@@ -1769,6 +1888,7 @@ dependencies = [
|
||||
"directories",
|
||||
"dotenv",
|
||||
"igd-next",
|
||||
"keyring",
|
||||
"ratatui",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -1863,7 +1983,7 @@ dependencies = [
|
||||
"getrandom 0.4.3",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1876,7 +1996,7 @@ dependencies = [
|
||||
"parking_lot",
|
||||
"rustix",
|
||||
"signal-hook",
|
||||
"windows-sys",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1886,7 +2006,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874"
|
||||
dependencies = [
|
||||
"rustix",
|
||||
"windows-sys",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2370,6 +2490,15 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.60.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
@@ -2379,6 +2508,71 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.53.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.15"
|
||||
@@ -2462,6 +2656,26 @@ dependencies = [
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
|
||||
dependencies = [
|
||||
"zeroize_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize_derive"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerotrie"
|
||||
version = "0.2.4"
|
||||
|
||||
@@ -28,6 +28,7 @@ serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
toml = "0.9"
|
||||
directories = "6"
|
||||
keyring = { version = "3", features = ["apple-native", "windows-native", "sync-secret-service"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3"
|
||||
|
||||
@@ -51,9 +51,15 @@ fn main() -> Result<()> {
|
||||
)
|
||||
})?;
|
||||
|
||||
let password = cli.password.clone().ok_or_else(|| {
|
||||
let password = persist::secret::resolve_password(
|
||||
cli.password.as_deref(),
|
||||
resolved.username.as_deref(),
|
||||
resolved.password_cmd.as_deref(),
|
||||
&persist::secret::KeyringStore,
|
||||
)
|
||||
.ok_or_else(|| {
|
||||
color_eyre::eyre::eyre!(
|
||||
"Password required: use --password or set SOULSEEK_PASSWORD env var"
|
||||
"Password required: use --password, set SOULSEEK_PASSWORD, or store one in the OS keychain"
|
||||
)
|
||||
})?;
|
||||
|
||||
|
||||
@@ -6,3 +6,4 @@
|
||||
|
||||
pub mod config;
|
||||
pub mod paths;
|
||||
pub mod secret;
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
use color_eyre::Result;
|
||||
|
||||
/// Where passwords live. The real implementation is the OS keychain
|
||||
/// ([`KeyringStore`]); tests use an in-memory fake. Passwords are never
|
||||
/// written to config.toml.
|
||||
pub trait SecretStore {
|
||||
fn get(&self, username: &str) -> Result<Option<String>>;
|
||||
fn set(&self, username: &str, password: &str) -> Result<()>;
|
||||
}
|
||||
|
||||
/// OS keychain (macOS Keychain / Windows Credential Manager / Linux Secret
|
||||
/// Service) under service name `soulseek-rs`, account = Soulseek username.
|
||||
pub struct KeyringStore;
|
||||
|
||||
const SERVICE: &str = "soulseek-rs";
|
||||
|
||||
impl SecretStore for KeyringStore {
|
||||
fn get(&self, username: &str) -> Result<Option<String>> {
|
||||
let entry = keyring::Entry::new(SERVICE, username)
|
||||
.map_err(|e| color_eyre::eyre::eyre!("keyring: {e}"))?;
|
||||
match entry.get_password() {
|
||||
Ok(password) => Ok(Some(password)),
|
||||
Err(keyring::Error::NoEntry) => Ok(None),
|
||||
Err(e) => Err(color_eyre::eyre::eyre!("keyring: {e}")),
|
||||
}
|
||||
}
|
||||
|
||||
fn set(&self, username: &str, password: &str) -> Result<()> {
|
||||
keyring::Entry::new(SERVICE, username)
|
||||
.and_then(|entry| entry.set_password(password))
|
||||
.map_err(|e| color_eyre::eyre::eyre!("keyring: {e}"))
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolve the password with precedence: CLI/env (`cli_password`, already
|
||||
/// merged by clap) > OS keychain > `password_cmd` from config.toml. Returns
|
||||
/// `None` when no source has one — the caller shows the login screen.
|
||||
///
|
||||
/// Keychain errors are downgraded to "not found": on headless boxes without
|
||||
/// a secret service the fallbacks must still work.
|
||||
pub fn resolve_password(
|
||||
cli_password: Option<&str>,
|
||||
username: Option<&str>,
|
||||
password_cmd: Option<&str>,
|
||||
store: &dyn SecretStore,
|
||||
) -> Option<String> {
|
||||
if let Some(password) = cli_password {
|
||||
return Some(password.to_string());
|
||||
}
|
||||
if let Some(username) = username
|
||||
&& let Ok(Some(password)) = store.get(username)
|
||||
{
|
||||
return Some(password);
|
||||
}
|
||||
password_cmd.and_then(run_password_cmd)
|
||||
}
|
||||
|
||||
/// Run `password_cmd` through the platform shell and return trimmed stdout.
|
||||
fn run_password_cmd(cmd: &str) -> Option<String> {
|
||||
let output = if cfg!(windows) {
|
||||
std::process::Command::new("cmd").args(["/C", cmd]).output()
|
||||
} else {
|
||||
std::process::Command::new("sh").args(["-c", cmd]).output()
|
||||
}
|
||||
.ok()?;
|
||||
if !output.status.success() {
|
||||
return None;
|
||||
}
|
||||
let password = String::from_utf8(output.stdout).ok()?;
|
||||
let password = password.trim_end_matches(['\r', '\n']);
|
||||
(!password.is_empty()).then(|| password.to_string())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Default)]
|
||||
struct FakeStore {
|
||||
secrets: RefCell<HashMap<String, String>>,
|
||||
fail: bool,
|
||||
}
|
||||
|
||||
impl SecretStore for FakeStore {
|
||||
fn get(&self, username: &str) -> Result<Option<String>> {
|
||||
if self.fail {
|
||||
return Err(color_eyre::eyre::eyre!("no secret service"));
|
||||
}
|
||||
Ok(self.secrets.borrow().get(username).cloned())
|
||||
}
|
||||
|
||||
fn set(&self, username: &str, password: &str) -> Result<()> {
|
||||
self.secrets
|
||||
.borrow_mut()
|
||||
.insert(username.into(), password.into());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cli_password_wins_over_everything() {
|
||||
let store = FakeStore::default();
|
||||
store.set("alice", "from-keyring").unwrap();
|
||||
let got = resolve_password(
|
||||
Some("from-cli"),
|
||||
Some("alice"),
|
||||
Some("echo from-cmd"),
|
||||
&store,
|
||||
);
|
||||
assert_eq!(got.as_deref(), Some("from-cli"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keyring_used_when_no_cli_password() {
|
||||
let store = FakeStore::default();
|
||||
store.set("alice", "from-keyring").unwrap();
|
||||
let got = resolve_password(None, Some("alice"), None, &store);
|
||||
assert_eq!(got.as_deref(), Some("from-keyring"));
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn password_cmd_used_when_keyring_has_nothing() {
|
||||
let store = FakeStore::default();
|
||||
let got = resolve_password(
|
||||
None,
|
||||
Some("alice"),
|
||||
Some("echo from-cmd"),
|
||||
&store,
|
||||
);
|
||||
assert_eq!(got.as_deref(), Some("from-cmd"));
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn keyring_failure_falls_through_to_password_cmd() {
|
||||
let store = FakeStore {
|
||||
fail: true,
|
||||
..FakeStore::default()
|
||||
};
|
||||
let got = resolve_password(
|
||||
None,
|
||||
Some("alice"),
|
||||
Some("echo from-cmd"),
|
||||
&store,
|
||||
);
|
||||
assert_eq!(got.as_deref(), Some("from-cmd"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn none_when_no_source_has_a_password() {
|
||||
let store = FakeStore::default();
|
||||
assert_eq!(resolve_password(None, Some("alice"), None, &store), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_username_means_no_keyring_lookup() {
|
||||
let store = FakeStore {
|
||||
fail: true,
|
||||
..FakeStore::default()
|
||||
};
|
||||
assert_eq!(resolve_password(None, None, None, &store), None);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn failing_password_cmd_yields_none() {
|
||||
let store = FakeStore::default();
|
||||
assert_eq!(
|
||||
resolve_password(None, Some("alice"), Some("false"), &store),
|
||||
None
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user