1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-30 16:18:07 +01:00
metamask-extension/test/unit/app/controllers/permissions/permissions-middleware-test.js
Erik Marks 8f40d03299
Add approval controller (#9401)
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.
2020-12-14 08:04:26 -08:00

950 lines
24 KiB
JavaScript

import { strict as assert } from 'assert'
import sinon from 'sinon'
import { METADATA_STORE_KEY } from '../../../../../app/scripts/controllers/permissions/enums'
import { PermissionsController } from '../../../../../app/scripts/controllers/permissions'
import { getUserApprovalPromise, grantPermissions } from './helpers'
import {
constants,
getters,
getPermControllerOpts,
getPermissionsMiddleware,
} from './mocks'
const { CAVEATS, ERRORS, PERMS, RPC_REQUESTS } = getters
const { ACCOUNTS, DOMAINS, PERM_NAMES } = constants
const initPermController = () => {
return new PermissionsController({
...getPermControllerOpts(),
})
}
const createApprovalSpies = (permController) => {
sinon.spy(permController.approvals, '_add')
}
const getNextApprovalId = (permController) => {
return permController.approvals._approvals.keys().next().value
}
const validatePermission = (perm, name, origin, caveats) => {
assert.equal(
name,
perm.parentCapability,
'should have expected permission name',
)
assert.equal(origin, perm.invoker, 'should have expected permission origin')
if (caveats) {
assert.deepEqual(
caveats,
perm.caveats,
'should have expected permission caveats',
)
} else {
assert.ok(!perm.caveats, 'should not have any caveats')
}
}
describe('permissions middleware', function () {
describe('wallet_requestPermissions', function () {
let permController
beforeEach(function () {
permController = initPermController()
permController.notifyAccountsChanged = sinon.fake()
})
it('grants permissions on user approval', async function () {
createApprovalSpies(permController)
const aMiddleware = getPermissionsMiddleware(
permController,
DOMAINS.a.origin,
)
const req = RPC_REQUESTS.requestPermission(
DOMAINS.a.origin,
PERM_NAMES.eth_accounts,
)
const res = {}
const userApprovalPromise = getUserApprovalPromise(permController)
const pendingApproval = assert.doesNotReject(
aMiddleware(req, res),
'should not reject permissions request',
)
await userApprovalPromise
assert.ok(
permController.approvals._add.calledOnce,
'should have added single approval request',
)
const id = getNextApprovalId(permController)
const approvedReq = PERMS.approvedRequest(
id,
PERMS.requests.eth_accounts(),
)
await permController.approvePermissionsRequest(
approvedReq,
ACCOUNTS.a.permitted,
)
await pendingApproval
assert.ok(
res.result && !res.error,
'response should have result and no error',
)
assert.equal(
res.result.length,
1,
'origin should have single approved permission',
)
validatePermission(
res.result[0],
PERM_NAMES.eth_accounts,
DOMAINS.a.origin,
CAVEATS.eth_accounts(ACCOUNTS.a.permitted),
)
const aAccounts = await permController.getAccounts(DOMAINS.a.origin)
assert.deepEqual(
aAccounts,
[ACCOUNTS.a.primary],
'origin should have correct accounts',
)
assert.ok(
permController.notifyAccountsChanged.calledOnceWith(
DOMAINS.a.origin,
aAccounts,
),
'expected notification call should have been made',
)
})
it('handles serial approved requests that overwrite existing permissions', async function () {
const aMiddleware = getPermissionsMiddleware(
permController,
DOMAINS.a.origin,
)
// create first request
const req1 = RPC_REQUESTS.requestPermission(
DOMAINS.a.origin,
PERM_NAMES.eth_accounts,
)
const res1 = {}
// send, approve, and validate first request
// note use of ACCOUNTS.a.permitted
let userApprovalPromise = getUserApprovalPromise(permController)
const pendingApproval1 = assert.doesNotReject(
aMiddleware(req1, res1),
'should not reject permissions request',
)
await userApprovalPromise
const id1 = getNextApprovalId(permController)
const approvedReq1 = PERMS.approvedRequest(
id1,
PERMS.requests.eth_accounts(),
)
await permController.approvePermissionsRequest(
approvedReq1,
ACCOUNTS.a.permitted,
)
await pendingApproval1
assert.ok(
res1.result && !res1.error,
'response should have result and no error',
)
assert.equal(
res1.result.length,
1,
'origin should have single approved permission',
)
validatePermission(
res1.result[0],
PERM_NAMES.eth_accounts,
DOMAINS.a.origin,
CAVEATS.eth_accounts(ACCOUNTS.a.permitted),
)
const accounts1 = await permController.getAccounts(DOMAINS.a.origin)
assert.deepEqual(
accounts1,
[ACCOUNTS.a.primary],
'origin should have correct accounts',
)
assert.ok(
permController.notifyAccountsChanged.calledOnceWith(
DOMAINS.a.origin,
accounts1,
),
'expected notification call should have been made',
)
// create second request
const requestedPerms2 = {
...PERMS.requests.eth_accounts(),
...PERMS.requests.test_method(),
}
const req2 = RPC_REQUESTS.requestPermissions(DOMAINS.a.origin, {
...requestedPerms2,
})
const res2 = {}
// send, approve, and validate second request
// note use of ACCOUNTS.b.permitted
userApprovalPromise = getUserApprovalPromise(permController)
const pendingApproval2 = assert.doesNotReject(
aMiddleware(req2, res2),
'should not reject permissions request',
)
await userApprovalPromise
const id2 = getNextApprovalId(permController)
const approvedReq2 = PERMS.approvedRequest(id2, { ...requestedPerms2 })
await permController.approvePermissionsRequest(
approvedReq2,
ACCOUNTS.b.permitted,
)
await pendingApproval2
assert.ok(
res2.result && !res2.error,
'response should have result and no error',
)
assert.equal(
res2.result.length,
2,
'origin should have single approved permission',
)
validatePermission(
res2.result[0],
PERM_NAMES.eth_accounts,
DOMAINS.a.origin,
CAVEATS.eth_accounts(ACCOUNTS.b.permitted),
)
validatePermission(
res2.result[1],
PERM_NAMES.test_method,
DOMAINS.a.origin,
)
const accounts2 = await permController.getAccounts(DOMAINS.a.origin)
assert.deepEqual(
accounts2,
[ACCOUNTS.b.primary],
'origin should have correct accounts',
)
assert.equal(
permController.notifyAccountsChanged.callCount,
2,
'should have called notification method 2 times in total',
)
assert.ok(
permController.notifyAccountsChanged.lastCall.calledWith(
DOMAINS.a.origin,
accounts2,
),
'expected notification call should have been made',
)
})
it('rejects permissions on user rejection', async function () {
createApprovalSpies(permController)
const aMiddleware = getPermissionsMiddleware(
permController,
DOMAINS.a.origin,
)
const req = RPC_REQUESTS.requestPermission(
DOMAINS.a.origin,
PERM_NAMES.eth_accounts,
)
const res = {}
const expectedError = ERRORS.rejectPermissionsRequest.rejection()
const userApprovalPromise = getUserApprovalPromise(permController)
const requestRejection = assert.rejects(
aMiddleware(req, res),
expectedError,
'request should be rejected with correct error',
)
await userApprovalPromise
assert.ok(
permController.approvals._add.calledOnce,
'should have added single approval request',
)
const id = getNextApprovalId(permController)
await permController.rejectPermissionsRequest(id)
await requestRejection
assert.ok(
!res.result && res.error && res.error.message === expectedError.message,
'response should have expected error and no result',
)
const aAccounts = await permController.getAccounts(DOMAINS.a.origin)
assert.deepEqual(
aAccounts,
[],
'origin should have have correct accounts',
)
assert.ok(
permController.notifyAccountsChanged.notCalled,
'should not have called notification method',
)
})
it('rejects requests with unknown permissions', async function () {
createApprovalSpies(permController)
const aMiddleware = getPermissionsMiddleware(
permController,
DOMAINS.a.origin,
)
const req = RPC_REQUESTS.requestPermissions(DOMAINS.a.origin, {
...PERMS.requests.does_not_exist(),
...PERMS.requests.test_method(),
})
const res = {}
const expectedError = ERRORS.rejectPermissionsRequest.methodNotFound(
PERM_NAMES.does_not_exist,
)
await assert.rejects(
aMiddleware(req, res),
expectedError,
'request should be rejected with correct error',
)
assert.ok(
permController.approvals._add.notCalled,
'no approval requests should have been added',
)
assert.ok(
!res.result && res.error && res.error.message === expectedError.message,
'response should have expected error and no result',
)
assert.ok(
permController.notifyAccountsChanged.notCalled,
'should not have called notification method',
)
})
it('accepts only a single pending permissions request per origin', async function () {
createApprovalSpies(permController)
// two middlewares for two origins
const aMiddleware = getPermissionsMiddleware(
permController,
DOMAINS.a.origin,
)
const bMiddleware = getPermissionsMiddleware(
permController,
DOMAINS.b.origin,
)
// create and start processing first request for first origin
const reqA1 = RPC_REQUESTS.requestPermission(
DOMAINS.a.origin,
PERM_NAMES.test_method,
)
const resA1 = {}
let userApprovalPromise = getUserApprovalPromise(permController)
const requestApproval1 = assert.doesNotReject(
aMiddleware(reqA1, resA1),
'should not reject permissions request',
)
await userApprovalPromise
// create and start processing first request for second origin
const reqB1 = RPC_REQUESTS.requestPermission(
DOMAINS.b.origin,
PERM_NAMES.test_method,
)
const resB1 = {}
userApprovalPromise = getUserApprovalPromise(permController)
const requestApproval2 = assert.doesNotReject(
bMiddleware(reqB1, resB1),
'should not reject permissions request',
)
await userApprovalPromise
assert.ok(
permController.approvals._add.calledTwice,
'should have added two approval requests',
)
// create and start processing second request for first origin,
// which should throw
const reqA2 = RPC_REQUESTS.requestPermission(
DOMAINS.a.origin,
PERM_NAMES.test_method,
)
const resA2 = {}
userApprovalPromise = getUserApprovalPromise(permController)
const expectedError = ERRORS.pendingApprovals.requestAlreadyPending(
DOMAINS.a.origin,
)
const requestApprovalFail = assert.rejects(
aMiddleware(reqA2, resA2),
expectedError,
'request should be rejected with correct error',
)
await userApprovalPromise
await requestApprovalFail
assert.ok(
!resA2.result &&
resA2.error &&
resA2.error.message === expectedError.message,
'response should have expected error and no result',
)
assert.equal(
permController.approvals._add.callCount,
3,
'should have attempted to create three pending approvals',
)
assert.equal(
permController.approvals._approvals.size,
2,
'should only have created two pending approvals',
)
// now, remaining pending requests should be approved without issue
for (const id of permController.approvals._approvals.keys()) {
await permController.approvePermissionsRequest(
PERMS.approvedRequest(id, PERMS.requests.test_method()),
)
}
await requestApproval1
await requestApproval2
assert.ok(
resA1.result && !resA1.error,
'first response should have result and no error',
)
assert.equal(
resA1.result.length,
1,
'first origin should have single approved permission',
)
assert.ok(
resB1.result && !resB1.error,
'second response should have result and no error',
)
assert.equal(
resB1.result.length,
1,
'second origin should have single approved permission',
)
})
})
describe('restricted methods', function () {
let permController
beforeEach(function () {
permController = initPermController()
})
it('prevents restricted method access for unpermitted domain', async function () {
const aMiddleware = getPermissionsMiddleware(
permController,
DOMAINS.a.origin,
)
const req = RPC_REQUESTS.test_method(DOMAINS.a.origin)
const res = {}
const expectedError = ERRORS.rpcCap.unauthorized()
await assert.rejects(
aMiddleware(req, res),
expectedError,
'request should be rejected with correct error',
)
assert.ok(
!res.result && res.error && res.error.code === expectedError.code,
'response should have expected error and no result',
)
})
it('allows restricted method access for permitted domain', async function () {
const bMiddleware = getPermissionsMiddleware(
permController,
DOMAINS.b.origin,
)
grantPermissions(
permController,
DOMAINS.b.origin,
PERMS.finalizedRequests.test_method(),
)
const req = RPC_REQUESTS.test_method(DOMAINS.b.origin, true)
const res = {}
await assert.doesNotReject(bMiddleware(req, res), 'should not reject')
assert.ok(
res.result && res.result === 1,
'response should have correct result',
)
})
})
describe('eth_accounts', function () {
let permController
beforeEach(function () {
permController = initPermController()
})
it('returns empty array for non-permitted domain', async function () {
const aMiddleware = getPermissionsMiddleware(
permController,
DOMAINS.a.origin,
)
const req = RPC_REQUESTS.eth_accounts(DOMAINS.a.origin)
const res = {}
await assert.doesNotReject(aMiddleware(req, res), 'should not reject')
assert.ok(
res.result && !res.error,
'response should have result and no error',
)
assert.deepEqual(res.result, [], 'response should have correct result')
})
it('returns correct accounts for permitted domain', async function () {
const aMiddleware = getPermissionsMiddleware(
permController,
DOMAINS.a.origin,
)
grantPermissions(
permController,
DOMAINS.a.origin,
PERMS.finalizedRequests.eth_accounts(ACCOUNTS.a.permitted),
)
const req = RPC_REQUESTS.eth_accounts(DOMAINS.a.origin)
const res = {}
await assert.doesNotReject(aMiddleware(req, res), 'should not reject')
assert.ok(
res.result && !res.error,
'response should have result and no error',
)
assert.deepEqual(
res.result,
[ACCOUNTS.a.primary],
'response should have correct result',
)
})
})
describe('eth_requestAccounts', function () {
let permController
beforeEach(function () {
permController = initPermController()
})
it('requests accounts for unpermitted origin, and approves on user approval', async function () {
createApprovalSpies(permController)
const userApprovalPromise = getUserApprovalPromise(permController)
const aMiddleware = getPermissionsMiddleware(
permController,
DOMAINS.a.origin,
)
const req = RPC_REQUESTS.eth_requestAccounts(DOMAINS.a.origin)
const res = {}
const pendingApproval = assert.doesNotReject(
aMiddleware(req, res),
'should not reject permissions request',
)
await userApprovalPromise
assert.ok(
permController.approvals._add.calledOnce,
'should have added single approval request',
)
const id = getNextApprovalId(permController)
const approvedReq = PERMS.approvedRequest(
id,
PERMS.requests.eth_accounts(),
)
await permController.approvePermissionsRequest(
approvedReq,
ACCOUNTS.a.permitted,
)
// wait for permission to be granted
await pendingApproval
const perms = permController.permissions.getPermissionsForDomain(
DOMAINS.a.origin,
)
assert.equal(
perms.length,
1,
'domain should have correct number of permissions',
)
validatePermission(
perms[0],
PERM_NAMES.eth_accounts,
DOMAINS.a.origin,
CAVEATS.eth_accounts(ACCOUNTS.a.permitted),
)
// we should also see the accounts on the response
assert.ok(
res.result && !res.error,
'response should have result and no error',
)
assert.deepEqual(
res.result,
[ACCOUNTS.a.primary],
'result should have correct accounts',
)
// we should also be able to get the accounts independently
const aAccounts = await permController.getAccounts(DOMAINS.a.origin)
assert.deepEqual(
aAccounts,
[ACCOUNTS.a.primary],
'origin should have have correct accounts',
)
})
it('requests accounts for unpermitted origin, and rejects on user rejection', async function () {
createApprovalSpies(permController)
const userApprovalPromise = getUserApprovalPromise(permController)
const aMiddleware = getPermissionsMiddleware(
permController,
DOMAINS.a.origin,
)
const req = RPC_REQUESTS.eth_requestAccounts(DOMAINS.a.origin)
const res = {}
const expectedError = ERRORS.rejectPermissionsRequest.rejection()
const requestRejection = assert.rejects(
aMiddleware(req, res),
expectedError,
'request should be rejected with correct error',
)
await userApprovalPromise
assert.ok(
permController.approvals._add.calledOnce,
'should have added single approval request',
)
const id = getNextApprovalId(permController)
await permController.rejectPermissionsRequest(id)
await requestRejection
assert.ok(
!res.result && res.error && res.error.message === expectedError.message,
'response should have expected error and no result',
)
const aAccounts = await permController.getAccounts(DOMAINS.a.origin)
assert.deepEqual(
aAccounts,
[],
'origin should have have correct accounts',
)
})
it('directly returns accounts for permitted domain', async function () {
const cMiddleware = getPermissionsMiddleware(
permController,
DOMAINS.c.origin,
)
grantPermissions(
permController,
DOMAINS.c.origin,
PERMS.finalizedRequests.eth_accounts(ACCOUNTS.c.permitted),
)
const req = RPC_REQUESTS.eth_requestAccounts(DOMAINS.c.origin)
const res = {}
await assert.doesNotReject(cMiddleware(req, res), 'should not reject')
assert.ok(
res.result && !res.error,
'response should have result and no error',
)
assert.deepEqual(
res.result,
[ACCOUNTS.c.primary],
'response should have correct result',
)
})
it('rejects new requests when request already pending', async function () {
let unlock
const unlockPromise = new Promise((resolve) => {
unlock = resolve
})
permController.getUnlockPromise = () => unlockPromise
const cMiddleware = getPermissionsMiddleware(
permController,
DOMAINS.c.origin,
)
grantPermissions(
permController,
DOMAINS.c.origin,
PERMS.finalizedRequests.eth_accounts(ACCOUNTS.c.permitted),
)
const req = RPC_REQUESTS.eth_requestAccounts(DOMAINS.c.origin)
const res = {}
// this will block until we resolve the unlock Promise
const requestApproval = assert.doesNotReject(
cMiddleware(req, res),
'should not reject',
)
// this will reject because of the already pending request
await assert.rejects(
cMiddleware({ ...req }, {}),
ERRORS.eth_requestAccounts.requestAlreadyPending(DOMAINS.c.origin),
)
// now unlock and let through the first request
unlock()
await requestApproval
assert.ok(
res.result && !res.error,
'response should have result and no error',
)
assert.deepEqual(
res.result,
[ACCOUNTS.c.primary],
'response should have correct result',
)
})
})
describe('metamask_sendDomainMetadata', function () {
let permController, clock
beforeEach(function () {
permController = initPermController()
clock = sinon.useFakeTimers(1)
})
afterEach(function () {
clock.restore()
})
it('records domain metadata', async function () {
const name = 'BAZ'
const cMiddleware = getPermissionsMiddleware(
permController,
DOMAINS.c.origin,
)
const req = RPC_REQUESTS.metamask_sendDomainMetadata(
DOMAINS.c.origin,
name,
)
const res = {}
await assert.doesNotReject(cMiddleware(req, res), 'should not reject')
assert.ok(res.result, 'result should be true')
const metadataStore = permController.store.getState()[METADATA_STORE_KEY]
assert.deepEqual(
metadataStore,
{
[DOMAINS.c.origin]: {
name,
host: DOMAINS.c.host,
lastUpdated: 1,
},
},
'metadata should have been added to store',
)
})
it('records domain metadata and preserves extensionId', async function () {
const extensionId = 'fooExtension'
const name = 'BAZ'
const cMiddleware = getPermissionsMiddleware(
permController,
DOMAINS.c.origin,
extensionId,
)
const req = RPC_REQUESTS.metamask_sendDomainMetadata(
DOMAINS.c.origin,
name,
)
const res = {}
await assert.doesNotReject(cMiddleware(req, res), 'should not reject')
assert.ok(res.result, 'result should be true')
const metadataStore = permController.store.getState()[METADATA_STORE_KEY]
assert.deepEqual(
metadataStore,
{ [DOMAINS.c.origin]: { name, extensionId, lastUpdated: 1 } },
'metadata should have been added to store',
)
})
it('should not record domain metadata if no name', async function () {
const name = null
const cMiddleware = getPermissionsMiddleware(
permController,
DOMAINS.c.origin,
)
const req = RPC_REQUESTS.metamask_sendDomainMetadata(
DOMAINS.c.origin,
name,
)
const res = {}
await assert.doesNotReject(cMiddleware(req, res), 'should not reject')
assert.ok(res.result, 'result should be true')
const metadataStore = permController.store.getState()[METADATA_STORE_KEY]
assert.deepEqual(
metadataStore,
{},
'metadata should not have been added to store',
)
})
it('should not record domain metadata if no metadata', async function () {
const cMiddleware = getPermissionsMiddleware(
permController,
DOMAINS.c.origin,
)
const req = RPC_REQUESTS.metamask_sendDomainMetadata(DOMAINS.c.origin)
delete req.domainMetadata
const res = {}
await assert.doesNotReject(cMiddleware(req, res), 'should not reject')
assert.ok(res.result, 'result should be true')
const metadataStore = permController.store.getState()[METADATA_STORE_KEY]
assert.deepEqual(
metadataStore,
{},
'metadata should not have been added to store',
)
})
})
})