1use crate::utils::HelperErrors;
11use clap::{Parser, ValueHint};
12use hopr_crypto_types::keypairs::{ChainKeypair, Keypair};
13use hopr_primitive_types::primitives::Address;
14use hoprd_keypair::key_pair::{HoprKeys, IdentityRetrievalModes};
15use std::{
16 collections::HashMap,
17 env, fs,
18 path::{Path, PathBuf},
19};
20use tracing::{debug, error, info, warn};
21use uuid::Uuid;
22
23pub fn read_identity(file: &Path, password: &str) -> Result<(String, HoprKeys), HelperErrors> {
24 let file_str = file
25 .to_str()
26 .ok_or(HelperErrors::IncorrectFilename(file.to_string_lossy().to_string()))?;
27
28 match HoprKeys::read_eth_keystore(file_str, password) {
29 Ok((keys, needs_migration)) => {
30 if needs_migration {
31 keys.write_eth_keystore(file_str, password)
32 .map_err(HelperErrors::KeyStoreError)?;
33 }
34 let file_key = file
35 .file_name()
36 .ok_or(HelperErrors::ParseError("Failed to extract file name".into()))?;
37 Ok((
38 String::from(
39 file_key
40 .to_str()
41 .ok_or(HelperErrors::ParseError("Failed to extract file key".into()))?,
42 ),
43 keys,
44 ))
45 }
46 Err(e) => {
47 error!("Could not decrypt keystore file at {}. {}", file_str, e.to_string());
48 Err(HelperErrors::UnableToReadIdentity)
49 }
50 }
51}
52
53pub fn read_identities(files: Vec<PathBuf>, password: &str) -> Result<HashMap<String, HoprKeys>, HelperErrors> {
60 let mut results: HashMap<String, HoprKeys> = HashMap::with_capacity(files.len());
61
62 for file in files.iter() {
63 let id_path = file
64 .to_str()
65 .ok_or(HelperErrors::IncorrectFilename(file.to_string_lossy().to_string()))?;
66
67 match HoprKeys::try_from(IdentityRetrievalModes::FromFile { password, id_path }) {
68 Ok(keys) => {
69 results.insert(id_path.into(), keys);
70 }
71 Err(e) => {
72 warn!("Could not read keystore file at {} due to {}", id_path, e.to_string())
73 }
74 }
75 }
76
77 Ok(results)
78}
79
80pub fn update_identity_password(
82 keys: HoprKeys,
83 path: &Path,
84 password: &str,
85) -> Result<(String, HoprKeys), HelperErrors> {
86 let file_path = path
87 .to_str()
88 .ok_or(HelperErrors::IncorrectFilename(path.to_string_lossy().to_string()))?;
89
90 if path.exists() && path.is_file() && file_path.ends_with(".id") {
91 fs::remove_file(file_path).map_err(|_err| HelperErrors::UnableToUpdateIdentityPassword)?;
93 keys.write_eth_keystore(file_path, password)?;
94 Ok((String::from(file_path), keys))
95 } else {
96 warn!(
97 "Could not update keystore file at {}. {}",
98 file_path, "File name does not end with `.id`"
99 );
100 Err(HelperErrors::UnableToUpdateIdentityPassword)
101 }
102}
103
104pub fn create_identity(
112 dir_name: &str,
113 password: &str,
114 maybe_name: &Option<String>,
115) -> Result<(String, HoprKeys), HelperErrors> {
116 fs::create_dir_all(dir_name)?;
118
119 let id = Uuid::new_v4();
120
121 let file_name = match maybe_name {
123 Some(name) => {
124 if name.ends_with(".id") {
126 name.to_owned()
127 } else {
128 format!("{name}.id")
129 }
130 }
131 None => format!("{}.id", { id.to_string() }),
133 };
134
135 let mut file_path = PathBuf::from(dir_name);
136
137 file_path.push(file_name);
139
140 let file_path_str = file_path
141 .to_str()
142 .ok_or(HelperErrors::IncorrectFilename(file_path.to_string_lossy().to_string()))?;
143
144 Ok((
145 file_path_str.into(),
146 IdentityRetrievalModes::FromIdIntoFile {
147 id,
148 password,
149 id_path: file_path_str,
150 }
151 .try_into()?,
152 ))
153}
154
155pub trait ArgEnvReader<T, K> {
156 fn get_key(&self) -> Option<K>;
158
159 fn read(&self, default_env_name: &str) -> Result<T, HelperErrors>;
161
162 fn read_default(&self) -> Result<T, HelperErrors>;
164}
165
166#[derive(Debug, Clone, Parser, Default)]
168pub struct PrivateKeyArgs {
169 #[clap(
171 long,
172 short = 'k',
173 help = "Private key to unlock the account that broadcasts the transaction",
174 name = "private_key",
175 value_name = "PRIVATE_KEY"
176 )]
177 pub private_key: Option<String>,
178}
179
180impl ArgEnvReader<ChainKeypair, String> for PrivateKeyArgs {
181 fn get_key(&self) -> Option<String> {
183 self.private_key.to_owned()
184 }
185
186 fn read(&self, default_env_name: &str) -> Result<ChainKeypair, HelperErrors> {
188 let pri_key = if let Some(pk) = self.get_key() {
189 info!("Reading private key from CLI");
190 pk
191 } else if let Ok(env_pk) = env::var(default_env_name) {
192 info!("Reading private key from environment variable {:?}", default_env_name);
193 env_pk
194 } else if let Ok(prompt_pk) = rpassword::prompt_password("Enter private key:") {
195 info!("Reading private key from prompt");
196 prompt_pk
197 } else {
198 error!(
199 "Unable to read private key from environment variable: {:?}",
200 default_env_name
201 );
202 return Err(HelperErrors::UnableToReadPrivateKey(default_env_name.into()));
203 };
204
205 let priv_key_without_prefix = pri_key.strip_prefix("0x").unwrap_or(&pri_key).to_string();
207
208 let decoded_key = hex::decode(priv_key_without_prefix)
209 .map_err(|e| HelperErrors::UnableToReadPrivateKey(format!("Failed to decode private key: {:?}", e)))?;
210 ChainKeypair::from_secret(&decoded_key)
211 .map_err(|e| HelperErrors::UnableToReadPrivateKey(format!("Failed to create keypair: {:?}", e)))
212 }
213
214 fn read_default(&self) -> Result<ChainKeypair, HelperErrors> {
216 self.read("PRIVATE_KEY")
217 }
218}
219
220#[derive(Debug, Clone, Parser, Default)]
222pub struct ManagerPrivateKeyArgs {
223 #[clap(
225 long,
226 short = 'q',
227 help = "Private key to unlock the account with privilege that broadcasts the transaction",
228 name = "manager_private_key",
229 value_name = "MANAGER_PRIVATE_KEY"
230 )]
231 pub manager_private_key: Option<String>,
232}
233
234impl ArgEnvReader<ChainKeypair, String> for ManagerPrivateKeyArgs {
235 fn get_key(&self) -> Option<String> {
237 self.manager_private_key.to_owned()
238 }
239
240 fn read(&self, default_env_name: &str) -> Result<ChainKeypair, HelperErrors> {
242 let pri_key = if let Some(pk) = self.get_key() {
243 info!("Reading manager private key from CLI");
244 pk
245 } else if let Ok(env_pk) = env::var(default_env_name) {
246 info!(
247 "Reading manager private key from environment variable {:?}",
248 default_env_name
249 );
250 env_pk
251 } else if let Ok(prompt_pk) = rpassword::prompt_password("Enter manager private key:") {
252 info!("Reading manager private key from prompt");
253 prompt_pk
254 } else {
255 error!(
256 "Unable to read private key from environment variable: {:?}",
257 default_env_name
258 );
259 return Err(HelperErrors::UnableToReadPrivateKey(default_env_name.into()));
260 };
261
262 let priv_key_without_prefix = pri_key.strip_prefix("0x").unwrap_or(&pri_key).to_string();
264 let decoded_key = hex::decode(priv_key_without_prefix)
265 .map_err(|e| HelperErrors::UnableToReadPrivateKey(format!("Failed to decode private key: {:?}", e)))?;
266 ChainKeypair::from_secret(&decoded_key)
267 .map_err(|e| HelperErrors::UnableToReadPrivateKey(format!("Failed to create keypair: {:?}", e)))
268 }
269
270 fn read_default(&self) -> Result<ChainKeypair, HelperErrors> {
272 self.read("MANAGER_PRIVATE_KEY")
273 }
274}
275
276#[derive(Debug, Clone, Parser, Default)]
282pub struct PasswordArgs {
283 #[clap(
285 short,
286 long,
287 help = "The path to read the password. If not specified, use the IDENTITY_PASSWORD environment variable.",
288 value_hint = ValueHint::FilePath,
289 name = "password_path",
290 value_name = "PASSWORD_PATH"
291 )]
292 pub password_path: Option<PathBuf>,
293}
294
295impl ArgEnvReader<String, PathBuf> for PasswordArgs {
296 fn get_key(&self) -> Option<PathBuf> {
298 self.password_path.clone()
299 }
300
301 fn read(&self, default_env_name: &str) -> Result<String, HelperErrors> {
303 let pwd = if let Some(pwd_path) = self.get_key() {
304 info!("reading password from cli");
305 fs::read_to_string(pwd_path).map_err(HelperErrors::UnableToReadFromPath)?
306 } else {
307 info!("reading password from env {:?}", default_env_name);
308 env::var(default_env_name).map_err(|_| HelperErrors::UnableToReadPassword)?
309 };
310
311 Ok(pwd)
312 }
313
314 fn read_default(&self) -> Result<String, HelperErrors> {
316 self.read("IDENTITY_PASSWORD")
317 }
318}
319
320#[derive(Debug, Clone, Parser, Default)]
326pub struct NewPasswordArgs {
327 #[clap(
329 short,
330 long,
331 help = "The path to read the new password. If not specified, use the NEW_IDENTITY_PASSWORD environment variable.",
332 value_hint = ValueHint::FilePath,
333 name = "new_password_path",
334 value_name = "NEW_IDENTITY_PASSWORD"
335 )]
336 pub new_password_path: Option<PathBuf>,
337}
338
339impl ArgEnvReader<String, PathBuf> for NewPasswordArgs {
340 fn get_key(&self) -> Option<PathBuf> {
342 self.new_password_path.clone()
343 }
344
345 fn read(&self, default_env_name: &str) -> Result<String, HelperErrors> {
347 let pwd = if let Some(pwd_path) = self.get_key() {
348 info!("reading password from cli");
349 fs::read_to_string(pwd_path).map_err(HelperErrors::UnableToReadFromPath)?
350 } else {
351 info!("reading password from env {:?}", default_env_name);
352 env::var(default_env_name).map_err(|_| HelperErrors::UnableToReadPassword)?
353 };
354
355 Ok(pwd)
356 }
357
358 fn read_default(&self) -> Result<String, HelperErrors> {
360 self.read("NEW_IDENTITY_PASSWORD")
361 }
362}
363
364#[derive(Debug, Clone, Parser, Default)]
366pub struct IdentityFromDirectoryArgs {
367 #[clap(
369 help = "Path to the directory that stores identity files",
370 long,
371 short = 'd',
372 value_hint = ValueHint::DirPath,
373 required = false
374 )]
375 pub identity_directory: Option<String>,
376
377 #[clap(
379 help = "Only use identity files with prefix",
380 long,
381 short = 'x',
382 default_value = None,
383 required = false
384 )]
385 pub identity_prefix: Option<String>,
386}
387
388impl IdentityFromDirectoryArgs {
389 pub fn get_files_from_directory(self) -> Result<Vec<PathBuf>, HelperErrors> {
391 let IdentityFromDirectoryArgs {
392 identity_directory,
393 identity_prefix,
394 } = self;
395 let id_dir = identity_directory.ok_or(HelperErrors::MissingIdentityDirectory)?;
396 debug!(target: "identity_reader_from_directory", "Reading dir {}", &id_dir);
397
398 let directory = fs::read_dir(Path::new(&id_dir))?;
400 let files: Vec<PathBuf> = directory
404 .into_iter() .filter_map(|r| r.ok())
406 .map(|r| r.path()) .filter(|r| r.is_file() && r.to_str().unwrap().contains("id")) .filter(|r| match &identity_prefix {
409 Some(id_prf) => r.file_stem().unwrap().to_str().unwrap().starts_with(id_prf.as_str()),
410 _ => true,
411 })
412 .collect();
413 info!(target: "identity_reader_from_directory", "{} path read from dir", &files.len());
414 Ok(files)
415 }
416}
417
418#[derive(Debug, Clone, Parser, Default)]
420pub struct IdentityFileArgs {
421 #[clap(help = "Get identity file(s) from a directory", flatten)]
423 pub identity_from_directory: Option<IdentityFromDirectoryArgs>,
424
425 #[clap(
427 short,
428 long,
429 help = "The path to an identity file",
430 value_hint = ValueHint::FilePath,
431 name = "identity_from_path"
432 )]
433 pub identity_from_path: Option<PathBuf>,
434
435 #[clap(help = "Password for the identit(ies)", flatten)]
437 pub password: PasswordArgs,
438}
439
440impl IdentityFileArgs {
441 pub fn get_files(self) -> Result<Vec<PathBuf>, HelperErrors> {
443 let IdentityFileArgs {
444 identity_from_directory,
445 identity_from_path,
446 ..
447 } = self;
448 debug!(target: "identity_location_reader", "Read from dir {}, path {}", &identity_from_directory.is_some(), &identity_from_path.is_some());
449
450 let mut files: Vec<PathBuf> = Vec::new();
451 if let Some(id_dir_args) = identity_from_directory {
452 files = id_dir_args.get_files_from_directory()?;
453 };
454 if let Some(id_path) = identity_from_path {
455 debug!(target: "identity_location_reader", "Reading path {}", &id_path.as_path().display().to_string());
456 if id_path.exists() {
457 files.push(id_path);
458 info!(target: "identity_location_reader", "path read from path");
459 } else {
460 error!(target: "identity_location_reader", "Path {} does not exist.", &id_path.as_path().display().to_string());
461 }
462 }
463 Ok(files)
464 }
465
466 pub fn to_addresses(self) -> Result<Vec<Address>, HelperErrors> {
468 let files = self.clone().get_files()?;
469
470 if !files.is_empty() {
472 let pwd = self.password.read_default()?;
474
475 Ok(read_identities(files, &pwd)?
477 .values()
478 .map(|ni| ni.chain_key.public().0.to_address())
479 .collect())
480 } else {
481 Ok(Vec::<Address>::new())
482 }
483 }
484}
485
486#[cfg(test)]
487mod tests {
488 use super::*;
489 use tempfile::tempdir;
490
491 const DUMMY_PRIVATE_KEY: &str = "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";
492 const SPECIAL_ENV_KEY: &str = "59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d";
493
494 #[test]
495 fn read_pk_with_0x() -> anyhow::Result<()> {
496 let private_key_args = PrivateKeyArgs {
497 private_key: Some("0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80".to_string()),
498 };
499 let key = private_key_args.read_default()?;
500
501 let ref_decoded_value = hex::decode(&DUMMY_PRIVATE_KEY)?;
502 println!("ref_decoded_value {:?}", ref_decoded_value);
503
504 assert_eq!(
505 key.public().to_address().to_checksum(),
506 "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
507 "cannot read private key with 0x prefix"
508 );
509 Ok(())
510 }
511
512 #[test]
513 fn create_identities_from_directory_with_id_files() -> anyhow::Result<()> {
514 let _ = env_logger::builder().is_test(true).try_init();
515 let tmp = tempdir()?;
516
517 let path = tmp.path().to_str().unwrap();
518 let pwd = "password_create";
519 match create_identity(path, pwd, &Some(String::from("node1"))) {
520 Ok(_) => assert!(true),
521 _ => assert!(false),
522 }
523 Ok(())
524 }
525
526 #[test]
527 fn read_identity_from_path() -> anyhow::Result<()> {
528 let _ = env_logger::builder().is_test(true).try_init();
529 let tmp = tempdir()?;
530
531 let path = tmp.path().to_str().unwrap();
532 let pwd = "password";
533 let (_, created_id) = create_identity(path, pwd, &None)?;
534
535 let files = get_files(path, &None);
537 assert_eq!(files.len(), 1, "must have one identity file");
538
539 let read_id = read_identity(files[0].as_path(), &pwd)?;
540 assert_eq!(
541 read_id.1.chain_key.public().0.to_address(),
542 created_id.chain_key.public().0.to_address()
543 );
544 Ok(())
545 }
546
547 #[test]
548 fn update_identity_password_at_path() -> anyhow::Result<()> {
549 let _ = env_logger::builder().is_test(true).try_init();
550 let tmp = tempdir()?;
551
552 let path = tmp.path().to_str().unwrap();
553 let pwd = "password";
554 let (_, created_id) = create_identity(path, pwd, &None)?;
555
556 let files = get_files(path, &None);
558 assert_eq!(files.len(), 1, "must have one identity file");
559 let address = created_id.chain_key.public().0.to_address();
560
561 let new_pwd = "supersecured";
562 let (_, returned_key) = update_identity_password(created_id, &files[0].as_path(), new_pwd)?;
563
564 assert_eq!(
566 returned_key.chain_key.public().0.to_address(),
567 address,
568 "returned keys are identical"
569 );
570
571 let (_, read_id) = read_identity(files[0].as_path(), &new_pwd)?;
573 assert_eq!(
574 read_id.chain_key.public().0.to_address(),
575 address,
576 "cannot use the new password to read files"
577 );
578 Ok(())
579 }
580
581 #[test]
582 fn read_identities_from_directory_with_id_files() -> anyhow::Result<()> {
583 let _ = env_logger::builder().is_test(true).try_init();
584 let tmp = tempdir()?;
585
586 let path = tmp.path().to_str().unwrap();
587 let pwd = "password";
588 let (_, created_id) = create_identity(path, pwd, &None)?;
589
590 let files = get_files(path, &None);
592 let read_id = read_identities(files, &pwd.to_string())?;
593 assert_eq!(read_id.len(), 1);
594 assert_eq!(
595 read_id.values().next().unwrap().chain_key.public().0.to_address(),
596 created_id.chain_key.public().0.to_address()
597 );
598
599 debug!("Debug {:#?}", read_id);
601 debug!("Display {}", read_id.values().next().unwrap());
602 Ok(())
603 }
604
605 #[test]
606 fn read_identities_from_directory_with_id_files_but_wrong_password() -> anyhow::Result<()> {
607 let _ = env_logger::builder().is_test(true).try_init();
608 let tmp = tempdir()?;
609
610 let path = tmp.path().to_str().unwrap();
611 let pwd = "password";
612 let wrong_pwd = "wrong_password";
613 create_identity(path, pwd, &None)?;
614 let files = get_files(path, &None);
615 match read_identities(files, &wrong_pwd.to_string()) {
616 Ok(val) => assert_eq!(val.len(), 0),
617 _ => assert!(false),
618 }
619 Ok(())
620 }
621
622 #[test]
623 fn read_identities_from_directory_without_id_files() -> anyhow::Result<()> {
624 let tmp = tempdir()?;
625
626 let path = tmp.path().to_str().unwrap();
627 let files = get_files(path, &None);
628 match read_identities(files, &"".to_string()) {
629 Ok(val) => assert_eq!(val.len(), 0),
630 _ => assert!(false),
631 }
632 Ok(())
633 }
634
635 #[test]
636 fn read_identities_from_tmp_folder() -> anyhow::Result<()> {
637 let _ = env_logger::builder().is_test(true).try_init();
638 let tmp = tempdir()?;
639
640 let path = tmp.path().to_str().unwrap();
641 let pwd = "local";
642 create_identity(path, pwd, &Some("local-alice".into()))?;
643 let files = get_files(path, &None);
644 match read_identities(files, &pwd.to_string()) {
645 Ok(val) => assert_eq!(val.len(), 1),
646 _ => assert!(false),
647 }
648 Ok(())
649 }
650
651 #[test]
652 fn read_identities_from_tmp_folder_with_prefix() -> anyhow::Result<()> {
653 let _ = env_logger::builder().is_test(true).try_init();
654 let tmp = tempdir()?;
655
656 let path = tmp.path().to_str().unwrap();
657 let pwd = "local";
658 create_identity(path, pwd, &Some("local-alice".into()))?;
659 let files = get_files(path, &Some("local".to_string()));
660 match read_identities(files, &pwd.to_string()) {
661 Ok(val) => assert_eq!(val.len(), 1),
662 _ => assert!(false),
663 }
664 Ok(())
665 }
666
667 #[test]
668 fn read_identities_from_tmp_folder_no_match() -> anyhow::Result<()> {
669 let _ = env_logger::builder().is_test(true).try_init();
670 let tmp = tempdir()?;
671
672 let path = tmp.path().to_str().unwrap();
673 let pwd = "local";
674 create_identity(path, pwd, &Some("local-alice".into()))?;
675 let files = get_files(path, &Some("npm-".to_string()));
676 match read_identities(files, &pwd.to_string()) {
677 Ok(val) => assert_eq!(val.len(), 0),
678 _ => assert!(false),
679 }
680 Ok(())
681 }
682
683 #[test]
684 fn read_identities_from_tmp_folder_with_wrong_prefix() -> anyhow::Result<()> {
685 let _ = env_logger::builder().is_test(true).try_init();
686 let tmp = tempdir()?;
687
688 let path = tmp.path().to_str().unwrap();
689 let pwd = "local";
690 create_identity(path, pwd, &Some("local-alice".into()))?;
691
692 let files = get_files(path, &Some("alice".to_string()));
693 match read_identities(files, &pwd.to_string()) {
694 Ok(val) => assert_eq!(val.len(), 0),
695 _ => assert!(false),
696 }
697 Ok(())
698 }
699
700 #[test]
701 fn read_complete_identities_from_tmp_folder() -> anyhow::Result<()> {
702 let _ = env_logger::builder().is_test(true).try_init();
703 let tmp = tempdir()?;
704
705 let path = tmp.path().to_str().unwrap();
706 let name = "alice.id";
707 let pwd = "e2e-test";
708
709 let weak_crypto_alice_keystore = r#"{"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"6084fab56497402930d0833fbc17e7ea"},"ciphertext":"50c0cf2537d7bc0ab6dbb7909d21d3da6445e5bd2cb1236de7efbab33302ddf1dd6a0393c986f8c111fe73a22f36af88858d79d23882a5f991713cb798172069d060f28c680afc28743e8842e8e849ebc21209825e23465afcee52a49f9c4f6734061f91a45b4cc8fbd6b4c95cc4c1b487f0007ed88a1b46b5ebdda616013b3f7ba465f97352b9412e69e6690cee0330c0b25bcf5fc3cdf12e4167336997920df9d6b7d816943ab3817481b9","kdf":"scrypt","kdfparams":{"dklen":32,"n":2,"p":1,"r":8,"salt":"46e30c2d74ba04b881e99fb276ae6a970974499f6abe286a00a69ba774ace095"},"mac":"70dccb366e8ddde13ebeef9a6f35bbc1333176cff3d33a72c925ce23753b34f4"},"id":"b5babdf4-da20-4cc1-9484-58ea24f1b3ae","version":3}"#;
710 let alice_address = "0x838d3c1d2ff5c576d7b270aaaaaa67e619217aac";
712
713 fs::create_dir_all(path)?;
715 fs::write(PathBuf::from(path).join(name), weak_crypto_alice_keystore.as_bytes())?;
717
718 let files = get_files(path, &None);
719 let val = read_identities(files, &pwd.to_string())?;
720 assert_eq!(val.len(), 1);
721 assert_eq!(
722 val.values()
723 .next()
724 .unwrap()
725 .chain_key
726 .public()
727 .0
728 .to_address()
729 .to_string(),
730 alice_address
731 );
732 Ok(())
733 }
734
735 fn get_files(identity_directory: &str, identity_prefix: &Option<String>) -> Vec<PathBuf> {
736 let directory = fs::read_dir(Path::new(identity_directory))
738 .unwrap_or_else(|_| panic!("cannot read directory {}", identity_directory));
739
740 let files: Vec<PathBuf> = directory
744 .into_iter() .filter(|r| r.is_ok()) .map(|r| r.unwrap().path()) .filter(|r| r.is_file()) .filter(|r| r.to_str().unwrap().contains("id")) .filter(|r| match &identity_prefix {
750 Some(identity_prefix) => r
751 .file_stem()
752 .unwrap()
753 .to_str()
754 .unwrap()
755 .starts_with(identity_prefix.as_str()),
756 _ => true,
757 })
758 .collect();
759 files
760 }
761
762 #[test]
763 fn private_key_args_can_read_env_or_cli_args_in_different_scenarios() {
764 let pk_args_none = PrivateKeyArgs { private_key: None };
766 let pk_args_some = PrivateKeyArgs {
767 private_key: Some(DUMMY_PRIVATE_KEY.into()),
768 };
769
770 env::set_var("MANAGER_PK", SPECIAL_ENV_KEY);
772 if let Ok(kp_0) = pk_args_none.clone().read("MANAGER_PK") {
773 assert_eq!(
774 SPECIAL_ENV_KEY,
775 hex::encode(kp_0.secret().as_ref()),
776 "read a wrong private key from env with a special name"
777 );
778 } else {
779 panic!("cannot read private key from env when no cli arg is provied");
780 }
781
782 env::set_var("PRIVATE_KEY", DUMMY_PRIVATE_KEY);
784 if let Ok(kp_1) = pk_args_none.clone().read_default() {
785 assert_eq!(
786 DUMMY_PRIVATE_KEY,
787 hex::encode(kp_1.secret().as_ref()),
788 "read a wrong private key from env"
789 );
790 } else {
791 panic!("cannot read private key from env when no cli arg is provied");
792 }
793
794 env::set_var("PRIVATE_KEY", "0123");
796 if let Ok(kp_2) = pk_args_some.clone().read_default() {
797 assert_eq!(
798 DUMMY_PRIVATE_KEY,
799 hex::encode(kp_2.secret().as_ref()),
800 "read a wrong private key from cli"
801 );
802 } else {
803 panic!("cannot read private key from cli when both are provied");
804 }
805
806 env::remove_var("PRIVATE_KEY");
808
809 if let Ok(kp_3) = pk_args_some.read_default() {
811 assert_eq!(
812 DUMMY_PRIVATE_KEY,
813 hex::encode(kp_3.secret().as_ref()),
814 "read a wrong private key from env"
815 );
816 } else {
817 panic!("cannot read private key from env when no cli arg is provied nor env is set");
818 }
819
820 }
834
835 #[test]
836 fn password_args_can_read_env_or_cli_args_in_different_scenarios() -> anyhow::Result<()> {
837 let tmp = tempdir()?;
838 let path = tmp.path().to_str().unwrap();
839 create_file(path, None, 2)?;
840
841 let pwd_args_some = PasswordArgs {
843 password_path: Some(PathBuf::from(path).join("fileid1")),
844 };
845 let pwd_args_none = PasswordArgs { password_path: None };
846 let new_pwd_args_some = NewPasswordArgs {
847 new_password_path: Some(PathBuf::from(path).join("fileid2")),
848 };
849 let new_pwd_args_none = NewPasswordArgs {
850 new_password_path: None,
851 };
852 fs::write(&PathBuf::from(path).join("fileid2"), "supersound")?;
854
855 env::set_var("NEW_IDENTITY_PASSWORD", "ultraviolet");
857 if let Ok(pwd_0) = new_pwd_args_some.read_default() {
858 assert_eq!(
859 pwd_0,
860 "supersound".to_string(),
861 "read a wrong password from path in new_password_path"
862 );
863 } else {
864 panic!("cannot read new password from path");
865 }
866 if let Ok(pwd_1) = new_pwd_args_none.read_default() {
867 assert_eq!(
868 pwd_1,
869 "ultraviolet".to_string(),
870 "read a wrong password from cli in new_password_path"
871 );
872 } else {
873 panic!("cannot read new password from path");
874 }
875
876 env::set_var("IDENTITY_PASSWORD", "Hello");
877 if let Ok(kp_1) = pwd_args_some.clone().read_default() {
879 assert_eq!(kp_1, "Hello".to_string(), "read a wrong password from env");
880 } else {
881 panic!("cannot read password from env when cli arg is also provied");
882 }
883 if let Ok(kp_2) = pwd_args_none.clone().read_default() {
885 assert_eq!(kp_2, "Hello".to_string(), "read a wrong password from env");
886 } else {
887 panic!("cannot read password from env when no cli arg is provied");
888 }
889
890 env::remove_var("IDENTITY_PASSWORD");
892 assert!(pwd_args_none.read_default().is_err());
893
894 if let Ok(kp_3) = pwd_args_some.clone().read_default() {
896 assert_eq!(kp_3, "Hello".to_string(), "read a wrong password from path");
897 } else {
898 panic!("cannot read password from path when no env is provied");
899 }
900 Ok(())
901 }
902
903 #[test]
904 fn revert_get_dir_from_non_existing_dir() {
905 let path = "./tmp_non_exist";
906
907 let dir_args = IdentityFromDirectoryArgs {
908 identity_directory: Some(path.to_string()),
909 identity_prefix: None,
910 };
911
912 assert!(dir_args.get_files_from_directory().is_err());
913 }
914
915 #[test]
916 fn pass_get_empty_dir_from_existing_dir() -> anyhow::Result<()> {
917 let tmp = tempdir()?;
918 let path = tmp.path().to_str().unwrap();
919 create_file(path, None, 0)?;
920
921 let dir_args = IdentityFromDirectoryArgs {
922 identity_directory: Some(path.to_string()),
923 identity_prefix: None,
924 };
925
926 if let Ok(vp) = dir_args.get_files_from_directory() {
927 assert!(vp.is_empty())
928 } else {
929 panic!("failed to revert when the path contains no file")
930 }
931 Ok(())
932 }
933
934 #[test]
935 fn pass_get_dir_from_existing_dir() -> anyhow::Result<()> {
936 let tmp = tempdir()?;
937 let path = tmp.path().to_str().unwrap();
938 create_file(path, None, 4)?;
939
940 let dir_args = IdentityFromDirectoryArgs {
941 identity_directory: Some(path.to_string()),
942 identity_prefix: None,
943 };
944
945 if let Ok(vp) = dir_args.get_files_from_directory() {
946 assert_eq!(4, vp.len())
947 } else {
948 panic!("failed to get files")
949 }
950 Ok(())
951 }
952
953 #[test]
954 fn pass_get_path_from_existing_path() -> anyhow::Result<()> {
955 let tmp = tempdir()?;
956 let path = tmp.path().to_str().unwrap();
957 create_file(path, None, 4)?;
958
959 let id_path = PathBuf::from(format!("{path}/fileid1"));
960 let path_args: IdentityFileArgs = IdentityFileArgs {
961 identity_from_directory: None,
962 identity_from_path: Some(id_path),
963 password: PasswordArgs { password_path: None },
964 };
965
966 let vp = path_args.get_files()?;
967 assert_eq!(1, vp.len());
968 Ok(())
969 }
970
971 #[test]
972 fn pass_get_files_from_directory_and_path() -> anyhow::Result<()> {
973 let tmp_file = tempdir()?;
975 let path_file = tmp_file.path().to_str().unwrap();
976 create_file(path_file, None, 4)?;
977 let id_path = PathBuf::from(format!("{path_file}/fileid1"));
978
979 let tmp = tempdir()?;
981 let path = tmp.path().to_str().unwrap();
982 create_file(path, None, 4)?;
983
984 let dir_args = IdentityFromDirectoryArgs {
985 identity_directory: Some(path.to_string()),
986 identity_prefix: None,
987 };
988
989 let path_args: IdentityFileArgs = IdentityFileArgs {
990 identity_from_directory: Some(dir_args),
991 identity_from_path: Some(id_path),
992 password: PasswordArgs { password_path: None },
993 };
994
995 let vp = path_args.get_files()?;
996 assert_eq!(5, vp.len());
997
998 Ok(())
999 }
1000
1001 fn create_file(dir_name: &str, prefix: Option<String>, num: u32) -> anyhow::Result<()> {
1002 fs::create_dir_all(dir_name)?;
1004
1005 if num > 0 {
1006 for _n in 1..=num {
1007 let file_name = match prefix {
1008 Some(ref file_prefix) => format!("{file_prefix}{_n}"),
1009 None => format!("fileid{_n}"),
1010 };
1011
1012 let file_path = PathBuf::from(dir_name).join(file_name);
1013 fs::write(&file_path, "Hello")?;
1014 }
1015 }
1016 Ok(())
1017 }
1018}