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