add some unit tests (#234)

- add unit test for migrating single accounts
- add manifest v1 loading tests
- disable a test
- update comment
- update vscode runners
- add a unit test to make sure migrated maFiles can still be read
This commit is contained in:
Carson McManus 2023-06-26 12:44:14 -04:00 committed by GitHub
parent 46ddaad281
commit da44f49c56
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 236 additions and 239 deletions

8
.vscode/launch.json vendored
View file

@ -30,11 +30,11 @@
"cargo": {
"args": [
"build",
"--bin=steamguard-cli",
"--bin=steamguard",
"--package=steamguard-cli"
],
"filter": {
"name": "steamguard-cli",
"name": "steamguard",
"kind": "bin"
}
},
@ -49,11 +49,11 @@
"args": [
"test",
"--no-run",
"--bin=steamguard-cli",
"--bin=steamguard",
"--package=steamguard-cli"
],
"filter": {
"name": "steamguard-cli",
"name": "steamguard",
"kind": "bin"
}
},

View file

@ -105,7 +105,7 @@ impl AccountManager {
/// Must call `register_loaded_account` after loading the account.
fn load_account(
&self,
account_name: &String,
account_name: impl AsRef<str>,
) -> anyhow::Result<Arc<Mutex<SteamGuardAccount>>, ManifestAccountLoadError> {
let entry = self.get_entry(account_name)?;
self.load_account_by_entry(entry)
@ -234,24 +234,24 @@ impl AccountManager {
#[allow(dead_code)]
pub fn get_entry(
&self,
account_name: &String,
account_name: impl AsRef<str>,
) -> anyhow::Result<&ManifestEntry, ManifestAccountLoadError> {
self.manifest
.entries
.iter()
.find(|e| &e.account_name == account_name)
.find(|e| e.account_name == account_name.as_ref())
.ok_or(ManifestAccountLoadError::MissingManifestEntry)
}
#[allow(dead_code)]
pub fn get_entry_mut(
&mut self,
account_name: &String,
account_name: impl AsRef<str>,
) -> anyhow::Result<&mut ManifestEntry, ManifestAccountLoadError> {
self.manifest
.entries
.iter_mut()
.find(|e| &e.account_name == account_name)
.find(|e| e.account_name == account_name.as_ref())
.ok_or(ManifestAccountLoadError::MissingManifestEntry)
}
@ -263,11 +263,11 @@ impl AccountManager {
/// Fails if the account does not exist in the manifest entries.
pub fn get_account(
&self,
account_name: &String,
account_name: impl AsRef<str>,
) -> anyhow::Result<Arc<Mutex<SteamGuardAccount>>> {
let account = self
.accounts
.get(account_name)
.get(account_name.as_ref())
.cloned()
.ok_or(anyhow!("Account not loaded"));
account
@ -276,13 +276,13 @@ impl AccountManager {
/// Get or load the spcified account.
pub fn get_or_load_account(
&mut self,
account_name: &String,
account_name: impl AsRef<str>,
) -> anyhow::Result<Arc<Mutex<SteamGuardAccount>>, ManifestAccountLoadError> {
let account = self.get_account(account_name);
let account = self.get_account(account_name.as_ref());
if let Ok(account) = account {
return Ok(account);
}
let account = self.load_account(account_name)?;
let account = self.load_account(account_name.as_ref())?;
self.register_loaded_account(account.clone());
Ok(account)
}
@ -467,33 +467,17 @@ mod tests {
assert_eq!(manager.manifest.entries[0].filename, "asdf1234.maFile");
manager.load_accounts()?;
assert_eq!(manager.manifest.entries.len(), manager.accounts.len());
let account_name = "asdf1234".into();
let account_name = "asdf1234";
let account = manager.get_account(account_name)?;
let account = account.lock().unwrap();
assert_eq!(account.account_name, "asdf1234");
assert_eq!(account.revocation_code.expose_secret(), "R12345");
assert_eq!(
manager
.get_account(&account_name)?
.lock()
.unwrap()
.account_name,
"asdf1234"
);
assert_eq!(
manager
.get_account(&account_name)?
.lock()
.unwrap()
.revocation_code
.expose_secret(),
"R12345"
);
assert_eq!(
manager
.get_account(&account_name)?
.lock()
.unwrap()
.shared_secret,
account.shared_secret,
steamguard::token::TwoFactorSecret::parse_shared_secret(
"zvIayp3JPvtvX/QGHqsqKBk/44s=".into()
)?,
)
.unwrap(),
);
Ok(())
}
@ -531,35 +515,19 @@ mod tests {
loaded_manager.manifest.entries.len(),
loaded_manager.accounts.len()
);
let account_name = "asdf1234".into();
let account_name = "asdf1234";
let account = loaded_manager.get_account(account_name)?;
let account = account.lock().unwrap();
assert_eq!(account.account_name, "asdf1234");
assert_eq!(account.revocation_code.expose_secret(), "R12345");
assert_eq!(
loaded_manager
.get_account(&account_name)?
.lock()
.unwrap()
.account_name,
"asdf1234"
);
assert_eq!(
loaded_manager
.get_account(&account_name)?
.lock()
.unwrap()
.revocation_code
.expose_secret(),
"R12345"
);
assert_eq!(
loaded_manager
.get_account(&account_name)?
.lock()
.unwrap()
.shared_secret,
account.shared_secret,
steamguard::token::TwoFactorSecret::parse_shared_secret(
"zvIayp3JPvtvX/QGHqsqKBk/44s=".into()
)
.unwrap(),
);
Ok(())
}
@ -595,30 +563,13 @@ mod tests {
loaded_manager.manifest.entries.len(),
loaded_manager.accounts.len()
);
let account_name = "asdf1234".into();
let account_name = "asdf1234";
let account = loaded_manager.get_account(account_name)?;
let account = account.lock().unwrap();
assert_eq!(account.account_name, "asdf1234");
assert_eq!(account.revocation_code.expose_secret(), "R12345");
assert_eq!(
loaded_manager
.get_account(&account_name)?
.lock()
.unwrap()
.account_name,
"asdf1234"
);
assert_eq!(
loaded_manager
.get_account(&account_name)?
.lock()
.unwrap()
.revocation_code
.expose_secret(),
"R12345"
);
assert_eq!(
loaded_manager
.get_account(&account_name)?
.lock()
.unwrap()
.shared_secret,
account.shared_secret,
steamguard::token::TwoFactorSecret::parse_shared_secret(
"zvIayp3JPvtvX/QGHqsqKBk/44s=".into()
)
@ -660,30 +611,13 @@ mod tests {
loaded_manager.manifest.entries.len(),
loaded_manager.accounts.len()
);
let account_name = "asdf1234".into();
let account_name = "asdf1234";
let account = loaded_manager.get_account(account_name)?;
let account = account.lock().unwrap();
assert_eq!(account.account_name, "asdf1234");
assert_eq!(account.revocation_code.expose_secret(), "R12345");
assert_eq!(
loaded_manager
.get_account(&account_name)?
.lock()
.unwrap()
.account_name,
"asdf1234"
);
assert_eq!(
loaded_manager
.get_account(&account_name)?
.lock()
.unwrap()
.revocation_code
.expose_secret(),
"R12345"
);
assert_eq!(
loaded_manager
.get_account(&account_name)?
.lock()
.unwrap()
.shared_secret,
account.shared_secret,
steamguard::token::TwoFactorSecret::parse_shared_secret(
"zvIayp3JPvtvX/QGHqsqKBk/44s=".into()
)
@ -693,134 +627,45 @@ mod tests {
Ok(())
}
// #[test]
// fn test_sda_compatibility_1() -> anyhow::Result<()> {
// let path = Path::new("src/fixtures/maFiles/compat/1-account/manifest.json");
// assert!(path.is_file());
// let mut manager = AccountManager::load(path)?;
// assert!(matches!(manager.entries.last().unwrap().encryption, None));
// manager.load_accounts()?;
// let account_name = manager.entries.last().unwrap().account_name.clone();
// assert_eq!(
// account_name,
// manager
// .get_account(&account_name)?
// .lock()
// .unwrap()
// .account_name
// );
// Ok(())
// }
// #[test]
// fn test_sda_compatibility_1_encrypted() -> anyhow::Result<()> {
// let path = Path::new("src/fixtures/maFiles/compat/1-account-encrypted/manifest.json");
// assert!(path.is_file());
// let mut manifest = Manifest::load(path)?;
// assert!(matches!(
// manifest.entries.last().unwrap().encryption,
// Some(_)
// ));
// manifest.submit_passkey(Some("password".into()));
// manifest.load_accounts()?;
// let account_name = manifest.entries.last().unwrap().account_name.clone();
// assert_eq!(
// account_name,
// manifest
// .get_account(&account_name)?
// .lock()
// .unwrap()
// .account_name
// );
// Ok(())
// }
// #[test]
// fn test_sda_compatibility_no_webcookie() -> anyhow::Result<()> {
// let path = Path::new("src/fixtures/maFiles/compat/no-webcookie/manifest.json");
// assert!(path.is_file());
// let mut manifest = Manifest::load(path)?;
// assert!(matches!(manifest.entries.last().unwrap().encryption, None));
// assert!(matches!(manifest.load_accounts(), Ok(_)));
// let account_name = manifest.entries.last().unwrap().account_name.clone();
// let account = manifest.get_account(&account_name)?;
// assert_eq!(account_name, account.lock().unwrap().account_name);
// assert_eq!(
// account
// .lock()
// .unwrap()
// .session
// .as_ref()
// .unwrap()
// .expose_secret()
// .web_cookie,
// None
// );
// Ok(())
// }
// #[test]
// fn test_sda_compatibility_2() -> anyhow::Result<()> {
// let path = Path::new("src/fixtures/maFiles/compat/2-account/manifest.json");
// assert!(path.is_file());
// let mut manifest = Manifest::load(path)?;
// assert!(matches!(manifest.entries.last().unwrap().encryption, None));
// manifest.load_accounts()?;
// let account_name = manifest.entries[0].account_name.clone();
// let account = manifest.get_account(&account_name)?;
// assert_eq!(account_name, account.lock().unwrap().account_name);
// assert_eq!(
// account.lock().unwrap().revocation_code.expose_secret(),
// "R12345"
// );
// assert_eq!(
// account
// .lock()
// .unwrap()
// .session
// .as_ref()
// .unwrap()
// .expose_secret()
// .steam_id,
// 1234
// );
// let account_name = manifest.entries[1].account_name.clone();
// let account = manifest.get_account(&account_name)?;
// assert_eq!(account_name, account.lock().unwrap().account_name);
// assert_eq!(
// account.lock().unwrap().revocation_code.expose_secret(),
// "R56789"
// );
// assert_eq!(
// account
// .lock()
// .unwrap()
// .session
// .as_ref()
// .unwrap()
// .expose_secret()
// .steam_id,
// 5678
// );
// Ok(())
// }
// #[cfg(test)]
// mod manifest_upgrades {
// use super::*;
// #[test]
// fn test_missing_account_name() {
// let path = Path::new("src/fixtures/maFiles/compat/missing-account-name/manifest.json");
// assert!(path.is_file());
// let mut manager = AccountManager::load(path).unwrap();
// assert_eq!(manager.entries.len(), 1);
// assert_eq!(manager.entries[0].account_name, "".to_string());
// assert!(manager.is_missing_account_name());
// manager.auto_upgrade().unwrap();
// assert_eq!(manager.entries[0].account_name, "example".to_string());
// }
// }
#[test]
fn should_load_manifest_v1() -> anyhow::Result<()> {
#[derive(Debug)]
struct Test {
manifest: &'static str,
passkey: Option<SecretString>,
}
let cases = vec![
Test {
manifest: "src/fixtures/maFiles/manifest-v1/1-account/manifest.json",
passkey: None,
},
// FIXME: disabled because of #233
// Test {
// manifest: "src/fixtures/maFiles/manifest-v1/1-account-encrypted/manifest.json",
// passkey: Some(SecretString::new("password".into())),
// },
Test {
manifest: "src/fixtures/maFiles/manifest-v1/2-account/manifest.json",
passkey: None,
},
Test {
manifest: "src/fixtures/maFiles/manifest-v1/missing-account-name/manifest.json",
passkey: None,
},
];
for case in cases {
eprintln!("testing: {:?}", case);
let mut manager = AccountManager::load(Path::new(case.manifest))?;
manager.submit_passkey(case.passkey.clone());
manager.load_accounts()?;
assert_eq!(manager.manifest.version, CURRENT_MANIFEST_VERSION);
assert_eq!(manager.manifest.entries[0].account_name, "example");
assert_eq!(manager.manifest.entries[0].steam_id, 1234);
let account = manager.get_account("example").unwrap();
let account = account.lock().unwrap();
assert_eq!(account.account_name, "example");
assert_eq!(account.steam_id, 1234);
}
Ok(())
}
}

View file

@ -263,7 +263,7 @@ pub fn load_and_upgrade_sda_account(path: &Path) -> anyhow::Result<SteamGuardAcc
#[cfg(test)]
mod tests {
use crate::accountmanager::CURRENT_MANIFEST_VERSION;
use crate::{accountmanager::CURRENT_MANIFEST_VERSION, AccountManager};
use super::*;
@ -311,4 +311,129 @@ mod tests {
}
Ok(())
}
#[test]
fn should_migrate_single_accounts() -> anyhow::Result<()> {
#[derive(Debug)]
struct Test {
mafile: &'static str,
account_name: &'static str,
steam_id: u64,
}
let cases = vec![
Test {
mafile: "src/fixtures/maFiles/compat/1-account/1234.maFile",
account_name: "example",
steam_id: 1234,
},
Test {
mafile: "src/fixtures/maFiles/compat/2-account/1234.maFile",
account_name: "example",
steam_id: 1234,
},
Test {
mafile: "src/fixtures/maFiles/compat/2-account/5678.maFile",
account_name: "example2",
steam_id: 5678,
},
Test {
mafile: "src/fixtures/maFiles/compat/missing-account-name/1234.maFile",
account_name: "example",
steam_id: 1234,
},
Test {
mafile: "src/fixtures/maFiles/compat/no-webcookie/nowebcookie.maFile",
account_name: "example",
steam_id: 1234,
},
Test {
mafile: "src/fixtures/maFiles/compat/null-oauthtoken/nulloauthtoken.maFile",
account_name: "example",
steam_id: 1234,
},
];
for case in cases {
eprintln!("testing: {:?}", case);
let account = load_and_upgrade_sda_account(Path::new(case.mafile))?;
assert_eq!(account.account_name, case.account_name);
assert_eq!(account.steam_id, case.steam_id);
}
Ok(())
}
#[test]
fn should_migrate_to_latest_version_save_and_load_again() -> anyhow::Result<()> {
#[derive(Debug)]
struct Test {
dir: &'static str,
passkey: Option<SecretString>,
}
let cases = vec![
Test {
dir: "src/fixtures/maFiles/compat/1-account/",
passkey: None,
},
// FIXME: disabled because of #233
// Test {
// manifest: "src/fixtures/maFiles/compat/1-account-encrypted/",
// passkey: Some(SecretString::new("password".into())),
// },
Test {
dir: "src/fixtures/maFiles/compat/2-account/",
passkey: None,
},
Test {
dir: "src/fixtures/maFiles/compat/missing-account-name/",
passkey: None,
},
Test {
dir: "src/fixtures/maFiles/compat/no-webcookie/",
passkey: None,
},
Test {
dir: "src/fixtures/maFiles/compat/null-oauthtoken/",
passkey: None,
},
];
for case in cases {
eprintln!("testing: {:?}", case);
let temp = tempdir::TempDir::new("steamguard-cli-test")?;
for file in std::fs::read_dir(case.dir)? {
let file = file?;
let path = file.path();
eprintln!("copying {:?}", path);
let dest = temp.path().join(path.file_name().unwrap());
eprintln!("to {:?}", dest);
std::fs::copy(&path, dest)?;
}
let (manifest, accounts) = do_migrate(
Path::join(temp.path(), "manifest.json").as_path(),
case.passkey.as_ref(),
)?;
assert_eq!(manifest.version, CURRENT_MANIFEST_VERSION);
assert_eq!(manifest.entries[0].account_name, "example");
assert_eq!(manifest.entries[0].steam_id, 1234);
assert_eq!(accounts[0].account_name, "example");
assert_eq!(accounts[0].steam_id, 1234);
let mut manager =
AccountManager::from_manifest(manifest, temp.path().to_str().unwrap().to_owned());
manager.submit_passkey(case.passkey.clone());
manager.register_accounts(accounts);
manager.save()?;
let path = Path::join(temp.path(), "manifest.json");
let mut manager = AccountManager::load(path.as_path())?;
manager.submit_passkey(case.passkey.clone());
manager.load_accounts()?;
let account = manager.get_or_load_account("example")?;
let account = account.lock().unwrap();
assert_eq!(account.account_name, "example");
assert_eq!(account.steam_id, 1234);
}
Ok(())
}
}

View file

@ -60,7 +60,7 @@ pub trait EntryEncryptor {
pub struct LegacySdaCompatible;
impl LegacySdaCompatible {
const PBKDF2_ITERATIONS: u32 = 50000; // This is excessive, but necessary to maintain compatibility with SteamDesktopAuthenticator.
const PBKDF2_ITERATIONS: u32 = 50000; // This is necessary to maintain compatibility with SteamDesktopAuthenticator.
const KEY_SIZE_BYTES: usize = 32;
fn get_encryption_key(passkey: &str, salt: &str) -> anyhow::Result<[u8; Self::KEY_SIZE_BYTES]> {

View file

@ -0,0 +1 @@
Z0HJDSN9EuFOpKEeBzftCWxTsh0sV6QQriLTVrn37FyGNaXhGgzeHlvPfHgkXKCYbALTgx/B2fLh1CEojKO1/eqEgN+982CadR3EXk+vH1k5AMuGhMXPpsEeIh27ltxrdAEzWdlPlAyentBgOKlTCoN6iF+EZVORvp2pPaMrebyHi8/5Y+XC3HrMgfgmP7lFGpUgZK8f0mKB/pGaW+0/3oVikggBK3MIWlh4s9bC9LlMy5H+oU0n/Iu3P9dpbko1bDMKIbUKEPzS3wHXyQRg32zPIfONR0bswb7QTfAhoKixZrAenQluX3lXRL0JFafNEPzUY4r/DJ1pIMLK9cEvbzqwsPth6jrIZRd+zvgnshfNnGLCblYkPo4fGwePuhX2W2w6qgFMpo69rkSp1Zz6JKC/gH9YyL4a8N768ml9H1so5XBm7eB+fMRIL7bHof+V1CxGXX3z1RvjGRHPwKcrKLvffxTTs/dBHb9UDFtTprk=

View file

@ -0,0 +1 @@
ZQqJk4KW7pb5bndkra244z3ttIks58UplODn5IgljrOK3bKORSwrnZYb9Iv6YsirmrT0hQ3tx381GhQu4Nyj/PFHHCfFTrduaCoMLRrHDsmsexOW93Yo02acHXNfPSvxtjGfZpsuIZVlhVy8JDH/ESXp88cKn2zqjXQWu6pLnah1YPlpZuTfArw69+Em7V1OH1CoKnsYuhCo/x4u7fXhMNgDlRBvDbO4enGzaixonPu9er5Zp6iNEeuAUqmD0DHASygNmzBhUBHv8Avng8YKbvu611yQVT2KybnIoL+Q11Y36GoFhWskmG3lLwh/1OlReGwJ1iX0lDDthoel/Ygj6EC2+wkR8V7eMQf48R15xdBILYTzcrjtiuLCr9MBc/HM/ToEa3QCGwGkXvshR/meJ1BiqaRARKfvJcj4eMSpiUvhDe1QFXXjfXRdetJcknyJ8Pv6v10G/OV3ELYwdx2dYL5C+Ao4qj9QCjoD8bb/juCjtZoSxMncbm4T7ORbXs/Ulx+TEuOUmRAjxr+zaWzzO7ZfYJMhIPz+LSixKpaVmxP89DEK5LJ1T6jA50QmPft6AbOMuoq99haWH9lMgqrIfBB+ZNNHSEE9PwMUxhX/TQP3oJgmrnZcMV4nBOcovbWM5s+odu2mvzoAcRMHVxEztASbMBwdJ7amJvoRRrXTtp642FQHAe8pFPlW14x1hShXAO4YfYkmmAhLqlujammuQ6bRg7XjBvh0zE88i5UBRiRsH+MV35u3c0ciugUmbrAZ0u9Yfv19MNSMVNMsREDuxQ==

View file

@ -0,0 +1 @@
{"version":1,"entries":[{"filename":"1234.maFile","steam_id":1234,"account_name":"example","encryption":{"iv":"ifChnv66eA+/dYqGsQMIOA==","salt":"O2K8FAOWK9c=","scheme":"LegacySdaCompatible"}}]}

View file

@ -0,0 +1,17 @@
{
"encrypted": true,
"first_run": true,
"entries": [
{
"encryption_iv": "ifChnv66eA+/dYqGsQMIOA==",
"encryption_salt": "O2K8FAOWK9c=",
"filename": "1234.maFile",
"steamid": 1234
}
],
"periodic_checking": false,
"periodic_checking_interval": 5,
"periodic_checking_checkall": false,
"auto_confirm_market_transactions": false,
"auto_confirm_trades": false
}

View file

@ -0,0 +1 @@
{"account_name":"example","steam_id":1234,"serial_number":"kljasfhds","revocation_code":"R12345","shared_secret":"zvIayp3JPvtvX/QGHqsqKBk/44s=","token_gid":"jkkjlhkhjgf","identity_secret":"kjsdlwowiqe=","uri":"otpauth://totp/Steam:example?secret=ASDF&issuer=Steam","device_id":"android:99d2ad0e-4bad-4247-b111-26393aae0be3","secret_1":"sklduhfgsdlkjhf=","tokens":null}

View file

@ -0,0 +1 @@
{"version":1,"entries":[{"filename":"1234.maFile","steam_id":1234,"account_name":"example","encryption":null}]}

View file

@ -0,0 +1 @@
{"account_name":"example","steam_id":1234,"serial_number":"kljasfhds","revocation_code":"R12345","shared_secret":"zvIayp3JPvtvX/QGHqsqKBk/44s=","token_gid":"jkkjlhkhjgf","identity_secret":"kjsdlwowiqe=","uri":"otpauth://totp/Steam:example?secret=ASDF&issuer=Steam","device_id":"android:99d2ad0e-4bad-4247-b111-26393aae0be3","secret_1":"sklduhfgsdlkjhf=","tokens":null}

View file

@ -0,0 +1 @@
{"account_name":"example2","steam_id":5678,"serial_number":"kljasfhds","revocation_code":"R56789","shared_secret":"zvIayp3JPvtvX/QGHqsqKBk/44s=","token_gid":"jkkjlhkhjgf","identity_secret":"kjsdlwowiqe=","uri":"otpauth://totp/Steam:example?secret=ASDF&issuer=Steam","device_id":"android:99d2ad0e-4bad-4247-b111-26393aae0be3","secret_1":"sklduhfgsdlkjhf=","tokens":null}

View file

@ -0,0 +1 @@
{"version":1,"entries":[{"filename":"1234.maFile","steam_id":1234,"account_name":"example","encryption":null},{"filename":"5678.maFile","steam_id":5678,"account_name":"example2","encryption":null}]}

View file

@ -0,0 +1 @@
{"account_name":"example","steam_id":1234,"serial_number":"kljasfhds","revocation_code":"R12345","shared_secret":"zvIayp3JPvtvX/QGHqsqKBk/44s=","token_gid":"jkkjlhkhjgf","identity_secret":"kjsdlwowiqe=","uri":"otpauth://totp/Steam:example?secret=ASDF&issuer=Steam","device_id":"android:99d2ad0e-4bad-4247-b111-26393aae0be3","secret_1":"sklduhfgsdlkjhf=","tokens":null}

View file

@ -0,0 +1 @@
{"version":1,"entries":[{"filename":"1234.maFile","steam_id":1234,"account_name":"example","encryption":null}]}