A Metamask fork with Infura removed and default networks editable
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ciphermask/test/unit/app/controllers/permissions/permissions-log-controller-...

691 lines
17 KiB

import { strict as assert } from 'assert'
import ObservableStore from 'obs-store'
import nanoid from 'nanoid'
import { useFakeTimers } from 'sinon'
import PermissionsLogController
from '../../../../../app/scripts/controllers/permissions/permissionsLog'
import {
LOG_LIMIT,
LOG_METHOD_TYPES,
} from '../../../../../app/scripts/controllers/permissions/enums'
import {
validateActivityEntry,
} from './helpers'
import {
constants,
getters,
noop,
} from './mocks'
const {
ERRORS,
PERMS,
RPC_REQUESTS,
} = getters
const {
ACCOUNT_ARRAYS,
EXPECTED_HISTORIES,
ORIGINS,
PERM_NAMES,
REQUEST_IDS,
RESTRICTED_METHODS,
} = constants
let clock
const initPermLog = () => {
return new PermissionsLogController({
store: new ObservableStore(),
restrictedMethods: RESTRICTED_METHODS,
})
}
const mockNext = (handler) => {
if (handler) {
handler(noop)
}
}
const initMiddleware = (permLog) => {
const middleware = permLog.createMiddleware()
return (req, res, next = mockNext) => {
middleware(req, res, next)
}
}
const initClock = () => {
clock = useFakeTimers(1)
}
const tearDownClock = () => {
clock.restore()
}
const getSavedMockNext = (arr) => (handler) => {
arr.push(handler)
}
describe('permissions log', function () {
describe('activity log', function () {
let permLog, logMiddleware
beforeEach(function () {
permLog = initPermLog()
logMiddleware = initMiddleware(permLog)
})
it('records activity for restricted methods', function () {
let log, req, res
// test_method, success
req = RPC_REQUESTS.test_method(ORIGINS.a)
req.id = REQUEST_IDS.a
res = { foo: 'bar' }
logMiddleware({ ...req }, res)
log = permLog.getActivityLog()
const entry1 = log[0]
assert.equal(log.length, 1, 'log should have single entry')
validateActivityEntry(
entry1, { ...req }, { ...res },
LOG_METHOD_TYPES.restricted, true
)
// eth_accounts, failure
req = RPC_REQUESTS.eth_accounts(ORIGINS.b)
req.id = REQUEST_IDS.b
res = { error: new Error('Unauthorized.') }
logMiddleware({ ...req }, res)
log = permLog.getActivityLog()
const entry2 = log[1]
assert.equal(log.length, 2, 'log should have 2 entries')
validateActivityEntry(
entry2, { ...req }, { ...res },
LOG_METHOD_TYPES.restricted, false
)
// eth_requestAccounts, success
req = RPC_REQUESTS.eth_requestAccounts(ORIGINS.c)
req.id = REQUEST_IDS.c
res = { result: ACCOUNT_ARRAYS.c }
logMiddleware({ ...req }, res)
log = permLog.getActivityLog()
const entry3 = log[2]
assert.equal(log.length, 3, 'log should have 3 entries')
validateActivityEntry(
entry3, { ...req }, { ...res },
LOG_METHOD_TYPES.restricted, true
)
// test_method, no response
req = RPC_REQUESTS.test_method(ORIGINS.a)
req.id = REQUEST_IDS.a
res = null
logMiddleware({ ...req }, res)
log = permLog.getActivityLog()
const entry4 = log[3]
assert.equal(log.length, 4, 'log should have 4 entries')
validateActivityEntry(
entry4, { ...req }, null,
LOG_METHOD_TYPES.restricted, false
)
// validate final state
assert.equal(entry1, log[0], 'first log entry should remain')
assert.equal(entry2, log[1], 'second log entry should remain')
assert.equal(entry3, log[2], 'third log entry should remain')
assert.equal(entry4, log[3], 'fourth log entry should remain')
})
it('handles responses added out of order', function () {
let log
const handlerArray = []
const id1 = nanoid()
const id2 = nanoid()
const id3 = nanoid()
const req = RPC_REQUESTS.test_method(ORIGINS.a)
// get make requests
req.id = id1
const res1 = { foo: id1 }
logMiddleware({ ...req }, { ...res1 }, getSavedMockNext(handlerArray))
req.id = id2
const res2 = { foo: id2 }
logMiddleware({ ...req }, { ...res2 }, getSavedMockNext(handlerArray))
req.id = id3
const res3 = { foo: id3 }
logMiddleware({ ...req }, { ...res3 }, getSavedMockNext(handlerArray))
// verify log state
log = permLog.getActivityLog()
assert.equal(log.length, 3, 'log should have 3 entries')
const entry1 = log[0]
const entry2 = log[1]
const entry3 = log[2]
assert.ok(
(
entry1.id === id1 && entry1.response === null &&
entry2.id === id2 && entry2.response === null &&
entry3.id === id3 && entry3.response === null
),
'all entries should be in correct order and without responses'
)
// call response handlers
for (const i of [1, 2, 0]) {
handlerArray[i](noop)
}
// verify log state again
log = permLog.getActivityLog()
assert.equal(log.length, 3, 'log should have 3 entries')
// verify all entries
log = permLog.getActivityLog()
validateActivityEntry(
log[0], { ...req, id: id1 }, { ...res1 },
LOG_METHOD_TYPES.restricted, true
)
validateActivityEntry(
log[1], { ...req, id: id2 }, { ...res2 },
LOG_METHOD_TYPES.restricted, true
)
validateActivityEntry(
log[2], { ...req, id: id3 }, { ...res3 },
LOG_METHOD_TYPES.restricted, true
)
})
it('handles a lack of response', function () {
let req = RPC_REQUESTS.test_method(ORIGINS.a)
req.id = REQUEST_IDS.a
let res = { foo: 'bar' }
// noop for next handler prevents recording of response
logMiddleware({ ...req }, res, noop)
let log = permLog.getActivityLog()
const entry1 = log[0]
assert.equal(log.length, 1, 'log should have single entry')
validateActivityEntry(
entry1, { ...req }, null,
LOG_METHOD_TYPES.restricted, true
)
// next request should be handled as normal
req = RPC_REQUESTS.eth_accounts(ORIGINS.b)
req.id = REQUEST_IDS.b
res = { result: ACCOUNT_ARRAYS.b }
logMiddleware({ ...req }, res)
log = permLog.getActivityLog()
const entry2 = log[1]
assert.equal(log.length, 2, 'log should have 2 entries')
validateActivityEntry(
entry2, { ...req }, { ...res },
LOG_METHOD_TYPES.restricted, true
)
// validate final state
assert.equal(entry1, log[0], 'first log entry remains')
assert.equal(entry2, log[1], 'second log entry remains')
})
it('ignores expected methods', function () {
let log = permLog.getActivityLog()
assert.equal(log.length, 0, 'log should be empty')
const res = { foo: 'bar' }
const req1 = RPC_REQUESTS.wallet_sendDomainMetadata(ORIGINS.c, 'foobar')
const req2 = RPC_REQUESTS.custom(ORIGINS.b, 'eth_getBlockNumber')
const req3 = RPC_REQUESTS.custom(ORIGINS.b, 'net_version')
logMiddleware(req1, res)
logMiddleware(req2, res)
logMiddleware(req3, res)
log = permLog.getActivityLog()
assert.equal(log.length, 0, 'log should still be empty')
})
it('enforces log limit', function () {
const req = RPC_REQUESTS.test_method(ORIGINS.a)
const res = { foo: 'bar' }
// max out log
let lastId
for (let i = 0; i < LOG_LIMIT; i++) {
lastId = nanoid()
logMiddleware({ ...req, id: lastId }, { ...res })
}
// check last entry valid
let log = permLog.getActivityLog()
assert.equal(
log.length, LOG_LIMIT, 'log should have LOG_LIMIT num entries'
)
validateActivityEntry(
log[LOG_LIMIT - 1], { ...req, id: lastId }, res,
LOG_METHOD_TYPES.restricted, true
)
// store the id of the current second entry
const nextFirstId = log[1]['id']
// add one more entry to log, putting it over the limit
lastId = nanoid()
logMiddleware({ ...req, id: lastId }, { ...res })
// check log length
log = permLog.getActivityLog()
assert.equal(
log.length, LOG_LIMIT, 'log should have LOG_LIMIT num entries'
)
// check first and last entries
validateActivityEntry(
log[0], { ...req, id: nextFirstId }, res,
LOG_METHOD_TYPES.restricted, true
)
validateActivityEntry(
log[LOG_LIMIT - 1], { ...req, id: lastId }, res,
LOG_METHOD_TYPES.restricted, true
)
})
})
describe('permissions history', function () {
let permLog, logMiddleware
beforeEach(function () {
permLog = initPermLog()
logMiddleware = initMiddleware(permLog)
initClock()
})
afterEach(function () {
tearDownClock()
})
it('only updates history on responses', function () {
let permHistory
const req = RPC_REQUESTS.requestPermission(
ORIGINS.a, PERM_NAMES.test_method
)
const res = { result: [ PERMS.granted.test_method() ] }
// noop => no response
logMiddleware({ ...req }, { ...res }, noop)
permHistory = permLog.getHistory()
assert.deepEqual(permHistory, {}, 'history should not have been updated')
// response => records granted permissions
logMiddleware({ ...req }, { ...res })
permHistory = permLog.getHistory()
assert.equal(
Object.keys(permHistory).length, 1,
'history should have single origin'
)
assert.ok(
Boolean(permHistory[ORIGINS.a]),
'history should have expected origin'
)
})
it('ignores malformed permissions requests', function () {
const req = RPC_REQUESTS.requestPermission(
ORIGINS.a, PERM_NAMES.test_method
)
delete req.params
const res = { result: [ PERMS.granted.test_method() ] }
// no params => no response
logMiddleware({ ...req }, { ...res })
assert.deepEqual(permLog.getHistory(), {}, 'history should not have been updated')
})
it('records and updates account history as expected', async function () {
let permHistory
const req = RPC_REQUESTS.requestPermission(
ORIGINS.a, PERM_NAMES.eth_accounts
)
const res = {
result: [ PERMS.granted.eth_accounts(ACCOUNT_ARRAYS.a) ],
}
logMiddleware({ ...req }, { ...res })
// validate history
permHistory = permLog.getHistory()
assert.deepEqual(
permHistory,
EXPECTED_HISTORIES.case1[0],
'should have correct history'
)
// mock permission requested again, with another approved account
clock.tick(1)
res.result = [ PERMS.granted.eth_accounts([ ACCOUNT_ARRAYS.a[0] ]) ]
logMiddleware({ ...req }, { ...res })
permHistory = permLog.getHistory()
assert.deepEqual(
permHistory,
EXPECTED_HISTORIES.case1[1],
'should have correct history'
)
})
it('handles eth_accounts response without caveats', async function () {
const req = RPC_REQUESTS.requestPermission(
ORIGINS.a, PERM_NAMES.eth_accounts
)
const res = {
result: [ PERMS.granted.eth_accounts(ACCOUNT_ARRAYS.a) ],
}
delete res.result[0].caveats
logMiddleware({ ...req }, { ...res })
// validate history
assert.deepEqual(
permLog.getHistory(), EXPECTED_HISTORIES.case2[0],
'should have expected history'
)
})
it('handles extra caveats for eth_accounts', async function () {
const req = RPC_REQUESTS.requestPermission(
ORIGINS.a, PERM_NAMES.eth_accounts
)
const res = {
result: [ PERMS.granted.eth_accounts(ACCOUNT_ARRAYS.a) ],
}
res.result[0].caveats.push({ foo: 'bar' })
logMiddleware({ ...req }, { ...res })
// validate history
assert.deepEqual(
permLog.getHistory(),
EXPECTED_HISTORIES.case1[0],
'should have correct history'
)
})
// wallet_requestPermissions returns all permissions approved for the
// requesting origin, including old ones
it('handles unrequested permissions on the response', async function () {
const req = RPC_REQUESTS.requestPermission(
ORIGINS.a, PERM_NAMES.eth_accounts
)
const res = {
result: [
PERMS.granted.eth_accounts(ACCOUNT_ARRAYS.a),
PERMS.granted.test_method(),
],
}
logMiddleware({ ...req }, { ...res })
// validate history
assert.deepEqual(
permLog.getHistory(),
EXPECTED_HISTORIES.case1[0],
'should have correct history'
)
})
it('does not update history if no new permissions are approved', async function () {
let req = RPC_REQUESTS.requestPermission(
ORIGINS.a, PERM_NAMES.test_method
)
let res = {
result: [
PERMS.granted.test_method(),
],
}
logMiddleware({ ...req }, { ...res })
// validate history
assert.deepEqual(
permLog.getHistory(),
EXPECTED_HISTORIES.case4[0],
'should have correct history'
)
// new permission requested, but not approved
clock.tick(1)
req = RPC_REQUESTS.requestPermission(
ORIGINS.a, PERM_NAMES.eth_accounts
)
res = {
result: [
PERMS.granted.test_method(),
],
}
logMiddleware({ ...req }, { ...res })
// validate history
assert.deepEqual(
permLog.getHistory(),
EXPECTED_HISTORIES.case4[0],
'should have same history as before'
)
})
it('records and updates history for multiple origins, regardless of response order', async function () {
let permHistory
// make first round of requests
const round1 = []
const handlers1 = []
// first origin
round1.push({
req: RPC_REQUESTS.requestPermission(
ORIGINS.a, PERM_NAMES.test_method
),
res: {
result: [ PERMS.granted.test_method() ],
},
})
// second origin
round1.push({
req: RPC_REQUESTS.requestPermission(
ORIGINS.b, PERM_NAMES.eth_accounts
),
res: {
result: [ PERMS.granted.eth_accounts(ACCOUNT_ARRAYS.b) ],
},
})
// third origin
round1.push({
req: RPC_REQUESTS.requestPermissions(ORIGINS.c, {
[PERM_NAMES.test_method]: {},
[PERM_NAMES.eth_accounts]: {},
}),
res: {
result: [
PERMS.granted.test_method(),
PERMS.granted.eth_accounts(ACCOUNT_ARRAYS.c),
],
},
})
// make requests and process responses out of order
round1.forEach((x) => {
logMiddleware({ ...x.req }, { ...x.res }, getSavedMockNext(handlers1))
})
for (const i of [1, 2, 0]) {
handlers1[i](noop)
}
// validate history
permHistory = permLog.getHistory()
assert.deepEqual(
permHistory, EXPECTED_HISTORIES.case3[0],
'should have expected history'
)
// make next round of requests
clock.tick(1)
const round2 = []
// we're just gonna process these in order
// first origin
round2.push({
req: RPC_REQUESTS.requestPermission(
ORIGINS.a, PERM_NAMES.test_method
),
res: {
result: [ PERMS.granted.test_method() ],
},
})
// nothing for second origin
// third origin
round2.push({
req: RPC_REQUESTS.requestPermissions(ORIGINS.c, {
[PERM_NAMES.eth_accounts]: {},
}),
res: {
result: [
PERMS.granted.eth_accounts(ACCOUNT_ARRAYS.b),
],
},
})
// make requests
round2.forEach((x) => {
logMiddleware({ ...x.req }, { ...x.res })
})
// validate history
permHistory = permLog.getHistory()
assert.deepEqual(
permHistory, EXPECTED_HISTORIES.case3[1],
'should have expected history'
)
})
})
describe('instance method edge cases', function () {
it('logAccountExposure errors on invalid params', function () {
const permLog = initPermLog()
assert.throws(
() => {
permLog.logAccountExposure('', ACCOUNT_ARRAYS.a)
},
ERRORS.logAccountExposure.invalidParams(),
'should throw expected error'
)
assert.throws(
() => {
permLog.logAccountExposure(null, ACCOUNT_ARRAYS.a)
},
ERRORS.logAccountExposure.invalidParams(),
'should throw expected error'
)
assert.throws(
() => {
permLog.logAccountExposure('foo', {})
},
ERRORS.logAccountExposure.invalidParams(),
'should throw expected error'
)
assert.throws(
() => {
permLog.logAccountExposure('foo', [])
},
ERRORS.logAccountExposure.invalidParams(),
'should throw expected error'
)
})
})
})