mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-01 00:28:06 +01:00
8f40d03299
This PR introduces the new approval controller to the extension codebase. We use it for the permissions controller's pending approval functionality. The approval controller sets us up for a new pattern of requesting and managing user confirmations in RPC methods. Along with the generic RPC method middleware, the approval controller will allow us to eliminate our message managers, and decouple various method handlers from our provider stack, making the implementations more portable between the extension and mobile.
1539 lines
46 KiB
JavaScript
1539 lines
46 KiB
JavaScript
import { strict as assert } from 'assert'
|
|
import { find } from 'lodash'
|
|
import sinon from 'sinon'
|
|
|
|
import {
|
|
METADATA_STORE_KEY,
|
|
METADATA_CACHE_MAX_SIZE,
|
|
} from '../../../../../app/scripts/controllers/permissions/enums'
|
|
|
|
import { PermissionsController } from '../../../../../app/scripts/controllers/permissions'
|
|
|
|
import { getRequestUserApprovalHelper, grantPermissions } from './helpers'
|
|
|
|
import {
|
|
constants,
|
|
getters,
|
|
getNotifyDomain,
|
|
getNotifyAllDomains,
|
|
getPermControllerOpts,
|
|
} from './mocks'
|
|
|
|
const { ERRORS, NOTIFICATIONS, PERMS } = getters
|
|
|
|
const {
|
|
ALL_ACCOUNTS,
|
|
ACCOUNTS,
|
|
DUMMY_ACCOUNT,
|
|
DOMAINS,
|
|
PERM_NAMES,
|
|
REQUEST_IDS,
|
|
EXTRA_ACCOUNT,
|
|
} = constants
|
|
|
|
const initNotifications = () => {
|
|
return Object.values(DOMAINS).reduce((acc, domain) => {
|
|
acc[domain.origin] = []
|
|
return acc
|
|
}, {})
|
|
}
|
|
|
|
const initPermController = (notifications = initNotifications()) => {
|
|
return new PermissionsController({
|
|
...getPermControllerOpts(),
|
|
notifyDomain: getNotifyDomain(notifications),
|
|
notifyAllDomains: getNotifyAllDomains(notifications),
|
|
})
|
|
}
|
|
|
|
describe('permissions controller', function () {
|
|
describe('constructor', function () {
|
|
it('throws on undefined argument', function () {
|
|
assert.throws(
|
|
() => new PermissionsController(),
|
|
'should throw on undefined argument',
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('getAccounts', function () {
|
|
let permController
|
|
|
|
beforeEach(function () {
|
|
permController = initPermController()
|
|
grantPermissions(
|
|
permController,
|
|
DOMAINS.a.origin,
|
|
PERMS.finalizedRequests.eth_accounts(ACCOUNTS.a.permitted),
|
|
)
|
|
grantPermissions(
|
|
permController,
|
|
DOMAINS.b.origin,
|
|
PERMS.finalizedRequests.eth_accounts(ACCOUNTS.b.permitted),
|
|
)
|
|
})
|
|
|
|
it('gets permitted accounts for permitted origins', async function () {
|
|
const aAccounts = await permController.getAccounts(DOMAINS.a.origin)
|
|
const bAccounts = await permController.getAccounts(DOMAINS.b.origin)
|
|
|
|
assert.deepEqual(
|
|
aAccounts,
|
|
[ACCOUNTS.a.primary],
|
|
'first origin should have correct accounts',
|
|
)
|
|
assert.deepEqual(
|
|
bAccounts,
|
|
[ACCOUNTS.b.primary],
|
|
'second origin should have correct accounts',
|
|
)
|
|
})
|
|
|
|
it('does not get accounts for unpermitted origins', async function () {
|
|
const cAccounts = await permController.getAccounts(DOMAINS.c.origin)
|
|
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,
|
|
DOMAINS.a.origin,
|
|
PERMS.finalizedRequests.eth_accounts(ACCOUNTS.a.permitted),
|
|
)
|
|
grantPermissions(
|
|
permController,
|
|
DOMAINS.b.origin,
|
|
PERMS.finalizedRequests.test_method(),
|
|
)
|
|
|
|
assert.ok(
|
|
permController.hasPermission(DOMAINS.a.origin, 'eth_accounts'),
|
|
'should return true for granted permission',
|
|
)
|
|
assert.ok(
|
|
permController.hasPermission(DOMAINS.b.origin, 'test_method'),
|
|
'should return true for granted permission',
|
|
)
|
|
|
|
assert.ok(
|
|
!permController.hasPermission(DOMAINS.a.origin, 'test_method'),
|
|
'should return false for non-granted permission',
|
|
)
|
|
assert.ok(
|
|
!permController.hasPermission(DOMAINS.b.origin, '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(DOMAINS.b.origin, '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,
|
|
DOMAINS.a.origin,
|
|
PERMS.finalizedRequests.eth_accounts(ACCOUNTS.a.permitted),
|
|
)
|
|
grantPermissions(
|
|
permController,
|
|
DOMAINS.b.origin,
|
|
PERMS.finalizedRequests.eth_accounts(ACCOUNTS.b.permitted),
|
|
)
|
|
grantPermissions(
|
|
permController,
|
|
DOMAINS.c.origin,
|
|
PERMS.finalizedRequests.eth_accounts(ACCOUNTS.c.permitted),
|
|
)
|
|
|
|
let aAccounts = await permController.getAccounts(DOMAINS.a.origin)
|
|
let bAccounts = await permController.getAccounts(DOMAINS.b.origin)
|
|
let cAccounts = await permController.getAccounts(DOMAINS.c.origin)
|
|
|
|
assert.deepEqual(
|
|
aAccounts,
|
|
[ACCOUNTS.a.primary],
|
|
'first origin should have correct accounts',
|
|
)
|
|
assert.deepEqual(
|
|
bAccounts,
|
|
[ACCOUNTS.b.primary],
|
|
'second origin should have correct accounts',
|
|
)
|
|
assert.deepEqual(
|
|
cAccounts,
|
|
[ACCOUNTS.c.primary],
|
|
'third origin should have correct accounts',
|
|
)
|
|
|
|
permController.clearPermissions()
|
|
|
|
Object.keys(notifications).forEach((origin) => {
|
|
assert.deepEqual(
|
|
notifications[origin],
|
|
[NOTIFICATIONS.removedAccounts()],
|
|
'origin should have single metamask_accountsChanged:[] notification',
|
|
)
|
|
})
|
|
|
|
aAccounts = await permController.getAccounts(DOMAINS.a.origin)
|
|
bAccounts = await permController.getAccounts(DOMAINS.b.origin)
|
|
cAccounts = await permController.getAccounts(DOMAINS.c.origin)
|
|
|
|
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,
|
|
DOMAINS.a.origin,
|
|
PERMS.finalizedRequests.eth_accounts(ACCOUNTS.a.permitted),
|
|
)
|
|
grantPermissions(
|
|
permController,
|
|
DOMAINS.b.origin,
|
|
PERMS.finalizedRequests.eth_accounts(ACCOUNTS.b.permitted),
|
|
)
|
|
})
|
|
|
|
it('removes permissions for multiple domains', async function () {
|
|
let aAccounts = await permController.getAccounts(DOMAINS.a.origin)
|
|
let bAccounts = await permController.getAccounts(DOMAINS.b.origin)
|
|
|
|
assert.deepEqual(
|
|
aAccounts,
|
|
[ACCOUNTS.a.primary],
|
|
'first origin should have correct accounts',
|
|
)
|
|
assert.deepEqual(
|
|
bAccounts,
|
|
[ACCOUNTS.b.primary],
|
|
'second origin should have correct accounts',
|
|
)
|
|
|
|
permController.removePermissionsFor({
|
|
[DOMAINS.a.origin]: [PERM_NAMES.eth_accounts],
|
|
[DOMAINS.b.origin]: [PERM_NAMES.eth_accounts],
|
|
})
|
|
|
|
aAccounts = await permController.getAccounts(DOMAINS.a.origin)
|
|
bAccounts = await permController.getAccounts(DOMAINS.b.origin)
|
|
|
|
assert.deepEqual(aAccounts, [], 'first origin should have no accounts')
|
|
assert.deepEqual(bAccounts, [], 'second origin should have no accounts')
|
|
|
|
assert.deepEqual(
|
|
notifications[DOMAINS.a.origin],
|
|
[NOTIFICATIONS.removedAccounts()],
|
|
'first origin should have correct notification',
|
|
)
|
|
assert.deepEqual(
|
|
notifications[DOMAINS.b.origin],
|
|
[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,
|
|
DOMAINS.b.origin,
|
|
PERMS.finalizedRequests.test_method(),
|
|
)
|
|
|
|
let bPermissions = permController.permissions.getPermissionsForDomain(
|
|
DOMAINS.b.origin,
|
|
)
|
|
|
|
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({
|
|
[DOMAINS.b.origin]: [PERM_NAMES.test_method],
|
|
})
|
|
|
|
bPermissions = permController.permissions.getPermissionsForDomain(
|
|
DOMAINS.b.origin,
|
|
)
|
|
|
|
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({
|
|
[DOMAINS.b.origin]: [PERM_NAMES.eth_accounts],
|
|
})
|
|
|
|
const aAccounts = await permController.getAccounts(DOMAINS.a.origin)
|
|
const bAccounts = await permController.getAccounts(DOMAINS.b.origin)
|
|
|
|
assert.deepEqual(
|
|
aAccounts,
|
|
[ACCOUNTS.a.primary],
|
|
'first origin should have correct accounts',
|
|
)
|
|
assert.deepEqual(bAccounts, [], 'second origin should have no accounts')
|
|
|
|
assert.deepEqual(
|
|
notifications[DOMAINS.a.origin],
|
|
[],
|
|
'first origin should have no notifications',
|
|
)
|
|
assert.deepEqual(
|
|
notifications[DOMAINS.b.origin],
|
|
[NOTIFICATIONS.removedAccounts()],
|
|
'second origin should have correct notification',
|
|
)
|
|
|
|
assert.deepEqual(
|
|
Object.keys(permController.permissions.getDomains()),
|
|
[DOMAINS.a.origin],
|
|
'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({
|
|
[DOMAINS.c.origin]: [PERM_NAMES.eth_accounts],
|
|
})
|
|
|
|
assert.deepEqual(
|
|
notifications[DOMAINS.c.origin],
|
|
[NOTIFICATIONS.removedAccounts()],
|
|
'unknown origin should have notification',
|
|
)
|
|
|
|
const aAccounts = await permController.getAccounts(DOMAINS.a.origin)
|
|
const bAccounts = await permController.getAccounts(DOMAINS.b.origin)
|
|
|
|
assert.deepEqual(
|
|
aAccounts,
|
|
[ACCOUNTS.a.primary],
|
|
'first origin should have correct accounts',
|
|
)
|
|
assert.deepEqual(
|
|
bAccounts,
|
|
[ACCOUNTS.b.primary],
|
|
'second origin should have correct accounts',
|
|
)
|
|
|
|
assert.deepEqual(
|
|
Object.keys(permController.permissions.getDomains()),
|
|
[DOMAINS.a.origin, DOMAINS.b.origin],
|
|
'should have correct domains',
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('validatePermittedAccounts', function () {
|
|
let permController
|
|
|
|
beforeEach(function () {
|
|
permController = initPermController()
|
|
grantPermissions(
|
|
permController,
|
|
DOMAINS.a.origin,
|
|
PERMS.finalizedRequests.eth_accounts(ACCOUNTS.a.permitted),
|
|
)
|
|
grantPermissions(
|
|
permController,
|
|
DOMAINS.b.origin,
|
|
PERMS.finalizedRequests.eth_accounts(ACCOUNTS.b.permitted),
|
|
)
|
|
})
|
|
|
|
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,
|
|
DOMAINS.a.origin,
|
|
PERMS.finalizedRequests.eth_accounts(ACCOUNTS.a.permitted),
|
|
)
|
|
grantPermissions(
|
|
permController,
|
|
DOMAINS.b.origin,
|
|
PERMS.finalizedRequests.eth_accounts(ACCOUNTS.b.permitted),
|
|
)
|
|
})
|
|
|
|
it('should throw if account is not a string', async function () {
|
|
await assert.rejects(
|
|
() => permController.addPermittedAccount(DOMAINS.a.origin, {}),
|
|
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(DOMAINS.a.origin, 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(DOMAINS.c.origin, EXTRA_ACCOUNT),
|
|
ERRORS.addPermittedAccount.invalidOrigin(),
|
|
'should throw on origin without permissions',
|
|
)
|
|
})
|
|
|
|
it('should throw if origin lacks eth_accounts permission', async function () {
|
|
grantPermissions(
|
|
permController,
|
|
DOMAINS.c.origin,
|
|
PERMS.finalizedRequests.test_method(),
|
|
)
|
|
|
|
await assert.rejects(
|
|
() =>
|
|
permController.addPermittedAccount(DOMAINS.c.origin, 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(
|
|
DOMAINS.a.origin,
|
|
ACCOUNTS.a.permitted[0],
|
|
),
|
|
ERRORS.addPermittedAccount.alreadyPermitted(),
|
|
'should throw if account is already permitted',
|
|
)
|
|
})
|
|
|
|
it('should successfully add permitted account', async function () {
|
|
await permController.addPermittedAccount(DOMAINS.a.origin, EXTRA_ACCOUNT)
|
|
|
|
const accounts = await permController._getPermittedAccounts(
|
|
DOMAINS.a.origin,
|
|
)
|
|
|
|
assert.deepEqual(
|
|
accounts,
|
|
[...ACCOUNTS.a.permitted, EXTRA_ACCOUNT],
|
|
'origin should have correct accounts',
|
|
)
|
|
|
|
assert.deepEqual(
|
|
notifications[DOMAINS.a.origin][0],
|
|
NOTIFICATIONS.newAccounts([ACCOUNTS.a.primary]),
|
|
'origin should have correct notification',
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('removePermittedAccount', function () {
|
|
let permController, notifications
|
|
|
|
beforeEach(function () {
|
|
notifications = initNotifications()
|
|
permController = initPermController(notifications)
|
|
grantPermissions(
|
|
permController,
|
|
DOMAINS.a.origin,
|
|
PERMS.finalizedRequests.eth_accounts(ACCOUNTS.a.permitted),
|
|
)
|
|
grantPermissions(
|
|
permController,
|
|
DOMAINS.b.origin,
|
|
PERMS.finalizedRequests.eth_accounts(ACCOUNTS.b.permitted),
|
|
)
|
|
})
|
|
|
|
it('should throw if account is not a string', async function () {
|
|
await assert.rejects(
|
|
() => permController.removePermittedAccount(DOMAINS.a.origin, {}),
|
|
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(
|
|
DOMAINS.a.origin,
|
|
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(
|
|
DOMAINS.c.origin,
|
|
EXTRA_ACCOUNT,
|
|
),
|
|
ERRORS.removePermittedAccount.invalidOrigin(),
|
|
'should throw on origin without permissions',
|
|
)
|
|
})
|
|
|
|
it('should throw if origin lacks eth_accounts permission', async function () {
|
|
grantPermissions(
|
|
permController,
|
|
DOMAINS.c.origin,
|
|
PERMS.finalizedRequests.test_method(),
|
|
)
|
|
|
|
await assert.rejects(
|
|
() =>
|
|
permController.removePermittedAccount(
|
|
DOMAINS.c.origin,
|
|
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(
|
|
DOMAINS.b.origin,
|
|
ACCOUNTS.c.permitted[0],
|
|
),
|
|
ERRORS.removePermittedAccount.notPermitted(),
|
|
'should throw if account is not permitted',
|
|
)
|
|
})
|
|
|
|
it('should successfully remove permitted account', async function () {
|
|
await permController.removePermittedAccount(
|
|
DOMAINS.a.origin,
|
|
ACCOUNTS.a.permitted[1],
|
|
)
|
|
|
|
const accounts = await permController._getPermittedAccounts(
|
|
DOMAINS.a.origin,
|
|
)
|
|
|
|
assert.deepEqual(
|
|
accounts,
|
|
ACCOUNTS.a.permitted.filter((acc) => acc !== ACCOUNTS.a.permitted[1]),
|
|
'origin should have correct accounts',
|
|
)
|
|
|
|
assert.deepEqual(
|
|
notifications[DOMAINS.a.origin][0],
|
|
NOTIFICATIONS.newAccounts([ACCOUNTS.a.primary]),
|
|
'origin should have correct notification',
|
|
)
|
|
})
|
|
|
|
it('should remove eth_accounts permission if removing only permitted account', async function () {
|
|
await permController.removePermittedAccount(
|
|
DOMAINS.b.origin,
|
|
ACCOUNTS.b.permitted[0],
|
|
)
|
|
|
|
const accounts = await permController.getAccounts(DOMAINS.b.origin)
|
|
|
|
assert.deepEqual(accounts, [], 'origin should have no accounts')
|
|
|
|
const permission = await permController.permissions.getPermission(
|
|
DOMAINS.b.origin,
|
|
PERM_NAMES.eth_accounts,
|
|
)
|
|
|
|
assert.equal(
|
|
permission,
|
|
undefined,
|
|
'origin should not have eth_accounts permission',
|
|
)
|
|
|
|
assert.deepEqual(
|
|
notifications[DOMAINS.b.origin][0],
|
|
NOTIFICATIONS.removedAccounts(),
|
|
'origin should have correct notification',
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('removeAllAccountPermissions', function () {
|
|
let permController, notifications
|
|
|
|
beforeEach(function () {
|
|
notifications = initNotifications()
|
|
permController = initPermController(notifications)
|
|
grantPermissions(
|
|
permController,
|
|
DOMAINS.a.origin,
|
|
PERMS.finalizedRequests.eth_accounts(ACCOUNTS.a.permitted),
|
|
)
|
|
grantPermissions(
|
|
permController,
|
|
DOMAINS.b.origin,
|
|
PERMS.finalizedRequests.eth_accounts(ACCOUNTS.b.permitted),
|
|
)
|
|
grantPermissions(
|
|
permController,
|
|
DOMAINS.c.origin,
|
|
PERMS.finalizedRequests.eth_accounts(ACCOUNTS.b.permitted),
|
|
)
|
|
})
|
|
|
|
it('should throw if account is not a string', async function () {
|
|
await assert.rejects(
|
|
() => permController.removeAllAccountPermissions({}),
|
|
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.removeAllAccountPermissions(DUMMY_ACCOUNT),
|
|
ERRORS.validatePermittedAccounts.nonKeyringAccount(DUMMY_ACCOUNT),
|
|
'should throw on non-keyring account',
|
|
)
|
|
})
|
|
|
|
it('should remove permitted account from single origin', async function () {
|
|
await permController.removeAllAccountPermissions(ACCOUNTS.a.permitted[1])
|
|
|
|
const accounts = await permController._getPermittedAccounts(
|
|
DOMAINS.a.origin,
|
|
)
|
|
|
|
assert.deepEqual(
|
|
accounts,
|
|
ACCOUNTS.a.permitted.filter((acc) => acc !== ACCOUNTS.a.permitted[1]),
|
|
'origin should have correct accounts',
|
|
)
|
|
|
|
assert.deepEqual(
|
|
notifications[DOMAINS.a.origin][0],
|
|
NOTIFICATIONS.newAccounts([ACCOUNTS.a.primary]),
|
|
'origin should have correct notification',
|
|
)
|
|
})
|
|
|
|
it('should permitted account from multiple origins', async function () {
|
|
await permController.removeAllAccountPermissions(ACCOUNTS.b.permitted[0])
|
|
|
|
const bAccounts = await permController.getAccounts(DOMAINS.b.origin)
|
|
assert.deepEqual(bAccounts, [], 'first origin should no accounts')
|
|
|
|
const cAccounts = await permController.getAccounts(DOMAINS.c.origin)
|
|
assert.deepEqual(cAccounts, [], 'second origin no accounts')
|
|
|
|
assert.deepEqual(
|
|
notifications[DOMAINS.b.origin][0],
|
|
NOTIFICATIONS.removedAccounts(),
|
|
'first origin should have correct notification',
|
|
)
|
|
|
|
assert.deepEqual(
|
|
notifications[DOMAINS.c.origin][0],
|
|
NOTIFICATIONS.removedAccounts(),
|
|
'second origin should have correct notification',
|
|
)
|
|
})
|
|
|
|
it('should remove eth_accounts permission if removing only permitted account', async function () {
|
|
await permController.removeAllAccountPermissions(ACCOUNTS.b.permitted[0])
|
|
|
|
const accounts = await permController.getAccounts(DOMAINS.b.origin)
|
|
|
|
assert.deepEqual(accounts, [], 'origin should have no accounts')
|
|
|
|
const permission = await permController.permissions.getPermission(
|
|
DOMAINS.b.origin,
|
|
PERM_NAMES.eth_accounts,
|
|
)
|
|
|
|
assert.equal(
|
|
permission,
|
|
undefined,
|
|
'origin should not have eth_accounts permission',
|
|
)
|
|
|
|
assert.deepEqual(
|
|
notifications[DOMAINS.b.origin][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(),
|
|
ACCOUNTS.a.permitted,
|
|
)
|
|
|
|
assert.deepEqual(
|
|
perm,
|
|
PERMS.finalizedRequests.eth_accounts(ACCOUNTS.a.permitted),
|
|
)
|
|
})
|
|
|
|
it('replaces caveat of eth_accounts permission', async function () {
|
|
const perm = await permController.finalizePermissionsRequest(
|
|
PERMS.finalizedRequests.eth_accounts(ACCOUNTS.a.permitted),
|
|
ACCOUNTS.b.permitted,
|
|
)
|
|
|
|
assert.deepEqual(
|
|
perm,
|
|
PERMS.finalizedRequests.eth_accounts(ACCOUNTS.b.permitted),
|
|
'permission should have correct caveat',
|
|
)
|
|
})
|
|
|
|
it('handles non-eth_accounts permission', async function () {
|
|
const perm = await permController.finalizePermissionsRequest(
|
|
PERMS.finalizedRequests.test_method(),
|
|
ACCOUNTS.b.permitted,
|
|
)
|
|
|
|
assert.deepEqual(
|
|
perm,
|
|
PERMS.finalizedRequests.test_method(),
|
|
'permission should have correct caveat',
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('preferences state update', function () {
|
|
let permController, notifications, preferences, identities
|
|
|
|
beforeEach(function () {
|
|
identities = ALL_ACCOUNTS.reduce((identitiesAcc, account) => {
|
|
identitiesAcc[account] = {}
|
|
return identitiesAcc
|
|
}, {})
|
|
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,
|
|
DOMAINS.b.origin,
|
|
PERMS.finalizedRequests.eth_accounts([
|
|
...ACCOUNTS.a.permitted,
|
|
EXTRA_ACCOUNT,
|
|
]),
|
|
)
|
|
grantPermissions(
|
|
permController,
|
|
DOMAINS.c.origin,
|
|
PERMS.finalizedRequests.eth_accounts(ACCOUNTS.a.permitted),
|
|
)
|
|
})
|
|
|
|
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[DOMAINS.b.origin],
|
|
[],
|
|
'should not have emitted notification',
|
|
)
|
|
assert.deepEqual(
|
|
notifications[DOMAINS.c.origin],
|
|
[],
|
|
'should not have emitted notification',
|
|
)
|
|
})
|
|
|
|
it('should emit notification if account already first in array for each connected site', async function () {
|
|
identities[ACCOUNTS.a.permitted[0]] = { lastSelected: 1000 }
|
|
assert(preferences.subscribe.calledOnce)
|
|
assert(preferences.subscribe.firstCall.args.length === 1)
|
|
const onPreferencesUpdate = preferences.subscribe.firstCall.args[0]
|
|
|
|
await onPreferencesUpdate({ selectedAddress: ACCOUNTS.a.permitted[0] })
|
|
|
|
assert.deepEqual(
|
|
notifications[DOMAINS.b.origin],
|
|
[NOTIFICATIONS.newAccounts([ACCOUNTS.a.primary])],
|
|
'should not have emitted notification',
|
|
)
|
|
assert.deepEqual(
|
|
notifications[DOMAINS.c.origin],
|
|
[NOTIFICATIONS.newAccounts([ACCOUNTS.a.primary])],
|
|
'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[DOMAINS.b.origin],
|
|
[NOTIFICATIONS.newAccounts([EXTRA_ACCOUNT])],
|
|
'should have emitted notification',
|
|
)
|
|
assert.deepEqual(
|
|
notifications[DOMAINS.c.origin],
|
|
[],
|
|
'should not have emitted notification',
|
|
)
|
|
})
|
|
|
|
it('should emit notification for multiple connected domains', async function () {
|
|
identities[ACCOUNTS.a.permitted[1]] = { lastSelected: 1000 }
|
|
assert(preferences.subscribe.calledOnce)
|
|
assert(preferences.subscribe.firstCall.args.length === 1)
|
|
const onPreferencesUpdate = preferences.subscribe.firstCall.args[0]
|
|
|
|
await onPreferencesUpdate({ selectedAddress: ACCOUNTS.a.permitted[1] })
|
|
|
|
assert.deepEqual(
|
|
notifications[DOMAINS.b.origin],
|
|
[NOTIFICATIONS.newAccounts([ACCOUNTS.a.permitted[1]])],
|
|
'should have emitted notification',
|
|
)
|
|
assert.deepEqual(
|
|
notifications[DOMAINS.c.origin],
|
|
[NOTIFICATIONS.newAccounts([ACCOUNTS.c.primary])],
|
|
'should have emitted notification',
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('approvePermissionsRequest', function () {
|
|
let permController, requestUserApproval
|
|
|
|
beforeEach(function () {
|
|
permController = initPermController()
|
|
requestUserApproval = getRequestUserApprovalHelper(permController)
|
|
})
|
|
|
|
it('does nothing if called on non-existing request', async function () {
|
|
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',
|
|
)
|
|
})
|
|
|
|
it('rejects request with bad accounts param', async function () {
|
|
const request = PERMS.approvedRequest(
|
|
REQUEST_IDS.a,
|
|
PERMS.requests.eth_accounts(),
|
|
)
|
|
|
|
const rejectionPromise = assert.rejects(
|
|
requestUserApproval(REQUEST_IDS.a),
|
|
ERRORS.validatePermittedAccounts.invalidParam(),
|
|
'should reject with "null" accounts',
|
|
)
|
|
|
|
await permController.approvePermissionsRequest(request, null)
|
|
await rejectionPromise
|
|
})
|
|
|
|
it('rejects request with no permissions', async function () {
|
|
const request = PERMS.approvedRequest(REQUEST_IDS.a, {})
|
|
|
|
const requestRejection = assert.rejects(
|
|
requestUserApproval(REQUEST_IDS.a),
|
|
ERRORS.approvePermissionsRequest.noPermsRequested(),
|
|
'should reject if no permissions in request',
|
|
)
|
|
|
|
await permController.approvePermissionsRequest(
|
|
request,
|
|
ACCOUNTS.a.permitted,
|
|
)
|
|
await requestRejection
|
|
})
|
|
|
|
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 requestUserApproval(REQUEST_IDS.a)
|
|
}, 'should not reject single valid request')
|
|
|
|
await permController.approvePermissionsRequest(
|
|
request,
|
|
ACCOUNTS.a.permitted,
|
|
)
|
|
await requestApproval
|
|
|
|
assert.deepEqual(
|
|
perms,
|
|
PERMS.finalizedRequests.eth_accounts(ACCOUNTS.a.permitted),
|
|
'should produce expected approved permissions',
|
|
)
|
|
})
|
|
|
|
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 requestUserApproval(REQUEST_IDS.a, DOMAINS.a.origin)
|
|
}, 'should not reject request')
|
|
|
|
const approval2 = assert.doesNotReject(async () => {
|
|
perms2 = await requestUserApproval(REQUEST_IDS.b, DOMAINS.b.origin)
|
|
}, 'should not reject request')
|
|
|
|
// approve out of order
|
|
await permController.approvePermissionsRequest(
|
|
request2,
|
|
ACCOUNTS.b.permitted,
|
|
)
|
|
// add a non-existing request to the mix
|
|
await permController.approvePermissionsRequest(
|
|
request3,
|
|
ACCOUNTS.c.permitted,
|
|
)
|
|
await permController.approvePermissionsRequest(
|
|
request1,
|
|
ACCOUNTS.a.permitted,
|
|
)
|
|
|
|
await approval1
|
|
await approval2
|
|
|
|
assert.deepEqual(
|
|
perms1,
|
|
PERMS.finalizedRequests.eth_accounts(ACCOUNTS.a.permitted),
|
|
'first request should produce expected approved permissions',
|
|
)
|
|
|
|
assert.deepEqual(
|
|
perms2,
|
|
PERMS.finalizedRequests.eth_accounts(ACCOUNTS.b.permitted),
|
|
'second request should produce expected approved permissions',
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('rejectPermissionsRequest', function () {
|
|
let permController, requestUserApproval
|
|
|
|
beforeEach(async function () {
|
|
permController = initPermController()
|
|
requestUserApproval = getRequestUserApprovalHelper(permController)
|
|
})
|
|
|
|
it('does nothing if called on non-existing request', async function () {
|
|
permController.approvals.add = sinon.fake.throws(
|
|
new Error('should not call add'),
|
|
)
|
|
|
|
await assert.doesNotReject(
|
|
permController.rejectPermissionsRequest(REQUEST_IDS.a),
|
|
'should not throw on non-existing request',
|
|
)
|
|
})
|
|
|
|
it('rejects single existing request', async function () {
|
|
const requestRejection = assert.rejects(
|
|
requestUserApproval(REQUEST_IDS.a),
|
|
ERRORS.rejectPermissionsRequest.rejection(),
|
|
'should reject with expected error',
|
|
)
|
|
|
|
await permController.rejectPermissionsRequest(REQUEST_IDS.a)
|
|
await requestRejection
|
|
})
|
|
|
|
it('rejects requests regardless of order', async function () {
|
|
const requestRejection1 = assert.rejects(
|
|
requestUserApproval(REQUEST_IDS.b, DOMAINS.b.origin),
|
|
ERRORS.rejectPermissionsRequest.rejection(),
|
|
'should reject with expected error',
|
|
)
|
|
|
|
const requestRejection2 = assert.rejects(
|
|
requestUserApproval(REQUEST_IDS.c, DOMAINS.c.origin),
|
|
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
|
|
})
|
|
})
|
|
|
|
// see permissions-middleware-test for testing the middleware itself
|
|
describe('createMiddleware', function () {
|
|
let permController, clock
|
|
|
|
beforeEach(function () {
|
|
permController = initPermController()
|
|
clock = sinon.useFakeTimers(1)
|
|
})
|
|
|
|
afterEach(function () {
|
|
clock.restore()
|
|
})
|
|
|
|
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: DOMAINS.a.origin,
|
|
})
|
|
}, 'should not throw')
|
|
|
|
assert.equal(typeof middleware, 'function', 'should return function')
|
|
})
|
|
|
|
it('should create a middleware with extensionId', function () {
|
|
const extensionId = 'fooExtension'
|
|
|
|
let middleware
|
|
assert.doesNotThrow(() => {
|
|
middleware = permController.createMiddleware({
|
|
origin: DOMAINS.a.origin,
|
|
extensionId,
|
|
})
|
|
}, 'should not throw')
|
|
|
|
assert.equal(typeof middleware, 'function', 'should return function')
|
|
|
|
const metadataStore = permController.store.getState()[METADATA_STORE_KEY]
|
|
|
|
assert.deepEqual(
|
|
metadataStore[DOMAINS.a.origin],
|
|
{ extensionId, lastUpdated: 1 },
|
|
'metadata should be stored',
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('notifyAccountsChanged', function () {
|
|
let notifications, permController
|
|
|
|
beforeEach(function () {
|
|
notifications = initNotifications()
|
|
permController = initPermController(notifications)
|
|
sinon.spy(permController.permissionsLog, 'updateAccountsHistory')
|
|
})
|
|
|
|
it('notifyAccountsChanged records history and sends notification', async function () {
|
|
sinon.spy(permController, '_isUnlocked')
|
|
|
|
permController.notifyAccountsChanged(
|
|
DOMAINS.a.origin,
|
|
ACCOUNTS.a.permitted,
|
|
)
|
|
|
|
assert.ok(
|
|
permController._isUnlocked.calledOnce,
|
|
'_isUnlocked should have been called once',
|
|
)
|
|
|
|
assert.ok(
|
|
permController.permissionsLog.updateAccountsHistory.calledOnce,
|
|
'permissionsLog.updateAccountsHistory should have been called once',
|
|
)
|
|
|
|
assert.deepEqual(
|
|
notifications[DOMAINS.a.origin],
|
|
[NOTIFICATIONS.newAccounts(ACCOUNTS.a.permitted)],
|
|
'origin should have correct notification',
|
|
)
|
|
})
|
|
|
|
it('notifyAccountsChanged does nothing if _isUnlocked returns false', async function () {
|
|
permController._isUnlocked = sinon.fake.returns(false)
|
|
|
|
permController.notifyAccountsChanged(
|
|
DOMAINS.a.origin,
|
|
ACCOUNTS.a.permitted,
|
|
)
|
|
|
|
assert.ok(
|
|
permController._isUnlocked.calledOnce,
|
|
'_isUnlocked should have been called once',
|
|
)
|
|
|
|
assert.ok(
|
|
permController.permissionsLog.updateAccountsHistory.notCalled,
|
|
'permissionsLog.updateAccountsHistory should not have been called',
|
|
)
|
|
})
|
|
|
|
it('notifyAccountsChanged throws on invalid origin', async function () {
|
|
assert.throws(
|
|
() => permController.notifyAccountsChanged(4, ACCOUNTS.a.permitted),
|
|
ERRORS.notifyAccountsChanged.invalidOrigin(4),
|
|
'should throw expected error for non-string origin',
|
|
)
|
|
|
|
assert.throws(
|
|
() => permController.notifyAccountsChanged('', ACCOUNTS.a.permitted),
|
|
ERRORS.notifyAccountsChanged.invalidOrigin(''),
|
|
'should throw expected error for empty string origin',
|
|
)
|
|
})
|
|
|
|
it('notifyAccountsChanged throws on invalid accounts', async function () {
|
|
assert.throws(
|
|
() => permController.notifyAccountsChanged(DOMAINS.a.origin, 4),
|
|
ERRORS.notifyAccountsChanged.invalidAccounts(),
|
|
'should throw expected error for truthy non-array accounts',
|
|
)
|
|
|
|
assert.throws(
|
|
() => permController.notifyAccountsChanged(DOMAINS.a.origin, null),
|
|
ERRORS.notifyAccountsChanged.invalidAccounts(),
|
|
'should throw expected error for falsy non-array accounts',
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('addDomainMetadata', function () {
|
|
let permController, clock
|
|
|
|
function getMockMetadata(size) {
|
|
const dummyData = {}
|
|
for (let i = 0; i < size; i++) {
|
|
const key = i.toString()
|
|
dummyData[key] = {}
|
|
}
|
|
return dummyData
|
|
}
|
|
|
|
beforeEach(function () {
|
|
permController = initPermController()
|
|
permController._setDomainMetadata = sinon.fake()
|
|
clock = sinon.useFakeTimers(1)
|
|
})
|
|
|
|
afterEach(function () {
|
|
clock.restore()
|
|
})
|
|
|
|
it('calls setter function with expected new state when adding domain', function () {
|
|
permController.store.getState = sinon.fake.returns({
|
|
[METADATA_STORE_KEY]: {
|
|
[DOMAINS.a.origin]: {
|
|
foo: 'bar',
|
|
},
|
|
},
|
|
})
|
|
|
|
permController.addDomainMetadata(DOMAINS.b.origin, { foo: 'bar' })
|
|
|
|
assert.ok(
|
|
permController.store.getState.called,
|
|
'should have called store.getState',
|
|
)
|
|
assert.equal(
|
|
permController._setDomainMetadata.getCalls().length,
|
|
1,
|
|
'should have called _setDomainMetadata once',
|
|
)
|
|
assert.deepEqual(permController._setDomainMetadata.lastCall.args, [
|
|
{
|
|
[DOMAINS.a.origin]: {
|
|
foo: 'bar',
|
|
},
|
|
[DOMAINS.b.origin]: {
|
|
foo: 'bar',
|
|
host: DOMAINS.b.host,
|
|
lastUpdated: 1,
|
|
},
|
|
},
|
|
])
|
|
})
|
|
|
|
it('calls setter function with expected new states when updating existing domain', function () {
|
|
permController.store.getState = sinon.fake.returns({
|
|
[METADATA_STORE_KEY]: {
|
|
[DOMAINS.a.origin]: {
|
|
foo: 'bar',
|
|
},
|
|
[DOMAINS.b.origin]: {
|
|
bar: 'baz',
|
|
},
|
|
},
|
|
})
|
|
|
|
permController.addDomainMetadata(DOMAINS.b.origin, { foo: 'bar' })
|
|
|
|
assert.ok(
|
|
permController.store.getState.called,
|
|
'should have called store.getState',
|
|
)
|
|
assert.equal(
|
|
permController._setDomainMetadata.getCalls().length,
|
|
1,
|
|
'should have called _setDomainMetadata once',
|
|
)
|
|
assert.deepEqual(permController._setDomainMetadata.lastCall.args, [
|
|
{
|
|
[DOMAINS.a.origin]: {
|
|
foo: 'bar',
|
|
},
|
|
[DOMAINS.b.origin]: {
|
|
foo: 'bar',
|
|
bar: 'baz',
|
|
host: DOMAINS.b.host,
|
|
lastUpdated: 1,
|
|
},
|
|
},
|
|
])
|
|
})
|
|
|
|
it('pops metadata on add when too many origins are pending', function () {
|
|
sinon.spy(permController._pendingSiteMetadata, 'delete')
|
|
|
|
const mockMetadata = getMockMetadata(METADATA_CACHE_MAX_SIZE)
|
|
const expectedDeletedOrigin = Object.keys(mockMetadata)[0]
|
|
|
|
permController.store.getState = sinon.fake.returns({
|
|
[METADATA_STORE_KEY]: { ...mockMetadata },
|
|
})
|
|
|
|
// populate permController._pendingSiteMetadata, as though these origins
|
|
// were actually added
|
|
Object.keys(mockMetadata).forEach((origin) => {
|
|
permController._pendingSiteMetadata.add(origin)
|
|
})
|
|
|
|
permController.addDomainMetadata(DOMAINS.a.origin, { foo: 'bar' })
|
|
|
|
assert.ok(
|
|
permController.store.getState.called,
|
|
'should have called store.getState',
|
|
)
|
|
|
|
const expectedMetadata = {
|
|
...mockMetadata,
|
|
[DOMAINS.a.origin]: {
|
|
foo: 'bar',
|
|
host: DOMAINS.a.host,
|
|
lastUpdated: 1,
|
|
},
|
|
}
|
|
delete expectedMetadata[expectedDeletedOrigin]
|
|
|
|
assert.ok(
|
|
permController._pendingSiteMetadata.delete.calledOnceWithExactly(
|
|
expectedDeletedOrigin,
|
|
),
|
|
'should have called _pendingSiteMetadata.delete once',
|
|
)
|
|
assert.equal(
|
|
permController._setDomainMetadata.getCalls().length,
|
|
1,
|
|
'should have called _setDomainMetadata once',
|
|
)
|
|
assert.deepEqual(permController._setDomainMetadata.lastCall.args, [
|
|
expectedMetadata,
|
|
])
|
|
})
|
|
})
|
|
|
|
describe('_trimDomainMetadata', function () {
|
|
const permController = initPermController()
|
|
|
|
it('trims domain metadata for domains without permissions', function () {
|
|
const metadataArg = {
|
|
[DOMAINS.a.origin]: {},
|
|
[DOMAINS.b.origin]: {},
|
|
}
|
|
|
|
permController.permissions.getDomains = sinon.fake.returns({
|
|
[DOMAINS.a.origin]: {},
|
|
})
|
|
|
|
const metadataResult = permController._trimDomainMetadata(metadataArg)
|
|
|
|
assert.equal(
|
|
permController.permissions.getDomains.getCalls().length,
|
|
1,
|
|
'should have called permissions.getDomains once',
|
|
)
|
|
assert.deepEqual(
|
|
metadataResult,
|
|
{
|
|
[DOMAINS.a.origin]: {},
|
|
},
|
|
'should have produced expected state',
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('miscellanea and edge cases', function () {
|
|
it('requestAccountsPermissionWithId calls _requestAccountsPermission with an explicit request ID', async function () {
|
|
const permController = initPermController()
|
|
const _requestPermissions = sinon
|
|
.stub(permController, '_requestPermissions')
|
|
.resolves()
|
|
await permController.requestAccountsPermissionWithId('example.com')
|
|
assert.ok(
|
|
_requestPermissions.calledOnceWithExactly(
|
|
sinon.match.object.and(sinon.match.has('origin')),
|
|
{ eth_accounts: {} },
|
|
sinon.match.string.and(sinon.match.truthy),
|
|
),
|
|
)
|
|
_requestPermissions.restore()
|
|
})
|
|
})
|
|
})
|