1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-10-26 05:13:37 +02:00
metamask-extension/test/unit/app/controllers/permissions/permissions-middleware-test.js
Erik Marks f6f8e5cc4a
Robustify permissions controller requestUserApproval tests (#9064)
* convert requestUserApproval mock to wrapper
2020-07-27 14:35:09 -07:00

877 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 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')
}
}
const initPermController = () => {
return new PermissionsController({
...getPermControllerOpts(),
})
}
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 () {
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.equal(
permController.pendingApprovals.size, 1,
'perm controller should have single pending approval',
)
const id = permController.pendingApprovals.keys().next().value
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 = permController.pendingApprovals.keys().next().value
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 = permController.pendingApprovals.keys().next().value
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 () {
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.equal(
permController.pendingApprovals.size, 1,
'perm controller should have single pending approval',
)
const id = permController.pendingApprovals.keys().next().value
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 () {
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.equal(
permController.pendingApprovals.size, 0,
'perm controller should have no pending approvals',
)
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 () {
const expectedError = ERRORS.pendingApprovals.requestAlreadyPending()
// 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.equal(
permController.pendingApprovals.size, 2,
'perm controller should have expected number of pending approvals',
)
// 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 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',
)
// first requests for both origins should remain
assert.equal(
permController.pendingApprovals.size, 2,
'perm controller should have expected number of pending approvals',
)
// now, remaining pending requests should be approved without issue
for (const id of permController.pendingApprovals.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',
)
assert.equal(
permController.pendingApprovals.size, 0,
'perm controller should have expected number of pending approvals',
)
})
})
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 () {
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.equal(
permController.pendingApprovals.size, 1,
'perm controller should have single pending approval',
)
const id = permController.pendingApprovals.keys().next().value
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 () {
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.equal(
permController.pendingApprovals.size, 1,
'perm controller should have single pending approval',
)
const id = permController.pendingApprovals.keys().next().value
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(),
)
// 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('wallet_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.wallet_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.wallet_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.wallet_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.wallet_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',
)
})
})
})