Add 'addPermittedAccount' method to permissions controller (#8344)

This method adds the given account to the given origin's list of
exposed accounts. This method is not yet used, but it will be in
subsequent PRs (e.g. #8312)

This method has been added to the background API, and a wrapper action
creator has been written as well.
feature/default_network_editable
Mark Stacey 5 years ago committed by GitHub
parent 1f49772ca3
commit 15616a33ca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 50
      app/scripts/controllers/permissions/index.js
  2. 1
      app/scripts/metamask-controller.js
  3. 18
      test/unit/app/controllers/permissions/mocks.js
  4. 87
      test/unit/app/controllers/permissions/permissions-controller-test.js
  5. 14
      ui/app/store/actions.js

@ -306,6 +306,40 @@ export class PermissionsController {
} }
} }
/**
* Expose an account to the given origin. Changes the eth_accounts
* permissions and emits accountsChanged.
*
* Throws error if the origin or account is invalid, or if the update fails.
*
* @param {string} origin - The origin to expose the account to.
* @param {string} account - The new account to expose.
*/
async addPermittedAccount (origin, account) {
const domains = this.permissions.getDomains()
if (!domains[origin]) {
throw new Error('Unrecognized domain')
}
this.validatePermittedAccounts([account])
const oldPermittedAccounts = this._getPermittedAccounts(origin)
if (!oldPermittedAccounts) {
throw new Error('Origin does not have \'eth_accounts\' permission')
} else if (oldPermittedAccounts.includes(account)) {
throw new Error('Account is already permitted')
}
this.permissions.updateCaveatFor(
origin, 'eth_accounts', CAVEAT_NAMES.exposedAccounts, [...oldPermittedAccounts, account]
)
const permittedAccounts = await this.getAccounts(origin)
this.notifyDomain(origin, {
method: NOTIFICATION_NAMES.accountsChanged,
result: permittedAccounts,
})
}
/** /**
* Finalizes a permissions request. Throws if request validation fails. * Finalizes a permissions request. Throws if request validation fails.
* Clones the passed-in parameters to prevent inadvertent modification. * Clones the passed-in parameters to prevent inadvertent modification.
@ -419,6 +453,22 @@ export class PermissionsController {
}) })
} }
/**
* Get current set of permitted accounts for the given origin
*
* @param {string} origin - The origin to obtain permitted accounts for
* @returns {Array<string>|null} The list of permitted accounts
*/
_getPermittedAccounts (origin) {
const permittedAccounts = this.permissions
.getPermission(origin, 'eth_accounts')
?.caveats
?.find((caveat) => caveat.name === CAVEAT_NAMES.exposedAccounts)
?.value
return permittedAccounts || null
}
/** /**
* When a new account is selected in the UI, emit accountsChanged to each origin * When a new account is selected in the UI, emit accountsChanged to each origin
* where the selected account is exposed. * where the selected account is exposed.

@ -576,6 +576,7 @@ export default class MetamaskController extends EventEmitter {
getApprovedAccounts: nodeify(permissionsController.getAccounts.bind(permissionsController)), getApprovedAccounts: nodeify(permissionsController.getAccounts.bind(permissionsController)),
rejectPermissionsRequest: nodeify(permissionsController.rejectPermissionsRequest, permissionsController), rejectPermissionsRequest: nodeify(permissionsController.rejectPermissionsRequest, permissionsController),
removePermissionsFor: permissionsController.removePermissionsFor.bind(permissionsController), removePermissionsFor: permissionsController.removePermissionsFor.bind(permissionsController),
addPermittedAccount: nodeify(permissionsController.addPermittedAccount, permissionsController),
legacyExposeAccounts: nodeify(permissionsController.legacyExposeAccounts, permissionsController), legacyExposeAccounts: nodeify(permissionsController.legacyExposeAccounts, permissionsController),
getRequestAccountTabIds: (cb) => cb(null, this.getRequestAccountTabIds()), getRequestAccountTabIds: (cb) => cb(null, this.getRequestAccountTabIds()),

@ -330,6 +330,24 @@ export const getters = deepFreeze({
}, },
}, },
addPermittedAccount: {
alreadyPermitted: () => {
return {
message: 'Account is already permitted',
}
},
invalidOrigin: () => {
return {
message: 'Unrecognized domain',
}
},
noEthAccountsPermission: () => {
return {
message: 'Origin does not have \'eth_accounts\' permission',
}
},
},
legacyExposeAccounts: { legacyExposeAccounts: {
badOrigin: () => { badOrigin: () => {
return { return {

@ -461,6 +461,93 @@ describe('permissions controller', function () {
}) })
}) })
describe('addPermittedAccount', function () {
let permController, notifications
beforeEach(function () {
notifications = initNotifications()
permController = initPermController(notifications)
grantPermissions(
permController, ORIGINS.a,
PERMS.finalizedRequests.eth_accounts(ACCOUNT_ARRAYS.a)
)
grantPermissions(
permController, ORIGINS.b,
PERMS.finalizedRequests.eth_accounts(ACCOUNT_ARRAYS.b)
)
})
it('should throw if account is not a string', async function () {
await assert.rejects(
() => permController.addPermittedAccount(ORIGINS.a, {}),
ERRORS.validatePermittedAccounts.nonKeyringAccount({}),
'should throw on non-string account param'
)
})
it('should throw if given account is not in keyring', async function () {
await assert.rejects(
() => permController.addPermittedAccount(ORIGINS.a, DUMMY_ACCOUNT),
ERRORS.validatePermittedAccounts.nonKeyringAccount(DUMMY_ACCOUNT),
'should throw on non-keyring account'
)
})
it('should throw if origin is invalid', async function () {
await assert.rejects(
() => permController.addPermittedAccount(false, EXTRA_ACCOUNT),
ERRORS.addPermittedAccount.invalidOrigin(),
'should throw on invalid origin'
)
})
it('should throw if origin lacks any permissions', async function () {
await assert.rejects(
() => permController.addPermittedAccount(ORIGINS.c, EXTRA_ACCOUNT),
ERRORS.addPermittedAccount.invalidOrigin(),
'should throw on origin without permissions'
)
})
it('should throw if origin lacks eth_accounts permission', async function () {
grantPermissions(
permController, ORIGINS.c,
PERMS.finalizedRequests.test_method()
)
await assert.rejects(
() => permController.addPermittedAccount(ORIGINS.c, EXTRA_ACCOUNT),
ERRORS.addPermittedAccount.noEthAccountsPermission(),
'should throw on origin without eth_accounts permission'
)
})
it('should throw if account is already permitted', async function () {
await assert.rejects(
() => permController.addPermittedAccount(ORIGINS.a, ACCOUNT_ARRAYS.c[0]),
ERRORS.addPermittedAccount.alreadyPermitted(),
'should throw if account is already permitted'
)
})
it('should successfully add permitted account', async function () {
await permController.addPermittedAccount(ORIGINS.a, EXTRA_ACCOUNT)
const accounts = await permController.getAccounts(ORIGINS.a)
assert.deepEqual(
accounts, [...ACCOUNT_ARRAYS.a, EXTRA_ACCOUNT],
'origin should have correct accounts'
)
assert.deepEqual(
notifications[ORIGINS.a][0],
NOTIFICATIONS.newAccounts([...ACCOUNT_ARRAYS.a, EXTRA_ACCOUNT]),
'origin should have correct notification'
)
})
})
describe('finalizePermissionsRequest', function () { describe('finalizePermissionsRequest', function () {
let permController let permController

@ -1227,6 +1227,20 @@ export function showAccountDetail (address) {
} }
} }
export function addPermittedAccount (origin, address) {
return async (dispatch) => {
await new Promise((resolve, reject) => {
background.addPermittedAccount(origin, address, (error) => {
if (error) {
return reject(error)
}
resolve()
})
})
await forceUpdateMetamaskState(dispatch)
}
}
export function showAccountsPage () { export function showAccountsPage () {
return { return {
type: actionConstants.SHOW_ACCOUNTS_PAGE, type: actionConstants.SHOW_ACCOUNTS_PAGE,

Loading…
Cancel
Save