1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00
metamask-extension/test/unit/app/controllers/permissions/permissions-controller-test.js
Erik Marks a2a51e78d1
Add PermissionsController.removePermittedAccount (#8354)
* add PermissionsController.removePermittedAccount and corresponding UI action

* remove eth_accounts permission on removing last account
2020-04-20 11:34:56 -07:00

1373 lines
41 KiB
JavaScript

import { strict as assert } from 'assert'
import { find } from 'lodash'
import nanoid from 'nanoid'
import sinon from 'sinon'
import {
METADATA_STORE_KEY,
WALLET_PREFIX,
} from '../../../../../app/scripts/controllers/permissions/enums'
import {
PermissionsController,
addInternalMethodPrefix,
} from '../../../../../app/scripts/controllers/permissions'
import {
grantPermissions,
} from './helpers'
import {
noop,
constants,
getters,
getNotifyDomain,
getNotifyAllDomains,
getPermControllerOpts,
} from './mocks'
const {
ERRORS,
NOTIFICATIONS,
PERMS,
} = getters
const {
ALL_ACCOUNTS,
ACCOUNT_ARRAYS,
DUMMY_ACCOUNT,
ORIGINS,
PERM_NAMES,
REQUEST_IDS,
EXTRA_ACCOUNT,
} = constants
const initNotifications = () => {
return Object.values(ORIGINS).reduce((acc, domain) => {
acc[domain] = []
return acc
}, {})
}
const initPermController = (notifications = initNotifications()) => {
return new PermissionsController({
...getPermControllerOpts(),
notifyDomain: getNotifyDomain(notifications),
notifyAllDomains: getNotifyAllDomains(notifications),
})
}
const getMockRequestUserApprovalFunction = (permController) => (id, origin) => {
return new Promise((resolve, reject) => {
permController.pendingApprovals.set(id, { origin, resolve, reject })
})
}
describe('permissions controller', function () {
describe('getAccounts', function () {
let permController
beforeEach(function () {
permController = initPermController()
grantPermissions(
permController, ORIGINS.a,
PERMS.finalizedRequests.eth_accounts(ACCOUNT_ARRAYS.a)
)
grantPermissions(
permController, ORIGINS.b,
PERMS.finalizedRequests.eth_accounts(ACCOUNT_ARRAYS.b)
)
})
it('gets permitted accounts for permitted origins', async function () {
const aAccounts = await permController.getAccounts(ORIGINS.a)
const bAccounts = await permController.getAccounts(ORIGINS.b)
assert.deepEqual(
aAccounts, ACCOUNT_ARRAYS.a,
'first origin should have correct accounts'
)
assert.deepEqual(
bAccounts, ACCOUNT_ARRAYS.b,
'second origin should have correct accounts'
)
})
it('does not get accounts for unpermitted origins', async function () {
const cAccounts = await permController.getAccounts(ORIGINS.c)
assert.deepEqual(cAccounts, [], 'origin should have no accounts')
})
it('does not handle "MetaMask" origin as special case', async function () {
const metamaskAccounts = await permController.getAccounts('MetaMask')
assert.deepEqual(metamaskAccounts, [], 'origin should have no accounts')
})
})
describe('hasPermission', function () {
it('returns correct values', async function () {
const permController = initPermController()
grantPermissions(
permController, ORIGINS.a,
PERMS.finalizedRequests.eth_accounts(ACCOUNT_ARRAYS.a)
)
grantPermissions(
permController, ORIGINS.b,
PERMS.finalizedRequests.test_method()
)
assert.ok(
permController.hasPermission(ORIGINS.a, 'eth_accounts'),
'should return true for granted permission'
)
assert.ok(
permController.hasPermission(ORIGINS.b, 'test_method'),
'should return true for granted permission'
)
assert.ok(
!permController.hasPermission(ORIGINS.a, 'test_method'),
'should return false for non-granted permission'
)
assert.ok(
!permController.hasPermission(ORIGINS.b, 'eth_accounts'),
'should return true for non-granted permission'
)
assert.ok(
!permController.hasPermission('foo', 'eth_accounts'),
'should return false for unknown origin'
)
assert.ok(
!permController.hasPermission(ORIGINS.b, 'foo'),
'should return false for unknown permission'
)
})
})
describe('clearPermissions', function () {
it('notifies all appropriate domains and removes permissions', async function () {
const notifications = initNotifications()
const 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)
)
grantPermissions(
permController, ORIGINS.c,
PERMS.finalizedRequests.eth_accounts(ACCOUNT_ARRAYS.c)
)
let aAccounts = await permController.getAccounts(ORIGINS.a)
let bAccounts = await permController.getAccounts(ORIGINS.b)
let cAccounts = await permController.getAccounts(ORIGINS.c)
assert.deepEqual(
aAccounts, ACCOUNT_ARRAYS.a,
'first origin should have correct accounts'
)
assert.deepEqual(
bAccounts, ACCOUNT_ARRAYS.b,
'second origin should have correct accounts'
)
assert.deepEqual(
cAccounts, ACCOUNT_ARRAYS.c,
'third origin should have correct accounts'
)
permController.clearPermissions()
Object.keys(notifications).forEach((origin) => {
assert.deepEqual(
notifications[origin],
[ NOTIFICATIONS.removedAccounts() ],
'origin should have single wallet_accountsChanged:[] notification',
)
})
aAccounts = await permController.getAccounts(ORIGINS.a)
bAccounts = await permController.getAccounts(ORIGINS.b)
cAccounts = await permController.getAccounts(ORIGINS.c)
assert.deepEqual(aAccounts, [], 'first origin should have no accounts')
assert.deepEqual(bAccounts, [], 'second origin should have no accounts')
assert.deepEqual(cAccounts, [], 'third origin should have no accounts')
Object.keys(notifications).forEach((origin) => {
assert.deepEqual(
permController.permissions.getPermissionsForDomain(origin),
[],
'origin should have no permissions'
)
})
assert.deepEqual(
Object.keys(permController.permissions.getDomains()), [],
'all domains should be deleted'
)
})
})
describe('removePermissionsFor', 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('removes permissions for multiple domains', async function () {
let aAccounts = await permController.getAccounts(ORIGINS.a)
let bAccounts = await permController.getAccounts(ORIGINS.b)
assert.deepEqual(
aAccounts, ACCOUNT_ARRAYS.a,
'first origin should have correct accounts'
)
assert.deepEqual(
bAccounts, ACCOUNT_ARRAYS.b,
'second origin should have correct accounts'
)
permController.removePermissionsFor({
[ORIGINS.a]: [PERM_NAMES.eth_accounts],
[ORIGINS.b]: [PERM_NAMES.eth_accounts],
})
aAccounts = await permController.getAccounts(ORIGINS.a)
bAccounts = await permController.getAccounts(ORIGINS.b)
assert.deepEqual(aAccounts, [], 'first origin should have no accounts')
assert.deepEqual(bAccounts, [], 'second origin should have no accounts')
assert.deepEqual(
notifications[ORIGINS.a], [NOTIFICATIONS.removedAccounts()],
'first origin should have correct notification'
)
assert.deepEqual(
notifications[ORIGINS.b], [NOTIFICATIONS.removedAccounts()],
'second origin should have correct notification'
)
assert.deepEqual(
Object.keys(permController.permissions.getDomains()), [],
'all domains should be deleted'
)
})
it('only removes targeted permissions from single domain', async function () {
grantPermissions(
permController, ORIGINS.b, PERMS.finalizedRequests.test_method()
)
let bPermissions = permController.permissions.getPermissionsForDomain(ORIGINS.b)
assert.ok(
(
bPermissions.length === 2 &&
find(bPermissions, { parentCapability: PERM_NAMES.eth_accounts }) &&
find(bPermissions, { parentCapability: PERM_NAMES.test_method })
),
'origin should have correct permissions'
)
permController.removePermissionsFor({
[ORIGINS.b]: [PERM_NAMES.test_method],
})
bPermissions = permController.permissions.getPermissionsForDomain(ORIGINS.b)
assert.ok(
(
bPermissions.length === 1 &&
find(bPermissions, { parentCapability: PERM_NAMES.eth_accounts })
),
'only targeted permission should have been removed'
)
})
it('removes permissions for a single domain, without affecting another', async function () {
permController.removePermissionsFor({
[ORIGINS.b]: [PERM_NAMES.eth_accounts],
})
const aAccounts = await permController.getAccounts(ORIGINS.a)
const bAccounts = await permController.getAccounts(ORIGINS.b)
assert.deepEqual(
aAccounts, ACCOUNT_ARRAYS.a,
'first origin should have correct accounts'
)
assert.deepEqual(bAccounts, [], 'second origin should have no accounts')
assert.deepEqual(
notifications[ORIGINS.a], [],
'first origin should have no notifications'
)
assert.deepEqual(
notifications[ORIGINS.b], [NOTIFICATIONS.removedAccounts()],
'second origin should have correct notification'
)
assert.deepEqual(
Object.keys(permController.permissions.getDomains()), [ORIGINS.a],
'only first origin should remain'
)
})
it('send notification but does not affect permissions for unknown domain', async function () {
// it knows nothing of this origin
permController.removePermissionsFor({
[ORIGINS.c]: [PERM_NAMES.eth_accounts],
})
assert.deepEqual(
notifications[ORIGINS.c], [NOTIFICATIONS.removedAccounts()],
'unknown origin should have notification'
)
const aAccounts = await permController.getAccounts(ORIGINS.a)
const bAccounts = await permController.getAccounts(ORIGINS.b)
assert.deepEqual(
aAccounts, ACCOUNT_ARRAYS.a,
'first origin should have correct accounts'
)
assert.deepEqual(
bAccounts, ACCOUNT_ARRAYS.b,
'second origin should have correct accounts'
)
assert.deepEqual(
Object.keys(permController.permissions.getDomains()),
[ORIGINS.a, ORIGINS.b],
'should have correct domains'
)
})
})
describe('validatePermittedAccounts', function () {
let permController
beforeEach(function () {
permController = initPermController()
grantPermissions(
permController, ORIGINS.a,
PERMS.finalizedRequests.eth_accounts(ACCOUNT_ARRAYS.a)
)
grantPermissions(
permController, ORIGINS.b,
PERMS.finalizedRequests.eth_accounts(ACCOUNT_ARRAYS.b)
)
})
it('throws error on non-array accounts', async function () {
await assert.throws(
() => permController.validatePermittedAccounts(undefined),
ERRORS.validatePermittedAccounts.invalidParam(),
'should throw on undefined'
)
await assert.throws(
() => permController.validatePermittedAccounts(false),
ERRORS.validatePermittedAccounts.invalidParam(),
'should throw on false'
)
await assert.throws(
() => permController.validatePermittedAccounts(true),
ERRORS.validatePermittedAccounts.invalidParam(),
'should throw on true'
)
await assert.throws(
() => permController.validatePermittedAccounts({}),
ERRORS.validatePermittedAccounts.invalidParam(),
'should throw on non-array object'
)
})
it('throws error on empty array of accounts', async function () {
await assert.throws(
() => permController.validatePermittedAccounts([]),
ERRORS.validatePermittedAccounts.invalidParam(),
'should throw on empty array'
)
})
it('throws error if any account value is not in keyring', async function () {
const keyringAccounts = await permController.getKeyringAccounts()
await assert.throws(
() => permController.validatePermittedAccounts([DUMMY_ACCOUNT]),
ERRORS.validatePermittedAccounts.nonKeyringAccount(DUMMY_ACCOUNT),
'should throw on non-keyring account'
)
await assert.throws(
() => permController.validatePermittedAccounts(keyringAccounts.concat(DUMMY_ACCOUNT)),
ERRORS.validatePermittedAccounts.nonKeyringAccount(DUMMY_ACCOUNT),
'should throw on non-keyring account with other accounts'
)
})
it('succeeds if all accounts are in keyring', async function () {
const keyringAccounts = await permController.getKeyringAccounts()
await assert.doesNotThrow(
() => permController.validatePermittedAccounts(keyringAccounts),
'should not throw on all keyring accounts'
)
await assert.doesNotThrow(
() => permController.validatePermittedAccounts([ keyringAccounts[0] ]),
'should not throw on single keyring account'
)
await assert.doesNotThrow(
() => permController.validatePermittedAccounts([ keyringAccounts[1] ]),
'should not throw on single keyring account'
)
})
})
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.a[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('removePermittedAccount', 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.removePermittedAccount(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.removePermittedAccount(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.removePermittedAccount(false, EXTRA_ACCOUNT),
ERRORS.removePermittedAccount.invalidOrigin(),
'should throw on invalid origin'
)
})
it('should throw if origin lacks any permissions', async function () {
await assert.rejects(
() => permController.removePermittedAccount(ORIGINS.c, EXTRA_ACCOUNT),
ERRORS.removePermittedAccount.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.removePermittedAccount(ORIGINS.c, EXTRA_ACCOUNT),
ERRORS.removePermittedAccount.noEthAccountsPermission(),
'should throw on origin without eth_accounts permission'
)
})
it('should throw if account is not permitted', async function () {
await assert.rejects(
() => permController.removePermittedAccount(ORIGINS.b, ACCOUNT_ARRAYS.c[0]),
ERRORS.removePermittedAccount.notPermitted(),
'should throw if account is not permitted'
)
})
it('should successfully remove permitted account', async function () {
await permController.removePermittedAccount(ORIGINS.a, ACCOUNT_ARRAYS.a[1])
const accounts = await permController.getAccounts(ORIGINS.a)
assert.deepEqual(
accounts, ACCOUNT_ARRAYS.a.filter((acc) => acc !== ACCOUNT_ARRAYS.a[1]),
'origin should have correct accounts'
)
assert.deepEqual(
notifications[ORIGINS.a][0],
NOTIFICATIONS.newAccounts(ACCOUNT_ARRAYS.a.filter((acc) => acc !== ACCOUNT_ARRAYS.a[1])),
'origin should have correct notification'
)
})
it('should remove eth_accounts permission if removing only permitted account', async function () {
await permController.removePermittedAccount(ORIGINS.b, ACCOUNT_ARRAYS.b[0])
const accounts = await permController.getAccounts(ORIGINS.b)
assert.deepEqual(
accounts, [],
'origin should have no accounts'
)
const permission = await permController.permissions.getPermission(
ORIGINS.b, PERM_NAMES.eth_accounts
)
assert.equal(permission, undefined, 'origin should not have eth_accounts permission')
assert.deepEqual(
notifications[ORIGINS.b][0],
NOTIFICATIONS.removedAccounts(),
'origin should have correct notification'
)
})
})
describe('finalizePermissionsRequest', function () {
let permController
beforeEach(function () {
permController = initPermController()
})
it('throws on non-keyring accounts', async function () {
await assert.rejects(
permController.finalizePermissionsRequest(
PERMS.requests.eth_accounts(), [DUMMY_ACCOUNT]
),
ERRORS.validatePermittedAccounts.nonKeyringAccount(DUMMY_ACCOUNT),
'should throw on non-keyring account'
)
})
it('adds caveat to eth_accounts permission', async function () {
const perm = await permController.finalizePermissionsRequest(
PERMS.requests.eth_accounts(),
ACCOUNT_ARRAYS.a,
)
assert.deepEqual(perm, PERMS.finalizedRequests.eth_accounts(ACCOUNT_ARRAYS.a))
})
it('replaces caveat of eth_accounts permission', async function () {
const perm = await permController.finalizePermissionsRequest(
PERMS.finalizedRequests.eth_accounts(ACCOUNT_ARRAYS.a),
ACCOUNT_ARRAYS.b,
)
assert.deepEqual(
perm, PERMS.finalizedRequests.eth_accounts(ACCOUNT_ARRAYS.b),
'permission should have correct caveat'
)
})
it('handles non-eth_accounts permission', async function () {
const perm = await permController.finalizePermissionsRequest(
PERMS.finalizedRequests.test_method(),
ACCOUNT_ARRAYS.b,
)
assert.deepEqual(
perm, PERMS.finalizedRequests.test_method(),
'permission should have correct caveat'
)
})
})
describe('legacyExposeAccounts', function () {
let permController, notifications
beforeEach(function () {
notifications = initNotifications()
permController = initPermController(notifications)
})
it('successfully exposes accounts and updates permissions history', async function () {
let aAccounts = await permController.getAccounts(ORIGINS.a)
assert.deepEqual(aAccounts, [], 'origin should have no accounts')
await permController.legacyExposeAccounts(ORIGINS.a, ACCOUNT_ARRAYS.a)
aAccounts = await permController.getAccounts(ORIGINS.a)
assert.deepEqual(aAccounts, ACCOUNT_ARRAYS.a, 'origin should have correct accounts')
// now, permissions history should be updated
const permissionsHistory = permController.permissionsLog.getHistory()
const historyOrigins = Object.keys(permissionsHistory)
assert.equal(historyOrigins.length, 1, 'should have single origin')
assert.equal(historyOrigins[0], ORIGINS.a, 'should have correct origin')
assert.ok(
permissionsHistory[ORIGINS.a].eth_accounts,
'history should have eth_accounts entry'
)
assert.deepEqual(
Object.keys(permissionsHistory[ORIGINS.a].eth_accounts.accounts),
ACCOUNT_ARRAYS.a,
'should have expected eth_accounts entry accounts'
)
// notification should also have been sent
assert.deepEqual(
notifications[ORIGINS.a][0],
NOTIFICATIONS.newAccounts(ACCOUNT_ARRAYS.a),
'first origin should have correct notification'
)
})
it('throws if called on origin with existing exposed accounts', async function () {
grantPermissions(
permController, ORIGINS.a,
PERMS.finalizedRequests.eth_accounts(ACCOUNT_ARRAYS.a)
)
const aAccounts = await permController.getAccounts(ORIGINS.a)
assert.deepEqual(aAccounts, ACCOUNT_ARRAYS.a, 'origin should have correct accounts')
await assert.rejects(
permController.legacyExposeAccounts(ORIGINS.a, ACCOUNT_ARRAYS.b),
ERRORS.legacyExposeAccounts.forbiddenUsage(),
'should throw if called on origin with existing exposed accounts'
)
const permissionsHistory = permController.permissionsLog.getHistory()
assert.deepEqual(
permissionsHistory, {},
'should not have modified history'
)
assert.deepEqual(
notifications[ORIGINS.a], [],
'should not have sent notification'
)
})
it('throws if called with bad accounts', async function () {
await assert.rejects(
permController.legacyExposeAccounts(ORIGINS.a, []),
ERRORS.validatePermittedAccounts.invalidParam(),
'should throw if called with no accounts'
)
const permissionsHistory = permController.permissionsLog.getHistory()
assert.deepEqual(
permissionsHistory, {},
'should not have modified history'
)
assert.deepEqual(
notifications[ORIGINS.a], [],
'should not have sent notification'
)
})
it('throws if called with bad origin', async function () {
await assert.rejects(
permController.legacyExposeAccounts(null, ACCOUNT_ARRAYS.a),
ERRORS.legacyExposeAccounts.badOrigin(),
'should throw if called with invalid origin'
)
const permissionsHistory = permController.permissionsLog.getHistory()
assert.deepEqual(
permissionsHistory, {},
'should not have modified history'
)
Object.keys(notifications).forEach((domain) => {
assert.deepEqual(
notifications[domain], [],
'should not have sent notification'
)
})
})
})
describe('preferences state update', function () {
let permController, notifications, preferences, identities
beforeEach(function () {
identities = ALL_ACCOUNTS.reduce(
(identities, account) => {
identities[account] = {}
return identities
},
{}
)
preferences = {
getState: sinon.stub(),
subscribe: sinon.stub(),
}
preferences.getState.returns({
identities,
selectedAddress: DUMMY_ACCOUNT,
})
notifications = initNotifications()
permController = new PermissionsController({
...getPermControllerOpts(),
notifyDomain: getNotifyDomain(notifications),
notifyAllDomains: getNotifyAllDomains(notifications),
preferences,
})
grantPermissions(
permController, ORIGINS.b,
PERMS.finalizedRequests.eth_accounts([...ACCOUNT_ARRAYS.a, EXTRA_ACCOUNT])
)
grantPermissions(
permController, ORIGINS.c,
PERMS.finalizedRequests.eth_accounts(ACCOUNT_ARRAYS.a)
)
})
it('should throw if given invalid account', async function () {
assert(preferences.subscribe.calledOnce)
assert(preferences.subscribe.firstCall.args.length === 1)
const onPreferencesUpdate = preferences.subscribe.firstCall.args[0]
await assert.rejects(
() => onPreferencesUpdate({ selectedAddress: {} }),
ERRORS._handleAccountSelected.invalidParams(),
'should throw if account is not a string'
)
})
it('should do nothing if account not permitted for any origins', async function () {
assert(preferences.subscribe.calledOnce)
assert(preferences.subscribe.firstCall.args.length === 1)
const onPreferencesUpdate = preferences.subscribe.firstCall.args[0]
await onPreferencesUpdate({ selectedAddress: DUMMY_ACCOUNT })
assert.deepEqual(
notifications[ORIGINS.b], [],
'should not have emitted notification'
)
assert.deepEqual(
notifications[ORIGINS.c], [],
'should not have emitted notification'
)
})
it('should emit notification if account already first in array for each connected site', async function () {
identities[ACCOUNT_ARRAYS.a[0]] = { lastSelected: 1000 }
assert(preferences.subscribe.calledOnce)
assert(preferences.subscribe.firstCall.args.length === 1)
const onPreferencesUpdate = preferences.subscribe.firstCall.args[0]
await onPreferencesUpdate({ selectedAddress: ACCOUNT_ARRAYS.a[0] })
assert.deepEqual(
notifications[ORIGINS.b],
[NOTIFICATIONS.newAccounts([...ACCOUNT_ARRAYS.a, EXTRA_ACCOUNT])],
'should not have emitted notification'
)
assert.deepEqual(
notifications[ORIGINS.c],
[NOTIFICATIONS.newAccounts(ACCOUNT_ARRAYS.a)],
'should not have emitted notification'
)
})
it('should emit notification just for connected domains', async function () {
identities[EXTRA_ACCOUNT] = { lastSelected: 1000 }
assert(preferences.subscribe.calledOnce)
assert(preferences.subscribe.firstCall.args.length === 1)
const onPreferencesUpdate = preferences.subscribe.firstCall.args[0]
await onPreferencesUpdate({ selectedAddress: EXTRA_ACCOUNT })
assert.deepEqual(
notifications[ORIGINS.b],
[NOTIFICATIONS.newAccounts([ EXTRA_ACCOUNT, ...ACCOUNT_ARRAYS.a ])],
'should have emitted notification'
)
assert.deepEqual(
notifications[ORIGINS.c], [],
'should not have emitted notification'
)
})
it('should emit notification for multiple connected domains', async function () {
identities[ACCOUNT_ARRAYS.a[1]] = { lastSelected: 1000 }
assert(preferences.subscribe.calledOnce)
assert(preferences.subscribe.firstCall.args.length === 1)
const onPreferencesUpdate = preferences.subscribe.firstCall.args[0]
await onPreferencesUpdate({ selectedAddress: ACCOUNT_ARRAYS.a[1] })
const accountsWithoutFirst = ACCOUNT_ARRAYS.a
.filter((account) => account !== ACCOUNT_ARRAYS.a[1])
const expectedAccounts = [ ACCOUNT_ARRAYS.a[1], ...accountsWithoutFirst ]
assert.deepEqual(
notifications[ORIGINS.b],
[NOTIFICATIONS.newAccounts([ ...expectedAccounts, EXTRA_ACCOUNT ])],
'should have emitted notification'
)
assert.deepEqual(
notifications[ORIGINS.c],
[NOTIFICATIONS.newAccounts(expectedAccounts)],
'should have emitted notification'
)
})
})
describe('approvePermissionsRequest', function () {
let permController, mockRequestUserApproval
beforeEach(function () {
permController = initPermController()
mockRequestUserApproval = getMockRequestUserApprovalFunction(
permController
)
})
it('does nothing if called on non-existing request', async function () {
assert.equal(
permController.pendingApprovals.size, 0,
'pending approvals should be empty on init',
)
sinon.spy(permController, 'finalizePermissionsRequest')
const request = PERMS.approvedRequest(REQUEST_IDS.a, null)
await assert.doesNotReject(
permController.approvePermissionsRequest(request, null),
'should not throw on non-existing request'
)
assert.ok(
permController.finalizePermissionsRequest.notCalled,
'should not call finalizePermissionRequest'
)
assert.equal(
permController.pendingApprovals.size, 0,
'pending approvals should still be empty after request',
)
})
it('rejects request with bad accounts param', async function () {
const request = PERMS.approvedRequest(
REQUEST_IDS.a,
PERMS.requests.eth_accounts()
)
const requestRejection = assert.rejects(
mockRequestUserApproval(REQUEST_IDS.a),
ERRORS.validatePermittedAccounts.invalidParam(),
'should reject bad accounts'
)
await permController.approvePermissionsRequest(request, null)
await requestRejection
assert.equal(
permController.pendingApprovals.size, 0,
'pending approvals should be empty after rejection',
)
})
it('rejects request with no permissions', async function () {
const request = PERMS.approvedRequest(REQUEST_IDS.a, {})
const requestRejection = assert.rejects(
mockRequestUserApproval(REQUEST_IDS.a),
ERRORS.approvePermissionsRequest.noPermsRequested(),
'should reject if no permissions in request'
)
await permController.approvePermissionsRequest(request, ACCOUNT_ARRAYS.a)
await requestRejection
assert.equal(
permController.pendingApprovals.size, 0,
'pending approvals should be empty after rejection',
)
})
it('approves valid request', async function () {
const request = PERMS.approvedRequest(REQUEST_IDS.a, PERMS.requests.eth_accounts())
let perms
const requestApproval = assert.doesNotReject(
async () => {
perms = await mockRequestUserApproval(REQUEST_IDS.a)
},
'should not reject single valid request'
)
await permController.approvePermissionsRequest(request, ACCOUNT_ARRAYS.a)
await requestApproval
assert.deepEqual(
perms, PERMS.finalizedRequests.eth_accounts(ACCOUNT_ARRAYS.a),
'should produce expected approved permissions'
)
assert.equal(
permController.pendingApprovals.size, 0,
'pending approvals should be empty after approval',
)
})
it('approves valid requests regardless of order', async function () {
const request1 = PERMS.approvedRequest(REQUEST_IDS.a, PERMS.requests.eth_accounts())
const request2 = PERMS.approvedRequest(REQUEST_IDS.b, PERMS.requests.eth_accounts())
const request3 = PERMS.approvedRequest(REQUEST_IDS.c, PERMS.requests.eth_accounts())
let perms1, perms2
const approval1 = assert.doesNotReject(
async () => {
perms1 = await mockRequestUserApproval(REQUEST_IDS.a)
},
'should not reject request'
)
const approval2 = assert.doesNotReject(
async () => {
perms2 = await mockRequestUserApproval(REQUEST_IDS.b)
},
'should not reject request'
)
// approve out of order
await permController.approvePermissionsRequest(request2, ACCOUNT_ARRAYS.b)
// add a non-existing request to the mix
await permController.approvePermissionsRequest(request3, ACCOUNT_ARRAYS.c)
await permController.approvePermissionsRequest(request1, ACCOUNT_ARRAYS.a)
await approval1
await approval2
assert.deepEqual(
perms1, PERMS.finalizedRequests.eth_accounts(ACCOUNT_ARRAYS.a),
'first request should produce expected approved permissions'
)
assert.deepEqual(
perms2, PERMS.finalizedRequests.eth_accounts(ACCOUNT_ARRAYS.b),
'second request should produce expected approved permissions'
)
assert.equal(
permController.pendingApprovals.size, 0,
'pending approvals should be empty after approvals',
)
})
})
describe('rejectPermissionsRequest', function () {
let permController, mockRequestUserApproval
beforeEach(async function () {
permController = initPermController()
mockRequestUserApproval = getMockRequestUserApprovalFunction(
permController
)
})
it('does nothing if called on non-existing request', async function () {
assert.equal(
permController.pendingApprovals.size, 0,
'pending approvals should be empty on init',
)
await assert.doesNotReject(
permController.rejectPermissionsRequest(REQUEST_IDS.a),
'should not throw on non-existing request'
)
assert.equal(
permController.pendingApprovals.size, 0,
'pending approvals should still be empty after request',
)
})
it('rejects single existing request', async function () {
const requestRejection = assert.rejects(
mockRequestUserApproval(REQUEST_IDS.a),
ERRORS.rejectPermissionsRequest.rejection(),
'should reject with expected error'
)
await permController.rejectPermissionsRequest(REQUEST_IDS.a)
await requestRejection
assert.equal(
permController.pendingApprovals.size, 0,
'pending approvals should be empty after rejection',
)
})
it('rejects requests regardless of order', async function () {
const requestRejection1 = assert.rejects(
mockRequestUserApproval(REQUEST_IDS.b),
ERRORS.rejectPermissionsRequest.rejection(),
'should reject with expected error'
)
const requestRejection2 = assert.rejects(
mockRequestUserApproval(REQUEST_IDS.c),
ERRORS.rejectPermissionsRequest.rejection(),
'should reject with expected error'
)
// reject out of order
await permController.rejectPermissionsRequest(REQUEST_IDS.c)
// add a non-existing request to the mix
await permController.rejectPermissionsRequest(REQUEST_IDS.a)
await permController.rejectPermissionsRequest(REQUEST_IDS.b)
await requestRejection1
await requestRejection2
assert.equal(
permController.pendingApprovals.size, 0,
'pending approvals should be empty after approval',
)
})
})
// see permissions-middleware-test for testing the middleware itself
describe('createMiddleware', function () {
let permController
beforeEach(function () {
permController = initPermController()
})
it('should throw on bad origin', function () {
assert.throws(
() => permController.createMiddleware({ origin: {} }),
ERRORS.createMiddleware.badOrigin(),
'should throw expected error'
)
assert.throws(
() => permController.createMiddleware({ origin: '' }),
ERRORS.createMiddleware.badOrigin(),
'should throw expected error'
)
assert.throws(
() => permController.createMiddleware({}),
ERRORS.createMiddleware.badOrigin(),
'should throw expected error'
)
})
it('should create a middleware', function () {
let middleware
assert.doesNotThrow(
() => {
middleware = permController.createMiddleware({ origin: ORIGINS.a })
},
'should not throw'
)
assert.equal(
typeof middleware, 'function',
'should return function'
)
assert.equal(
middleware.name, 'engineAsMiddleware',
'function name should be "engineAsMiddleware"'
)
})
it('should create a middleware with extensionId', function () {
const extensionId = 'fooExtension'
let middleware
assert.doesNotThrow(
() => {
middleware = permController.createMiddleware({
origin: ORIGINS.a,
extensionId,
})
},
'should not throw'
)
assert.equal(
typeof middleware, 'function',
'should return function'
)
assert.equal(
middleware.name, 'engineAsMiddleware',
'function name should be "engineAsMiddleware"'
)
const metadataStore = permController.store.getState()[METADATA_STORE_KEY]
assert.deepEqual(
metadataStore[ORIGINS.a], { extensionId },
'metadata should be stored'
)
})
})
describe('notifyDomain', function () {
let notifications, permController
beforeEach(function () {
notifications = initNotifications()
permController = initPermController(notifications)
sinon.spy(permController.permissionsLog, 'updateAccountsHistory')
})
it('notifyDomain handles accountsChanged', async function () {
permController.notifyDomain(
ORIGINS.a,
NOTIFICATIONS.newAccounts(ACCOUNT_ARRAYS.a),
)
assert.ok(
permController.permissionsLog.updateAccountsHistory.calledOnce,
'permissionsLog.updateAccountsHistory should have been called once'
)
assert.deepEqual(
notifications[ORIGINS.a],
[ NOTIFICATIONS.newAccounts(ACCOUNT_ARRAYS.a) ],
'origin should have correct notification'
)
})
it('notifyDomain handles notifications other than accountsChanged', async function () {
permController.notifyDomain(ORIGINS.a, NOTIFICATIONS.test())
assert.ok(
permController.permissionsLog.updateAccountsHistory.notCalled,
'permissionsLog.updateAccountsHistory should not have been called'
)
assert.deepEqual(
notifications[ORIGINS.a],
[ NOTIFICATIONS.test() ],
'origin should have correct notification'
)
})
})
describe('miscellanea and edge cases', function () {
let permController
beforeEach(function () {
permController = initPermController()
})
it('_addPendingApproval: should throw if adding origin twice', function () {
const id = nanoid()
const origin = ORIGINS.a
permController._addPendingApproval(id, origin, noop, noop)
const otherId = nanoid()
assert.throws(
() => permController._addPendingApproval(otherId, origin, noop, noop),
ERRORS.pendingApprovals.duplicateOriginOrId(otherId, origin),
'should throw expected error'
)
assert.equal(
permController.pendingApprovals.size, 1,
'pending approvals should have single entry',
)
assert.equal(
permController.pendingApprovalOrigins.size, 1,
'pending approval origins should have single item',
)
assert.deepEqual(
permController.pendingApprovals.get(id),
{ origin, resolve: noop, reject: noop },
'pending approvals should have expected entry'
)
assert.ok(
permController.pendingApprovalOrigins.has(origin),
'pending approval origins should have expected item',
)
})
it('addInternalMethodPrefix', function () {
const str = 'foo'
const res = addInternalMethodPrefix(str)
assert.equal(res, WALLET_PREFIX + str, 'should prefix correctly')
})
})
})