1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-22 09:57:02 +01:00

Add subject types (#13026)

This PR introduces the concept of subject _types_ to be associated with each subject in the `SubjectMetadataController`, and used for control flow in our RPC stack (`setupProviderEngine` and so forth).

We already differentiate between "types" of subjects in various places on an ad hoc basis via boolean flags (e.g. `isInternal` in our RPC stack) or the presence/absence of certain values in the subject's metadata (specifically `metadata.extensionId`). The status quo is manageable if not ideal, but will start to become untenable with the introduction of Snaps in the near future.

Therefore, this PR establishes a `SUBJECT_TYPES` enum and adds the `subjectType` property to the metadata of each subject. A new migration is added to accomplish this. Finally, we specify and `INTERNAL` subject type to distinguish internal from external requests.
This commit is contained in:
Erik Marks 2021-12-08 15:37:29 -08:00 committed by GitHub
parent 2e181fb06c
commit fc1ffae406
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 188 additions and 8 deletions

View File

@ -12,6 +12,7 @@ const sendMetadata = {
implementation: sendMetadataHandler, implementation: sendMetadataHandler,
hookNames: { hookNames: {
addSubjectMetadata: true, addSubjectMetadata: true,
subjectType: true,
}, },
}; };
export default sendMetadata; export default sendMetadata;
@ -20,6 +21,7 @@ export default sendMetadata;
* @typedef {Record<string, Function>} SendMetadataOptions * @typedef {Record<string, Function>} SendMetadataOptions
* @property {Function} addSubjectMetadata - A function that records subject * @property {Function} addSubjectMetadata - A function that records subject
* metadata, bound to the requesting origin. * metadata, bound to the requesting origin.
* @property {string} subjectType - The type of the requesting origin / subject.
*/ */
/** /**
@ -29,11 +31,23 @@ export default sendMetadata;
* @param {Function} end - The json-rpc-engine 'end' callback. * @param {Function} end - The json-rpc-engine 'end' callback.
* @param {SendMetadataOptions} options * @param {SendMetadataOptions} options
*/ */
function sendMetadataHandler(req, res, _next, end, { addSubjectMetadata }) { function sendMetadataHandler(
req,
res,
_next,
end,
{ addSubjectMetadata, subjectType },
) {
const { params } = req; const { params } = req;
if (params && typeof params === 'object' && !Array.isArray(params)) { if (params && typeof params === 'object' && !Array.isArray(params)) {
const { icon = null, name = null, ...remainingParams } = params; const { icon = null, name = null, ...remainingParams } = params;
addSubjectMetadata({ ...remainingParams, iconUrl: icon, name });
addSubjectMetadata({
...remainingParams,
iconUrl: icon,
name,
subjectType,
});
} else { } else {
return end(ethErrors.rpc.invalidParams({ data: params })); return end(ethErrors.rpc.invalidParams({ data: params }));
} }

View File

@ -58,7 +58,10 @@ import {
import { UI_NOTIFICATIONS } from '../../shared/notifications'; import { UI_NOTIFICATIONS } from '../../shared/notifications';
import { toChecksumHexAddress } from '../../shared/modules/hexstring-utils'; import { toChecksumHexAddress } from '../../shared/modules/hexstring-utils';
import { MILLISECOND } from '../../shared/constants/time'; import { MILLISECOND } from '../../shared/constants/time';
import { POLLING_TOKEN_ENVIRONMENT_TYPES } from '../../shared/constants/app'; import {
POLLING_TOKEN_ENVIRONMENT_TYPES,
SUBJECT_TYPES,
} from '../../shared/constants/app';
import { hexToDecimal } from '../../ui/helpers/utils/conversions.util'; import { hexToDecimal } from '../../ui/helpers/utils/conversions.util';
import ComposableObservableStore from './lib/ComposableObservableStore'; import ComposableObservableStore from './lib/ComposableObservableStore';
@ -2634,7 +2637,12 @@ export default class MetamaskController extends EventEmitter {
*/ */
setupProviderConnection(outStream, sender, isInternal) { setupProviderConnection(outStream, sender, isInternal) {
const origin = isInternal ? 'metamask' : new URL(sender.url).origin; const origin = isInternal ? 'metamask' : new URL(sender.url).origin;
let subjectType = isInternal
? SUBJECT_TYPES.INTERNAL
: SUBJECT_TYPES.WEBSITE;
if (sender.id !== this.extension.runtime.id) { if (sender.id !== this.extension.runtime.id) {
subjectType = SUBJECT_TYPES.EXTENSION;
this.subjectMetadataController.addSubjectMetadata(origin, { this.subjectMetadataController.addSubjectMetadata(origin, {
extensionId: sender.id, extensionId: sender.id,
}); });
@ -2649,7 +2657,7 @@ export default class MetamaskController extends EventEmitter {
origin, origin,
location: sender.url, location: sender.url,
tabId, tabId,
isInternal, subjectType,
}); });
// setup connection // setup connection
@ -2672,14 +2680,15 @@ export default class MetamaskController extends EventEmitter {
} }
/** /**
* A method for creating a provider that is safely restricted for the requesting domain. * A method for creating a provider that is safely restricted for the requesting subject.
*
* @param {Object} options - Provider engine options * @param {Object} options - Provider engine options
* @param {string} options.origin - The origin of the sender * @param {string} options.origin - The origin of the sender
* @param {string} options.location - The full URL of the sender * @param {string} options.location - The full URL of the sender
* @param {string} options.subjectType - The type of the sender subject.
* @param {tabId} [options.tabId] - The tab ID of the sender - if the sender is within a tab * @param {tabId} [options.tabId] - The tab ID of the sender - if the sender is within a tab
* @param {boolean} [options.isInternal] - True if called for a connection to an internal process
**/ **/
setupProviderEngine({ origin, location, tabId, isInternal = false }) { setupProviderEngine({ origin, location, subjectType, tabId }) {
// setup json rpc engine stack // setup json rpc engine stack
const engine = new JsonRpcEngine(); const engine = new JsonRpcEngine();
const { provider, blockTracker } = this; const { provider, blockTracker } = this;
@ -2715,6 +2724,8 @@ export default class MetamaskController extends EventEmitter {
createMethodMiddleware({ createMethodMiddleware({
origin, origin,
subjectType,
// Miscellaneous // Miscellaneous
addSubjectMetadata: this.subjectMetadataController.addSubjectMetadata.bind( addSubjectMetadata: this.subjectMetadataController.addSubjectMetadata.bind(
this.subjectMetadataController, this.subjectMetadataController,
@ -2796,7 +2807,7 @@ export default class MetamaskController extends EventEmitter {
// filter and subscription polyfills // filter and subscription polyfills
engine.push(filterMiddleware); engine.push(filterMiddleware);
engine.push(subscriptionManager.middleware); engine.push(subscriptionManager.middleware);
if (!isInternal) { if (subjectType !== SUBJECT_TYPES.INTERNAL) {
// permissions // permissions
engine.push( engine.push(
this.permissionController.createPermissionMiddleware({ this.permissionController.createPermissionMiddleware({

View File

@ -0,0 +1,41 @@
import { cloneDeep } from 'lodash';
import { SUBJECT_TYPES } from '../../../shared/constants/app';
const version = 69;
/**
* Adds the `subjectType` property to all subject metadata.
*/
export default {
version,
async migrate(originalVersionedData) {
const versionedData = cloneDeep(originalVersionedData);
versionedData.meta.version = version;
const state = versionedData.data;
const newState = transformState(state);
versionedData.data = newState;
return versionedData;
},
};
function transformState(state) {
if (typeof state?.SubjectMetadataController?.subjectMetadata === 'object') {
const {
SubjectMetadataController: { subjectMetadata },
} = state;
// mutate SubjectMetadataController.subjectMetadata in place
Object.values(subjectMetadata).forEach((metadata) => {
if (
metadata &&
typeof metadata === 'object' &&
!Array.isArray(metadata)
) {
metadata.subjectType = metadata.extensionId
? SUBJECT_TYPES.EXTENSION
: SUBJECT_TYPES.WEBSITE;
}
});
}
return state;
}

View File

@ -0,0 +1,102 @@
import { SUBJECT_TYPES } from '../../../shared/constants/app';
import migration69 from './069';
describe('migration #69', () => {
it('should update the version metadata', async () => {
const oldStorage = {
meta: {
version: 68,
},
data: {},
};
const newStorage = await migration69.migrate(oldStorage);
expect(newStorage.meta).toStrictEqual({
version: 69,
});
});
it('should migrate all data', async () => {
const oldStorage = {
meta: {
version: 68,
},
data: {
FooController: { a: 'b' },
SubjectMetadataController: {
subjectMetadata: {
'https://1inch.exchange': {
iconUrl:
'https://1inch.exchange/assets/favicon/favicon-32x32.png',
name: 'DEX Aggregator - 1inch.exchange',
origin: 'https://1inch.exchange',
extensionId: null,
},
'https://ascii-tree-generator.com': {
iconUrl: 'https://ascii-tree-generator.com/favicon.ico',
name: 'ASCII Tree Generator',
origin: 'https://ascii-tree-generator.com',
extensionId: 'ascii-tree-generator-extension',
},
'https://null.com': null,
'https://foo.com': 'bad data',
'https://bar.com': ['bad data'],
},
},
},
};
const newStorage = await migration69.migrate(oldStorage);
expect(newStorage).toStrictEqual({
meta: {
version: 69,
},
data: {
FooController: { a: 'b' },
SubjectMetadataController: {
subjectMetadata: {
'https://1inch.exchange': {
iconUrl:
'https://1inch.exchange/assets/favicon/favicon-32x32.png',
name: 'DEX Aggregator - 1inch.exchange',
origin: 'https://1inch.exchange',
extensionId: null,
subjectType: SUBJECT_TYPES.WEBSITE,
},
'https://ascii-tree-generator.com': {
iconUrl: 'https://ascii-tree-generator.com/favicon.ico',
name: 'ASCII Tree Generator',
origin: 'https://ascii-tree-generator.com',
extensionId: 'ascii-tree-generator-extension',
subjectType: SUBJECT_TYPES.EXTENSION,
},
'https://null.com': null,
'https://foo.com': 'bad data',
'https://bar.com': ['bad data'],
},
},
},
});
});
it('should handle missing SubjectMetadataController', async () => {
const oldStorage = {
meta: {
version: 68,
},
data: {
FooController: { a: 'b' },
},
};
const newStorage = await migration69.migrate(oldStorage);
expect(newStorage).toStrictEqual({
meta: {
version: 69,
},
data: {
FooController: { a: 'b' },
},
});
});
});

View File

@ -72,6 +72,7 @@ import m065 from './065';
import m066 from './066'; import m066 from './066';
import m067 from './067'; import m067 from './067';
import m068 from './068'; import m068 from './068';
import m069 from './069';
const migrations = [ const migrations = [
m002, m002,
@ -141,6 +142,7 @@ const migrations = [
m066, m066,
m067, m067,
m068, m068,
m069,
]; ];
export default migrations; export default migrations;

View File

@ -45,6 +45,16 @@ export const MESSAGE_TYPE = {
WATCH_ASSET_LEGACY: 'metamask_watchAsset', WATCH_ASSET_LEGACY: 'metamask_watchAsset',
}; };
/**
* The different kinds of subjects that MetaMask may interact with, including
* third parties and itself (e.g. when the background communicated with the UI).
*/
export const SUBJECT_TYPES = {
WEBSITE: 'website',
EXTENSION: 'extension',
INTERNAL: 'internal',
};
export const POLLING_TOKEN_ENVIRONMENT_TYPES = { export const POLLING_TOKEN_ENVIRONMENT_TYPES = {
[ENVIRONMENT_TYPE_POPUP]: 'popupGasPollTokens', [ENVIRONMENT_TYPE_POPUP]: 'popupGasPollTokens',
[ENVIRONMENT_TYPE_NOTIFICATION]: 'notificationGasPollTokens', [ENVIRONMENT_TYPE_NOTIFICATION]: 'notificationGasPollTokens',