|
|
|
import { strict as assert } from 'assert'
|
|
|
|
import { find } from 'lodash'
|
|
|
|
import nanoid from 'nanoid'
|
|
|
|
import sinon from 'sinon'
|
|
|
|
|
|
|
|
import {
|
|
|
|
METADATA_STORE_KEY,
|
|
|
|
METADATA_CACHE_MAX_SIZE,
|
|
|
|
WALLET_PREFIX,
|
|
|
|
} from '../../../../../app/scripts/controllers/permissions/enums'
|
|
|
|
|
|
|
|
import {
|
|
|
|
PermissionsController,
|
|
|
|
addInternalMethodPrefix,
|
|
|
|
} from '../../../../../app/scripts/controllers/permissions'
|
|
|
|
|
|
|
|
import {
|
|
|
|
getRequestUserApprovalHelper,
|
|
|
|
grantPermissions,
|
|
|
|
} from './helpers'
|
|
|
|
|
|
|
|
import {
|
|
|
|
noop,
|
|
|
|
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('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 wallet_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 () {
|
|
|
|
|
|
|
|
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 rejectionPromise = assert.rejects(
|
|
|
|
requestUserApproval(REQUEST_IDS.a),
|
|
|
|
ERRORS.validatePermittedAccounts.invalidParam(),
|
|
|
|
'should reject with "null" accounts',
|
|
|
|
)
|
|
|
|
|
|
|
|
await permController.approvePermissionsRequest(request, null)
|
|
|
|
await rejectionPromise
|
|
|
|
|
|
|
|
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(
|
|
|
|
requestUserApproval(REQUEST_IDS.a),
|
|
|
|
ERRORS.approvePermissionsRequest.noPermsRequested(),
|
|
|
|
'should reject if no permissions in request',
|
|
|
|
)
|
|
|
|
|
|
|
|
await permController.approvePermissionsRequest(request, ACCOUNTS.a.permitted)
|
|
|
|
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 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',
|
|
|
|
)
|
|
|
|
|
|
|
|
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 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',
|
|
|
|
)
|
|
|
|
|
|
|
|
assert.equal(
|
|
|
|
permController.pendingApprovals.size, 0,
|
|
|
|
'pending approvals should be empty after approvals',
|
|
|
|
)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
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 () {
|
|
|
|
|
|
|
|
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(
|
|
|
|
requestUserApproval(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(
|
|
|
|
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
|
|
|
|
|
|
|
|
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, 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',
|
|
|
|
)
|
|
|
|
|
|
|
|
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: DOMAINS.a.origin,
|
|
|
|
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[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 () {
|
|
|
|
|
|
|
|
permController.notifyAccountsChanged(
|
|
|
|
DOMAINS.a.origin,
|
|
|
|
ACCOUNTS.a.permitted,
|
|
|
|
)
|
|
|
|
|
|
|
|
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 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 () {
|
|
|
|
|
|
|
|
let permController
|
|
|
|
|
|
|
|
beforeEach(function () {
|
|
|
|
permController = initPermController()
|
|
|
|
})
|
|
|
|
|
|
|
|
it('requestAccountsPermissionWithId calls _requestAccountsPermission with an explicit request ID', async function () {
|
|
|
|
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()
|
|
|
|
})
|
|
|
|
|
|
|
|
it('_addPendingApproval: should throw if adding origin twice', function () {
|
|
|
|
|
|
|
|
const id = nanoid()
|
|
|
|
const origin = DOMAINS.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')
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|