gnostr_asyncgit/sync/
cred.rs1use git2::CredentialHelper;
4
5use super::{
6 remotes::{
7 get_default_remote_for_fetch_in_repo, get_default_remote_for_push_in_repo,
8 get_default_remote_in_repo,
9 },
10 repository::repo,
11 RepoPath,
12};
13use crate::error::{Error, Result};
14
15#[derive(Debug, Clone, Default, PartialEq, Eq)]
17pub struct BasicAuthCredential {
18 pub username: Option<String>,
20 pub password: Option<String>,
22}
23
24impl BasicAuthCredential {
25 pub const fn is_complete(&self) -> bool {
27 self.username.is_some() && self.password.is_some()
28 }
29 pub const fn new(username: Option<String>, password: Option<String>) -> Self {
31 Self { username, password }
32 }
33}
34
35pub fn need_username_password(repo_path: &RepoPath) -> Result<bool> {
37 let repo = repo(repo_path)?;
38 let remote = repo.find_remote(&get_default_remote_in_repo(&repo)?)?;
39 let url = remote
40 .pushurl()
41 .or_else(|| remote.url())
42 .ok_or(Error::UnknownRemote)?
43 .to_owned();
44 let is_http = url.starts_with("http");
45 Ok(is_http)
46}
47
48pub fn need_username_password_for_fetch(repo_path: &RepoPath) -> Result<bool> {
52 let repo = repo(repo_path)?;
53 let remote = repo.find_remote(&get_default_remote_for_fetch_in_repo(&repo)?)?;
54 let url = remote
55 .url()
56 .or_else(|| remote.url())
57 .ok_or(Error::UnknownRemote)?
58 .to_owned();
59 let is_http = url.starts_with("http");
60 Ok(is_http)
61}
62
63pub fn need_username_password_for_push(repo_path: &RepoPath) -> Result<bool> {
67 let repo = repo(repo_path)?;
68 let remote = repo.find_remote(&get_default_remote_for_push_in_repo(&repo)?)?;
69 let url = remote
70 .pushurl()
71 .or_else(|| remote.url())
72 .ok_or(Error::UnknownRemote)?
73 .to_owned();
74 let is_http = url.starts_with("http");
75 Ok(is_http)
76}
77
78pub fn extract_username_password(repo_path: &RepoPath) -> Result<BasicAuthCredential> {
80 let repo = repo(repo_path)?;
81 let url = repo
82 .find_remote(&get_default_remote_in_repo(&repo)?)?
83 .url()
84 .ok_or(Error::UnknownRemote)?
85 .to_owned();
86 let mut helper = CredentialHelper::new(&url);
87
88 if let Ok(config) = repo.config() {
93 helper.config(&config);
94 }
95
96 Ok(match helper.execute() {
97 Some((username, password)) => BasicAuthCredential::new(Some(username), Some(password)),
98 None => extract_cred_from_url(&url),
99 })
100}
101
102pub fn extract_username_password_for_fetch(repo_path: &RepoPath) -> Result<BasicAuthCredential> {
106 let repo = repo(repo_path)?;
107 let url = repo
108 .find_remote(&get_default_remote_for_fetch_in_repo(&repo)?)?
109 .url()
110 .ok_or(Error::UnknownRemote)?
111 .to_owned();
112 let mut helper = CredentialHelper::new(&url);
113
114 if let Ok(config) = repo.config() {
119 helper.config(&config);
120 }
121
122 Ok(match helper.execute() {
123 Some((username, password)) => BasicAuthCredential::new(Some(username), Some(password)),
124 None => extract_cred_from_url(&url),
125 })
126}
127
128pub fn extract_username_password_for_push(repo_path: &RepoPath) -> Result<BasicAuthCredential> {
132 let repo = repo(repo_path)?;
133 let url = repo
134 .find_remote(&get_default_remote_for_push_in_repo(&repo)?)?
135 .url()
136 .ok_or(Error::UnknownRemote)?
137 .to_owned();
138 let mut helper = CredentialHelper::new(&url);
139
140 if let Ok(config) = repo.config() {
145 helper.config(&config);
146 }
147
148 Ok(match helper.execute() {
149 Some((username, password)) => BasicAuthCredential::new(Some(username), Some(password)),
150 None => extract_cred_from_url(&url),
151 })
152}
153
154pub fn extract_cred_from_url(url: &str) -> BasicAuthCredential {
156 url::Url::parse(url).map_or_else(
157 |_| BasicAuthCredential::new(None, None),
158 |url| {
159 BasicAuthCredential::new(
160 if url.username() == "" {
161 None
162 } else {
163 Some(url.username().to_owned())
164 },
165 url.password().map(std::borrow::ToOwned::to_owned),
166 )
167 },
168 )
169}
170
171#[cfg(test)]
172mod tests {
173 use serial_test::serial;
174
175 use crate::sync::{
176 cred::{
177 extract_cred_from_url, extract_username_password, need_username_password,
178 BasicAuthCredential,
179 },
180 remotes::DEFAULT_REMOTE_NAME,
181 tests::repo_init,
182 RepoPath,
183 };
184
185 #[test]
186 fn test_credential_complete() {
187 assert_eq!(
188 BasicAuthCredential::new(Some("username".to_owned()), Some("password".to_owned()))
189 .is_complete(),
190 true
191 );
192 }
193
194 #[test]
195 fn test_credential_not_complete() {
196 assert_eq!(
197 BasicAuthCredential::new(None, Some("password".to_owned())).is_complete(),
198 false
199 );
200 assert_eq!(
201 BasicAuthCredential::new(Some("username".to_owned()), None).is_complete(),
202 false
203 );
204 assert_eq!(BasicAuthCredential::new(None, None).is_complete(), false);
205 }
206
207 #[test]
208 fn test_extract_username_from_url() {
209 assert_eq!(
210 extract_cred_from_url("https://user@github.com"),
211 BasicAuthCredential::new(Some("user".to_owned()), None)
212 );
213 }
214
215 #[test]
216 fn test_extract_username_password_from_url() {
217 assert_eq!(
218 extract_cred_from_url("https://user:pwd@github.com"),
219 BasicAuthCredential::new(Some("user".to_owned()), Some("pwd".to_owned()))
220 );
221 }
222
223 #[test]
224 fn test_extract_nothing_from_url() {
225 assert_eq!(
226 extract_cred_from_url("https://github.com"),
227 BasicAuthCredential::new(None, None)
228 );
229 }
230
231 #[test]
232 #[serial]
233 fn test_need_username_password_if_https() {
234 let (_td, repo) = repo_init().unwrap();
235 let root = repo.path().parent().unwrap();
236 let repo_path: &RepoPath = &root.as_os_str().to_str().unwrap().into();
237
238 repo.remote(DEFAULT_REMOTE_NAME, "http://user@github.com")
239 .unwrap();
240
241 assert_eq!(need_username_password(repo_path).unwrap(), true);
242 }
243
244 #[test]
245 #[serial]
246 fn test_dont_need_username_password_if_ssh() {
247 let (_td, repo) = repo_init().unwrap();
248 let root = repo.path().parent().unwrap();
249 let repo_path: &RepoPath = &root.as_os_str().to_str().unwrap().into();
250
251 repo.remote(DEFAULT_REMOTE_NAME, "git@github.com:user/repo")
252 .unwrap();
253
254 assert_eq!(need_username_password(repo_path).unwrap(), false);
255 }
256
257 #[test]
258 #[serial]
259 fn test_dont_need_username_password_if_pushurl_ssh() {
260 let (_td, repo) = repo_init().unwrap();
261 let root = repo.path().parent().unwrap();
262 let repo_path: &RepoPath = &root.as_os_str().to_str().unwrap().into();
263
264 repo.remote(DEFAULT_REMOTE_NAME, "http://user@github.com")
265 .unwrap();
266 repo.remote_set_pushurl(DEFAULT_REMOTE_NAME, Some("git@github.com:user/repo"))
267 .unwrap();
268
269 assert_eq!(need_username_password(repo_path).unwrap(), false);
270 }
271
272 #[test]
273 #[serial]
274 #[should_panic]
275 fn test_error_if_no_remote_when_trying_to_retrieve_if_need_username_password() {
276 let (_td, repo) = repo_init().unwrap();
277 let root = repo.path().parent().unwrap();
278 let repo_path: &RepoPath = &root.as_os_str().to_str().unwrap().into();
279
280 need_username_password(repo_path).unwrap();
281 }
282
283 #[test]
284 #[serial]
285 fn test_extract_username_password_from_repo() {
286 let (_td, repo) = repo_init().unwrap();
287 let root = repo.path().parent().unwrap();
288 let repo_path: &RepoPath = &root.as_os_str().to_str().unwrap().into();
289
290 repo.remote(DEFAULT_REMOTE_NAME, "http://user:pass@github.com")
291 .unwrap();
292
293 assert_eq!(
294 extract_username_password(repo_path).unwrap(),
295 BasicAuthCredential::new(Some("user".to_owned()), Some("pass".to_owned()))
296 );
297 }
298
299 #[test]
300 #[serial]
301 fn test_extract_username_from_repo() {
302 let (_td, repo) = repo_init().unwrap();
303 let root = repo.path().parent().unwrap();
304 let repo_path: &RepoPath = &root.as_os_str().to_str().unwrap().into();
305
306 repo.remote(DEFAULT_REMOTE_NAME, "http://user@github.com")
307 .unwrap();
308
309 assert_eq!(
310 extract_username_password(repo_path).unwrap(),
311 BasicAuthCredential::new(Some("user".to_owned()), None)
312 );
313 }
314
315 #[test]
316 #[serial]
317 #[should_panic]
318 fn test_error_if_no_remote_when_trying_to_extract_username_password() {
319 let (_td, repo) = repo_init().unwrap();
320 let root = repo.path().parent().unwrap();
321 let repo_path: &RepoPath = &root.as_os_str().to_str().unwrap().into();
322
323 extract_username_password(repo_path).unwrap();
324 }
325}