Skip to content

Commit

Permalink
API Lifecycle Implementation, Part 2 (#217)
Browse files Browse the repository at this point in the history
* additon of 'get_mnemonic' API function

* rustfmt

* added 'change_password' api function

* rustfmt

* add 'delete_wallet' function'

* rustfmt
  • Loading branch information
yeastplume authored Sep 11, 2019
1 parent 41c0058 commit 78e30aa
Show file tree
Hide file tree
Showing 10 changed files with 430 additions and 14 deletions.
124 changes: 124 additions & 0 deletions api/src/owner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
119 changes: 119 additions & 0 deletions api/src/owner_rpc_s.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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())
}
}
70 changes: 64 additions & 6 deletions impls/src/lifecycle/default.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()))?;
Expand Down Expand Up @@ -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> {
Expand Down
14 changes: 11 additions & 3 deletions impls/src/lifecycle/seed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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(
Expand Down Expand Up @@ -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)?;

Expand All @@ -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
Expand Down
Loading

0 comments on commit 78e30aa

Please sign in to comment.