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