diff --git a/ethcore/src/account_provider/mod.rs b/ethcore/src/account_provider/mod.rs
index d31ded682..74d24e7f5 100755
--- a/ethcore/src/account_provider/mod.rs
+++ b/ethcore/src/account_provider/mod.rs
@@ -264,9 +264,9 @@ impl AccountProvider {
Ok(Address::from(account.address).into())
}
- /// Import a new presale wallet.
- pub fn import_wallet(&self, json: &[u8], password: &str) -> Result
{
- let account = self.sstore.import_wallet(SecretVaultRef::Root, json, password)?;
+ /// Import a new wallet.
+ pub fn import_wallet(&self, json: &[u8], password: &str, gen_id: bool) -> Result {
+ let account = self.sstore.import_wallet(SecretVaultRef::Root, json, password, gen_id)?;
if self.blacklisted_accounts.contains(&account.address) {
self.sstore.remove_account(&account, password)?;
return Err(SSError::InvalidAccount.into());
diff --git a/ethstore/src/accounts_dir/disk.rs b/ethstore/src/accounts_dir/disk.rs
index 46a0ecf39..144611844 100755
--- a/ethstore/src/accounts_dir/disk.rs
+++ b/ethstore/src/accounts_dir/disk.rs
@@ -153,22 +153,30 @@ impl DiskDirectory where T: KeyFileManager {
)
}
- /// insert account with given file name
- pub fn insert_with_filename(&self, account: SafeAccount, filename: String) -> Result {
+
+ /// insert account with given filename. if the filename is a duplicate of any stored account and dedup is set to
+ /// true, a random suffix is appended to the filename.
+ pub fn insert_with_filename(&self, account: SafeAccount, mut filename: String, dedup: bool) -> Result {
+ // path to keyfile
+ let mut keyfile_path = self.path.join(filename.as_str());
+
+ // check for duplicate filename and append random suffix
+ if dedup && keyfile_path.exists() {
+ let suffix = ::random::random_string(4);
+ filename.push_str(&format!("-{}", suffix));
+ keyfile_path.set_file_name(&filename);
+ }
+
// update account filename
let original_account = account.clone();
let mut account = account;
- account.filename = Some(filename.clone());
+ account.filename = Some(filename);
{
- // Path to keyfile
- let mut keyfile_path = self.path.clone();
- keyfile_path.push(filename.as_str());
-
// save the file
let mut file = fs::File::create(&keyfile_path)?;
- // Write key content
+ // write key content
self.key_manager.write(original_account, &mut file).map_err(|e| Error::Custom(format!("{:?}", e)))?;
file.flush()?;
@@ -200,17 +208,13 @@ impl KeyDirectory for DiskDirectory where T: KeyFileManager {
fn update(&self, account: SafeAccount) -> Result {
// Disk store handles updates correctly iff filename is the same
- self.insert(account)
+ let filename = account_filename(&account);
+ self.insert_with_filename(account, filename, false)
}
fn insert(&self, account: SafeAccount) -> Result {
- // build file path
- let filename = account.filename.as_ref().cloned().unwrap_or_else(|| {
- let timestamp = time::strftime("%Y-%m-%dT%H-%M-%S", &time::now_utc()).expect("Time-format string is valid.");
- format!("UTC--{}Z--{}", timestamp, Uuid::from(account.id))
- });
-
- self.insert_with_filename(account, filename)
+ let filename = account_filename(&account);
+ self.insert_with_filename(account, filename, true)
}
fn remove(&self, account: &SafeAccount) -> Result<(), Error> {
@@ -286,6 +290,14 @@ impl KeyFileManager for DiskKeyFileManager {
}
}
+fn account_filename(account: &SafeAccount) -> String {
+ // build file path
+ account.filename.clone().unwrap_or_else(|| {
+ let timestamp = time::strftime("%Y-%m-%dT%H-%M-%S", &time::now_utc()).expect("Time-format string is valid.");
+ format!("UTC--{}Z--{}", timestamp, Uuid::from(account.id))
+ })
+}
+
#[cfg(test)]
mod test {
extern crate tempdir;
@@ -317,6 +329,38 @@ mod test {
let _ = fs::remove_dir_all(dir);
}
+ #[test]
+ fn should_handle_duplicate_filenames() {
+ // given
+ let mut dir = env::temp_dir();
+ dir.push("ethstore_should_handle_duplicate_filenames");
+ let keypair = Random.generate().unwrap();
+ let password = "hello world";
+ let directory = RootDiskDirectory::create(dir.clone()).unwrap();
+
+ // when
+ let account = SafeAccount::create(&keypair, [0u8; 16], password, 1024, "Test".to_owned(), "{}".to_owned());
+ let filename = "test".to_string();
+ let dedup = true;
+
+ directory.insert_with_filename(account.clone(), "foo".to_string(), dedup).unwrap();
+ let file1 = directory.insert_with_filename(account.clone(), filename.clone(), dedup).unwrap().filename.unwrap();
+ let file2 = directory.insert_with_filename(account.clone(), filename.clone(), dedup).unwrap().filename.unwrap();
+ let file3 = directory.insert_with_filename(account.clone(), filename.clone(), dedup).unwrap().filename.unwrap();
+
+ // then
+ // the first file should have the original names
+ assert_eq!(file1, filename);
+
+ // the following duplicate files should have a suffix appended
+ assert!(file2 != file3);
+ assert_eq!(file2.len(), filename.len() + 5);
+ assert_eq!(file3.len(), filename.len() + 5);
+
+ // cleanup
+ let _ = fs::remove_dir_all(dir);
+ }
+
#[test]
fn should_manage_vaults() {
// given
diff --git a/ethstore/src/accounts_dir/vault.rs b/ethstore/src/accounts_dir/vault.rs
index b270bb181..1ef85402a 100755
--- a/ethstore/src/accounts_dir/vault.rs
+++ b/ethstore/src/accounts_dir/vault.rs
@@ -106,7 +106,7 @@ impl VaultDiskDirectory {
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");
- vault.insert_with_filename(account, filename)?;
+ vault.insert_with_filename(account, filename, true)?;
}
Ok(())
diff --git a/ethstore/src/ethstore.rs b/ethstore/src/ethstore.rs
index 4c6309a11..40a687fc9 100755
--- a/ethstore/src/ethstore.rs
+++ b/ethstore/src/ethstore.rs
@@ -166,9 +166,14 @@ impl SecretStore for EthStore {
self.insert_account(vault, keypair.secret().clone(), password)
}
- fn import_wallet(&self, vault: SecretVaultRef, json: &[u8], password: &str) -> Result {
+ fn import_wallet(&self, vault: SecretVaultRef, json: &[u8], password: &str, gen_id: bool) -> Result {
let json_keyfile = json::KeyFile::load(json).map_err(|_| Error::InvalidKeyFile("Invalid JSON format".to_owned()))?;
let mut safe_account = SafeAccount::from_file(json_keyfile, None);
+
+ if gen_id {
+ safe_account.id = Random::random();
+ }
+
let secret = safe_account.crypto.secret(password).map_err(|_| Error::InvalidPassword)?;
safe_account.address = KeyPair::from_secret(secret)?.address();
self.store.import(vault, safe_account)
diff --git a/ethstore/src/secret_store.rs b/ethstore/src/secret_store.rs
index b46e6f2d8..ebac2f992 100755
--- a/ethstore/src/secret_store.rs
+++ b/ethstore/src/secret_store.rs
@@ -116,7 +116,7 @@ pub trait SecretStore: SimpleSecretStore {
/// Imports presale wallet
fn import_presale(&self, vault: SecretVaultRef, json: &[u8], password: &str) -> Result;
/// Imports existing JSON wallet
- fn import_wallet(&self, vault: SecretVaultRef, json: &[u8], password: &str) -> Result;
+ fn import_wallet(&self, vault: SecretVaultRef, json: &[u8], password: &str, gen_id: bool) -> Result;
/// Copies account between stores and vaults.
fn copy_account(&self, new_store: &SimpleSecretStore, new_vault: SecretVaultRef, account: &StoreAccountRef, password: &str, new_password: &str) -> Result<(), Error>;
/// Checks if password matches given account.
diff --git a/rpc/src/v1/impls/parity_accounts.rs b/rpc/src/v1/impls/parity_accounts.rs
index 128415bbd..8de8fea90 100644
--- a/rpc/src/v1/impls/parity_accounts.rs
+++ b/rpc/src/v1/impls/parity_accounts.rs
@@ -95,7 +95,7 @@ impl ParityAccounts for ParityAccountsClient {
let store = self.account_provider()?;
store.import_presale(json.as_bytes(), &pass)
- .or_else(|_| store.import_wallet(json.as_bytes(), &pass))
+ .or_else(|_| store.import_wallet(json.as_bytes(), &pass, true))
.map(Into::into)
.map_err(|e| errors::account("Could not create account.", e))
}
diff --git a/rpc/src/v1/tests/mocked/parity_accounts.rs b/rpc/src/v1/tests/mocked/parity_accounts.rs
index 540bea903..258310b85 100644
--- a/rpc/src/v1/tests/mocked/parity_accounts.rs
+++ b/rpc/src/v1/tests/mocked/parity_accounts.rs
@@ -480,7 +480,7 @@ fn should_export_account() {
// given
let tester = setup();
let wallet = r#"{"id":"6a186c80-7797-cff2-bc2e-7c1d6a6cc76e","version":3,"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"a1c6ff99070f8032ca1c4e8add006373"},"ciphertext":"df27e3db64aa18d984b6439443f73660643c2d119a6f0fa2fa9a6456fc802d75","kdf":"pbkdf2","kdfparams":{"c":10240,"dklen":32,"prf":"hmac-sha256","salt":"ddc325335cda5567a1719313e73b4842511f3e4a837c9658eeb78e51ebe8c815"},"mac":"3dc888ae79cbb226ff9c455669f6cf2d79be72120f2298f6cb0d444fddc0aa3d"},"address":"0042e5d2a662eeaca8a7e828c174f98f35d8925b","name":"parity-export-test","meta":"{\"passwordHint\":\"parity-export-test\",\"timestamp\":1490017814987}"}"#;
- tester.accounts.import_wallet(wallet.as_bytes(), "parity-export-test").unwrap();
+ tester.accounts.import_wallet(wallet.as_bytes(), "parity-export-test", false).unwrap();
let accounts = tester.accounts.accounts().unwrap();
assert_eq!(accounts.len(), 1);
@@ -501,6 +501,26 @@ fn should_export_account() {
assert_eq!(result, Some(response.into()));
}
+#[test]
+fn should_import_wallet() {
+ let tester = setup();
+
+ let id = "6a186c80-7797-cff2-bc2e-7c1d6a6cc76e";
+ let request = r#"{"jsonrpc":"2.0","method":"parity_newAccountFromWallet","params":["{\"id\":\"\",\"version\":3,\"crypto\":{\"cipher\":\"aes-128-ctr\",\"cipherparams\":{\"iv\":\"478736fb55872c1baf01b27b1998c90b\"},\"ciphertext\":\"fe5a63cc0055d7b0b3b57886f930ad9b63f48950d1348145d95996c41e05f4e0\",\"kdf\":\"pbkdf2\",\"kdfparams\":{\"c\":10240,\"dklen\":32,\"prf\":\"hmac-sha256\",\"salt\":\"658436d6738a19731149a98744e5cf02c8d5aa1f8e80c1a43cc9351c70a984e4\"},\"mac\":\"c7384b26ecf25539d942030230062af9b69de5766cbcc4690bffce1536644631\"},\"address\":\"00bac56a8a27232baa044c03f43bf3648c961735\",\"name\":\"hello world\",\"meta\":\"{}\"}", "himom"],"id":1}"#;
+ let request = request.replace("", id);
+ let response = r#"{"jsonrpc":"2.0","result":"0x00bac56a8a27232baa044c03f43bf3648c961735","id":1}"#;
+
+ let res = tester.io.handle_request_sync(&request).unwrap();
+
+ assert_eq!(res, response);
+
+ let account_meta = tester.accounts.account_meta("0x00bac56a8a27232baa044c03f43bf3648c961735".into()).unwrap();
+ let account_uuid: String = account_meta.uuid.unwrap().into();
+
+ // the RPC should import the account with a new id
+ assert!(account_uuid != id);
+}
+
#[test]
fn should_sign_message() {
let tester = setup();