Add permissions controller unit tests (#7969)
* add permissions controller, log, middleware, and restricted method unit tests * fix permissions-related bugs * convert permissions log to controller-like class * add permissions unit test coverage requirements * update rpc-cap Co-Authored-By: Whymarrh Whitby <whymarrh.whitby@gmail.com> Co-Authored-By: Mark Stacey <markjstacey@gmail.com>feature/default_network_editable
parent
45efbbecb7
commit
b1d090ac4d
@ -0,0 +1,6 @@ |
||||
module.exports = { |
||||
branches: 95, |
||||
lines: 95, |
||||
functions: 95, |
||||
statements: 95, |
||||
} |
@ -0,0 +1,117 @@ |
||||
import { strict as assert } from 'assert' |
||||
|
||||
import { noop } from './mocks' |
||||
|
||||
/** |
||||
* Grants the given permissions to the given origin, using the given permissions |
||||
* controller. |
||||
* |
||||
* Just a wrapper for an rpc-cap middleware function. |
||||
* |
||||
* @param {PermissionsController} permController - The permissions controller. |
||||
* @param {string} origin - The origin to grant permissions to. |
||||
* @param {Object} permissions - The permissions to grant. |
||||
*/ |
||||
export function grantPermissions (permController, origin, permissions) { |
||||
permController.permissions.grantNewPermissions( |
||||
origin, permissions, {}, noop |
||||
) |
||||
} |
||||
|
||||
/** |
||||
* Sets the underlying rpc-cap requestUserApproval function, and returns |
||||
* a promise that's resolved once it has been set. |
||||
* |
||||
* This function must be called on the given permissions controller every |
||||
* time you want such a Promise. As of writing, it's only called once per test. |
||||
* |
||||
* @param {PermissionsController} - A permissions controller. |
||||
* @returns {Promise<void>} A Promise that resolves once a pending approval |
||||
* has been set. |
||||
*/ |
||||
export function getUserApprovalPromise (permController) { |
||||
return new Promise((resolveForCaller) => { |
||||
permController.permissions.requestUserApproval = async (req) => { |
||||
const { origin, metadata: { id } } = req |
||||
|
||||
return new Promise((resolve, reject) => { |
||||
permController.pendingApprovals.set(id, { origin, resolve, reject }) |
||||
resolveForCaller() |
||||
}) |
||||
} |
||||
}) |
||||
} |
||||
|
||||
/** |
||||
* Validates an activity log entry with respect to a request, response, and |
||||
* relevant metadata. |
||||
* |
||||
* @param {Object} entry - The activity log entry to validate. |
||||
* @param {Object} req - The request that generated the entry. |
||||
* @param {Object} [res] - The response for the request, if any. |
||||
* @param {'restricted'|'internal'} methodType - The method log controller method type of the request. |
||||
* @param {boolean} success - Whether the request succeeded or not. |
||||
*/ |
||||
export function validateActivityEntry ( |
||||
entry, req, res, methodType, success |
||||
) { |
||||
assert.doesNotThrow( |
||||
() => { |
||||
_validateActivityEntry( |
||||
entry, req, res, methodType, success |
||||
) |
||||
}, |
||||
'should have expected activity entry' |
||||
) |
||||
} |
||||
|
||||
function _validateActivityEntry ( |
||||
entry, req, res, methodType, success |
||||
) { |
||||
|
||||
assert.ok(entry, 'entry should exist') |
||||
|
||||
assert.equal(entry.id, req.id) |
||||
assert.equal(entry.method, req.method) |
||||
assert.equal(entry.origin, req.origin) |
||||
assert.equal(entry.methodType, methodType) |
||||
assert.deepEqual( |
||||
entry.request, req, |
||||
'entry.request should equal the request' |
||||
) |
||||
|
||||
if (res) { |
||||
|
||||
assert.ok( |
||||
( |
||||
Number.isInteger(entry.requestTime) && |
||||
Number.isInteger(entry.responseTime) |
||||
), |
||||
'request and response times should be numbers' |
||||
) |
||||
assert.ok( |
||||
(entry.requestTime <= entry.responseTime), |
||||
'request time should be less than response time' |
||||
) |
||||
|
||||
assert.equal(entry.success, success) |
||||
assert.deepEqual( |
||||
entry.response, res, |
||||
'entry.response should equal the response' |
||||
) |
||||
} else { |
||||
|
||||
assert.ok( |
||||
Number.isInteger(entry.requestTime) && entry.requestTime > 0, |
||||
'entry should have non-zero request time' |
||||
) |
||||
assert.ok( |
||||
( |
||||
entry.success === null && |
||||
entry.responseTime === null && |
||||
entry.response === null |
||||
), |
||||
'entry response values should be null' |
||||
) |
||||
} |
||||
} |
@ -0,0 +1,691 @@ |
||||
import { ethErrors, ERROR_CODES } from 'eth-json-rpc-errors' |
||||
import deepFreeze from 'deep-freeze-strict' |
||||
|
||||
import _getRestrictedMethods |
||||
from '../../../../../app/scripts/controllers/permissions/restrictedMethods' |
||||
|
||||
import { |
||||
CAVEAT_NAMES, |
||||
NOTIFICATION_NAMES, |
||||
} from '../../../../../app/scripts/controllers/permissions/enums' |
||||
|
||||
/** |
||||
* README |
||||
* This file contains three primary kinds of mocks: |
||||
* - Mocks for initializing a permissions controller and getting a permissions |
||||
* middleware |
||||
* - Functions for getting various mock objects consumed or produced by |
||||
* permissions controller methods |
||||
* - Immutable mock values like Ethereum accounts and expected states |
||||
*/ |
||||
|
||||
export const noop = () => {} |
||||
|
||||
/** |
||||
* Mock Permissions Controller and Middleware |
||||
*/ |
||||
|
||||
const platform = { |
||||
openExtensionInBrowser: noop, |
||||
} |
||||
|
||||
const keyringAccounts = deepFreeze([ |
||||
'0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', |
||||
'0xc42edfcc21ed14dda456aa0756c153f7985d8813', |
||||
]) |
||||
|
||||
const getKeyringAccounts = async () => [ ...keyringAccounts ] |
||||
|
||||
// perm controller initialization helper
|
||||
const getRestrictedMethods = (permController) => { |
||||
return { |
||||
|
||||
// the actual, production restricted methods
|
||||
..._getRestrictedMethods(permController), |
||||
|
||||
// our own dummy method for testing
|
||||
'test_method': { |
||||
description: `This method is only for testing.`, |
||||
method: (req, res, __, end) => { |
||||
if (req.params[0]) { |
||||
res.result = 1 |
||||
} else { |
||||
res.result = 0 |
||||
} |
||||
end() |
||||
}, |
||||
}, |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Gets default mock constructor options for a permissions controller. |
||||
* |
||||
* @returns {Object} A PermissionsController constructor options object. |
||||
*/ |
||||
export function getPermControllerOpts () { |
||||
return { |
||||
platform, |
||||
getKeyringAccounts, |
||||
notifyDomain: noop, |
||||
notifyAllDomains: noop, |
||||
getRestrictedMethods, |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Gets a Promise-wrapped permissions controller middleware function. |
||||
* |
||||
* @param {PermissionsController} permController - The permissions controller to get a |
||||
* middleware for. |
||||
* @param {string} origin - The origin for the middleware. |
||||
* @param {string} extensionId - The extension id for the middleware. |
||||
* @returns {Function} A Promise-wrapped middleware function with convenient default args. |
||||
*/ |
||||
export function getPermissionsMiddleware (permController, origin, extensionId) { |
||||
const middleware = permController.createMiddleware({ origin, extensionId }) |
||||
return (req, res = {}, next = noop, end) => { |
||||
return new Promise((resolve, reject) => { |
||||
|
||||
end = end || _end |
||||
|
||||
middleware(req, res, next, end) |
||||
|
||||
// emulates json-rpc-engine error handling
|
||||
function _end (err) { |
||||
if (err || res.error) { |
||||
reject(err || res.error) |
||||
} else { |
||||
resolve(res) |
||||
} |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* @param {Object} notifications - An object that will store notifications produced |
||||
* by the permissions controller. |
||||
* @returns {Function} A function passed to the permissions controller at initialization, |
||||
* for recording notifications. |
||||
*/ |
||||
export const getNotifyDomain = (notifications = {}) => (origin, notification) => { |
||||
notifications[origin].push(notification) |
||||
} |
||||
|
||||
/** |
||||
* @param {Object} notifications - An object that will store notifications produced |
||||
* by the permissions controller. |
||||
* @returns {Function} A function passed to the permissions controller at initialization, |
||||
* for recording notifications. |
||||
*/ |
||||
export const getNotifyAllDomains = (notifications = {}) => (notification) => { |
||||
Object.keys(notifications).forEach((origin) => { |
||||
notifications[origin].push(notification) |
||||
}) |
||||
} |
||||
|
||||
/** |
||||
* Constants and Mock Objects |
||||
* - e.g. permissions, caveats, and permission requests |
||||
*/ |
||||
|
||||
const ORIGINS = { |
||||
a: 'foo.xyz', |
||||
b: 'bar.abc', |
||||
c: 'baz.def', |
||||
} |
||||
|
||||
const PERM_NAMES = { |
||||
eth_accounts: 'eth_accounts', |
||||
test_method: 'test_method', |
||||
does_not_exist: 'does_not_exist', |
||||
} |
||||
|
||||
const ACCOUNT_ARRAYS = { |
||||
a: [ ...keyringAccounts ], |
||||
b: [keyringAccounts[0]], |
||||
c: [keyringAccounts[1]], |
||||
} |
||||
|
||||
/** |
||||
* Helpers for getting mock caveats. |
||||
*/ |
||||
const CAVEATS = { |
||||
|
||||
/** |
||||
* Gets a correctly formatted eth_accounts exposedAccounts caveat. |
||||
* |
||||
* @param {Array<string>} accounts - The accounts for the caveat |
||||
* @returns {Object} An eth_accounts exposedAccounts caveats |
||||
*/ |
||||
eth_accounts: (accounts) => { |
||||
return { |
||||
type: 'filterResponse', |
||||
value: accounts, |
||||
name: CAVEAT_NAMES.exposedAccounts, |
||||
} |
||||
}, |
||||
} |
||||
|
||||
/** |
||||
* Each function here corresponds to what would be a type or interface consumed |
||||
* by permissions controller functions if we used TypeScript. |
||||
*/ |
||||
const PERMS = { |
||||
|
||||
/** |
||||
* The argument to approvePermissionsRequest |
||||
* @param {string} id - The rpc-cap permissions request id. |
||||
* @param {Object} permissions - The approved permissions, request-formatted. |
||||
*/ |
||||
approvedRequest: (id, permissions = {}) => { |
||||
return { |
||||
permissions: { ...permissions }, |
||||
metadata: { id }, |
||||
} |
||||
}, |
||||
|
||||
/** |
||||
* Requested permissions objects, as passed to wallet_requestPermissions. |
||||
*/ |
||||
requests: { |
||||
|
||||
/** |
||||
* @returns {Object} A permissions request object with eth_accounts |
||||
*/ |
||||
eth_accounts: () => { |
||||
return { eth_accounts: {} } |
||||
}, |
||||
|
||||
/** |
||||
* @returns {Object} A permissions request object with test_method |
||||
*/ |
||||
test_method: () => { |
||||
return { test_method: {} } |
||||
}, |
||||
|
||||
/** |
||||
* @returns {Object} A permissions request object with does_not_exist |
||||
*/ |
||||
does_not_exist: () => { |
||||
return { does_not_exist: {} } |
||||
}, |
||||
}, |
||||
|
||||
/** |
||||
* Finalized permission requests, as returned by finalizePermissionsRequest |
||||
*/ |
||||
finalizedRequests: { |
||||
|
||||
/** |
||||
* @param {Array<string>} accounts - The accounts for the eth_accounts permission caveat |
||||
* @returns {Object} A finalized permissions request object with eth_accounts and its caveat |
||||
*/ |
||||
eth_accounts: (accounts) => { |
||||
return { |
||||
eth_accounts: { |
||||
caveats: [CAVEATS.eth_accounts(accounts)], |
||||
} } |
||||
}, |
||||
|
||||
/** |
||||
* @returns {Object} A finalized permissions request object with test_method |
||||
*/ |
||||
test_method: () => { |
||||
return { |
||||
test_method: {}, |
||||
} |
||||
}, |
||||
}, |
||||
|
||||
/** |
||||
* Partial members of res.result for successful: |
||||
* - wallet_requestPermissions |
||||
* - wallet_getPermissions |
||||
*/ |
||||
granted: { |
||||
|
||||
/** |
||||
* @param {Array<string>} accounts - The accounts for the eth_accounts permission caveat |
||||
* @returns {Object} A granted permissions object with eth_accounts and its caveat |
||||
*/ |
||||
eth_accounts: (accounts) => { |
||||
return { |
||||
parentCapability: PERM_NAMES.eth_accounts, |
||||
caveats: [CAVEATS.eth_accounts(accounts)], |
||||
} |
||||
}, |
||||
|
||||
/** |
||||
* @returns {Object} A granted permissions object with test_method |
||||
*/ |
||||
test_method: () => { |
||||
return { |
||||
parentCapability: PERM_NAMES.test_method, |
||||
} |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
/** |
||||
* Objects with function values for getting correctly formatted permissions, |
||||
* caveats, errors, permissions requests etc. |
||||
*/ |
||||
export const getters = deepFreeze({ |
||||
|
||||
CAVEATS, |
||||
|
||||
PERMS, |
||||
|
||||
/** |
||||
* Getters for errors by the method or workflow that throws them. |
||||
*/ |
||||
ERRORS: { |
||||
|
||||
validatePermittedAccounts: { |
||||
|
||||
invalidParam: () => { |
||||
return { |
||||
name: 'Error', |
||||
message: 'Must provide non-empty array of account(s).', |
||||
} |
||||
}, |
||||
|
||||
nonKeyringAccount: (account) => { |
||||
return { |
||||
name: 'Error', |
||||
message: `Unknown account: ${account}`, |
||||
} |
||||
}, |
||||
}, |
||||
|
||||
finalizePermissionsRequest: { |
||||
grantEthAcountsFailure: (origin) => { |
||||
return { |
||||
// name: 'EthereumRpcError',
|
||||
message: `Failed to add 'eth_accounts' to '${origin}'.`, |
||||
code: ERROR_CODES.rpc.internal, |
||||
} |
||||
}, |
||||
}, |
||||
|
||||
updatePermittedAccounts: { |
||||
invalidOrigin: () => { |
||||
return { |
||||
message: 'No such permission exists for the given domain.', |
||||
} |
||||
}, |
||||
}, |
||||
|
||||
legacyExposeAccounts: { |
||||
badOrigin: () => { |
||||
return { |
||||
message: 'Must provide non-empty string origin.', |
||||
} |
||||
}, |
||||
forbiddenUsage: () => { |
||||
return { |
||||
name: 'Error', |
||||
message: 'May not call legacyExposeAccounts on origin with exposed accounts.', |
||||
} |
||||
}, |
||||
}, |
||||
|
||||
handleNewAccountSelected: { |
||||
invalidParams: () => { |
||||
return { |
||||
name: 'Error', |
||||
message: 'Should provide non-empty origin and account strings.', |
||||
} |
||||
}, |
||||
}, |
||||
|
||||
approvePermissionsRequest: { |
||||
noPermsRequested: () => { |
||||
return { |
||||
message: 'Must request at least one permission.', |
||||
} |
||||
}, |
||||
}, |
||||
|
||||
rejectPermissionsRequest: { |
||||
rejection: () => { |
||||
return { |
||||
message: ethErrors.provider.userRejectedRequest().message, |
||||
} |
||||
}, |
||||
methodNotFound: (methodName) => { |
||||
return { |
||||
message: `The method '${methodName}' does not exist / is not available.`, |
||||
} |
||||
}, |
||||
}, |
||||
|
||||
createMiddleware: { |
||||
badOrigin: () => { |
||||
return { |
||||
message: 'Must provide non-empty string origin.', |
||||
} |
||||
}, |
||||
}, |
||||
|
||||
rpcCap: { |
||||
unauthorized: () => { |
||||
return { |
||||
code: 4100, |
||||
} |
||||
}, |
||||
}, |
||||
|
||||
logAccountExposure: { |
||||
invalidParams: () => { |
||||
return { |
||||
message: 'Must provide non-empty string origin and array of accounts.', |
||||
} |
||||
}, |
||||
}, |
||||
|
||||
pendingApprovals: { |
||||
duplicateOriginOrId: (id, origin) => { |
||||
return { |
||||
message: `Pending approval with id ${id} or origin ${origin} already exists.`, |
||||
} |
||||
}, |
||||
requestAlreadyPending: () => { |
||||
return { |
||||
message: 'Permissions request already pending; please wait.', |
||||
} |
||||
}, |
||||
}, |
||||
}, |
||||
|
||||
/** |
||||
* Getters for notifications produced by the permissions controller. |
||||
*/ |
||||
NOTIFICATIONS: { |
||||
|
||||
/** |
||||
* Gets a removed accounts notification. |
||||
* |
||||
* @returns {Object} An accountsChanged notification with an empty array as its result |
||||
*/ |
||||
removedAccounts: () => { |
||||
return { |
||||
method: NOTIFICATION_NAMES.accountsChanged, |
||||
result: [], |
||||
} |
||||
}, |
||||
|
||||
/** |
||||
* Gets a new accounts notification. |
||||
* |
||||
* @param {Array<string>} accounts - The accounts added to the notification. |
||||
* @returns {Object} An accountsChanged notification with the given accounts as its result |
||||
*/ |
||||
newAccounts: (accounts) => { |
||||
return { |
||||
method: NOTIFICATION_NAMES.accountsChanged, |
||||
result: accounts, |
||||
} |
||||
}, |
||||
|
||||
/** |
||||
* Gets a test notification that doesn't occur in practice. |
||||
* |
||||
* @returns {Object} A notification with the 'test_notification' method name |
||||
*/ |
||||
test: () => { |
||||
return { |
||||
method: 'test_notification', |
||||
result: true, |
||||
} |
||||
}, |
||||
}, |
||||
|
||||
/** |
||||
* Getters for mock RPC request objects. |
||||
*/ |
||||
RPC_REQUESTS: { |
||||
|
||||
/** |
||||
* Gets an arbitrary RPC request object. |
||||
* |
||||
* @param {string} origin - The origin of the request |
||||
* @param {string} method - The request method |
||||
* @param {Array<any>} params - The request parameters |
||||
* @param {string} [id] - The request id |
||||
* @returns {Object} An RPC request object |
||||
*/ |
||||
custom: (origin, method, params = [], id) => { |
||||
const req = { |
||||
origin, |
||||
method, |
||||
params, |
||||
} |
||||
if (id !== undefined) { |
||||
req.id = id |
||||
} |
||||
return req |
||||
}, |
||||
|
||||
/** |
||||
* Gets an eth_accounts RPC request object. |
||||
* |
||||
* @param {string} origin - The origin of the request |
||||
* @returns {Object} An RPC request object |
||||
*/ |
||||
eth_accounts: (origin) => { |
||||
return { |
||||
origin, |
||||
method: 'eth_accounts', |
||||
params: [], |
||||
} |
||||
}, |
||||
|
||||
/** |
||||
* Gets a test_method RPC request object. |
||||
* |
||||
* @param {string} origin - The origin of the request |
||||
* @param {boolean} param - The request param |
||||
* @returns {Object} An RPC request object |
||||
*/ |
||||
test_method: (origin, param = false) => { |
||||
return { |
||||
origin, |
||||
method: 'test_method', |
||||
params: [param], |
||||
} |
||||
}, |
||||
|
||||
/** |
||||
* Gets an eth_requestAccounts RPC request object. |
||||
* |
||||
* @param {string} origin - The origin of the request |
||||
* @returns {Object} An RPC request object |
||||
*/ |
||||
eth_requestAccounts: (origin) => { |
||||
return { |
||||
origin, |
||||
method: 'eth_requestAccounts', |
||||
params: [], |
||||
} |
||||
}, |
||||
|
||||
/** |
||||
* Gets a wallet_requestPermissions RPC request object, |
||||
* for a single permission. |
||||
* |
||||
* @param {string} origin - The origin of the request |
||||
* @param {string} permissionName - The name of the permission to request |
||||
* @returns {Object} An RPC request object |
||||
*/ |
||||
requestPermission: (origin, permissionName) => { |
||||
return { |
||||
origin, |
||||
method: 'wallet_requestPermissions', |
||||
params: [ PERMS.requests[permissionName]() ], |
||||
} |
||||
}, |
||||
|
||||
/** |
||||
* Gets a wallet_requestPermissions RPC request object, |
||||
* for multiple permissions. |
||||
* |
||||
* @param {string} origin - The origin of the request |
||||
* @param {Object} permissions - A permission request object |
||||
* @returns {Object} An RPC request object |
||||
*/ |
||||
requestPermissions: (origin, permissions = {}) => { |
||||
return { |
||||
origin, |
||||
method: 'wallet_requestPermissions', |
||||
params: [ permissions ], |
||||
} |
||||
}, |
||||
|
||||
/** |
||||
* Gets a wallet_sendDomainMetadata RPC request object. |
||||
* |
||||
* @param {string} origin - The origin of the request |
||||
* @param {Object} name - The domainMetadata name |
||||
* @param {Array<any>} [args] - Any other data for the request's domainMetadata |
||||
* @returns {Object} An RPC request object |
||||
*/ |
||||
wallet_sendDomainMetadata: (origin, name, ...args) => { |
||||
return { |
||||
origin, |
||||
method: 'wallet_sendDomainMetadata', |
||||
domainMetadata: { |
||||
...args, |
||||
name, |
||||
}, |
||||
} |
||||
}, |
||||
}, |
||||
}) |
||||
|
||||
/** |
||||
* Objects with immutable mock values. |
||||
*/ |
||||
export const constants = deepFreeze({ |
||||
|
||||
DUMMY_ACCOUNT: '0xabc', |
||||
|
||||
REQUEST_IDS: { |
||||
a: '1', |
||||
b: '2', |
||||
c: '3', |
||||
}, |
||||
|
||||
ORIGINS: { ...ORIGINS }, |
||||
|
||||
ACCOUNT_ARRAYS: { ...ACCOUNT_ARRAYS }, |
||||
|
||||
PERM_NAMES: { ...PERM_NAMES }, |
||||
|
||||
RESTRICTED_METHODS: [ |
||||
'eth_accounts', |
||||
'test_method', |
||||
], |
||||
|
||||
/** |
||||
* Mock permissions history objects. |
||||
*/ |
||||
EXPECTED_HISTORIES: { |
||||
|
||||
case1: [ |
||||
{ |
||||
[ORIGINS.a]: { |
||||
[PERM_NAMES.eth_accounts]: { |
||||
lastApproved: 1, |
||||
accounts: { |
||||
[ACCOUNT_ARRAYS.a[0]]: 1, |
||||
[ACCOUNT_ARRAYS.a[1]]: 1, |
||||
}, |
||||
}, |
||||
}, |
||||
}, |
||||
{ |
||||
[ORIGINS.a]: { |
||||
[PERM_NAMES.eth_accounts]: { |
||||
lastApproved: 2, |
||||
accounts: { |
||||
[ACCOUNT_ARRAYS.a[0]]: 2, |
||||
[ACCOUNT_ARRAYS.a[1]]: 1, |
||||
}, |
||||
}, |
||||
}, |
||||
}, |
||||
], |
||||
|
||||
case2: [ |
||||
{ |
||||
[ORIGINS.a]: { |
||||
[PERM_NAMES.eth_accounts]: { |
||||
lastApproved: 1, |
||||
accounts: {}, |
||||
}, |
||||
}, |
||||
}, |
||||
], |
||||
|
||||
case3: [ |
||||
{ |
||||
[ORIGINS.a]: { |
||||
[PERM_NAMES.test_method]: { lastApproved: 1 }, |
||||
}, |
||||
[ORIGINS.b]: { |
||||
[PERM_NAMES.eth_accounts]: { |
||||
lastApproved: 1, |
||||
accounts: { |
||||
[ACCOUNT_ARRAYS.b[0]]: 1, |
||||
}, |
||||
}, |
||||
}, |
||||
[ORIGINS.c]: { |
||||
[PERM_NAMES.test_method]: { lastApproved: 1 }, |
||||
[PERM_NAMES.eth_accounts]: { |
||||
lastApproved: 1, |
||||
accounts: { |
||||
[ACCOUNT_ARRAYS.c[0]]: 1, |
||||
}, |
||||
}, |
||||
}, |
||||
}, |
||||
{ |
||||
[ORIGINS.a]: { |
||||
[PERM_NAMES.test_method]: { lastApproved: 2 }, |
||||
}, |
||||
[ORIGINS.b]: { |
||||
[PERM_NAMES.eth_accounts]: { |
||||
lastApproved: 1, |
||||
accounts: { |
||||
[ACCOUNT_ARRAYS.b[0]]: 1, |
||||
}, |
||||
}, |
||||
}, |
||||
[ORIGINS.c]: { |
||||
[PERM_NAMES.test_method]: { lastApproved: 1 }, |
||||
[PERM_NAMES.eth_accounts]: { |
||||
lastApproved: 2, |
||||
accounts: { |
||||
[ACCOUNT_ARRAYS.c[0]]: 1, |
||||
[ACCOUNT_ARRAYS.b[0]]: 2, |
||||
}, |
||||
}, |
||||
}, |
||||
}, |
||||
], |
||||
|
||||
case4: [ |
||||
{ |
||||
[ORIGINS.a]: { |
||||
[PERM_NAMES.test_method]: { |
||||
lastApproved: 1, |
||||
}, |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
}) |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,690 @@ |
||||
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' |
||||
) |
||||
}) |
||||
}) |
||||
}) |
@ -0,0 +1,752 @@ |
||||
import { strict as assert } from 'assert' |
||||
|
||||
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 { |
||||
ACCOUNT_ARRAYS, |
||||
ORIGINS, |
||||
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() |
||||
}) |
||||
|
||||
it('grants permissions on user approval', async function () { |
||||
|
||||
const aMiddleware = getPermissionsMiddleware(permController, ORIGINS.a) |
||||
|
||||
const req = RPC_REQUESTS.requestPermission( |
||||
ORIGINS.a, 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, ACCOUNT_ARRAYS.a) |
||||
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, |
||||
ORIGINS.a, |
||||
[CAVEATS.eth_accounts(ACCOUNT_ARRAYS.a)] |
||||
) |
||||
|
||||
const aAccounts = await permController.getAccounts(ORIGINS.a) |
||||
assert.deepEqual( |
||||
aAccounts, ACCOUNT_ARRAYS.a, |
||||
'origin should have correct accounts' |
||||
) |
||||
}) |
||||
|
||||
it('handles serial approved requests that overwrite existing permissions', async function () { |
||||
|
||||
const aMiddleware = getPermissionsMiddleware(permController, ORIGINS.a) |
||||
|
||||
// create first request
|
||||
|
||||
const req1 = RPC_REQUESTS.requestPermission( |
||||
ORIGINS.a, PERM_NAMES.eth_accounts |
||||
) |
||||
const res1 = {} |
||||
|
||||
// send, approve, and validate first request
|
||||
// note use of ACCOUNT_ARRAYS.a
|
||||
|
||||
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, ACCOUNT_ARRAYS.a) |
||||
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, |
||||
ORIGINS.a, |
||||
[CAVEATS.eth_accounts(ACCOUNT_ARRAYS.a)] |
||||
) |
||||
|
||||
const accounts1 = await permController.getAccounts(ORIGINS.a) |
||||
assert.deepEqual( |
||||
accounts1, ACCOUNT_ARRAYS.a, |
||||
'origin should have correct accounts' |
||||
) |
||||
|
||||
// create second request
|
||||
|
||||
const requestedPerms2 = { |
||||
...PERMS.requests.eth_accounts(), |
||||
...PERMS.requests.test_method(), |
||||
} |
||||
|
||||
const req2 = RPC_REQUESTS.requestPermissions( |
||||
ORIGINS.a, { ...requestedPerms2 } |
||||
) |
||||
const res2 = {} |
||||
|
||||
// send, approve, and validate second request
|
||||
// note use of ACCOUNT_ARRAYS.b
|
||||
|
||||
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, ACCOUNT_ARRAYS.b) |
||||
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, |
||||
ORIGINS.a, |
||||
[CAVEATS.eth_accounts(ACCOUNT_ARRAYS.b)] |
||||
) |
||||
|
||||
validatePermission( |
||||
res2.result[1], |
||||
PERM_NAMES.test_method, |
||||
ORIGINS.a, |
||||
) |
||||
|
||||
const accounts2 = await permController.getAccounts(ORIGINS.a) |
||||
assert.deepEqual( |
||||
accounts2, ACCOUNT_ARRAYS.b, |
||||
'origin should have correct accounts' |
||||
) |
||||
}) |
||||
|
||||
it('rejects permissions on user rejection', async function () { |
||||
|
||||
const aMiddleware = getPermissionsMiddleware(permController, ORIGINS.a) |
||||
|
||||
const req = RPC_REQUESTS.requestPermission( |
||||
ORIGINS.a, 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(ORIGINS.a) |
||||
assert.deepEqual( |
||||
aAccounts, [], 'origin should have have correct accounts' |
||||
) |
||||
}) |
||||
|
||||
it('rejects requests with unknown permissions', async function () { |
||||
|
||||
const aMiddleware = getPermissionsMiddleware(permController, ORIGINS.a) |
||||
|
||||
const req = RPC_REQUESTS.requestPermissions( |
||||
ORIGINS.a, { |
||||
...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' |
||||
) |
||||
}) |
||||
|
||||
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, ORIGINS.a) |
||||
const bMiddleware = getPermissionsMiddleware(permController, ORIGINS.b) |
||||
|
||||
// create and start processing first request for first origin
|
||||
|
||||
const reqA1 = RPC_REQUESTS.requestPermission( |
||||
ORIGINS.a, 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( |
||||
ORIGINS.b, 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( |
||||
ORIGINS.a, 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, ORIGINS.a) |
||||
|
||||
const req = RPC_REQUESTS.test_method(ORIGINS.a) |
||||
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, ORIGINS.b) |
||||
|
||||
grantPermissions(permController, ORIGINS.b, PERMS.finalizedRequests.test_method()) |
||||
|
||||
const req = RPC_REQUESTS.test_method(ORIGINS.b, 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, ORIGINS.a) |
||||
|
||||
const req = RPC_REQUESTS.eth_accounts(ORIGINS.a) |
||||
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, ORIGINS.a) |
||||
|
||||
grantPermissions( |
||||
permController, ORIGINS.a, |
||||
PERMS.finalizedRequests.eth_accounts(ACCOUNT_ARRAYS.a) |
||||
) |
||||
|
||||
const req = RPC_REQUESTS.eth_accounts(ORIGINS.a) |
||||
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, ACCOUNT_ARRAYS.a, |
||||
'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, ORIGINS.a) |
||||
|
||||
const req = RPC_REQUESTS.eth_requestAccounts(ORIGINS.a) |
||||
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, ACCOUNT_ARRAYS.a) |
||||
|
||||
// at this point, the permission should have been granted
|
||||
const perms = permController.permissions.getPermissionsForDomain(ORIGINS.a) |
||||
|
||||
assert.equal( |
||||
perms.length, 1, |
||||
'domain should have correct number of permissions' |
||||
) |
||||
|
||||
validatePermission( |
||||
perms[0], |
||||
PERM_NAMES.eth_accounts, |
||||
ORIGINS.a, |
||||
[CAVEATS.eth_accounts(ACCOUNT_ARRAYS.a)] |
||||
) |
||||
|
||||
await pendingApproval |
||||
|
||||
// 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, ACCOUNT_ARRAYS.a, |
||||
'result should have correct accounts' |
||||
) |
||||
|
||||
// we should also be able to get the accounts independently
|
||||
const aAccounts = await permController.getAccounts(ORIGINS.a) |
||||
assert.deepEqual( |
||||
aAccounts, ACCOUNT_ARRAYS.a, '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, ORIGINS.a) |
||||
|
||||
const req = RPC_REQUESTS.eth_requestAccounts(ORIGINS.a) |
||||
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(ORIGINS.a) |
||||
assert.deepEqual( |
||||
aAccounts, [], 'origin should have have correct accounts' |
||||
) |
||||
}) |
||||
|
||||
it('directly returns accounts for permitted domain', async function () { |
||||
|
||||
const cMiddleware = getPermissionsMiddleware(permController, ORIGINS.c) |
||||
|
||||
grantPermissions( |
||||
permController, ORIGINS.c, |
||||
PERMS.finalizedRequests.eth_accounts(ACCOUNT_ARRAYS.c) |
||||
) |
||||
|
||||
const req = RPC_REQUESTS.eth_requestAccounts(ORIGINS.c) |
||||
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, ACCOUNT_ARRAYS.c, |
||||
'response should have correct result' |
||||
) |
||||
}) |
||||
}) |
||||
|
||||
describe('wallet_sendDomainMetadata', function () { |
||||
|
||||
let permController |
||||
|
||||
beforeEach(function () { |
||||
permController = initPermController() |
||||
}) |
||||
|
||||
it('records domain metadata', async function () { |
||||
|
||||
const name = 'BAZ' |
||||
|
||||
const cMiddleware = getPermissionsMiddleware(permController, ORIGINS.c) |
||||
|
||||
const req = RPC_REQUESTS.wallet_sendDomainMetadata(ORIGINS.c, 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, |
||||
{ [ORIGINS.c]: { name, extensionId: undefined } }, |
||||
'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, ORIGINS.c, extensionId) |
||||
|
||||
const req = RPC_REQUESTS.wallet_sendDomainMetadata(ORIGINS.c, 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, |
||||
{ [ORIGINS.c]: { name, extensionId } }, |
||||
'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, ORIGINS.c) |
||||
|
||||
const req = RPC_REQUESTS.wallet_sendDomainMetadata(ORIGINS.c, 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, ORIGINS.c) |
||||
|
||||
const req = RPC_REQUESTS.wallet_sendDomainMetadata(ORIGINS.c) |
||||
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' |
||||
) |
||||
}) |
||||
}) |
||||
}) |
@ -0,0 +1,35 @@ |
||||
import { strict as assert } from 'assert' |
||||
|
||||
import getRestrictedMethods |
||||
from '../../../../../app/scripts/controllers/permissions/restrictedMethods' |
||||
|
||||
describe('restricted methods', function () { |
||||
|
||||
// this method is tested extensively in other permissions tests
|
||||
describe('eth_accounts', function () { |
||||
|
||||
it('handles failure', async function () { |
||||
const restrictedMethods = getRestrictedMethods({ |
||||
getKeyringAccounts: async () => { |
||||
throw new Error('foo') |
||||
}, |
||||
}) |
||||
|
||||
const res = {} |
||||
restrictedMethods.eth_accounts.method(null, res, null, (err) => { |
||||
|
||||
const fooError = new Error('foo') |
||||
|
||||
assert.deepEqual( |
||||
err, fooError, |
||||
'should end with expected error' |
||||
) |
||||
|
||||
assert.deepEqual( |
||||
res, { error: fooError }, |
||||
'response should have expected error and no result' |
||||
) |
||||
}) |
||||
}) |
||||
}) |
||||
}) |
Loading…
Reference in new issue