Skip to main content

rskit_git/embedded/
auth.rs

1//! `git2` authentication helpers.
2
3use crate::auth::{DEFAULT_TOKEN_USERNAME, TransportAuth};
4use rskit_errors::AppResult;
5
6/// Builds remote callbacks from the configured transport auth.
7///
8/// Secret material is exposed via [`SecretString::expose`](rskit_util::SecretString::expose)
9/// only here, at the point it is handed to `git2::Cred`, so plaintext never
10/// escapes onto the repository handle or into logs.
11pub fn remote_callbacks(auth: Option<&TransportAuth>) -> AppResult<git2::RemoteCallbacks<'static>> {
12    let mut callbacks = git2::RemoteCallbacks::new();
13    match auth.cloned().unwrap_or_default() {
14        TransportAuth::Default => {}
15        TransportAuth::UsernamePassword { username, password } => {
16            callbacks.credentials(move |_, _, _| {
17                git2::Cred::userpass_plaintext(&username, password.expose())
18            });
19        }
20        TransportAuth::Token { username, token } => {
21            let username = username.unwrap_or_else(|| DEFAULT_TOKEN_USERNAME.to_string());
22            callbacks.credentials(move |_, _, _| {
23                git2::Cred::userpass_plaintext(&username, token.expose())
24            });
25        }
26        TransportAuth::SshKey {
27            username,
28            public_key,
29            private_key,
30            passphrase,
31        } => {
32            callbacks.credentials(move |_, _, _| {
33                git2::Cred::ssh_key(
34                    &username,
35                    public_key.as_deref(),
36                    &private_key,
37                    passphrase.as_ref().map(rskit_util::SecretString::expose),
38                )
39            });
40        }
41        TransportAuth::SshAgent { username } => {
42            callbacks.credentials(move |_, _, _| git2::Cred::ssh_key_from_agent(&username));
43        }
44    }
45    Ok(callbacks)
46}
47
48#[cfg(test)]
49mod tests {
50    use std::path::PathBuf;
51
52    use rskit_util::SecretString;
53
54    use super::*;
55
56    #[test]
57    fn builds_callbacks_for_all_supported_transport_auth_variants() {
58        let variants = [
59            TransportAuth::Default,
60            TransportAuth::UsernamePassword {
61                username: "user".to_string(),
62                password: SecretString::new("password"),
63            },
64            TransportAuth::Token {
65                username: None,
66                token: SecretString::new("token"),
67            },
68            TransportAuth::SshKey {
69                username: "git".to_string(),
70                public_key: None,
71                private_key: PathBuf::from("id_ed25519"),
72                passphrase: Some(SecretString::new("passphrase")),
73            },
74            TransportAuth::SshAgent {
75                username: "git".to_string(),
76            },
77        ];
78
79        for auth in variants {
80            let _callbacks =
81                remote_callbacks(Some(&auth)).expect("supported auth variant builds callbacks");
82        }
83    }
84
85    #[test]
86    fn remote_callbacks_defaults_when_auth_is_absent() {
87        let _callbacks = remote_callbacks(None).expect("default callbacks build");
88    }
89}