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