From 05fae3fa1e3530eb00f15a63e0c9756a5631f597 Mon Sep 17 00:00:00 2001 From: Erik Marks <25517051+rekmarks@users.noreply.github.com> Date: Mon, 15 Nov 2021 15:11:51 -0800 Subject: [PATCH] Add RPC method handler hook selection (#12664) Adds a property, `hookNames`, to each RPC method handler export in `app/scripts/lib/rpc-method-middleware` and a function, `selectHooks`, to select from them. `createMethodMiddleware` receives a giant `opts` object that includes a bunch of different methods from `MetaMaskController` and its subcontrollers. Each method implementation only requires a subset of these methods to do its work. Because they need some kind of name, we call these methods "hooks". With this change, whenever an RPC method is called, `selectHooks` will be called to ensure that each method only receives the hooks that it needs in order to do its job. This implementation is based on [work in `snaps-skunkworks`](https://github.com/MetaMask/snaps-skunkworks/blob/a3e1248/packages/rpc-methods/src/utils.ts#L17-L34) that will be merged in the near future. --- .../createMethodMiddleware.js | 30 ++++++++++++++++--- .../handlers/add-ethereum-chain.js | 8 +++++ .../handlers/get-provider-state.js | 3 ++ .../handlers/log-web3-shim-usage.js | 5 ++++ .../handlers/switch-ethereum-chain.js | 7 +++++ .../handlers/watch-asset.js | 3 ++ 6 files changed, 52 insertions(+), 4 deletions(-) diff --git a/app/scripts/lib/rpc-method-middleware/createMethodMiddleware.js b/app/scripts/lib/rpc-method-middleware/createMethodMiddleware.js index fa7798d0d..9c7935d3e 100644 --- a/app/scripts/lib/rpc-method-middleware/createMethodMiddleware.js +++ b/app/scripts/lib/rpc-method-middleware/createMethodMiddleware.js @@ -4,7 +4,7 @@ import handlers from './handlers'; const handlerMap = handlers.reduce((map, handler) => { for (const methodName of handler.methodNames) { - map.set(methodName, handler.implementation); + map.set(methodName, handler); } return map; }, new Map()); @@ -23,7 +23,6 @@ const handlerMap = handlers.reduce((map, handler) => { * Eventually, we'll want to extract this middleware into its own package. * * @param {Object} opts - The middleware options - * @param {Function} opts.sendMetrics - A function for sending a metrics event * @returns {(req: Object, res: Object, next: Function, end: Function) => void} */ export default function createMethodMiddleware(opts) { @@ -33,9 +32,32 @@ export default function createMethodMiddleware(opts) { return end(ethErrors.rpc.methodNotSupported()); } - if (handlerMap.has(req.method)) { - return handlerMap.get(req.method)(req, res, next, end, opts); + const handler = handlerMap.get(req.method); + if (handler) { + const { implementation, hookNames } = handler; + return implementation(req, res, next, end, selectHooks(opts, hookNames)); } + return next(); }; } + +/** + * Returns the subset of the specified `hooks` that are included in the + * `hookNames` object. This is a Principle of Least Authority (POLA) measure + * to ensure that each RPC method implementation only has access to the + * API "hooks" it needs to do its job. + * + * @param {Record} hooks - The hooks to select from. + * @param {Record} hookNames - The names of the hooks to select. + * @returns {Record | undefined} The selected hooks. + */ +function selectHooks(hooks, hookNames) { + if (hookNames) { + return Object.keys(hookNames).reduce((hookSubset, hookName) => { + hookSubset[hookName] = hooks[hookName]; + return hookSubset; + }, {}); + } + return undefined; +} diff --git a/app/scripts/lib/rpc-method-middleware/handlers/add-ethereum-chain.js b/app/scripts/lib/rpc-method-middleware/handlers/add-ethereum-chain.js index 50399c3ab..c6a85c2fb 100644 --- a/app/scripts/lib/rpc-method-middleware/handlers/add-ethereum-chain.js +++ b/app/scripts/lib/rpc-method-middleware/handlers/add-ethereum-chain.js @@ -12,6 +12,14 @@ import { CHAIN_ID_TO_NETWORK_ID_MAP } from '../../../../../shared/constants/netw const addEthereumChain = { methodNames: [MESSAGE_TYPE.ADD_ETHEREUM_CHAIN], implementation: addEthereumChainHandler, + hookNames: { + addCustomRpc: true, + getCurrentChainId: true, + findCustomRpcBy: true, + updateRpcTarget: true, + requestUserApproval: true, + sendMetrics: true, + }, }; export default addEthereumChain; diff --git a/app/scripts/lib/rpc-method-middleware/handlers/get-provider-state.js b/app/scripts/lib/rpc-method-middleware/handlers/get-provider-state.js index c38933b0d..afc09666a 100644 --- a/app/scripts/lib/rpc-method-middleware/handlers/get-provider-state.js +++ b/app/scripts/lib/rpc-method-middleware/handlers/get-provider-state.js @@ -9,6 +9,9 @@ import { MESSAGE_TYPE } from '../../../../../shared/constants/app'; const getProviderState = { methodNames: [MESSAGE_TYPE.GET_PROVIDER_STATE], implementation: getProviderStateHandler, + hookNames: { + getProviderState: true, + }, }; export default getProviderState; diff --git a/app/scripts/lib/rpc-method-middleware/handlers/log-web3-shim-usage.js b/app/scripts/lib/rpc-method-middleware/handlers/log-web3-shim-usage.js index 973d03f61..8793cdf20 100644 --- a/app/scripts/lib/rpc-method-middleware/handlers/log-web3-shim-usage.js +++ b/app/scripts/lib/rpc-method-middleware/handlers/log-web3-shim-usage.js @@ -10,6 +10,11 @@ import { MESSAGE_TYPE } from '../../../../../shared/constants/app'; const logWeb3ShimUsage = { methodNames: [MESSAGE_TYPE.LOG_WEB3_SHIM_USAGE], implementation: logWeb3ShimUsageHandler, + hookNames: { + sendMetrics: true, + getWeb3ShimUsageState: true, + setWeb3ShimUsageRecorded: true, + }, }; export default logWeb3ShimUsage; diff --git a/app/scripts/lib/rpc-method-middleware/handlers/switch-ethereum-chain.js b/app/scripts/lib/rpc-method-middleware/handlers/switch-ethereum-chain.js index d1e3d3a22..c2d73f16b 100644 --- a/app/scripts/lib/rpc-method-middleware/handlers/switch-ethereum-chain.js +++ b/app/scripts/lib/rpc-method-middleware/handlers/switch-ethereum-chain.js @@ -15,6 +15,13 @@ import { const switchEthereumChain = { methodNames: [MESSAGE_TYPE.SWITCH_ETHEREUM_CHAIN], implementation: switchEthereumChainHandler, + hookNames: { + getCurrentChainId: true, + findCustomRpcBy: true, + setProviderType: true, + updateRpcTarget: true, + requestUserApproval: true, + }, }; export default switchEthereumChain; diff --git a/app/scripts/lib/rpc-method-middleware/handlers/watch-asset.js b/app/scripts/lib/rpc-method-middleware/handlers/watch-asset.js index 95408eee3..a9fb503d9 100644 --- a/app/scripts/lib/rpc-method-middleware/handlers/watch-asset.js +++ b/app/scripts/lib/rpc-method-middleware/handlers/watch-asset.js @@ -3,6 +3,9 @@ import { MESSAGE_TYPE } from '../../../../../shared/constants/app'; const watchAsset = { methodNames: [MESSAGE_TYPE.WATCH_ASSET, MESSAGE_TYPE.WATCH_ASSET_LEGACY], implementation: watchAssetHandler, + hookNames: { + handleWatchAssetRequest: true, + }, }; export default watchAsset;