API Lifecycle Implementation, Part 2 (#217)

* additon of 'get_mnemonic' API function

* rustfmt

* added 'change_password' api function

* rustfmt

* add 'delete_wallet' function'

* rustfmt
This commit is contained in:
Yeastplume 2019-09-11 10:27:30 +01:00 committed by GitHub
parent 41c0058e84
commit 78e30aa779
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 430 additions and 14 deletions

View file

@ -1565,6 +1565,130 @@ where
let lc = w_lock.lc_provider()?;
lc.close_wallet(name)
}
/// Return the BIP39 mnemonic for the given wallet. This function will decrypt
/// the wallet's seed file with the given password, and thus does not need the
/// wallet to be open.
///
/// # Arguments
///
/// * `name`: Reserved for future use, use `None` for the time being.
/// * `password`: The password used to encrypt the seed file.
///
/// # Returns
/// * Ok(BIP-39 mneminc) if successful
/// * or [`libwallet::Error`](../grin_wallet_libwallet/struct.Error.html) if an error is encountered.
///
/// # Example
/// Set up as in [`new`](struct.Owner.html#method.new) method above.
/// ```
/// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config);
///
/// use grin_core::global::ChainTypes;
///
/// // Set up as above
/// # let api_owner = Owner::new(wallet.clone());
///
/// let pw = ZeroingString::from("my_password");
/// let res = api_owner.get_mnemonic(None, pw);
///
/// if let Ok(mne) = res {
/// // ...
/// }
/// ```
pub fn get_mnemonic(
&self,
name: Option<&str>,
password: ZeroingString,
) -> Result<ZeroingString, Error> {
let mut w_lock = self.wallet_inst.lock();
let lc = w_lock.lc_provider()?;
lc.get_mnemonic(name, password)
}
/// Changes a wallet's password, meaning the old seed file is decrypted with the old password,
/// and a new seed file is created with the same mnemonic and encrypted with the new password.
///
/// This function temporarily backs up the old seed file until a test-decryption of the new
/// file is confirmed to contain the same seed as the original seed file, at which point the
/// backup is deleted. If this operation fails for an unknown reason, the backup file will still
/// exist in the wallet's data directory encrypted with the old password.
///
/// # Arguments
///
/// * `name`: Reserved for future use, use `None` for the time being.
/// * `old`: The password used to encrypt the existing seed file (i.e. old password)
/// * `new`: The password to be used to encrypt the new seed file
///
/// # Returns
/// * Ok(()) if successful
/// * or [`libwallet::Error`](../grin_wallet_libwallet/struct.Error.html) if an error is encountered.
///
/// # Example
/// Set up as in [`new`](struct.Owner.html#method.new) method above.
/// ```
/// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config);
///
/// use grin_core::global::ChainTypes;
///
/// // Set up as above
/// # let api_owner = Owner::new(wallet.clone());
///
/// let old = ZeroingString::from("my_password");
/// let new = ZeroingString::from("new_password");
/// let res = api_owner.change_password(None, old, new);
///
/// if let Ok(mne) = res {
/// // ...
/// }
/// ```
pub fn change_password(
&self,
name: Option<&str>,
old: ZeroingString,
new: ZeroingString,
) -> Result<(), Error> {
let mut w_lock = self.wallet_inst.lock();
let lc = w_lock.lc_provider()?;
lc.change_password(name, old, new)
}
/// Deletes a wallet, removing the config file, seed file and all data files.
/// Obviously, use with extreme caution and plenty of user warning
///
/// Highly recommended that the wallet be explicitly closed first via the `close_wallet`
/// function.
///
/// # Arguments
///
/// * `name`: Reserved for future use, use `None` for the time being.
///
/// # Returns
/// * Ok if successful
/// * or [`libwallet::Error`](../grin_wallet_libwallet/struct.Error.html) if an error is encountered.
///
/// # Example
/// Set up as in [`new`](struct.Owner.html#method.new) method above.
/// ```
/// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config);
///
/// use grin_core::global::ChainTypes;
///
/// // Set up as above
/// # let api_owner = Owner::new(wallet.clone());
///
/// let res = api_owner.delete_wallet(None);
///
/// if let Ok(_) = res {
/// // ...
/// }
/// ```
pub fn delete_wallet(&self, name: Option<&str>) -> Result<(), Error> {
let mut w_lock = self.wallet_inst.lock();
let lc = w_lock.lc_provider()?;
lc.delete_wallet(name)
}
}
#[doc(hidden)]

View file

@ -1587,6 +1587,102 @@ pub trait OwnerRpcS {
*/
fn close_wallet(&self, name: Option<String>) -> Result<(), ErrorKind>;
/**
Networked version of [Owner::get_mnemonic](struct.Owner.html#method.get_mnemonic).
```
# grin_wallet_api::doctest_helper_json_rpc_owner_assert_response!(
# r#"
{
"jsonrpc": "2.0",
"method": "get_mnemonic",
"params": {
"name": null,
"password": ""
},
"id": 1
}
# "#
# ,
# r#"
{
"id": 1,
"jsonrpc": "2.0",
"result": {
"Ok": "fat twenty mean degree forget shell check candy immense awful flame next during february bulb bike sun wink theory day kiwi embrace peace lunch"
}
}
# "#
# , true, 0, false, false, false);
```
*/
fn get_mnemonic(&self, name: Option<String>, password: String) -> Result<String, ErrorKind>;
/**
Networked version of [Owner::change_password](struct.Owner.html#method.change_password).
```
# grin_wallet_api::doctest_helper_json_rpc_owner_assert_response!(
# r#"
{
"jsonrpc": "2.0",
"method": "change_password",
"params": {
"name": null,
"old": "",
"new": "new_password"
},
"id": 1
}
# "#
# ,
# r#"
{
"id": 1,
"jsonrpc": "2.0",
"result": {
"Ok": null
}
}
# "#
# , true, 0, false, false, false);
```
*/
fn change_password(
&self,
name: Option<String>,
old: String,
new: String,
) -> Result<(), ErrorKind>;
/**
Networked version of [Owner::delete_wallet](struct.Owner.html#method.delete_wallet).
```
# grin_wallet_api::doctest_helper_json_rpc_owner_assert_response!(
# r#"
{
"jsonrpc": "2.0",
"method": "delete_wallet",
"params": {
"name": null
},
"id": 1
}
# "#
# ,
# r#"
{
"id": 1,
"jsonrpc": "2.0",
"result": {
"Ok": null
}
}
# "#
# , true, 0, false, false, false);
```
*/
fn delete_wallet(&self, name: Option<String>) -> Result<(), ErrorKind>;
}
impl<'a, L, C, K> OwnerRpcS for Owner<'a, L, C, K>
@ -1843,4 +1939,27 @@ where
let n = name.as_ref().map(|s| s.as_str());
Owner::close_wallet(self, n).map_err(|e| e.kind())
}
fn get_mnemonic(&self, name: Option<String>, password: String) -> Result<String, ErrorKind> {
let n = name.as_ref().map(|s| s.as_str());
let res =
Owner::get_mnemonic(self, n, ZeroingString::from(password)).map_err(|e| e.kind())?;
Ok(format!("{}", &*res))
}
fn change_password(
&self,
name: Option<String>,
old: String,
new: String,
) -> Result<(), ErrorKind> {
let n = name.as_ref().map(|s| s.as_str());
Owner::change_password(self, n, ZeroingString::from(old), ZeroingString::from(new))
.map_err(|e| e.kind())
}
fn delete_wallet(&self, name: Option<String>) -> Result<(), ErrorKind> {
let n = name.as_ref().map(|s| s.as_str());
Owner::delete_wallet(self, n).map_err(|e| e.kind())
}
}

View file

@ -202,8 +202,9 @@ where
}
Ok(d) => d,
};
let wallet_seed = WalletSeed::from_file(&data_dir_name, password)
.context(ErrorKind::Lifecycle("Error opening wallet".into()))?;
let wallet_seed = WalletSeed::from_file(&data_dir_name, password).context(
ErrorKind::Lifecycle("Error opening wallet (is password correct?)".into()),
)?;
let keychain = wallet_seed
.derive_keychain(global::is_floonet())
.context(ErrorKind::Lifecycle("Error deriving keychain".into()))?;
@ -270,12 +271,69 @@ where
Ok(())
}
fn change_password(&self, _old: String, _new: String) -> Result<(), Error> {
unimplemented!()
fn change_password(
&self,
_name: Option<&str>,
old: ZeroingString,
new: ZeroingString,
) -> Result<(), Error> {
let mut data_dir_name = PathBuf::from(self.data_dir.clone());
data_dir_name.push(GRIN_WALLET_DIR);
let data_dir_name = data_dir_name.to_str().unwrap();
// get seed for later check
let orig_wallet_seed = WalletSeed::from_file(&data_dir_name, old).context(
ErrorKind::Lifecycle("Error opening wallet seed file".into()),
)?;
let orig_mnemonic = orig_wallet_seed
.to_mnemonic()
.context(ErrorKind::Lifecycle("Error recovering mnemonic".into()))?;
// Back up existing seed, and keep track of filename as we're deleting it
// once the password change is confirmed
let backup_name = WalletSeed::backup_seed(data_dir_name).context(ErrorKind::Lifecycle(
"Error temporarily backing up existing seed".into(),
))?;
// Delete seed file
WalletSeed::delete_seed_file(data_dir_name).context(ErrorKind::Lifecycle(
"Unable to delete seed file for password change".into(),
))?;
// Init a new file
let _ = WalletSeed::init_file(
data_dir_name,
0,
Some(ZeroingString::from(orig_mnemonic)),
new.clone(),
);
info!("Wallet seed file created");
let new_wallet_seed = WalletSeed::from_file(&data_dir_name, new).context(
ErrorKind::Lifecycle("Error opening wallet seed file".into()),
)?;
if orig_wallet_seed != new_wallet_seed {
let msg = format!(
"New and Old wallet seeds are not equal on password change, not removing backups."
);
return Err(ErrorKind::Lifecycle(msg).into());
}
// Removin
info!("Password change confirmed, removing old seed file.");
fs::remove_file(backup_name).context(ErrorKind::IO)?;
Ok(())
}
fn delete_wallet(&self, _name: Option<String>, _password: String) -> Result<(), Error> {
unimplemented!()
fn delete_wallet(&self, _name: Option<&str>) -> Result<(), Error> {
let data_dir_name = PathBuf::from(self.data_dir.clone());
warn!(
"Removing all wallet data from: {}",
data_dir_name.to_str().unwrap()
);
fs::remove_dir_all(data_dir_name).context(ErrorKind::IO)?;
Ok(())
}
fn wallet_inst(&mut self) -> Result<&mut Box<dyn WalletBackend<'a, C, K> + 'a>, Error> {

View file

@ -94,7 +94,7 @@ impl WalletSeed {
}
}
pub fn backup_seed(data_file_dir: &str) -> Result<(), Error> {
pub fn backup_seed(data_file_dir: &str) -> Result<String, Error> {
let seed_file_name = &format!("{}{}{}", data_file_dir, MAIN_SEPARATOR, SEED_FILE,);
let mut path = Path::new(seed_file_name).to_path_buf();
@ -114,7 +114,7 @@ impl WalletSeed {
))?;
}
warn!("{} backed up as {}", seed_file_name, backup_seed_file_name);
Ok(())
Ok(backup_seed_file_name)
}
pub fn recover_from_phrase(
@ -180,7 +180,6 @@ impl WalletSeed {
data_file_dir: &str,
password: util::ZeroingString,
) -> Result<WalletSeed, Error> {
// TODO: Is this desirable any more?
// create directory if it doesn't exist
fs::create_dir_all(data_file_dir).context(ErrorKind::IO)?;
@ -205,6 +204,15 @@ impl WalletSeed {
Err(ErrorKind::WalletSeedDoesntExist)?
}
}
pub fn delete_seed_file(data_file_dir: &str) -> Result<(), Error> {
let seed_file_path = &format!("{}{}{}", data_file_dir, MAIN_SEPARATOR, SEED_FILE,);
if Path::new(seed_file_path).exists() {
debug!("Deleting wallet seed file at: {}", seed_file_path);
fs::remove_file(seed_file_path).context(ErrorKind::IO)?;
}
Ok(())
}
}
/// Encrypted wallet seed, for storing on disk and decrypting

View file

@ -112,10 +112,15 @@ where
) -> Result<(), Error>;
/// changes password
fn change_password(&self, old: String, new: String) -> Result<(), Error>;
fn change_password(
&self,
name: Option<&str>,
old: ZeroingString,
new: ZeroingString,
) -> Result<(), Error>;
/// deletes wallet
fn delete_wallet(&self, name: Option<String>, password: String) -> Result<(), Error>;
fn delete_wallet(&self, name: Option<&str>) -> Result<(), Error>;
/// return wallet instance
fn wallet_inst(&mut self) -> Result<&mut Box<dyn WalletBackend<'a, C, K> + 'a>, Error>;

View file

@ -405,8 +405,8 @@ where
let value: OUT = v;
Ok(Ok(value))
}
Err(e) => {
println!("Error deserializing: {:?}", e);
Err(_) => {
//println!("Error deserializing: {:?}", e);
let value: OUT = serde_json::from_value(json!("Null")).unwrap();
Ok(Ok(value))
}

View file

@ -0,0 +1,10 @@
{
"jsonrpc": "2.0",
"method": "change_password",
"params": {
"name": null,
"old": "passwoid",
"new": "password"
},
"id": 1
}

View file

@ -4,7 +4,7 @@
"params": {
"name": null,
"mnemonic": null,
"mnemonic_length": 0,
"mnemonic_length": 32,
"password": "passwoid"
},
"id": 1

View file

@ -0,0 +1,8 @@
{
"jsonrpc": "2.0",
"method": "delete_wallet",
"params": {
"name": null
},
"id": 1
}

View file

@ -367,7 +367,91 @@ fn owner_v3_lifecycle() -> Result<(), grin_wallet_controller::Error> {
println!("RES 16: {:?}", res);
assert!(res.is_ok());
//17) Change the password
let req = include_str!("data/v3_reqs/close_wallet.req.json");
let res =
send_request_enc::<String>(1, 1, "http://127.0.0.1:43420/v3/owner", &req, &shared_key)?;
println!("RES 17: {:?}", res);
assert!(res.is_ok());
let req = include_str!("data/v3_reqs/change_password.req.json");
let res =
send_request_enc::<String>(1, 1, "http://127.0.0.1:43420/v3/owner", &req, &shared_key)?;
println!("RES 17a: {:?}", res);
assert!(res.is_ok());
// 18) trying to open with old password should fail
let req = include_str!("data/v3_reqs/open_wallet.req.json");
let res =
send_request_enc::<String>(1, 1, "http://127.0.0.1:43420/v3/owner", &req, &shared_key)?;
println!("RES 18: {:?}", res);
assert!(res.is_err());
// 19) Open with new password
let req = serde_json::json!({
"jsonrpc": "2.0",
"id": 1,
"method": "open_wallet",
"params": {
"name": null,
"password": "password"
}
});
let res = send_request_enc::<String>(
1,
1,
"http://127.0.0.1:43420/v3/owner",
&req.to_string(),
&shared_key,
)?;
println!("RES 19: {:?}", res);
assert!(res.is_ok());
let token = res.unwrap();
// 20) Send a request with new token with changed password, ensure balances are still there and
// therefore seed is the same
let req = serde_json::json!({
"jsonrpc": "2.0",
"id": 1,
"method": "retrieve_summary_info",
"params": {
"token": token,
"refresh_from_node": true,
"minimum_confirmations": 1
}
});
let res = send_request_enc::<RetrieveSummaryInfoResp>(
1,
1,
"http://127.0.0.1:43420/v3/owner",
&req.to_string(),
&shared_key,
)?;
println!("RES 20: {:?}", res);
thread::sleep(Duration::from_millis(200));
assert_eq!(res.unwrap().1.amount_awaiting_finalization, 6000000000);
// 21) Delete the wallet (close first)
let req = include_str!("data/v3_reqs/close_wallet.req.json");
let res =
send_request_enc::<String>(1, 1, "http://127.0.0.1:43420/v3/owner", &req, &shared_key)?;
assert!(res.is_ok());
let req = include_str!("data/v3_reqs/delete_wallet.req.json");
let res =
send_request_enc::<String>(1, 1, "http://127.0.0.1:43420/v3/owner", &req, &shared_key)?;
println!("RES 21: {:?}", res);
assert!(res.is_ok());
// 22) Wallet should be gone
let req = include_str!("data/v3_reqs/open_wallet.req.json");
let res =
send_request_enc::<String>(1, 1, "http://127.0.0.1:43420/v3/owner", &req, &shared_key)?;
println!("RES 22: {:?}", res);
assert!(res.is_err());
clean_output_dir(test_dir);
Ok(())