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 pendingApproval = assert.doesNotReject( aMiddleware(req, res), 'should not reject permissions request' ) 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 const pendingApproval1 = assert.doesNotReject( aMiddleware(req1, res1), 'should not reject permissions request' ) 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 const pendingApproval2 = assert.doesNotReject( aMiddleware(req2, res2), 'should not reject permissions request' ) 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 requestRejection = assert.rejects( aMiddleware(req, res), expectedError, 'request should be rejected with correct error', ) 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 = {} const requestApproval1 = assert.doesNotReject( aMiddleware(reqA1, resA1), 'should not reject permissions request' ) // create and start processing first request for second origin const reqB1 = RPC_REQUESTS.requestPermission( DOMAINS.b.origin, PERM_NAMES.test_method ) const resB1 = {} const requestApproval2 = assert.doesNotReject( bMiddleware(reqB1, resB1), 'should not reject permissions request' ) 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 = {} await assert.rejects( aMiddleware(reqA2, resA2), expectedError, 'request should be rejected with correct error', ) 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' ) }) }) })