Reuse native macOS Git credentials
Some checks failed
Dependency security audit / rustsec (push) Has been cancelled

This commit is contained in:
2026-08-26 18:53:55 +02:00
parent 59cd8951f6
commit b93ae852e0
10 changed files with 327 additions and 36 deletions

View File

@@ -133,6 +133,49 @@ key_material = "keys"
Ok(())
}
#[cfg(not(any(target_os = "ios", target_os = "watchos")))]
#[test]
fn repository_https_remote_reuses_credentials_only_for_the_same_url() -> TestResult {
let fixture = ConfigurationFixture::new()?;
let vault = fixture.temporary.path().join("cwd/vault");
fs::create_dir_all(vault.join(".git"))?;
fs::write(
vault.join(".git/config"),
r#"[remote "origin"]
url = https://git.example.test/team/store.git
[remote "mirror"]
url = https://mirror.example.test/team/store.git
"#,
)?;
fixture.write_explicit(
r#"
vault = "../vault"
default_key = "0123456789ABCDEF0123456789ABCDEF01234567"
key_material = "keys"
[[git.remotes]]
name = "legacy-name"
url = "https://git.example.test/team/store.git"
server_id = "existing-server"
application_id = "existing-application"
"#,
)?;
let config = fixture
.loader()
.load(Some(Path::new("config/config.toml")))?;
let origin = config.git_remote(Some("origin")).expect("origin remote");
let (server, application) = origin.https_credentials().expect("HTTPS credentials");
assert_eq!(server.as_str(), "existing-server");
assert_eq!(application.as_str(), "existing-application");
let mirror = config.git_remote(Some("mirror")).expect("mirror remote");
let (server, application) = mirror.https_credentials().expect("HTTPS credentials");
assert_ne!(server.as_str(), "existing-server");
assert_ne!(application.as_str(), "existing-application");
Ok(())
}
#[test]
fn git_commit_identity_defaults_validates_and_persists() -> TestResult {
let fixture = ConfigurationFixture::new()?;

View File

@@ -32,6 +32,8 @@ struct MemoryState {
create_fault: Option<SecretStoreError>,
retrieves: usize,
protections: Vec<SecretProtection>,
platform_https: Option<(String, SecretBytes)>,
platform_https_requests: Vec<String>,
}
#[derive(Clone, Default)]
@@ -60,6 +62,13 @@ impl MemoryBackend {
self.0.lock().expect("test mutex").protections.clone()
}
fn with_platform_https(account: &str, password: &[u8]) -> Self {
let backend = Self::default();
backend.0.lock().expect("test mutex").platform_https =
Some((account.to_owned(), SecretBytes::new(password.to_vec())));
backend
}
fn take_fault(state: &mut MemoryState) -> Result<(), SecretStoreError> {
match state.fault.take() {
Some(error) => Err(error),
@@ -138,6 +147,22 @@ impl SecretStoreBackend for MemoryBackend {
.ok_or(SecretStoreError::Missing)
}
fn platform_https_git_credential(
&self,
endpoint: &url::Url,
) -> Result<Option<(String, SecretBytes)>, SecretStoreError> {
let mut state = self.0.lock().map_err(|_| SecretStoreError::Unavailable)?;
state
.platform_https_requests
.push(endpoint.as_str().to_owned());
Ok(state.platform_https.as_ref().map(|(account, password)| {
(
account.clone(),
SecretBytes::new(password.expose().to_vec()),
)
}))
}
fn lock(&self) -> Result<(), SecretStoreError> {
let mut state = self.0.lock().map_err(|_| SecretStoreError::Unavailable)?;
Self::take_fault(&mut state)
@@ -149,6 +174,42 @@ impl SecretStoreBackend for MemoryBackend {
}
}
#[test]
fn endpoint_lookup_uses_the_native_https_credential_before_private_storage() -> TestResult {
let backend = MemoryBackend::with_platform_https("alice", b"native-token");
let store = store(backend.clone());
store.unlock()?;
let remote = GitRemote::https(
"origin",
"https://example.test/team/store.git",
"server",
"application",
)?;
let (server, application) = remote.https_credentials().expect("HTTPS credentials");
store.store_https_git_credential(
server,
application,
"private-account",
SecretBytes::new(b"private-token".to_vec()),
)?;
let retrieves_before = backend.retrieves();
let credential = store.credential_for(&remote)?;
assert_eq!(credential.username(), "alice");
assert_eq!(credential.password(), b"native-token");
assert_eq!(backend.retrieves(), retrieves_before);
assert_eq!(
backend
.0
.lock()
.expect("test mutex")
.platform_https_requests,
["https://example.test/team/store.git".to_owned()]
);
Ok(())
}
#[derive(Clone, Default)]
struct MemoryPrompt(Arc<Mutex<MemoryPromptState>>);