gnostr_asyncgit/sync/
cred.rs1use git2::CredentialHelper;
4
5use super::{
6 RepoPath,
7 remotes::{
8 get_default_remote_for_fetch_in_repo,
9 get_default_remote_for_push_in_repo,
10 get_default_remote_in_repo,
11 },
12 repository::repo,
13};
14use crate::error::{Error, Result};
15
16#[derive(Debug, Clone, Default, PartialEq, Eq)]
18pub struct BasicAuthCredential {
19 pub username: Option<String>,
21 pub password: Option<String>,
23}
24
25impl BasicAuthCredential {
26 pub const fn is_complete(&self) -> bool {
28 self.username.is_some() && self.password.is_some()
29 }
30 pub const fn new(
32 username: Option<String>,
33 password: Option<String>,
34 ) -> Self {
35 Self { username, password }
36 }
37}
38
39pub fn need_username_password(repo_path: &RepoPath) -> Result<bool> {
41 let repo = repo(repo_path)?;
42 let remote =
43 repo.find_remote(&get_default_remote_in_repo(&repo)?)?;
44 let url = remote
45 .pushurl()
46 .or_else(|| remote.url())
47 .ok_or(Error::UnknownRemote)?
48 .to_owned();
49 let is_http = url.starts_with("http");
50 Ok(is_http)
51}
52
53pub fn need_username_password_for_fetch(
57 repo_path: &RepoPath,
58) -> Result<bool> {
59 let repo = repo(repo_path)?;
60 let remote = repo
61 .find_remote(&get_default_remote_for_fetch_in_repo(&repo)?)?;
62 let url = remote
63 .url()
64 .or_else(|| remote.url())
65 .ok_or(Error::UnknownRemote)?
66 .to_owned();
67 let is_http = url.starts_with("http");
68 Ok(is_http)
69}
70
71pub fn need_username_password_for_push(
75 repo_path: &RepoPath,
76) -> Result<bool> {
77 let repo = repo(repo_path)?;
78 let remote = repo
79 .find_remote(&get_default_remote_for_push_in_repo(&repo)?)?;
80 let url = remote
81 .pushurl()
82 .or_else(|| remote.url())
83 .ok_or(Error::UnknownRemote)?
84 .to_owned();
85 let is_http = url.starts_with("http");
86 Ok(is_http)
87}
88
89pub fn extract_username_password(
91 repo_path: &RepoPath,
92) -> Result<BasicAuthCredential> {
93 let repo = repo(repo_path)?;
94 let url = repo
95 .find_remote(&get_default_remote_in_repo(&repo)?)?
96 .url()
97 .ok_or(Error::UnknownRemote)?
98 .to_owned();
99 let mut helper = CredentialHelper::new(&url);
100
101 if let Ok(config) = repo.config() {
106 helper.config(&config);
107 }
108
109 Ok(match helper.execute() {
110 Some((username, password)) => {
111 BasicAuthCredential::new(Some(username), Some(password))
112 }
113 None => extract_cred_from_url(&url),
114 })
115}
116
117pub fn extract_username_password_for_fetch(
121 repo_path: &RepoPath,
122) -> Result<BasicAuthCredential> {
123 let repo = repo(repo_path)?;
124 let url = repo
125 .find_remote(&get_default_remote_for_fetch_in_repo(&repo)?)?
126 .url()
127 .ok_or(Error::UnknownRemote)?
128 .to_owned();
129 let mut helper = CredentialHelper::new(&url);
130
131 if let Ok(config) = repo.config() {
136 helper.config(&config);
137 }
138
139 Ok(match helper.execute() {
140 Some((username, password)) => {
141 BasicAuthCredential::new(Some(username), Some(password))
142 }
143 None => extract_cred_from_url(&url),
144 })
145}
146
147pub fn extract_username_password_for_push(
151 repo_path: &RepoPath,
152) -> Result<BasicAuthCredential> {
153 let repo = repo(repo_path)?;
154 let url = repo
155 .find_remote(&get_default_remote_for_push_in_repo(&repo)?)?
156 .url()
157 .ok_or(Error::UnknownRemote)?
158 .to_owned();
159 let mut helper = CredentialHelper::new(&url);
160
161 if let Ok(config) = repo.config() {
166 helper.config(&config);
167 }
168
169 Ok(match helper.execute() {
170 Some((username, password)) => {
171 BasicAuthCredential::new(Some(username), Some(password))
172 }
173 None => extract_cred_from_url(&url),
174 })
175}
176
177pub fn extract_cred_from_url(url: &str) -> BasicAuthCredential {
179 url::Url::parse(url).map_or_else(
180 |_| BasicAuthCredential::new(None, None),
181 |url| {
182 BasicAuthCredential::new(
183 if url.username() == "" {
184 None
185 } else {
186 Some(url.username().to_owned())
187 },
188 url.password().map(std::borrow::ToOwned::to_owned),
189 )
190 },
191 )
192}
193
194#[cfg(test)]
195mod tests {
196 use serial_test::serial;
197
198 use crate::sync::{
199 RepoPath,
200 cred::{
201 BasicAuthCredential, extract_cred_from_url,
202 extract_username_password, need_username_password,
203 },
204 remotes::DEFAULT_REMOTE_NAME,
205 tests::repo_init,
206 };
207
208 #[test]
209 fn test_credential_complete() {
210 assert_eq!(
211 BasicAuthCredential::new(
212 Some("username".to_owned()),
213 Some("password".to_owned())
214 )
215 .is_complete(),
216 true
217 );
218 }
219
220 #[test]
221 fn test_credential_not_complete() {
222 assert_eq!(
223 BasicAuthCredential::new(
224 None,
225 Some("password".to_owned())
226 )
227 .is_complete(),
228 false
229 );
230 assert_eq!(
231 BasicAuthCredential::new(
232 Some("username".to_owned()),
233 None
234 )
235 .is_complete(),
236 false
237 );
238 assert_eq!(
239 BasicAuthCredential::new(None, None).is_complete(),
240 false
241 );
242 }
243
244 #[test]
245 fn test_extract_username_from_url() {
246 assert_eq!(
247 extract_cred_from_url("https://user@github.com"),
248 BasicAuthCredential::new(Some("user".to_owned()), None)
249 );
250 }
251
252 #[test]
253 fn test_extract_username_password_from_url() {
254 assert_eq!(
255 extract_cred_from_url("https://user:pwd@github.com"),
256 BasicAuthCredential::new(
257 Some("user".to_owned()),
258 Some("pwd".to_owned())
259 )
260 );
261 }
262
263 #[test]
264 fn test_extract_nothing_from_url() {
265 assert_eq!(
266 extract_cred_from_url("https://github.com"),
267 BasicAuthCredential::new(None, None)
268 );
269 }
270
271 #[test]
272 #[serial]
273 fn test_need_username_password_if_https() {
274 let (_td, repo) = repo_init().unwrap();
275 let root = repo.path().parent().unwrap();
276 let repo_path: &RepoPath =
277 &root.as_os_str().to_str().unwrap().into();
278
279 repo.remote(DEFAULT_REMOTE_NAME, "http://user@github.com")
280 .unwrap();
281
282 assert_eq!(need_username_password(repo_path).unwrap(), true);
283 }
284
285 #[test]
286 #[serial]
287 fn test_dont_need_username_password_if_ssh() {
288 let (_td, repo) = repo_init().unwrap();
289 let root = repo.path().parent().unwrap();
290 let repo_path: &RepoPath =
291 &root.as_os_str().to_str().unwrap().into();
292
293 repo.remote(DEFAULT_REMOTE_NAME, "git@github.com:user/repo")
294 .unwrap();
295
296 assert_eq!(need_username_password(repo_path).unwrap(), false);
297 }
298
299 #[test]
300 #[serial]
301 fn test_dont_need_username_password_if_pushurl_ssh() {
302 let (_td, repo) = repo_init().unwrap();
303 let root = repo.path().parent().unwrap();
304 let repo_path: &RepoPath =
305 &root.as_os_str().to_str().unwrap().into();
306
307 repo.remote(DEFAULT_REMOTE_NAME, "http://user@github.com")
308 .unwrap();
309 repo.remote_set_pushurl(
310 DEFAULT_REMOTE_NAME,
311 Some("git@github.com:user/repo"),
312 )
313 .unwrap();
314
315 assert_eq!(need_username_password(repo_path).unwrap(), false);
316 }
317
318 #[test]
319 #[serial]
320 #[should_panic]
321 fn test_error_if_no_remote_when_trying_to_retrieve_if_need_username_password()
322 {
323 let (_td, repo) = repo_init().unwrap();
324 let root = repo.path().parent().unwrap();
325 let repo_path: &RepoPath =
326 &root.as_os_str().to_str().unwrap().into();
327
328 need_username_password(repo_path).unwrap();
329 }
330
331 #[test]
332 #[serial]
333 fn test_extract_username_password_from_repo() {
334 let (_td, repo) = repo_init().unwrap();
335 let root = repo.path().parent().unwrap();
336 let repo_path: &RepoPath =
337 &root.as_os_str().to_str().unwrap().into();
338
339 repo.remote(
340 DEFAULT_REMOTE_NAME,
341 "http://user:pass@github.com",
342 )
343 .unwrap();
344
345 assert_eq!(
346 extract_username_password(repo_path).unwrap(),
347 BasicAuthCredential::new(
348 Some("user".to_owned()),
349 Some("pass".to_owned())
350 )
351 );
352 }
353
354 #[test]
355 #[serial]
356 fn test_extract_username_from_repo() {
357 let (_td, repo) = repo_init().unwrap();
358 let root = repo.path().parent().unwrap();
359 let repo_path: &RepoPath =
360 &root.as_os_str().to_str().unwrap().into();
361
362 repo.remote(DEFAULT_REMOTE_NAME, "http://user@github.com")
363 .unwrap();
364
365 assert_eq!(
366 extract_username_password(repo_path).unwrap(),
367 BasicAuthCredential::new(Some("user".to_owned()), None)
368 );
369 }
370
371 #[test]
372 #[serial]
373 #[should_panic]
374 fn test_error_if_no_remote_when_trying_to_extract_username_password()
375 {
376 let (_td, repo) = repo_init().unwrap();
377 let root = repo.path().parent().unwrap();
378 let repo_path: &RepoPath =
379 &root.as_os_str().to_str().unwrap().into();
380
381 extract_username_password(repo_path).unwrap();
382 }
383}