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:
parent
2e181fb06c
commit
fc1ffae406
@ -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 }));
|
||||||
}
|
}
|
||||||
|
@ -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({
|
||||||
|
41
app/scripts/migrations/069.js
Normal file
41
app/scripts/migrations/069.js
Normal 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;
|
||||||
|
}
|
102
app/scripts/migrations/069.test.js
Normal file
102
app/scripts/migrations/069.test.js
Normal 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' },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -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;
|
||||||
|
@ -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',
|
||||||
|
Loading…
Reference in New Issue
Block a user