diff --git a/Cargo.lock b/Cargo.lock index c07e3376d..298377883 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -730,6 +730,7 @@ name = "ethstore" version = "0.1.0" dependencies = [ "docopt 0.6.80 (registry+https://github.com/rust-lang/crates.io-index)", + "ethcore-devtools 1.6.0", "ethcore-util 1.6.0", "ethcrypto 0.1.0", "ethkey 0.2.0", diff --git a/ethcore/src/account_provider/mod.rs b/ethcore/src/account_provider/mod.rs index 46690fabd..95b2ad855 100755 --- a/ethcore/src/account_provider/mod.rs +++ b/ethcore/src/account_provider/mod.rs @@ -280,7 +280,7 @@ impl AccountProvider { /// Returns each account along with name and meta. pub fn account_meta(&self, address: Address) -> Result { - let account = StoreAccountRef::root(address); + let account = self.sstore.account_ref(&address)?; Ok(AccountMeta { name: self.sstore.name(&account)?, meta: self.sstore.meta(&account)?, @@ -290,38 +290,38 @@ impl AccountProvider { /// Returns each account along with name and meta. pub fn set_account_name(&self, address: Address, name: String) -> Result<(), Error> { - self.sstore.set_name(&StoreAccountRef::root(address), name)?; + self.sstore.set_name(&self.sstore.account_ref(&address)?, name)?; Ok(()) } /// Returns each account along with name and meta. pub fn set_account_meta(&self, address: Address, meta: String) -> Result<(), Error> { - self.sstore.set_meta(&StoreAccountRef::root(address), meta)?; + self.sstore.set_meta(&self.sstore.account_ref(&address)?, meta)?; Ok(()) } /// Returns `true` if the password for `account` is `password`. `false` if not. pub fn test_password(&self, address: &Address, password: &str) -> Result { - self.sstore.test_password(&StoreAccountRef::root(address.clone()), password) + self.sstore.test_password(&self.sstore.account_ref(&address)?, password) .map_err(Into::into) } /// Permanently removes an account. pub fn kill_account(&self, address: &Address, password: &str) -> Result<(), Error> { - self.sstore.remove_account(&StoreAccountRef::root(address.clone()), &password)?; + self.sstore.remove_account(&self.sstore.account_ref(&address)?, &password)?; Ok(()) } /// Changes the password of `account` from `password` to `new_password`. Fails if incorrect `password` given. - pub fn change_password(&self, account: &Address, password: String, new_password: String) -> Result<(), Error> { - self.sstore.change_password(&StoreAccountRef::root(account.clone()), &password, &new_password) + pub fn change_password(&self, address: &Address, password: String, new_password: String) -> Result<(), Error> { + self.sstore.change_password(&self.sstore.account_ref(address)?, &password, &new_password) } /// Helper method used for unlocking accounts. fn unlock_account(&self, address: Address, password: String, unlock: Unlock) -> Result<(), Error> { // verify password by signing dump message // result may be discarded - let account = StoreAccountRef::root(address); + let account = self.sstore.account_ref(&address)?; let _ = self.sstore.sign(&account, &password, &Default::default())?; // check if account is already unlocked pernamently, if it is, do nothing @@ -374,20 +374,21 @@ impl AccountProvider { /// Checks if given account is unlocked pub fn is_unlocked(&self, address: Address) -> bool { let unlocked = self.unlocked.read(); - let account = StoreAccountRef::root(address); - unlocked.get(&account).is_some() + self.sstore.account_ref(&address) + .map(|r| unlocked.get(&r).is_some()) + .unwrap_or(false) } /// Signs the message. If password is not provided the account must be unlocked. pub fn sign(&self, address: Address, password: Option, message: Message) -> Result { - let account = StoreAccountRef::root(address); + let account = self.sstore.account_ref(&address)?; let password = password.map(Ok).unwrap_or_else(|| self.password(&account))?; Ok(self.sstore.sign(&account, &password, &message)?) } /// Signs given message with supplied token. Returns a token to use in next signing within this session. pub fn sign_with_token(&self, address: Address, token: AccountToken, message: Message) -> Result<(Signature, AccountToken), SignError> { - let account = StoreAccountRef::root(address); + let account = self.sstore.account_ref(&address)?; let is_std_password = self.sstore.test_password(&account, &token)?; let new_token = random_string(16); @@ -410,7 +411,7 @@ impl AccountProvider { pub fn decrypt_with_token(&self, address: Address, token: AccountToken, shared_mac: &[u8], message: &[u8]) -> Result<(Vec, AccountToken), SignError> { - let account = StoreAccountRef::root(address); + let account = self.sstore.account_ref(&address)?; let is_std_password = self.sstore.test_password(&account, &token)?; let new_token = random_string(16); @@ -431,7 +432,7 @@ impl AccountProvider { /// Decrypts a message. If password is not provided the account must be unlocked. pub fn decrypt(&self, address: Address, password: Option, shared_mac: &[u8], message: &[u8]) -> Result, SignError> { - let account = StoreAccountRef::root(address); + let account = self.sstore.account_ref(&address)?; let password = password.map(Ok).unwrap_or_else(|| self.password(&account))?; Ok(self.sstore.decrypt(&account, &password, shared_mac, message)?) } @@ -447,6 +448,51 @@ impl AccountProvider { .map(|a| a.into_iter().map(|a| a.address).collect()) .map_err(Into::into) } + + /// Create new vault. + pub fn create_vault(&self, name: &str, password: &str) -> Result<(), Error> { + self.sstore.create_vault(name, password) + .map_err(Into::into) + } + + /// Open existing vault. + pub fn open_vault(&self, name: &str, password: &str) -> Result<(), Error> { + self.sstore.open_vault(name, password) + .map_err(Into::into) + } + + /// Close previously opened vault. + pub fn close_vault(&self, name: &str) -> Result<(), Error> { + self.sstore.close_vault(name) + .map_err(Into::into) + } + + /// List all vaults + pub fn list_vaults(&self) -> Result, Error> { + self.sstore.list_vaults() + .map_err(Into::into) + } + + /// List all currently opened vaults + pub fn list_opened_vaults(&self) -> Result, Error> { + self.sstore.list_opened_vaults() + .map_err(Into::into) + } + + /// Change vault password. + pub fn change_vault_password(&self, name: &str, new_password: &str) -> Result<(), Error> { + self.sstore.change_vault_password(name, new_password) + .map_err(Into::into) + } + + /// Change vault of the given address. + pub fn change_vault(&self, address: Address, new_vault: &str) -> Result<(), Error> { + let new_vault_ref = if new_vault.is_empty() { SecretVaultRef::Root } else { SecretVaultRef::Vault(new_vault.to_owned()) }; + let old_account_ref = self.sstore.account_ref(&address)?; + self.sstore.change_account_vault(new_vault_ref, old_account_ref) + .map_err(Into::into) + .map(|_| ()) + } } #[cfg(test)] diff --git a/ethstore/Cargo.toml b/ethstore/Cargo.toml index 56830dfd2..091080348 100755 --- a/ethstore/Cargo.toml +++ b/ethstore/Cargo.toml @@ -23,6 +23,7 @@ parking_lot = "0.3" ethcrypto = { path = "../ethcrypto" } ethcore-util = { path = "../util" } smallvec = "0.3.1" +ethcore-devtools = { path = "../devtools" } [build-dependencies] serde_codegen = { version = "0.8", optional = true } diff --git a/ethstore/src/dir/disk.rs b/ethstore/src/dir/disk.rs index b1b3ecdd5..f78dba288 100755 --- a/ethstore/src/dir/disk.rs +++ b/ethstore/src/dir/disk.rs @@ -21,7 +21,7 @@ use time; use {json, SafeAccount, Error}; use json::Uuid; use super::{KeyDirectory, VaultKeyDirectory, VaultKeyDirectoryProvider, VaultKey}; -use super::vault::VaultDiskDirectory; +use super::vault::{VAULT_FILE_NAME, VaultDiskDirectory}; const IGNORED_FILES: &'static [&'static str] = &[ "thumbs.db", @@ -193,7 +193,7 @@ impl KeyDirectory for DiskDirectory where T: KeyFileManager { // and find entry with given address let to_remove = self.files()? .into_iter() - .find(|&(_, ref acc)| acc == account); + .find(|&(_, ref acc)| acc.id == account.id && acc.address == account.address); // remove it match to_remove { @@ -219,6 +219,21 @@ impl VaultKeyDirectoryProvider for DiskDirectory where T: KeyFileManager { let vault_dir = VaultDiskDirectory::at(&self.path, name, key)?; Ok(Box::new(vault_dir)) } + + fn list_vaults(&self) -> Result, Error> { + Ok(fs::read_dir(&self.path)? + .filter_map(|e| e.ok().map(|e| e.path())) + .filter_map(|path| { + let mut vault_file_path = path.clone(); + vault_file_path.push(VAULT_FILE_NAME); + if vault_file_path.is_file() { + path.file_name().and_then(|f| f.to_str()).map(|f| f.to_owned()) + } else { + None + } + }) + .collect()) + } } impl KeyFileManager for DiskKeyFileManager { @@ -240,6 +255,7 @@ mod test { use dir::{KeyDirectory, VaultKey}; use account::SafeAccount; use ethkey::{Random, Generator}; + use devtools::RandomTempPath; #[test] fn should_create_new_account() { @@ -295,4 +311,20 @@ mod test { // cleanup let _ = fs::remove_dir_all(dir); } + + #[test] + fn should_list_vaults() { + // given + let temp_path = RandomTempPath::new(); + let directory = RootDiskDirectory::create(&temp_path).unwrap(); + let vault_provider = directory.as_vault_provider().unwrap(); + vault_provider.create("vault1", VaultKey::new("password1", 1)).unwrap(); + vault_provider.create("vault2", VaultKey::new("password2", 1)).unwrap(); + + // then + let vaults = vault_provider.list_vaults().unwrap(); + assert_eq!(vaults.len(), 2); + assert!(vaults.iter().any(|v| &*v == "vault1")); + assert!(vaults.iter().any(|v| &*v == "vault2")); + } } diff --git a/ethstore/src/dir/mod.rs b/ethstore/src/dir/mod.rs index fc35b740b..356fb5b07 100755 --- a/ethstore/src/dir/mod.rs +++ b/ethstore/src/dir/mod.rs @@ -70,6 +70,8 @@ pub trait VaultKeyDirectoryProvider { fn create(&self, name: &str, key: VaultKey) -> Result, Error>; /// Open existing vault with given key fn open(&self, name: &str, key: VaultKey) -> Result, Error>; + /// List all vaults + fn list_vaults(&self) -> Result, Error>; } /// Vault directory @@ -78,8 +80,10 @@ pub trait VaultKeyDirectory: KeyDirectory { fn as_key_directory(&self) -> &KeyDirectory; /// Vault name fn name(&self) -> &str; + /// Get vault key + fn key(&self) -> VaultKey; /// Set new key for vault - fn set_key(&self, old_key: VaultKey, key: VaultKey) -> Result<(), SetKeyError>; + fn set_key(&self, key: VaultKey) -> Result<(), SetKeyError>; } pub use self::disk::RootDiskDirectory; diff --git a/ethstore/src/dir/vault.rs b/ethstore/src/dir/vault.rs index df67d9fe4..c068388f3 100755 --- a/ethstore/src/dir/vault.rs +++ b/ethstore/src/dir/vault.rs @@ -22,13 +22,15 @@ use super::super::account::Crypto; use super::{KeyDirectory, VaultKeyDirectory, VaultKey, SetKeyError}; use super::disk::{DiskDirectory, KeyFileManager}; -const VAULT_FILE_NAME: &'static str = "vault.json"; +/// Name of vault metadata file +pub const VAULT_FILE_NAME: &'static str = "vault.json"; /// Vault directory implementation pub type VaultDiskDirectory = DiskDirectory; /// Vault key file manager pub struct VaultKeyFileManager { + name: String, key: VaultKey, } @@ -48,7 +50,7 @@ impl VaultDiskDirectory { return Err(err); } - Ok(DiskDirectory::new(vault_dir_path, VaultKeyFileManager::new(key))) + Ok(DiskDirectory::new(vault_dir_path, VaultKeyFileManager::new(name, key))) } /// Open existing vault directory with given key @@ -62,7 +64,7 @@ impl VaultDiskDirectory { // check that passed key matches vault file check_vault_file(&vault_dir_path, &key)?; - Ok(DiskDirectory::new(vault_dir_path, VaultKeyFileManager::new(key))) + Ok(DiskDirectory::new(vault_dir_path, VaultKeyFileManager::new(name, key))) } fn create_temp_vault(&self, key: VaultKey) -> Result { @@ -84,12 +86,10 @@ impl VaultDiskDirectory { } } - fn copy_to_vault(&self, vault: &VaultDiskDirectory, vault_key: &VaultKey) -> Result<(), Error> { - let password = &self.key_manager().key.password; + fn copy_to_vault(&self, vault: &VaultDiskDirectory) -> Result<(), Error> { for account in self.load()? { let filename = account.filename.clone().expect("self is instance of DiskDirectory; DiskDirectory fills filename in load; qed"); - let new_account = account.change_password(password, &vault_key.password, vault_key.iterations)?; - vault.insert_with_filename(new_account, filename)?; + vault.insert_with_filename(account, filename)?; } Ok(()) @@ -107,19 +107,14 @@ impl VaultKeyDirectory for VaultDiskDirectory { } fn name(&self) -> &str { - self.path() - .expect("self is instance of DiskDirectory; DiskDirectory always returns path; qed") - .file_name() - .expect("last component of path is checked in make_vault_dir_path; it contains no fs-specific characters; file_name only returns None if last component is fs-specific; qed") - .to_str() - .expect("last component of path is checked in make_vault_dir_path; it contains only valid unicode characters; to_str fails when file_name is not valid unicode; qed") + &self.key_manager().name } - fn set_key(&self, key: VaultKey, new_key: VaultKey) -> Result<(), SetKeyError> { - if self.key_manager().key != key { - return Err(SetKeyError::NonFatalOld(Error::InvalidPassword)); - } + fn key(&self) -> VaultKey { + self.key_manager().key.clone() + } + fn set_key(&self, new_key: VaultKey) -> Result<(), SetKeyError> { let temp_vault = VaultDiskDirectory::create_temp_vault(self, new_key.clone()).map_err(|err| SetKeyError::NonFatalOld(err))?; let mut source_path = temp_vault.path().expect("temp_vault is instance of DiskDirectory; DiskDirectory always returns path; qed").clone(); let mut target_path = self.path().expect("self is instance of DiskDirectory; DiskDirectory always returns path; qed").clone(); @@ -127,7 +122,7 @@ impl VaultKeyDirectory for VaultDiskDirectory { source_path.push("next"); target_path.push("next"); - let temp_accounts = self.copy_to_vault(&temp_vault, &new_key) + let temp_accounts = self.copy_to_vault(&temp_vault) .and_then(|_| temp_vault.load()) .map_err(|err| { // ignore error, as we already processing error @@ -153,8 +148,9 @@ impl VaultKeyDirectory for VaultDiskDirectory { } impl VaultKeyFileManager { - pub fn new(key: VaultKey) -> Self { + pub fn new(name: &str, key: VaultKey) -> Self { VaultKeyFileManager { + name: name.into(), key: key, } } @@ -163,20 +159,16 @@ impl VaultKeyFileManager { impl KeyFileManager for VaultKeyFileManager { fn read(&self, filename: Option, reader: T) -> Result where T: io::Read { let vault_file = json::VaultKeyFile::load(reader).map_err(|e| Error::Custom(format!("{:?}", e)))?; - let safe_account = SafeAccount::from_vault_file(&self.key.password, vault_file, filename.clone())?; - if !safe_account.check_password(&self.key.password) { - warn!("Invalid vault key file: {:?}", filename); - return Err(Error::InvalidPassword); - } + let mut safe_account = SafeAccount::from_vault_file(&self.key.password, vault_file, filename.clone())?; + safe_account.meta = json::insert_vault_name_to_json_meta(&safe_account.meta, &self.name) + .map_err(|err| Error::Custom(format!("{:?}", err)))?; Ok(safe_account) } - fn write(&self, account: SafeAccount, writer: &mut T) -> Result<(), Error> where T: io::Write { - // all accounts share the same password - if !account.check_password(&self.key.password) { - return Err(Error::InvalidPassword); - } + fn write(&self, mut account: SafeAccount, writer: &mut T) -> Result<(), Error> where T: io::Write { + account.meta = json::remove_vault_name_from_json_meta(&account.meta) + .map_err(|err| Error::Custom(format!("{:?}", err)))?; let vault_file: json::VaultKeyFile = account.into_vault_file(self.key.iterations, &self.key.password)?; vault_file.write(writer).map_err(|e| Error::Custom(format!("{:?}", e))) @@ -243,10 +235,12 @@ fn check_vault_file

(vault_dir_path: P, key: &VaultKey) -> Result<(), Error> w #[cfg(test)] mod test { - use std::{env, fs}; + use std::fs; use std::io::Write; + use std::path::PathBuf; use dir::VaultKey; use super::{VAULT_FILE_NAME, check_vault_name, make_vault_dir_path, create_vault_file, check_vault_file, VaultDiskDirectory}; + use devtools::RandomTempPath; #[test] fn check_vault_name_succeeds() { @@ -282,10 +276,9 @@ mod test { #[test] fn create_vault_file_succeeds() { // given + let temp_path = RandomTempPath::new(); let key = VaultKey::new("password", 1024); - let mut dir = env::temp_dir(); - dir.push("create_vault_file_succeeds"); - let mut vault_dir = dir.clone(); + let mut vault_dir: PathBuf = temp_path.as_path().into(); vault_dir.push("vault"); fs::create_dir_all(&vault_dir).unwrap(); @@ -297,20 +290,16 @@ mod test { let mut vault_file_path = vault_dir.clone(); vault_file_path.push(VAULT_FILE_NAME); assert!(vault_file_path.exists() && vault_file_path.is_file()); - - // cleanup - let _ = fs::remove_dir_all(dir); } #[test] fn check_vault_file_succeeds() { // given + let temp_path = RandomTempPath::create_dir(); let key = VaultKey::new("password", 1024); let vault_file_contents = r#"{"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"758696c8dc6378ab9b25bb42790da2f5"},"ciphertext":"54eb50683717d41caaeb12ea969f2c159daada5907383f26f327606a37dc7168","kdf":"pbkdf2","kdfparams":{"c":1024,"dklen":32,"prf":"hmac-sha256","salt":"3c320fa566a1a7963ac8df68a19548d27c8f40bf92ef87c84594dcd5bbc402b6"},"mac":"9e5c2314c2a0781962db85611417c614bd6756666b6b1e93840f5b6ed895f003"}}"#; - let mut dir = env::temp_dir(); - dir.push("check_vault_file_succeeds"); - fs::create_dir_all(&dir).unwrap(); - let mut vault_file_path = dir.clone(); + let dir: PathBuf = temp_path.as_path().into(); + let mut vault_file_path: PathBuf = dir.clone(); vault_file_path.push(VAULT_FILE_NAME); { let mut vault_file = fs::File::create(vault_file_path).unwrap(); @@ -322,20 +311,16 @@ mod test { // then assert!(result.is_ok()); - - // cleanup - let _ = fs::remove_dir_all(dir); } #[test] fn check_vault_file_fails() { // given + let temp_path = RandomTempPath::create_dir(); let key = VaultKey::new("password1", 1024); - let mut dir = env::temp_dir(); - dir.push("check_vault_file_fails"); - let mut vault_file_path = dir.clone(); + let dir: PathBuf = temp_path.as_path().into(); + let mut vault_file_path: PathBuf = dir.clone(); vault_file_path.push(VAULT_FILE_NAME); - fs::create_dir_all(&dir).unwrap(); // when let result = check_vault_file(&dir, &key); @@ -355,17 +340,14 @@ mod test { // then assert!(result.is_err()); - - // cleanup - let _ = fs::remove_dir_all(dir); } #[test] fn vault_directory_can_be_created() { // given + let temp_path = RandomTempPath::new(); let key = VaultKey::new("password", 1024); - let mut dir = env::temp_dir(); - dir.push("vault_directory_can_be_created"); + let dir: PathBuf = temp_path.as_path().into(); // when let vault = VaultDiskDirectory::create(&dir, "vault", key.clone()); @@ -378,17 +360,14 @@ mod test { // then assert!(vault.is_ok()); - - // cleanup - let _ = fs::remove_dir_all(dir); } #[test] fn vault_directory_cannot_be_created_if_already_exists() { // given + let temp_path = RandomTempPath::new(); let key = VaultKey::new("password", 1024); - let mut dir = env::temp_dir(); - dir.push("vault_directory_cannot_be_created_if_already_exists"); + let dir: PathBuf = temp_path.as_path().into(); let mut vault_dir = dir.clone(); vault_dir.push("vault"); fs::create_dir_all(&vault_dir).unwrap(); @@ -398,25 +377,19 @@ mod test { // then assert!(vault.is_err()); - - // cleanup - let _ = fs::remove_dir_all(dir); } #[test] fn vault_directory_cannot_be_opened_if_not_exists() { // given + let temp_path = RandomTempPath::create_dir(); let key = VaultKey::new("password", 1024); - let mut dir = env::temp_dir(); - dir.push("vault_directory_cannot_be_opened_if_not_exists"); + let dir: PathBuf = temp_path.as_path().into(); // when let vault = VaultDiskDirectory::at(&dir, "vault", key); // then assert!(vault.is_err()); - - // cleanup - let _ = fs::remove_dir_all(dir); } } diff --git a/ethstore/src/ethstore.rs b/ethstore/src/ethstore.rs index add5be129..01ff5004d 100755 --- a/ethstore/src/ethstore.rs +++ b/ethstore/src/ethstore.rs @@ -54,6 +54,10 @@ impl SimpleSecretStore for EthStore { self.store.insert_account(vault, secret, password) } + fn account_ref(&self, address: &Address) -> Result { + self.store.account_ref(address) + } + fn accounts(&self) -> Result, Error> { self.store.accounts() } @@ -88,8 +92,20 @@ impl SimpleSecretStore for EthStore { self.store.close_vault(name) } - fn change_vault_password(&self, name: &str, password: &str, new_password: &str) -> Result<(), Error> { - self.store.change_vault_password(name, password, new_password) + fn list_vaults(&self) -> Result, Error> { + self.store.list_vaults() + } + + fn list_opened_vaults(&self) -> Result, Error> { + self.store.list_opened_vaults() + } + + fn change_vault_password(&self, name: &str, new_password: &str) -> Result<(), Error> { + self.store.change_vault_password(name, new_password) + } + + fn change_account_vault(&self, vault: SecretVaultRef, account: StoreAccountRef) -> Result { + self.store.change_account_vault(vault, account) } } @@ -121,12 +137,6 @@ impl SecretStore for EthStore { Ok(()) } - fn move_account(&self, new_store: &SimpleSecretStore, new_vault: SecretVaultRef, account: &StoreAccountRef, password: &str, new_password: &str) -> Result<(), Error> { - self.copy_account(new_store, new_vault, account, password, new_password)?; - self.remove_account(account, password)?; - Ok(()) - } - fn public(&self, account: &StoreAccountRef, password: &str) -> Result { let account = self.get(account)?; account.public(password) @@ -296,6 +306,32 @@ impl EthMultiStore { } + fn remove_safe_account(&self, account_ref: &StoreAccountRef, account: &SafeAccount) -> Result<(), Error> { + // Remove from dir + match account_ref.vault { + SecretVaultRef::Root => self.dir.remove(&account)?, + SecretVaultRef::Vault(ref vault_name) => self.vaults.lock().get(vault_name).ok_or(Error::VaultNotFound)?.remove(&account)?, + }; + + // Remove from cache + let mut cache = self.cache.write(); + let is_empty = { + if let Some(accounts) = cache.get_mut(account_ref) { + if let Some(position) = accounts.iter().position(|acc| acc == account) { + accounts.remove(position); + } + accounts.is_empty() + } else { + false + } + }; + + if is_empty { + cache.remove(account_ref); + } + + return Ok(()); + } } impl SimpleSecretStore for EthMultiStore { @@ -306,6 +342,14 @@ impl SimpleSecretStore for EthMultiStore { self.import(vault, account) } + fn account_ref(&self, address: &Address) -> Result { + self.reload_accounts()?; + self.cache.read().keys() + .find(|r| &r.address == address) + .cloned() + .ok_or(Error::InvalidAccount) + } + fn accounts(&self) -> Result, Error> { self.reload_accounts()?; Ok(self.cache.read().keys().cloned().collect()) @@ -320,50 +364,20 @@ impl SimpleSecretStore for EthMultiStore { continue; } - // Remove from dir - match account_ref.vault { - SecretVaultRef::Root => self.dir.remove(&account)?, - SecretVaultRef::Vault(ref vault_name) => self.vaults.lock().get(vault_name).ok_or(Error::VaultNotFound)?.remove(&account)?, - }; - - // Remove from cache - let mut cache = self.cache.write(); - let is_empty = { - if let Some(accounts) = cache.get_mut(account_ref) { - if let Some(position) = accounts.iter().position(|acc| acc == &account) { - accounts.remove(position); - } - accounts.is_empty() - } else { - false - } - }; - - if is_empty { - cache.remove(account_ref); - } - - return Ok(()); + return self.remove_safe_account(account_ref, &account); } Err(Error::InvalidPassword) } fn change_password(&self, account_ref: &StoreAccountRef, old_password: &str, new_password: &str) -> Result<(), Error> { - match account_ref.vault { - SecretVaultRef::Root => { - let accounts = self.get(account_ref)?; + let accounts = self.get(account_ref)?; - for account in accounts { - // Change password - let new_account = account.change_password(old_password, new_password, self.iterations)?; - self.update(account_ref, account, new_account)?; - } - Ok(()) - }, - SecretVaultRef::Vault(ref vault_name) => { - self.change_vault_password(vault_name, old_password, new_password) - }, + for account in accounts { + // Change password + let new_account = account.change_password(old_password, new_password, self.iterations)?; + self.update(account_ref, account, new_account)?; } + Ok(()) } fn sign(&self, account: &StoreAccountRef, password: &str, message: &Message) -> Result { @@ -435,10 +449,20 @@ impl SimpleSecretStore for EthMultiStore { Ok(()) } - fn change_vault_password(&self, name: &str, password: &str, new_password: &str) -> Result<(), Error> { + fn list_vaults(&self) -> Result, Error> { let vault_provider = self.dir.as_vault_provider().ok_or(Error::VaultsAreNotSupported)?; - let vault = vault_provider.open(name, VaultKey::new(password, self.iterations))?; - match vault.set_key(VaultKey::new(password, self.iterations), VaultKey::new(new_password, self.iterations)) { + vault_provider.list_vaults() + } + + fn list_opened_vaults(&self) -> Result, Error> { + Ok(self.vaults.lock().keys().cloned().collect()) + } + + fn change_vault_password(&self, name: &str, new_password: &str) -> Result<(), Error> { + let old_key = self.vaults.lock().get(name).map(|v| v.key()).ok_or(Error::VaultNotFound)?; + let vault_provider = self.dir.as_vault_provider().ok_or(Error::VaultsAreNotSupported)?; + let vault = vault_provider.open(name, old_key)?; + match vault.set_key(VaultKey::new(new_password, self.iterations)) { Ok(_) => { self.close_vault(name) .and_then(|_| self.open_vault(name, new_password)) @@ -446,7 +470,7 @@ impl SimpleSecretStore for EthMultiStore { Err(SetKeyError::Fatal(err)) => { let _ = self.close_vault(name); Err(err) - } + }, Err(SetKeyError::NonFatalNew(err)) => { let _ = self.close_vault(name) .and_then(|_| self.open_vault(name, new_password)); @@ -455,17 +479,28 @@ impl SimpleSecretStore for EthMultiStore { Err(SetKeyError::NonFatalOld(err)) => Err(err), } } + + fn change_account_vault(&self, vault: SecretVaultRef, account_ref: StoreAccountRef) -> Result { + if account_ref.vault == vault { + return Ok(account_ref); + } + + let account = self.get(&account_ref)?.into_iter().nth(0).ok_or(Error::InvalidAccount)?; + let new_account_ref = self.import(vault, account.clone())?; + self.remove_safe_account(&account_ref, &account)?; + self.reload_accounts()?; + Ok(new_account_ref) + } } #[cfg(test)] mod tests { - use std::{env, fs}; - use std::path::PathBuf; use dir::{KeyDirectory, MemoryDirectory, RootDiskDirectory}; use ethkey::{Random, Generator, KeyPair}; use secret_store::{SimpleSecretStore, SecretStore, SecretVaultRef, StoreAccountRef}; use super::{EthStore, EthMultiStore}; + use devtools::RandomTempPath; fn keypair() -> KeyPair { Random.generate().unwrap() @@ -481,26 +516,17 @@ mod tests { struct RootDiskDirectoryGuard { pub key_dir: Option>, - path: Option, + _path: RandomTempPath, } impl RootDiskDirectoryGuard { - pub fn new(test_name: &str) -> Self { - let mut path = env::temp_dir(); - path.push(test_name); - fs::create_dir_all(&path).unwrap(); + pub fn new() -> Self { + let temp_path = RandomTempPath::new(); + let disk_dir = Box::new(RootDiskDirectory::create(temp_path.as_path()).unwrap()); RootDiskDirectoryGuard { - key_dir: Some(Box::new(RootDiskDirectory::create(&path).unwrap())), - path: Some(path), - } - } - } - - impl Drop for RootDiskDirectoryGuard { - fn drop(&mut self) { - if let Some(path) = self.path.take() { - let _ = fs::remove_dir_all(path); + key_dir: Some(disk_dir), + _path: temp_path, } } } @@ -606,7 +632,7 @@ mod tests { #[test] fn should_create_and_open_vaults() { // given - let mut dir = RootDiskDirectoryGuard::new("should_create_and_open_vaults"); + let mut dir = RootDiskDirectoryGuard::new(); let store = EthStore::open(dir.key_dir.take().unwrap()).unwrap(); let name1 = "vault1"; let password1 = "password1"; let name2 = "vault2"; let password2 = "password2"; @@ -660,7 +686,7 @@ mod tests { #[test] fn should_move_vault_acounts() { // given - let mut dir = RootDiskDirectoryGuard::new("should_move_vault_acounts"); + let mut dir = RootDiskDirectoryGuard::new(); let store = EthStore::open(dir.key_dir.take().unwrap()).unwrap(); let name1 = "vault1"; let password1 = "password1"; let name2 = "vault2"; let password2 = "password2"; @@ -677,72 +703,42 @@ mod tests { let account3 = store.insert_account(SecretVaultRef::Root, keypair3.secret().clone(), password3).unwrap(); // then - store.move_account(&store, SecretVaultRef::Root, &account1, password1, password2).unwrap(); - store.move_account(&store, SecretVaultRef::Vault(name2.to_owned()), &account2, password1, password2).unwrap(); - store.move_account(&store, SecretVaultRef::Vault(name2.to_owned()), &account3, password3, password2).unwrap(); + let account1 = store.change_account_vault(SecretVaultRef::Root, account1.clone()).unwrap(); + let account2 = store.change_account_vault(SecretVaultRef::Vault(name2.to_owned()), account2.clone()).unwrap(); + let account3 = store.change_account_vault(SecretVaultRef::Vault(name2.to_owned()), account3).unwrap(); let accounts = store.accounts().unwrap(); assert_eq!(accounts.len(), 3); assert!(accounts.iter().any(|a| a == &StoreAccountRef::root(account1.address.clone()))); assert!(accounts.iter().any(|a| a == &StoreAccountRef::vault(name2, account2.address.clone()))); assert!(accounts.iter().any(|a| a == &StoreAccountRef::vault(name2, account3.address.clone()))); + + // and then + assert_eq!(store.meta(&StoreAccountRef::root(account1.address)).unwrap(), r#"{}"#); + assert_eq!(store.meta(&StoreAccountRef::vault("vault2", account2.address)).unwrap(), r#"{"vault":"vault2"}"#); + assert_eq!(store.meta(&StoreAccountRef::vault("vault2", account3.address)).unwrap(), r#"{"vault":"vault2"}"#); } #[test] fn should_not_remove_account_when_moving_to_self() { // given - let mut dir = RootDiskDirectoryGuard::new("should_not_remove_account_when_moving_to_self"); + let mut dir = RootDiskDirectoryGuard::new(); let store = EthStore::open(dir.key_dir.take().unwrap()).unwrap(); let password1 = "password1"; let keypair1 = keypair(); // when let account1 = store.insert_account(SecretVaultRef::Root, keypair1.secret().clone(), password1).unwrap(); - store.move_account(&store, SecretVaultRef::Root, &account1, password1, password1).unwrap(); + store.change_account_vault(SecretVaultRef::Root, account1).unwrap(); // then let accounts = store.accounts().unwrap(); assert_eq!(accounts.len(), 1); } - #[test] - fn should_not_move_account_when_vault_password_incorrect() { - // given - let mut dir = RootDiskDirectoryGuard::new("should_not_move_account_when_vault_password_incorrect"); - let store = EthStore::open(dir.key_dir.take().unwrap()).unwrap(); - let name1 = "vault1"; let password1 = "password1"; - let name2 = "vault2"; let password2 = "password2"; - let keypair1 = keypair(); - - // when - store.create_vault(name1, password1).unwrap(); - store.create_vault(name2, password2).unwrap(); - let account1 = store.insert_account(SecretVaultRef::Vault(name1.to_owned()), keypair1.secret().clone(), password1).unwrap(); - - // then - store.move_account(&store, SecretVaultRef::Root, &account1, password2, password1).unwrap_err(); - store.move_account(&store, SecretVaultRef::Vault(name2.to_owned()), &account1, password1, password1).unwrap_err(); - } - - #[test] - fn should_not_insert_account_when_vault_password_incorrect() { - // given - let mut dir = RootDiskDirectoryGuard::new("should_not_insert_account_when_vault_password_incorrect"); - let store = EthStore::open(dir.key_dir.take().unwrap()).unwrap(); - let name1 = "vault1"; let password1 = "password1"; - let password2 = "password2"; - let keypair1 = keypair(); - - // when - store.create_vault(name1, password1).unwrap(); - - // then - store.insert_account(SecretVaultRef::Vault(name1.to_owned()), keypair1.secret().clone(), password2).unwrap_err(); - } - #[test] fn should_remove_account_from_vault() { // given - let mut dir = RootDiskDirectoryGuard::new("should_remove_account_from_vault"); + let mut dir = RootDiskDirectoryGuard::new(); let store = EthStore::open(dir.key_dir.take().unwrap()).unwrap(); let name1 = "vault1"; let password1 = "password1"; let keypair1 = keypair(); @@ -760,7 +756,7 @@ mod tests { #[test] fn should_not_remove_account_from_vault_when_password_is_incorrect() { // given - let mut dir = RootDiskDirectoryGuard::new("should_not_remove_account_from_vault_when_password_is_incorrect"); + let mut dir = RootDiskDirectoryGuard::new(); let store = EthStore::open(dir.key_dir.take().unwrap()).unwrap(); let name1 = "vault1"; let password1 = "password1"; let password2 = "password2"; @@ -779,7 +775,7 @@ mod tests { #[test] fn should_change_vault_password() { // given - let mut dir = RootDiskDirectoryGuard::new("should_change_vault_password"); + let mut dir = RootDiskDirectoryGuard::new(); let store = EthStore::open(dir.key_dir.take().unwrap()).unwrap(); let name = "vault"; let password = "password"; let keypair = keypair(); @@ -791,9 +787,7 @@ mod tests { // then assert_eq!(store.accounts().unwrap().len(), 1); let new_password = "new_password"; - store.change_vault_password(name, "bad_password", new_password).unwrap_err(); - assert_eq!(store.accounts().unwrap().len(), 1); - store.change_vault_password(name, password, new_password).unwrap(); + store.change_vault_password(name, new_password).unwrap(); assert_eq!(store.accounts().unwrap().len(), 1); // and when @@ -803,4 +797,46 @@ mod tests { store.open_vault(name, new_password).unwrap(); assert_eq!(store.accounts().unwrap().len(), 1); } + + #[test] + fn should_have_different_passwords_for_vault_secret_and_meta() { + // given + let mut dir = RootDiskDirectoryGuard::new(); + let store = EthStore::open(dir.key_dir.take().unwrap()).unwrap(); + let name = "vault"; let password = "password"; + let secret_password = "sec_password"; + let keypair = keypair(); + + // when + store.create_vault(name, password).unwrap(); + let account_ref = store.insert_account(SecretVaultRef::Vault(name.to_owned()), keypair.secret().clone(), secret_password).unwrap(); + + // then + assert_eq!(store.accounts().unwrap().len(), 1); + let new_secret_password = "new_sec_password"; + store.change_password(&account_ref, secret_password, new_secret_password).unwrap(); + assert_eq!(store.accounts().unwrap().len(), 1); + } + + #[test] + fn should_list_opened_vaults() { + // given + let mut dir = RootDiskDirectoryGuard::new(); + let store = EthStore::open(dir.key_dir.take().unwrap()).unwrap(); + let name1 = "vault1"; let password1 = "password1"; + let name2 = "vault2"; let password2 = "password2"; + let name3 = "vault3"; let password3 = "password3"; + + // when + store.create_vault(name1, password1).unwrap(); + store.create_vault(name2, password2).unwrap(); + store.create_vault(name3, password3).unwrap(); + store.close_vault(name2).unwrap(); + + // then + let opened_vaults = store.list_opened_vaults().unwrap(); + assert_eq!(opened_vaults.len(), 2); + assert!(opened_vaults.iter().any(|v| &*v == name1)); + assert!(opened_vaults.iter().any(|v| &*v == name3)); + } } diff --git a/ethstore/src/json/mod.rs.in b/ethstore/src/json/mod.rs.in index 8c4495825..2cec82877 100755 --- a/ethstore/src/json/mod.rs.in +++ b/ethstore/src/json/mod.rs.in @@ -21,6 +21,6 @@ pub use self::kdf::{Kdf, KdfSer, Prf, Pbkdf2, Scrypt, KdfSerParams}; pub use self::key_file::KeyFile; pub use self::presale::{PresaleWallet, Encseed}; pub use self::vault_file::VaultFile; -pub use self::vault_key_file::{VaultKeyFile, VaultKeyMeta}; +pub use self::vault_key_file::{VaultKeyFile, VaultKeyMeta, insert_vault_name_to_json_meta, remove_vault_name_from_json_meta}; pub use self::version::Version; diff --git a/ethstore/src/json/vault_key_file.rs b/ethstore/src/json/vault_key_file.rs index dce0e6d99..83745bf1b 100755 --- a/ethstore/src/json/vault_key_file.rs +++ b/ethstore/src/json/vault_key_file.rs @@ -18,8 +18,13 @@ use std::io::{Read, Write}; use serde::{Deserialize, Deserializer, Error}; use serde::de::{Visitor, MapVisitor}; use serde_json; +use serde_json::value::Value; +use serde_json::error; use super::{Uuid, Version, Crypto, H160}; +/// Meta key name for vault field +const VAULT_NAME_META_KEY: &'static str = "vault"; + /// Key file as stored in vaults #[derive(Debug, PartialEq, Serialize)] pub struct VaultKeyFile { @@ -27,9 +32,9 @@ pub struct VaultKeyFile { pub id: Uuid, /// Key version pub version: Version, - /// Encrypted secret + /// Secret, encrypted with account password pub crypto: Crypto, - /// Encrypted serialized `VaultKeyMeta` + /// Serialized `VaultKeyMeta`, encrypted with vault password pub metacrypto: Crypto, } @@ -44,6 +49,38 @@ pub struct VaultKeyMeta { pub meta: Option, } +/// Insert vault name to the JSON meta field +pub fn insert_vault_name_to_json_meta(meta: &str, vault_name: &str) -> Result { + let mut meta = if meta.is_empty() { + Value::Object(serde_json::Map::new()) + } else { + serde_json::from_str(meta)? + }; + + if let Some(meta_obj) = meta.as_object_mut() { + meta_obj.insert(VAULT_NAME_META_KEY.to_owned(), Value::String(vault_name.to_owned())); + serde_json::to_string(meta_obj) + } else { + Err(error::Error::custom("Meta is expected to be a serialized JSON object")) + } +} + +/// Remove vault name from the JSON meta field +pub fn remove_vault_name_from_json_meta(meta: &str) -> Result { + let mut meta = if meta.is_empty() { + Value::Object(serde_json::Map::new()) + } else { + serde_json::from_str(meta)? + }; + + if let Some(meta_obj) = meta.as_object_mut() { + meta_obj.remove(VAULT_NAME_META_KEY); + serde_json::to_string(meta_obj) + } else { + Err(error::Error::custom("Meta is expected to be a serialized JSON object")) + } +} + enum VaultKeyFileField { Id, Version, @@ -244,7 +281,8 @@ impl VaultKeyMeta { #[cfg(test)] mod test { use serde_json; - use json::{VaultKeyFile, Version, Crypto, Cipher, Aes128Ctr, Kdf, Pbkdf2, Prf}; + use json::{VaultKeyFile, Version, Crypto, Cipher, Aes128Ctr, Kdf, Pbkdf2, Prf, + insert_vault_name_to_json_meta, remove_vault_name_from_json_meta}; #[test] fn to_and_from_json() { @@ -284,4 +322,28 @@ mod test { assert_eq!(file, deserialized); } + + #[test] + fn vault_name_inserted_to_json_meta() { + assert_eq!(insert_vault_name_to_json_meta(r#""#, "MyVault").unwrap(), r#"{"vault":"MyVault"}"#); + assert_eq!(insert_vault_name_to_json_meta(r#"{"tags":["kalabala"]}"#, "MyVault").unwrap(), r#"{"tags":["kalabala"],"vault":"MyVault"}"#); + } + + #[test] + fn vault_name_not_inserted_to_json_meta() { + assert!(insert_vault_name_to_json_meta(r#"///3533"#, "MyVault").is_err()); + assert!(insert_vault_name_to_json_meta(r#""string""#, "MyVault").is_err()); + } + + #[test] + fn vault_name_removed_from_json_meta() { + assert_eq!(remove_vault_name_from_json_meta(r#"{"vault":"MyVault"}"#).unwrap(), r#"{}"#); + assert_eq!(remove_vault_name_from_json_meta(r#"{"tags":["kalabala"],"vault":"MyVault"}"#).unwrap(), r#"{"tags":["kalabala"]}"#); + } + + #[test] + fn vault_name_not_removed_from_json_meta() { + assert!(remove_vault_name_from_json_meta(r#"///3533"#).is_err()); + assert!(remove_vault_name_from_json_meta(r#""string""#).is_err()); + } } diff --git a/ethstore/src/lib.rs b/ethstore/src/lib.rs index 3b92b439c..33327a01d 100755 --- a/ethstore/src/lib.rs +++ b/ethstore/src/lib.rs @@ -28,6 +28,7 @@ extern crate rustc_serialize; extern crate crypto as rcrypto; extern crate tiny_keccak; extern crate parking_lot; +extern crate ethcore_devtools as devtools; // reexport it nicely extern crate ethkey as _ethkey; diff --git a/ethstore/src/secret_store.rs b/ethstore/src/secret_store.rs index 7042f434a..57cba259e 100755 --- a/ethstore/src/secret_store.rs +++ b/ethstore/src/secret_store.rs @@ -47,6 +47,9 @@ pub trait SimpleSecretStore: Send + Sync { fn decrypt(&self, account: &StoreAccountRef, password: &str, shared_mac: &[u8], message: &[u8]) -> Result, Error>; fn accounts(&self) -> Result, Error>; + /// Get reference to some account with given address. + /// This method could be removed if we will guarantee that there is max(1) account for given address. + fn account_ref(&self, address: &Address) -> Result; /// Create new vault with given password fn create_vault(&self, name: &str, password: &str) -> Result<(), Error>; @@ -54,15 +57,20 @@ pub trait SimpleSecretStore: Send + Sync { fn open_vault(&self, name: &str, password: &str) -> Result<(), Error>; /// Close vault fn close_vault(&self, name: &str) -> Result<(), Error>; + /// List all vaults + fn list_vaults(&self) -> Result, Error>; + /// List all currently opened vaults + fn list_opened_vaults(&self) -> Result, Error>; /// Change vault password - fn change_vault_password(&self, name: &str, password: &str, new_password: &str) -> Result<(), Error>; + fn change_vault_password(&self, name: &str, new_password: &str) -> Result<(), Error>; + /// Cnage account' vault + fn change_account_vault(&self, vault: SecretVaultRef, account: StoreAccountRef) -> Result; } pub trait SecretStore: SimpleSecretStore { fn import_presale(&self, vault: SecretVaultRef, json: &[u8], password: &str) -> Result; fn import_wallet(&self, vault: SecretVaultRef, json: &[u8], password: &str) -> Result; fn copy_account(&self, new_store: &SimpleSecretStore, new_vault: SecretVaultRef, account: &StoreAccountRef, password: &str, new_password: &str) -> Result<(), Error>; - fn move_account(&self, new_store: &SimpleSecretStore, new_vault: SecretVaultRef, account: &StoreAccountRef, password: &str, new_password: &str) -> Result<(), Error>; fn test_password(&self, account: &StoreAccountRef, password: &str) -> Result; fn public(&self, account: &StoreAccountRef, password: &str) -> Result; diff --git a/rpc/src/v1/impls/parity_accounts.rs b/rpc/src/v1/impls/parity_accounts.rs index 1034b1df5..000e3c9eb 100644 --- a/rpc/src/v1/impls/parity_accounts.rs +++ b/rpc/src/v1/impls/parity_accounts.rs @@ -201,6 +201,53 @@ impl ParityAccounts for ParityAccountsClient { Ok(into_vec(store.list_geth_accounts(false))) } + + fn create_vault(&self, name: String, password: String) -> Result { + take_weak!(self.accounts) + .create_vault(&name, &password) + .map_err(|e| errors::account("Could not create vault.", e)) + .map(|_| true) + } + + fn open_vault(&self, name: String, password: String) -> Result { + take_weak!(self.accounts) + .open_vault(&name, &password) + .map_err(|e| errors::account("Could not open vault.", e)) + .map(|_| true) + } + + fn close_vault(&self, name: String) -> Result { + take_weak!(self.accounts) + .close_vault(&name) + .map_err(|e| errors::account("Could not close vault.", e)) + .map(|_| true) + } + + fn list_vaults(&self) -> Result, Error> { + take_weak!(self.accounts) + .list_vaults() + .map_err(|e| errors::account("Could not list vaults.", e)) + } + + fn list_opened_vaults(&self) -> Result, Error> { + take_weak!(self.accounts) + .list_opened_vaults() + .map_err(|e| errors::account("Could not list vaults.", e)) + } + + fn change_vault_password(&self, name: String, new_password: String) -> Result { + take_weak!(self.accounts) + .change_vault_password(&name, &new_password) + .map_err(|e| errors::account("Could not change vault password.", e)) + .map(|_| true) + } + + fn change_vault(&self, address: RpcH160, new_vault: String) -> Result { + take_weak!(self.accounts) + .change_vault(address.into(), &new_vault) + .map_err(|e| errors::account("Could not change vault.", e)) + .map(|_| true) + } } fn into_vec(a: Vec) -> Vec where diff --git a/rpc/src/v1/tests/mocked/parity_accounts.rs b/rpc/src/v1/tests/mocked/parity_accounts.rs index e245cb92f..3f329ca2b 100644 --- a/rpc/src/v1/tests/mocked/parity_accounts.rs +++ b/rpc/src/v1/tests/mocked/parity_accounts.rs @@ -17,6 +17,9 @@ use std::sync::Arc; use ethcore::account_provider::AccountProvider; +use ethstore::EthStore; +use ethstore::dir::RootDiskDirectory; +use devtools::RandomTempPath; use jsonrpc_core::IoHandler; use v1::{ParityAccounts, ParityAccountsClient}; @@ -30,21 +33,33 @@ fn accounts_provider() -> Arc { Arc::new(AccountProvider::transient_provider()) } -fn setup() -> ParityAccountsTester { - let accounts = accounts_provider(); - let parity_accounts = ParityAccountsClient::new(&accounts); +fn accounts_provider_with_vaults_support(temp_path: &str) -> Arc { + let root_keys_dir = RootDiskDirectory::create(temp_path).unwrap(); + let secret_store = EthStore::open(Box::new(root_keys_dir)).unwrap(); + Arc::new(AccountProvider::new(Box::new(secret_store))) +} +fn setup_with_accounts_provider(accounts_provider: Arc) -> ParityAccountsTester { + let parity_accounts = ParityAccountsClient::new(&accounts_provider); let mut io = IoHandler::default(); io.extend_with(parity_accounts.to_delegate()); let tester = ParityAccountsTester { - accounts: accounts, + accounts: accounts_provider, io: io, }; tester } +fn setup() -> ParityAccountsTester { + setup_with_accounts_provider(accounts_provider()) +} + +fn setup_with_vaults_support(temp_path: &str) -> ParityAccountsTester { + setup_with_accounts_provider(accounts_provider_with_vaults_support(temp_path)) +} + #[test] fn should_be_able_to_get_account_info() { let tester = setup(); @@ -217,3 +232,122 @@ fn should_be_able_to_remove_address() { let response = r#"{"jsonrpc":"2.0","result":{},"id":4}"#; assert_eq!(res, Some(response.into())); } + +#[test] +fn rpc_parity_new_vault() { + let temp_path = RandomTempPath::new(); + let tester = setup_with_vaults_support(temp_path.as_str()); + + let request = r#"{"jsonrpc": "2.0", "method": "parity_newVault", "params":["vault1", "password1"], "id": 1}"#; + let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; + + assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); + assert!(tester.accounts.close_vault("vault1").is_ok()); + assert!(tester.accounts.open_vault("vault1", "password1").is_ok()); +} + +#[test] +fn rpc_parity_open_vault() { + let temp_path = RandomTempPath::new(); + let tester = setup_with_vaults_support(temp_path.as_str()); + + assert!(tester.accounts.create_vault("vault1", "password1").is_ok()); + assert!(tester.accounts.close_vault("vault1").is_ok()); + + let request = r#"{"jsonrpc": "2.0", "method": "parity_openVault", "params":["vault1", "password1"], "id": 1}"#; + let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; + + assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); +} + +#[test] +fn rpc_parity_close_vault() { + let temp_path = RandomTempPath::new(); + let tester = setup_with_vaults_support(temp_path.as_str()); + + assert!(tester.accounts.create_vault("vault1", "password1").is_ok()); + + let request = r#"{"jsonrpc": "2.0", "method": "parity_closeVault", "params":["vault1"], "id": 1}"#; + let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; + + assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); +} + +#[test] +fn rpc_parity_change_vault_password() { + let temp_path = RandomTempPath::new(); + let tester = setup_with_vaults_support(temp_path.as_str()); + + assert!(tester.accounts.create_vault("vault1", "password1").is_ok()); + + let request = r#"{"jsonrpc": "2.0", "method": "parity_changeVaultPassword", "params":["vault1", "password2"], "id": 1}"#; + let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; + + assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); +} + +#[test] +fn rpc_parity_change_vault() { + let temp_path = RandomTempPath::new(); + let tester = setup_with_vaults_support(temp_path.as_str()); + + let (address, _) = tester.accounts.new_account_and_public("root_password").unwrap(); + assert!(tester.accounts.create_vault("vault1", "password1").is_ok()); + + let request = format!(r#"{{"jsonrpc": "2.0", "method": "parity_changeVault", "params":["0x{}", "vault1"], "id": 1}}"#, address.hex()); + let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; + + assert_eq!(tester.io.handle_request_sync(&request), Some(response.to_owned())); +} + +#[test] +fn rpc_parity_vault_adds_vault_field_to_acount_meta() { + let temp_path = RandomTempPath::new(); + let tester = setup_with_vaults_support(temp_path.as_str()); + + let (address1, _) = tester.accounts.new_account_and_public("root_password1").unwrap(); + let uuid1 = tester.accounts.account_meta(address1.clone()).unwrap().uuid.unwrap(); + assert!(tester.accounts.create_vault("vault1", "password1").is_ok()); + assert!(tester.accounts.change_vault(address1, "vault1").is_ok()); + + let request = r#"{"jsonrpc": "2.0", "method": "parity_allAccountsInfo", "params":[], "id": 1}"#; + let response = format!(r#"{{"jsonrpc":"2.0","result":{{"0x{}":{{"meta":"{{\"vault\":\"vault1\"}}","name":"","uuid":"{}"}}}},"id":1}}"#, address1.hex(), uuid1); + + assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); +} + +#[test] +fn rpc_parity_list_vaults() { + let temp_path = RandomTempPath::new(); + let tester = setup_with_vaults_support(temp_path.as_str()); + + assert!(tester.accounts.create_vault("vault1", "password1").is_ok()); + assert!(tester.accounts.create_vault("vault2", "password2").is_ok()); + + let request = r#"{"jsonrpc": "2.0", "method": "parity_listVaults", "params":[], "id": 1}"#; + let response1 = r#"{"jsonrpc":"2.0","result":["vault1","vault2"],"id":1}"#; + let response2 = r#"{"jsonrpc":"2.0","result":["vault2","vault1"],"id":1}"#; + + let actual_response = tester.io.handle_request_sync(request); + assert!(actual_response == Some(response1.to_owned()) + || actual_response == Some(response2.to_owned())); +} + +#[test] +fn rpc_parity_list_opened_vaults() { + let temp_path = RandomTempPath::new(); + let tester = setup_with_vaults_support(temp_path.as_str()); + + assert!(tester.accounts.create_vault("vault1", "password1").is_ok()); + assert!(tester.accounts.create_vault("vault2", "password2").is_ok()); + assert!(tester.accounts.create_vault("vault3", "password3").is_ok()); + assert!(tester.accounts.close_vault("vault2").is_ok()); + + let request = r#"{"jsonrpc": "2.0", "method": "parity_listOpenedVaults", "params":[], "id": 1}"#; + let response1 = r#"{"jsonrpc":"2.0","result":["vault1","vault3"],"id":1}"#; + let response2 = r#"{"jsonrpc":"2.0","result":["vault3","vault1"],"id":1}"#; + + let actual_response = tester.io.handle_request_sync(request); + assert!(actual_response == Some(response1.to_owned()) + || actual_response == Some(response2.to_owned())); +} diff --git a/rpc/src/v1/traits/parity_accounts.rs b/rpc/src/v1/traits/parity_accounts.rs index 595a33740..c8c37964d 100644 --- a/rpc/src/v1/traits/parity_accounts.rs +++ b/rpc/src/v1/traits/parity_accounts.rs @@ -105,5 +105,33 @@ build_rpc_trait! { /// Returns the accounts available for importing from Geth. #[rpc(name = "parity_listGethAccounts")] fn geth_accounts(&self) -> Result, Error>; + + /// Create new vault. + #[rpc(name = "parity_newVault")] + fn create_vault(&self, String, String) -> Result; + + /// Open existing vault. + #[rpc(name = "parity_openVault")] + fn open_vault(&self, String, String) -> Result; + + /// Close previously opened vault. + #[rpc(name = "parity_closeVault")] + fn close_vault(&self, String) -> Result; + + /// List all vaults. + #[rpc(name = "parity_listVaults")] + fn list_vaults(&self) -> Result, Error>; + + /// List all currently opened vaults. + #[rpc(name = "parity_listOpenedVaults")] + fn list_opened_vaults(&self) -> Result, Error>; + + /// Change vault password. + #[rpc(name = "parity_changeVaultPassword")] + fn change_vault_password(&self, String, String) -> Result; + + /// Change vault of the given address. + #[rpc(name = "parity_changeVault")] + fn change_vault(&self, H160, String) -> Result; } }