Proper default accounts RPCs (#4580)

* Default accounts setting - account provider

* RPC support for default accounts

* Updating JS code

* Rename whitelist to addresses
This commit is contained in:
Tomasz Drwięga 2017-02-20 16:33:12 +01:00 committed by Gav Wood
parent 1949d44d0c
commit 72998d3ce3
22 changed files with 511 additions and 161 deletions

View File

@ -23,7 +23,7 @@ use self::stores::{AddressBook, DappsSettingsStore, NewDappsPolicy};
use std::fmt;
use std::collections::{HashMap, HashSet};
use std::time::{Instant, Duration};
use util::RwLock;
use util::{FixedHash, RwLock};
use ethstore::{SimpleSecretStore, SecretStore, Error as SSError, EthStore, EthMultiStore,
random_string, SecretVaultRef, StoreAccountRef};
use ethstore::dir::MemoryDirectory;
@ -241,25 +241,88 @@ impl AccountProvider {
Ok(accounts.into_iter().map(|a| a.address).collect())
}
/// Sets a whitelist of accounts exposed for unknown dapps.
/// Sets addresses of accounts exposed for unknown dapps.
/// `None` means that all accounts will be visible.
pub fn set_new_dapps_whitelist(&self, accounts: Option<Vec<Address>>) -> Result<(), Error> {
/// If not `None` or empty it will also override default account.
pub fn set_new_dapps_addresses(&self, accounts: Option<Vec<Address>>) -> Result<(), Error> {
let current_default = self.new_dapps_default_address()?;
self.dapps_settings.write().set_policy(match accounts {
None => NewDappsPolicy::AllAccounts,
None => NewDappsPolicy::AllAccounts {
default: current_default,
},
Some(accounts) => NewDappsPolicy::Whitelist(accounts),
});
Ok(())
}
/// Gets a whitelist of accounts exposed for unknown dapps.
/// Gets addresses of accounts exposed for unknown dapps.
/// `None` means that all accounts will be visible.
pub fn new_dapps_whitelist(&self) -> Result<Option<Vec<Address>>, Error> {
pub fn new_dapps_addresses(&self) -> Result<Option<Vec<Address>>, Error> {
Ok(match self.dapps_settings.read().policy() {
NewDappsPolicy::AllAccounts => None,
NewDappsPolicy::AllAccounts { .. } => None,
NewDappsPolicy::Whitelist(accounts) => Some(accounts),
})
}
/// Sets a default account for unknown dapps.
/// This account will always be returned as the first one.
pub fn set_new_dapps_default_address(&self, address: Address) -> Result<(), Error> {
if !self.valid_addresses()?.contains(&address) {
return Err(SSError::InvalidAccount.into());
}
let mut settings = self.dapps_settings.write();
let new_policy = match settings.policy() {
NewDappsPolicy::AllAccounts { .. } => NewDappsPolicy::AllAccounts { default: address },
NewDappsPolicy::Whitelist(list) => NewDappsPolicy::Whitelist(Self::insert_default(list, address)),
};
settings.set_policy(new_policy);
Ok(())
}
/// Inserts given address as first in the vector, preventing duplicates.
fn insert_default(mut addresses: Vec<Address>, default: Address) -> Vec<Address> {
if let Some(position) = addresses.iter().position(|address| address == &default) {
addresses.swap(0, position);
} else {
addresses.insert(0, default);
}
addresses
}
/// Returns a list of accounts that new dapp should see.
/// First account is always the default account.
fn new_dapps_addresses_list(&self) -> Result<Vec<Address>, Error> {
match self.dapps_settings.read().policy() {
NewDappsPolicy::AllAccounts { default } => if default.is_zero() {
self.accounts()
} else {
Ok(Self::insert_default(self.accounts()?, default))
},
NewDappsPolicy::Whitelist(accounts) => {
let addresses = self.filter_addresses(accounts)?;
if addresses.is_empty() {
Ok(vec![self.accounts()?.get(0).cloned().unwrap_or(0.into())])
} else {
Ok(addresses)
}
},
}
}
/// Gets a default account for new dapps
/// Will return zero address in case the default is not set and there are no accounts configured.
pub fn new_dapps_default_address(&self) -> Result<Address, Error> {
Ok(self.new_dapps_addresses_list()?
.get(0)
.cloned()
.unwrap_or(0.into())
)
}
/// Gets a list of dapps recently requesting accounts.
pub fn recent_dapps(&self) -> Result<HashMap<DappId, u64>, Error> {
Ok(self.dapps_settings.read().recent_dapps())
@ -272,41 +335,74 @@ impl AccountProvider {
Ok(())
}
/// Gets addresses visile for dapp.
pub fn dapps_addresses(&self, dapp: DappId) -> Result<Vec<Address>, Error> {
let dapps = self.dapps_settings.read();
/// Gets addresses visible for given dapp.
pub fn dapp_addresses(&self, dapp: DappId) -> Result<Vec<Address>, Error> {
let accounts = self.dapps_settings.read().settings().get(&dapp).map(|settings| {
(settings.accounts.clone(), settings.default.clone())
});
let accounts = dapps.settings().get(&dapp).map(|settings| settings.accounts.clone());
match accounts {
Some(accounts) => Ok(accounts),
None => match dapps.policy() {
NewDappsPolicy::AllAccounts => self.accounts(),
NewDappsPolicy::Whitelist(accounts) => self.filter_addresses(accounts),
}
Some((Some(accounts), Some(default))) => self.filter_addresses(Self::insert_default(accounts, default)),
Some((Some(accounts), None)) => self.filter_addresses(accounts),
Some((None, Some(default))) => self.filter_addresses(Self::insert_default(self.new_dapps_addresses_list()?, default)),
_ => self.new_dapps_addresses_list(),
}
}
/// Returns default account for particular dapp falling back to other allowed accounts if necessary.
pub fn default_address(&self, dapp: DappId) -> Result<Address, Error> {
self.dapps_addresses(dapp)?
pub fn dapp_default_address(&self, dapp: DappId) -> Result<Address, Error> {
let dapp_default = self.dapp_addresses(dapp)?
.get(0)
.cloned()
.ok_or(SSError::InvalidAccount)
.cloned();
match dapp_default {
Some(default) => Ok(default),
None => self.new_dapps_default_address(),
}
}
/// Sets addresses visile for dapp.
pub fn set_dapps_addresses(&self, dapp: DappId, addresses: Vec<Address>) -> Result<(), Error> {
let addresses = self.filter_addresses(addresses)?;
self.dapps_settings.write().set_accounts(dapp, addresses);
/// Sets default address for given dapp.
/// Does not alter dapp addresses, but this account will always be returned as the first one.
pub fn set_dapp_default_address(&self, dapp: DappId, address: Address) -> Result<(), Error> {
if !self.valid_addresses()?.contains(&address) {
return Err(SSError::InvalidAccount.into());
}
self.dapps_settings.write().set_default(dapp, address);
Ok(())
}
/// Sets addresses visible for given dapp.
/// If `None` - falls back to dapps addresses
/// If not `None` and not empty it will also override default account.
pub fn set_dapp_addresses(&self, dapp: DappId, addresses: Option<Vec<Address>>) -> Result<(), Error> {
let (addresses, default) = match addresses {
Some(addresses) => {
let addresses = self.filter_addresses(addresses)?;
let default = addresses.get(0).cloned();
(Some(addresses), default)
},
None => (None, None),
};
let mut settings = self.dapps_settings.write();
if let Some(default) = default {
settings.set_default(dapp.clone(), default);
}
settings.set_accounts(dapp, addresses);
Ok(())
}
fn valid_addresses(&self) -> Result<HashSet<Address>, Error> {
Ok(self.addresses_info().into_iter()
.map(|(address, _)| address)
.chain(self.accounts()?)
.collect())
}
/// Removes addresses that are neither accounts nor in address book.
fn filter_addresses(&self, addresses: Vec<Address>) -> Result<Vec<Address>, Error> {
let valid = self.addresses_info().into_iter()
.map(|(address, _)| address)
.chain(self.accounts()?)
.collect::<HashSet<_>>();
let valid = self.valid_addresses()?;
Ok(addresses.into_iter()
.filter(|a| valid.contains(&a))
@ -743,44 +839,92 @@ mod tests {
}
#[test]
fn should_set_dapps_addresses() {
fn should_reset_dapp_addresses_to_default() {
// given
let ap = AccountProvider::transient_provider();
let app = DappId("app1".into());
// add accounts to address book
ap.set_address_name(1.into(), "1".into());
ap.set_address_name(2.into(), "2".into());
// set `AllAccounts` policy
ap.set_new_dapps_addresses(Some(vec![1.into(), 2.into()])).unwrap();
assert_eq!(ap.dapp_addresses(app.clone()).unwrap(), vec![1.into(), 2.into()]);
// Alter and check
ap.set_dapp_addresses(app.clone(), Some(vec![1.into(), 3.into()])).unwrap();
assert_eq!(ap.dapp_addresses(app.clone()).unwrap(), vec![1.into()]);
// Reset back to default
ap.set_dapp_addresses(app.clone(), None).unwrap();
assert_eq!(ap.dapp_addresses(app.clone()).unwrap(), vec![1.into(), 2.into()]);
}
#[test]
fn should_set_dapps_default_address() {
// given
let ap = AccountProvider::transient_provider();
let app = DappId("app1".into());
// set `AllAccounts` policy
ap.set_new_dapps_whitelist(None).unwrap();
ap.set_new_dapps_addresses(None).unwrap();
// add accounts to address book
ap.set_address_name(1.into(), "1".into());
ap.set_address_name(2.into(), "2".into());
// when
ap.set_dapps_addresses(app.clone(), vec![1.into(), 2.into(), 3.into()]).unwrap();
ap.set_dapp_addresses(app.clone(), Some(vec![1.into(), 2.into(), 3.into()])).unwrap();
assert_eq!(ap.dapp_addresses(app.clone()).unwrap(), vec![1.into(), 2.into()]);
assert_eq!(ap.dapp_default_address("app1".into()).unwrap(), 1.into());
// then
assert_eq!(ap.dapps_addresses(app.clone()).unwrap(), vec![1.into(), 2.into()]);
// when setting empty list
ap.set_dapp_addresses(app.clone(), Some(vec![])).unwrap();
// then default account is intact
assert_eq!(ap.dapp_addresses(app.clone()).unwrap(), vec![1.into()]);
assert_eq!(ap.dapp_default_address("app1".into()).unwrap(), 1.into());
// alter default account
ap.set_dapp_default_address("app1".into(), 2.into()).unwrap();
assert_eq!(ap.dapp_addresses(app.clone()).unwrap(), vec![2.into()]);
assert_eq!(ap.dapp_default_address("app1".into()).unwrap(), 2.into());
}
#[test]
fn should_set_dapps_policy() {
fn should_set_dapps_policy_and_default_account() {
// given
let ap = AccountProvider::transient_provider();
// default_account should be always available
assert_eq!(ap.new_dapps_default_address().unwrap(), 0.into());
let address = ap.new_account("test").unwrap();
ap.set_address_name(1.into(), "1".into());
// When returning nothing
ap.set_new_dapps_whitelist(Some(vec![])).unwrap();
assert_eq!(ap.dapps_addresses("app1".into()).unwrap(), vec![]);
// Default account set to first account by default
assert_eq!(ap.new_dapps_default_address().unwrap(), address);
assert_eq!(ap.dapp_default_address("app1".into()).unwrap(), address);
// Even when returning nothing
ap.set_new_dapps_addresses(Some(vec![])).unwrap();
// Default account is still returned
assert_eq!(ap.dapp_addresses("app1".into()).unwrap(), vec![address]);
// change to all
ap.set_new_dapps_whitelist(None).unwrap();
assert_eq!(ap.dapps_addresses("app1".into()).unwrap(), vec![address]);
ap.set_new_dapps_addresses(None).unwrap();
assert_eq!(ap.dapp_addresses("app1".into()).unwrap(), vec![address]);
// change to non-existent account
ap.set_new_dapps_whitelist(Some(vec![2.into()])).unwrap();
assert_eq!(ap.dapps_addresses("app1".into()).unwrap(), vec![]);
ap.set_new_dapps_addresses(Some(vec![2.into()])).unwrap();
assert_eq!(ap.dapp_addresses("app1".into()).unwrap(), vec![address]);
// change to a whitelist
ap.set_new_dapps_whitelist(Some(vec![1.into()])).unwrap();
assert_eq!(ap.dapps_addresses("app1".into()).unwrap(), vec![1.into()]);
// change to a addresses
ap.set_new_dapps_addresses(Some(vec![1.into()])).unwrap();
assert_eq!(ap.dapp_addresses("app1".into()).unwrap(), vec![1.into()]);
// it overrides default account
assert_eq!(ap.new_dapps_default_address().unwrap(), 1.into());
assert_eq!(ap.dapp_default_address("app1".into()).unwrap(), 1.into());
ap.set_new_dapps_default_address(address).unwrap();
assert_eq!(ap.new_dapps_default_address().unwrap(), address);
assert_eq!(ap.dapp_default_address("app1".into()).unwrap(), address);
}
}

View File

@ -92,13 +92,16 @@ impl AddressBook {
#[derive(Debug, Default, Clone, Eq, PartialEq)]
pub struct DappsSettings {
/// A list of visible accounts
pub accounts: Vec<Address>,
pub accounts: Option<Vec<Address>>,
/// Default account
pub default: Option<Address>,
}
impl From<JsonSettings> for DappsSettings {
fn from(s: JsonSettings) -> Self {
DappsSettings {
accounts: s.accounts.into_iter().map(Into::into).collect(),
accounts: s.accounts.map(|accounts| accounts.into_iter().map(Into::into).collect()),
default: s.default.map(Into::into),
}
}
}
@ -106,7 +109,8 @@ impl From<JsonSettings> for DappsSettings {
impl From<DappsSettings> for JsonSettings {
fn from(s: DappsSettings) -> Self {
JsonSettings {
accounts: s.accounts.into_iter().map(Into::into).collect(),
accounts: s.accounts.map(|accounts| accounts.into_iter().map(Into::into).collect()),
default: s.default.map(Into::into),
}
}
}
@ -114,14 +118,18 @@ impl From<DappsSettings> for JsonSettings {
/// Dapps user settings
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum NewDappsPolicy {
AllAccounts,
AllAccounts {
default: Address,
},
Whitelist(Vec<Address>),
}
impl From<JsonNewDappsPolicy> for NewDappsPolicy {
fn from(s: JsonNewDappsPolicy) -> Self {
match s {
JsonNewDappsPolicy::AllAccounts => NewDappsPolicy::AllAccounts,
JsonNewDappsPolicy::AllAccounts { default } => NewDappsPolicy::AllAccounts {
default: default.into(),
},
JsonNewDappsPolicy::Whitelist(accounts) => NewDappsPolicy::Whitelist(
accounts.into_iter().map(Into::into).collect()
),
@ -132,7 +140,9 @@ impl From<JsonNewDappsPolicy> for NewDappsPolicy {
impl From<NewDappsPolicy> for JsonNewDappsPolicy {
fn from(s: NewDappsPolicy) -> Self {
match s {
NewDappsPolicy::AllAccounts => JsonNewDappsPolicy::AllAccounts,
NewDappsPolicy::AllAccounts { default } => JsonNewDappsPolicy::AllAccounts {
default: default.into(),
},
NewDappsPolicy::Whitelist(accounts) => JsonNewDappsPolicy::Whitelist(
accounts.into_iter().map(Into::into).collect()
),
@ -230,7 +240,9 @@ impl DappsSettingsStore {
/// Returns current new dapps policy
pub fn policy(&self) -> NewDappsPolicy {
self.policy.get("default").cloned().unwrap_or(NewDappsPolicy::AllAccounts)
self.policy.get("default").cloned().unwrap_or(NewDappsPolicy::AllAccounts {
default: 0.into(),
})
}
/// Returns recent dapps with last accessed timestamp
@ -266,13 +278,22 @@ impl DappsSettingsStore {
}
/// Sets accounts for specific dapp.
pub fn set_accounts(&mut self, id: DappId, accounts: Vec<Address>) {
pub fn set_accounts(&mut self, id: DappId, accounts: Option<Vec<Address>>) {
{
let mut settings = self.settings.entry(id).or_insert_with(DappsSettings::default);
settings.accounts = accounts;
}
self.settings.save(JsonSettings::write);
}
/// Sets a default account for specific dapp.
pub fn set_default(&mut self, id: DappId, default: Address) {
{
let mut settings = self.settings.entry(id).or_insert_with(DappsSettings::default);
settings.default = Some(default);
}
self.settings.save(JsonSettings::write);
}
}
/// Disk-serializable HashMap
@ -385,13 +406,14 @@ mod tests {
let mut b = DappsSettingsStore::new(&path);
// when
b.set_accounts("dappOne".into(), vec![1.into(), 2.into()]);
b.set_accounts("dappOne".into(), Some(vec![1.into(), 2.into()]));
// then
let b = DappsSettingsStore::new(&path);
assert_eq!(b.settings(), hash_map![
"dappOne".into() => DappsSettings {
accounts: vec![1.into(), 2.into()],
accounts: Some(vec![1.into(), 2.into()]),
default: None,
}
]);
}
@ -422,7 +444,9 @@ mod tests {
let mut store = DappsSettingsStore::new(&path);
// Test default policy
assert_eq!(store.policy(), NewDappsPolicy::AllAccounts);
assert_eq!(store.policy(), NewDappsPolicy::AllAccounts {
default: 0.into(),
});
// when
store.set_policy(NewDappsPolicy::Whitelist(vec![1.into(), 2.into()]));

View File

@ -170,18 +170,30 @@ export default class Parity {
.execute('parity_generateSecretPhrase');
}
getDappsAddresses (dappId) {
getDappAddresses (dappId) {
return this._transport
.execute('parity_getDappsAddresses', dappId)
.execute('parity_getDappAddresses', dappId)
.then(outAddresses);
}
getNewDappsWhitelist () {
getDappDefaultAddress (dappId) {
return this._transport
.execute('parity_getNewDappsWhitelist')
.execute('parity_getDappDefaultAddress', dappId)
.then(outAddress);
}
getNewDappsAddresses () {
return this._transport
.execute('parity_getNewDappsAddresses')
.then((addresses) => addresses ? addresses.map(outAddress) : null);
}
getNewDappsDefaultAddress () {
return this._transport
.execute('parity_getNewDappsDefaultAddress')
.then(outAddress);
}
getVaultMeta (vaultName) {
return this._transport
.execute('parity_getVaultMeta', vaultName)
@ -391,9 +403,14 @@ export default class Parity {
.execute('parity_setAuthor', inAddress(address));
}
setDappsAddresses (dappId, addresses) {
setDappAddresses (dappId, addresses) {
return this._transport
.execute('parity_setDappsAddresses', dappId, inAddresses(addresses));
.execute('parity_setDappAddresses', dappId, inAddresses(addresses));
}
setDappDefaultAddress (dappId, address) {
return this._transport
.execute('parity_setDappDefaultAddress', dappId, address ? inAddress(address) : null);
}
setEngineSigner (address, password) {
@ -431,9 +448,14 @@ export default class Parity {
.execute('parity_setMode', mode);
}
setNewDappsWhitelist (addresses) {
setNewDappsAddresses (addresses) {
return this._transport
.execute('parity_setNewDappsWhitelist', addresses ? inAddresses(addresses) : null);
.execute('parity_setNewDappsAddresses', addresses ? inAddresses(addresses) : null);
}
setNewDappsDefaultAddress (address) {
return this._transport
.execute('parity_setNewDappsDefaultAddress', inAddress(address));
}
setTransactionsLimit (quantity) {

View File

@ -123,8 +123,10 @@ export default class Personal {
this._accountsInfo();
return;
case 'parity_setDappsAddresses':
case 'parity_setNewDappsWhitelist':
case 'parity_setDappAddresses':
case 'parity_setDappDefaultAddress':
case 'parity_setNewDappsAddresses':
case 'parity_setNewDappsDefaultAddress':
this._defaultAccount(true);
this._listAccounts();
return;

View File

@ -1186,9 +1186,9 @@ export default {
}
},
setDappsAddresses: {
setDappAddresses: {
subdoc: SUBDOC_ACCOUNTS,
desc: 'Sets the available addresses for a dapp.',
desc: 'Sets the available addresses for a dapp. When provided with non-empty list changes the default account as well.',
params: [
{
type: String,
@ -1197,7 +1197,7 @@ export default {
},
{
type: Array,
desc: 'Array of available accounts available to the dapp.',
desc: 'Array of available accounts available to the dapp or `null` for default list.',
example: ['0x407d73d8a49eeb85d32cf465507dd71d507100c1']
}
],
@ -1208,7 +1208,7 @@ export default {
}
},
getDappsAddresses: {
getDappAddresses: {
subdoc: SUBDOC_ACCOUNTS,
desc: 'Returns the list of accounts available to a specific dapp.',
params: [
@ -1225,13 +1225,52 @@ export default {
}
},
setNewDappsWhitelist: {
setDappDefaultAddress: {
subdoc: SUBDOC_ACCOUNTS,
desc: 'Changes dapp default address. Does not affect other accounts exposed for this dapp, but default account will always be retured as the first one.',
params: [
{
type: String,
desc: 'Dapp Id.',
example: 'web'
},
{
type: Address,
desc: 'Default Address.',
example: '0x407d73d8a49eeb85d32cf465507dd71d507100c1'
}
],
returns: {
type: Boolean,
desc: '`true` if the call was successful',
example: true
}
},
getDappDefaultAddress: {
subdoc: SUBDOC_ACCOUNTS,
desc: 'Returns a default account available to a specific dapp.',
params: [
{
type: String,
desc: 'Dapp Id.',
example: 'web'
}
],
returns: {
type: Address,
desc: 'Default Address',
example: '0x407d73d8a49eeb85d32cf465507dd71d507100c1'
}
},
setNewDappsAddresses: {
subdoc: SUBDOC_ACCOUNTS,
desc: 'Sets the list of accounts available to new dapps.',
params: [
{
type: Array,
desc: 'List of accounts available by default.',
desc: 'List of accounts available by default or `null` for all accounts.',
example: ['0x407d73d8a49eeb85d32cf465507dd71d507100c1']
}
],
@ -1242,7 +1281,7 @@ export default {
}
},
getNewDappsWhitelist: {
getNewDappsAddresses: {
subdoc: SUBDOC_ACCOUNTS,
desc: 'Returns the list of accounts available to a new dapps.',
params: [],
@ -1253,6 +1292,34 @@ export default {
}
},
setNewDappsDefaultAddress: {
subdoc: SUBDOC_ACCOUNTS,
desc: 'Changes global default address. This setting may be overriden for a specific dapp.',
params: [
{
type: Address,
desc: 'Default Address.',
example: '0x407d73d8a49eeb85d32cf465507dd71d507100c1'
}
],
returns: {
type: Boolean,
desc: '`true` if the call was successful',
example: true
}
},
getNewDappsDefaultAddress: {
subdoc: SUBDOC_ACCOUNTS,
desc: 'Returns a default account available to dapps.',
params: [],
returns: {
type: Address,
desc: 'Default Address',
example: '0x407d73d8a49eeb85d32cf465507dd71d507100c1'
}
},
listRecentDapps: {
subdoc: SUBDOC_ACCOUNTS,
desc: 'Returns a list of the most recent active dapps.',

View File

@ -102,7 +102,7 @@ export default class Store {
loadWhitelist () {
return this._api.parity
.getNewDappsWhitelist()
.getNewDappsAddresses()
.then((whitelist) => {
this.setWhitelist(whitelist);
})
@ -113,7 +113,7 @@ export default class Store {
updateWhitelist (whitelist) {
return this._api.parity
.setNewDappsWhitelist(whitelist)
.setNewDappsAddresses(whitelist)
.then(() => {
this.setWhitelist(whitelist);
})

View File

@ -31,8 +31,8 @@ let store;
function create () {
api = {
parity: {
getNewDappsWhitelist: sinon.stub().resolves(WHITELIST),
setNewDappsWhitelist: sinon.stub().resolves(true)
getNewDappsAddresses: sinon.stub().resolves(WHITELIST),
setNewDappsAddresses: sinon.stub().resolves(true)
}
};
@ -46,7 +46,7 @@ describe('modals/DappPermissions/store', () => {
describe('constructor', () => {
it('retrieves the whitelist via api', () => {
expect(api.parity.getNewDappsWhitelist).to.be.calledOnce;
expect(api.parity.getNewDappsAddresses).to.be.calledOnce;
});
it('sets the retrieved whitelist', () => {
@ -79,12 +79,12 @@ describe('modals/DappPermissions/store', () => {
store.closeModal();
});
it('calls setNewDappsWhitelist', () => {
expect(api.parity.setNewDappsWhitelist).to.have.been.calledOnce;
it('calls setNewDappsAddresses', () => {
expect(api.parity.setNewDappsAddresses).to.have.been.calledOnce;
});
it('has the default account in first position', () => {
expect(api.parity.setNewDappsWhitelist).to.have.been.calledWith(['789', '456']);
expect(api.parity.setNewDappsAddresses).to.have.been.calledWith(['789', '456']);
});
});

View File

@ -61,7 +61,7 @@ export default class AccountStore {
this.setDefaultAccount(address);
return this._api.parity
.setNewDappsWhitelist(accounts)
.setNewDappsAddresses(accounts)
.catch((error) => {
console.warn('makeDefaultAccount', error);
});
@ -78,7 +78,7 @@ export default class AccountStore {
return Promise
.all([
this._api.parity.getNewDappsWhitelist(),
this._api.parity.getNewDappsAddresses(),
this._api.parity.allAccountsInfo()
])
.then(([whitelist, accounts]) => {

View File

@ -76,8 +76,8 @@ describe('views/ParityBar/AccountStore', () => {
store.setAccounts.restore();
});
it('calls into parity_getNewDappsWhitelist', () => {
expect(api.parity.getNewDappsWhitelist).to.have.been.called;
it('calls into parity_getNewDappsAddresses', () => {
expect(api.parity.getNewDappsAddresses).to.have.been.called;
});
it('calls into parity_allAccountsInfo', () => {
@ -104,8 +104,8 @@ describe('views/ParityBar/AccountStore', () => {
return store.makeDefaultAccount(ACCOUNT_NEW);
});
it('calls into parity_setNewDappsWhitelist (with ordering)', () => {
expect(api.parity.setNewDappsWhitelist).to.have.been.calledWith([
it('calls into parity_setNewDappsAddresses (with ordering)', () => {
expect(api.parity.setNewDappsAddresses).to.have.been.calledWith([
ACCOUNT_NEW, ACCOUNT_FIRST, ACCOUNT_DEFAULT
]);
});

View File

@ -36,8 +36,8 @@ function createApi () {
parity: {
defaultAccount: sinon.stub().resolves(ACCOUNT_DEFAULT),
allAccountsInfo: sinon.stub().resolves(ACCOUNTS),
getNewDappsWhitelist: sinon.stub().resolves(null),
setNewDappsWhitelist: sinon.stub().resolves(true)
getNewDappsAddresses: sinon.stub().resolves(null),
setNewDappsAddresses: sinon.stub().resolves(true)
}
};

View File

@ -22,7 +22,9 @@ use hash;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct DappsSettings {
/// A list of accounts this Dapp can see.
pub accounts: Vec<hash::Address>,
pub accounts: Option<Vec<hash::Address>>,
/// Default account
pub default: Option<hash::Address>,
}
impl_serialization!(String => DappsSettings);
@ -40,7 +42,10 @@ impl_serialization!(String => DappsHistory);
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum NewDappsPolicy {
/// All accounts are exposed by default.
AllAccounts,
AllAccounts {
/// Default account, which should be returned as the first one.
default: hash::Address,
},
/// Only accounts listed here are exposed by default for new dapps.
Whitelist(Vec<hash::Address>),
}

View File

@ -221,7 +221,7 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> EthClient<C, SN, S, M, EM> where
let store = take_weak!(self.accounts);
store
.note_dapp_used(dapp.clone())
.and_then(|_| store.dapps_addresses(dapp))
.and_then(|_| store.dapp_addresses(dapp))
.map_err(|e| errors::internal("Could not fetch accounts.", e))
}
}
@ -308,10 +308,7 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> Eth for EthClient<C, SN, S, M, EM> where
let author = move || {
let mut miner = take_weak!(self.miner).author();
if miner == 0.into() {
let accounts = self.dapp_accounts(dapp.into())?;
if let Some(address) = accounts.get(0) {
miner = *address;
}
miner = self.dapp_accounts(dapp.into())?.get(0).cloned().unwrap_or_default();
}
Ok(RpcH160::from(miner))

View File

@ -187,7 +187,7 @@ impl Eth for EthClient {
let accounts = self.accounts
.note_dapp_used(dapp.clone())
.and_then(|_| self.accounts.dapps_addresses(dapp))
.and_then(|_| self.accounts.dapp_addresses(dapp))
.map_err(|e| errors::internal("Could not fetch accounts.", e))
.map(|accs| accs.into_iter().map(Into::<RpcH160>::into).collect());

View File

@ -18,7 +18,7 @@
use std::sync::{Arc, Weak};
use std::str::FromStr;
use std::collections::{BTreeMap, HashSet};
use futures::{self, Future, BoxFuture};
use futures::{future, Future, BoxFuture};
use util::{RotatingLogger, Address};
use util::misc::version_data;
@ -118,7 +118,7 @@ impl<C, M, S: ?Sized, U> Parity for ParityClient<C, M, S, U> where
let store = take_weak!(self.accounts);
let dapp_accounts = store
.note_dapp_used(dapp.clone().into())
.and_then(|_| store.dapps_addresses(dapp.into()))
.and_then(|_| store.dapp_addresses(dapp.into()))
.map_err(|e| errors::internal("Could not fetch accounts.", e))?
.into_iter().collect::<HashSet<_>>();
@ -146,16 +146,13 @@ impl<C, M, S: ?Sized, U> Parity for ParityClient<C, M, S, U> where
fn default_account(&self, meta: Self::Metadata) -> BoxFuture<H160, Error> {
let dapp_id = meta.dapp_id();
let default_account = move || {
Ok(take_weak!(self.accounts)
.dapps_addresses(dapp_id.into())
future::ok(
take_weakf!(self.accounts)
.dapp_default_address(dapp_id.into())
.map(Into::into)
.ok()
.and_then(|accounts| accounts.get(0).cloned())
.map(|acc| acc.into())
.unwrap_or_default())
};
futures::done(default_account()).boxed()
.unwrap_or_default()
).boxed()
}
fn transactions_limit(&self) -> Result<usize, Error> {

View File

@ -142,41 +142,69 @@ impl ParityAccounts for ParityAccountsClient {
Ok(true)
}
fn set_account_visibility(&self, _address: RpcH160, _dapp: RpcH256, _visible: bool) -> Result<bool, Error> {
Ok(false)
}
fn set_dapps_addresses(&self, dapp: DappId, addresses: Vec<RpcH160>) -> Result<bool, Error> {
fn set_dapp_addresses(&self, dapp: DappId, addresses: Option<Vec<RpcH160>>) -> Result<bool, Error> {
let store = take_weak!(self.accounts);
store.set_dapps_addresses(dapp.into(), into_vec(addresses))
store.set_dapp_addresses(dapp.into(), addresses.map(into_vec))
.map_err(|e| errors::account("Couldn't set dapp addresses.", e))
.map(|_| true)
}
fn dapp_addresses(&self, dapp: DappId) -> Result<Vec<RpcH160>, Error> {
let store = take_weak!(self.accounts);
store.dapp_addresses(dapp.into())
.map_err(|e| errors::account("Couldn't get dapp addresses.", e))
.map(into_vec)
}
fn set_dapp_default_address(&self, dapp: DappId, address: RpcH160) -> Result<bool, Error> {
let store = take_weak!(self.accounts);
store.set_dapp_default_address(dapp.into(), address.into())
.map_err(|e| errors::account("Couldn't set dapp default address.", e))
.map(|_| true)
}
fn dapp_default_address(&self, dapp: DappId) -> Result<RpcH160, Error> {
let store = take_weak!(self.accounts);
store.dapp_default_address(dapp.into())
.map_err(|e| errors::account("Couldn't get dapp default address.", e))
.map(Into::into)
}
fn set_new_dapps_addresses(&self, addresses: Option<Vec<RpcH160>>) -> Result<bool, Error> {
let store = take_weak!(self.accounts);
store
.set_new_dapps_addresses(addresses.map(into_vec))
.map_err(|e| errors::account("Couldn't set dapps addresses.", e))
.map(|_| true)
}
fn dapps_addresses(&self, dapp: DappId) -> Result<Vec<RpcH160>, Error> {
fn new_dapps_addresses(&self) -> Result<Option<Vec<RpcH160>>, Error> {
let store = take_weak!(self.accounts);
store.dapps_addresses(dapp.into())
store.new_dapps_addresses()
.map_err(|e| errors::account("Couldn't get dapps addresses.", e))
.map(into_vec)
.map(|accounts| accounts.map(into_vec))
}
fn set_new_dapps_whitelist(&self, whitelist: Option<Vec<RpcH160>>) -> Result<bool, Error> {
fn set_new_dapps_default_address(&self, address: RpcH160) -> Result<bool, Error> {
let store = take_weak!(self.accounts);
store
.set_new_dapps_whitelist(whitelist.map(into_vec))
.map_err(|e| errors::account("Couldn't set dapps whitelist.", e))
store.set_new_dapps_default_address(address.into())
.map_err(|e| errors::account("Couldn't set new dapps default address.", e))
.map(|_| true)
}
fn new_dapps_whitelist(&self) -> Result<Option<Vec<RpcH160>>, Error> {
fn new_dapps_default_address(&self) -> Result<RpcH160, Error> {
let store = take_weak!(self.accounts);
store.new_dapps_whitelist()
.map_err(|e| errors::account("Couldn't get dapps whitelist.", e))
.map(|accounts| accounts.map(into_vec))
store.new_dapps_default_address()
.map_err(|e| errors::account("Couldn't get new dapps default address.", e))
.map(Into::into)
}
fn recent_dapps(&self) -> Result<BTreeMap<DappId, u64>, Error> {

View File

@ -101,7 +101,7 @@ impl<D: Dispatcher + 'static> Personal for PersonalClient<D> {
let default = match request.from.as_ref() {
Some(account) => Ok(account.clone().into()),
None => accounts
.default_address(meta.dapp_id().into())
.dapp_default_address(meta.dapp_id().into())
.map_err(|e| errors::account("Cannot find default account.", e)),
};

View File

@ -86,7 +86,7 @@ impl<D: Dispatcher + 'static> SigningQueueClient<D> {
let accounts = take_weakf!(self.accounts);
let default_account = match default_account {
DefaultAccount::Provided(acc) => acc,
DefaultAccount::ForDapp(dapp) => accounts.default_address(dapp).ok().unwrap_or_default(),
DefaultAccount::ForDapp(dapp) => accounts.dapp_default_address(dapp).ok().unwrap_or_default(),
};
let dispatcher = self.dispatcher.clone();

View File

@ -55,7 +55,7 @@ impl<D: Dispatcher + 'static> SigningUnsafeClient<D> {
let accounts = take_weakf!(self.accounts);
let default = match account {
DefaultAccount::Provided(acc) => acc,
DefaultAccount::ForDapp(dapp) => accounts.default_address(dapp).ok().unwrap_or_default(),
DefaultAccount::ForDapp(dapp) => accounts.dapp_default_address(dapp).ok().unwrap_or_default(),
};
let dis = self.dispatcher.clone();

View File

@ -368,7 +368,7 @@ fn rpc_eth_gas_price() {
fn rpc_eth_accounts() {
let tester = EthTester::default();
let address = tester.accounts_provider.new_account("").unwrap();
tester.accounts_provider.set_new_dapps_whitelist(None).unwrap();
tester.accounts_provider.set_new_dapps_addresses(None).unwrap();
tester.accounts_provider.set_address_name(1.into(), "1".into());
tester.accounts_provider.set_address_name(10.into(), "10".into());
@ -377,14 +377,14 @@ fn rpc_eth_accounts() {
let response = r#"{"jsonrpc":"2.0","result":[""#.to_owned() + &format!("0x{:?}", address) + r#""],"id":1}"#;
assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned()));
tester.accounts_provider.set_new_dapps_whitelist(Some(vec![1.into()])).unwrap();
tester.accounts_provider.set_new_dapps_addresses(Some(vec![1.into()])).unwrap();
// even with some account it should return empty list (no dapp detected)
let request = r#"{"jsonrpc": "2.0", "method": "eth_accounts", "params": [], "id": 1}"#;
let response = r#"{"jsonrpc":"2.0","result":["0x0000000000000000000000000000000000000001"],"id":1}"#;
assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned()));
// when we add visible address it should return that.
tester.accounts_provider.set_dapps_addresses("app1".into(), vec![10.into()]).unwrap();
tester.accounts_provider.set_dapp_addresses("app1".into(), Some(vec![10.into()])).unwrap();
let request = r#"{"jsonrpc": "2.0", "method": "eth_accounts", "params": [], "id": 1}"#;
let response = r#"{"jsonrpc":"2.0","result":["0x000000000000000000000000000000000000000a"],"id":1}"#;
let mut meta = Metadata::default();

View File

@ -110,17 +110,19 @@ fn rpc_parity_accounts_info() {
assert_eq!(accounts.len(), 1);
let address = accounts[0];
deps.accounts.set_account_name(address.clone(), "Test".to_owned()).unwrap();
deps.accounts.set_account_meta(address.clone(), "{foo: 69}".to_owned()).unwrap();
deps.accounts.set_address_name(1.into(), "XX".into());
deps.accounts.set_account_name(address.clone(), "Test".into()).unwrap();
deps.accounts.set_account_meta(address.clone(), "{foo: 69}".into()).unwrap();
let request = r#"{"jsonrpc": "2.0", "method": "parity_accountsInfo", "params": [], "id": 1}"#;
let response = format!("{{\"jsonrpc\":\"2.0\",\"result\":{{\"0x{}\":{{\"name\":\"Test\"}}}},\"id\":1}}", address.hex());
assert_eq!(io.handle_request_sync(request), Some(response));
// Change the whitelist
deps.accounts.set_new_dapps_whitelist(Some(vec![1.into()])).unwrap();
let address = Address::from(1);
deps.accounts.set_new_dapps_addresses(Some(vec![address.clone()])).unwrap();
let request = r#"{"jsonrpc": "2.0", "method": "parity_accountsInfo", "params": [], "id": 1}"#;
let response = format!("{{\"jsonrpc\":\"2.0\",\"result\":{{}},\"id\":1}}");
let response = format!("{{\"jsonrpc\":\"2.0\",\"result\":{{\"0x{}\":{{\"name\":\"XX\"}}}},\"id\":1}}", address.hex());
assert_eq!(io.handle_request_sync(request), Some(response));
}

View File

@ -125,48 +125,87 @@ fn rpc_parity_set_and_get_dapps_accounts() {
// given
let tester = setup();
tester.accounts.set_address_name(10.into(), "10".into());
assert_eq!(tester.accounts.dapps_addresses("app1".into()).unwrap(), vec![]);
assert_eq!(tester.accounts.dapp_addresses("app1".into()).unwrap(), vec![]);
// when
let request = r#"{"jsonrpc": "2.0", "method": "parity_setDappsAddresses","params":["app1",["0x000000000000000000000000000000000000000a","0x0000000000000000000000000000000000000001"]], "id": 1}"#;
let request = r#"{"jsonrpc": "2.0", "method": "parity_setDappAddresses","params":["app1",["0x000000000000000000000000000000000000000a","0x0000000000000000000000000000000000000001"]], "id": 1}"#;
let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#;
assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned()));
// then
assert_eq!(tester.accounts.dapps_addresses("app1".into()).unwrap(), vec![10.into()]);
let request = r#"{"jsonrpc": "2.0", "method": "parity_getDappsAddresses","params":["app1"], "id": 1}"#;
assert_eq!(tester.accounts.dapp_addresses("app1".into()).unwrap(), vec![10.into()]);
let request = r#"{"jsonrpc": "2.0", "method": "parity_getDappAddresses","params":["app1"], "id": 1}"#;
let response = r#"{"jsonrpc":"2.0","result":["0x000000000000000000000000000000000000000a"],"id":1}"#;
assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned()));
}
#[test]
fn rpc_parity_set_and_get_dapp_default_address() {
// given
let tester = setup();
tester.accounts.set_address_name(10.into(), "10".into());
assert_eq!(tester.accounts.dapp_addresses("app1".into()).unwrap(), vec![]);
// when
let request = r#"{"jsonrpc": "2.0", "method": "parity_setDappDefaultAddress","params":["app1", "0x000000000000000000000000000000000000000a"], "id": 1}"#;
let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#;
assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned()));
// then
assert_eq!(tester.accounts.dapp_addresses("app1".into()).unwrap(), vec![10.into()]);
let request = r#"{"jsonrpc": "2.0", "method": "parity_getDappDefaultAddress","params":["app1"], "id": 1}"#;
let response = r#"{"jsonrpc":"2.0","result":"0x000000000000000000000000000000000000000a","id":1}"#;
assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned()));
}
#[test]
fn rpc_parity_set_and_get_new_dapps_whitelist() {
// given
let tester = setup();
// when set to whitelist
let request = r#"{"jsonrpc": "2.0", "method": "parity_setNewDappsWhitelist","params":[["0x000000000000000000000000000000000000000a"]], "id": 1}"#;
let request = r#"{"jsonrpc": "2.0", "method": "parity_setNewDappsAddresses","params":[["0x000000000000000000000000000000000000000a"]], "id": 1}"#;
let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#;
assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned()));
// then
assert_eq!(tester.accounts.new_dapps_whitelist().unwrap(), Some(vec![10.into()]));
let request = r#"{"jsonrpc": "2.0", "method": "parity_getNewDappsWhitelist","params":[], "id": 1}"#;
assert_eq!(tester.accounts.new_dapps_addresses().unwrap(), Some(vec![10.into()]));
let request = r#"{"jsonrpc": "2.0", "method": "parity_getNewDappsAddresses","params":[], "id": 1}"#;
let response = r#"{"jsonrpc":"2.0","result":["0x000000000000000000000000000000000000000a"],"id":1}"#;
assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned()));
// when set to empty
let request = r#"{"jsonrpc": "2.0", "method": "parity_setNewDappsWhitelist","params":[null], "id": 1}"#;
let request = r#"{"jsonrpc": "2.0", "method": "parity_setNewDappsAddresses","params":[null], "id": 1}"#;
let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#;
assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned()));
// then
assert_eq!(tester.accounts.new_dapps_whitelist().unwrap(), None);
let request = r#"{"jsonrpc": "2.0", "method": "parity_getNewDappsWhitelist","params":[], "id": 1}"#;
assert_eq!(tester.accounts.new_dapps_addresses().unwrap(), None);
let request = r#"{"jsonrpc": "2.0", "method": "parity_getNewDappsAddresses","params":[], "id": 1}"#;
let response = r#"{"jsonrpc":"2.0","result":null,"id":1}"#;
assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned()));
}
#[test]
fn rpc_parity_set_and_get_new_dapps_default_address() {
// given
let tester = setup();
tester.accounts.set_address_name(10.into(), "10".into());
assert_eq!(tester.accounts.new_dapps_default_address().unwrap(), 0.into());
// when
let request = r#"{"jsonrpc": "2.0", "method": "parity_setNewDappsDefaultAddress","params":["0x000000000000000000000000000000000000000a"], "id": 1}"#;
let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#;
assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned()));
// then
assert_eq!(tester.accounts.new_dapps_default_address().unwrap(), 10.into());
let request = r#"{"jsonrpc": "2.0", "method": "parity_getNewDappsDefaultAddress","params":[], "id": 1}"#;
let response = r#"{"jsonrpc":"2.0","result":"0x000000000000000000000000000000000000000a","id":1}"#;
assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned()));
}
#[test]
fn rpc_parity_recent_dapps() {
// given

View File

@ -70,28 +70,51 @@ build_rpc_trait! {
#[rpc(name = "parity_setAccountMeta")]
fn set_account_meta(&self, H160, String) -> Result<bool, Error>;
/// Sets account visibility.
/// @unimplemented
#[rpc(name = "parity_setAccountVisiblity")]
fn set_account_visibility(&self, H160, H256, bool) -> Result<bool, Error>;
/// Sets accounts exposed for particular dapp.
#[rpc(name = "parity_setDappsAddresses")]
fn set_dapps_addresses(&self, DappId, Vec<H160>) -> Result<bool, Error>;
/// Sets addresses exposed for particular dapp.
/// Setting a non-empty list will also override default account.
/// Setting `None` will resets visible account to what's visible for new dapps
/// (does not affect default account though)
#[rpc(name = "parity_setDappAddresses")]
fn set_dapp_addresses(&self, DappId, Option<Vec<H160>>) -> Result<bool, Error>;
/// Gets accounts exposed for particular dapp.
#[rpc(name = "parity_getDappsAddresses")]
fn dapps_addresses(&self, DappId) -> Result<Vec<H160>, Error>;
#[rpc(name = "parity_getDappAddresses")]
fn dapp_addresses(&self, DappId) -> Result<Vec<H160>, Error>;
/// Changes dapp default address.
/// Does not affect other accounts exposed for this dapp, but
/// default account will always be retured as the first one.
#[rpc(name = "parity_setDappDefaultAddress")]
fn set_dapp_default_address(&self, DappId, H160) -> Result<bool, Error>;
/// Returns current dapp default address.
/// If not set explicite for the dapp will return global default.
#[rpc(name = "parity_getDappDefaultAddress")]
fn dapp_default_address(&self, DappId) -> Result<H160, Error>;
/// Sets accounts exposed for new dapps.
/// `None` means that all accounts will be exposed.
#[rpc(name = "parity_setNewDappsWhitelist")]
fn set_new_dapps_whitelist(&self, Option<Vec<H160>>) -> Result<bool, Error>;
/// Setting a non-empty list will also override default account.
/// Setting `None` exposes all internal-managed accounts.
/// (does not affect default account though)
#[rpc(name = "parity_setNewDappsAddresses")]
fn set_new_dapps_addresses(&self, Option<Vec<H160>>) -> Result<bool, Error>;
/// Gets accounts exposed for new dapps.
/// `None` means that all accounts will be exposed.
#[rpc(name = "parity_getNewDappsWhitelist")]
fn new_dapps_whitelist(&self) -> Result<Option<Vec<H160>>, Error>;
/// `None` means that all accounts are exposes.
#[rpc(name = "parity_getNewDappsAddresses")]
fn new_dapps_addresses(&self) -> Result<Option<Vec<H160>>, Error>;
/// Changes default address for new dapps (global default address)
/// Does not affect other accounts exposed for new dapps, but
/// default account will always be retured as the first one.
#[rpc(name = "parity_setNewDappsDefaultAddress")]
fn set_new_dapps_default_address(&self, H160) -> Result<bool, Error>;
/// Returns current default address for new dapps (global default address)
/// In case it's not set explicite will return first available account.
/// If no accounts are available will return `0x0`
#[rpc(name = "parity_getNewDappsDefaultAddress")]
fn new_dapps_default_address(&self) -> Result<H160, Error>;
/// Returns identified dapps that recently used RPC
/// Includes last usage timestamp.