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
2020-11-02 17:41:28 -06:00

928 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',
)
})
})
})