mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-22 09:23:21 +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,
|
||||
hookNames: {
|
||||
addSubjectMetadata: true,
|
||||
subjectType: true,
|
||||
},
|
||||
};
|
||||
export default sendMetadata;
|
||||
@ -20,6 +21,7 @@ export default sendMetadata;
|
||||
* @typedef {Record<string, Function>} SendMetadataOptions
|
||||
* @property {Function} addSubjectMetadata - A function that records subject
|
||||
* 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 {SendMetadataOptions} options
|
||||
*/
|
||||
function sendMetadataHandler(req, res, _next, end, { addSubjectMetadata }) {
|
||||
function sendMetadataHandler(
|
||||
req,
|
||||
res,
|
||||
_next,
|
||||
end,
|
||||
{ addSubjectMetadata, subjectType },
|
||||
) {
|
||||
const { params } = req;
|
||||
if (params && typeof params === 'object' && !Array.isArray(params)) {
|
||||
const { icon = null, name = null, ...remainingParams } = params;
|
||||
addSubjectMetadata({ ...remainingParams, iconUrl: icon, name });
|
||||
|
||||
addSubjectMetadata({
|
||||
...remainingParams,
|
||||
iconUrl: icon,
|
||||
name,
|
||||
subjectType,
|
||||
});
|
||||
} else {
|
||||
return end(ethErrors.rpc.invalidParams({ data: params }));
|
||||
}
|
||||
|
@ -58,7 +58,10 @@ import {
|
||||
import { UI_NOTIFICATIONS } from '../../shared/notifications';
|
||||
import { toChecksumHexAddress } from '../../shared/modules/hexstring-utils';
|
||||
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 ComposableObservableStore from './lib/ComposableObservableStore';
|
||||
@ -2634,7 +2637,12 @@ export default class MetamaskController extends EventEmitter {
|
||||
*/
|
||||
setupProviderConnection(outStream, sender, isInternal) {
|
||||
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) {
|
||||
subjectType = SUBJECT_TYPES.EXTENSION;
|
||||
this.subjectMetadataController.addSubjectMetadata(origin, {
|
||||
extensionId: sender.id,
|
||||
});
|
||||
@ -2649,7 +2657,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
origin,
|
||||
location: sender.url,
|
||||
tabId,
|
||||
isInternal,
|
||||
subjectType,
|
||||
});
|
||||
|
||||
// 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 {string} options.origin - The origin 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 {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
|
||||
const engine = new JsonRpcEngine();
|
||||
const { provider, blockTracker } = this;
|
||||
@ -2715,6 +2724,8 @@ export default class MetamaskController extends EventEmitter {
|
||||
createMethodMiddleware({
|
||||
origin,
|
||||
|
||||
subjectType,
|
||||
|
||||
// Miscellaneous
|
||||
addSubjectMetadata: this.subjectMetadataController.addSubjectMetadata.bind(
|
||||
this.subjectMetadataController,
|
||||
@ -2796,7 +2807,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
// filter and subscription polyfills
|
||||
engine.push(filterMiddleware);
|
||||
engine.push(subscriptionManager.middleware);
|
||||
if (!isInternal) {
|
||||
if (subjectType !== SUBJECT_TYPES.INTERNAL) {
|
||||
// permissions
|
||||
engine.push(
|
||||
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 m067 from './067';
|
||||
import m068 from './068';
|
||||
import m069 from './069';
|
||||
|
||||
const migrations = [
|
||||
m002,
|
||||
@ -141,6 +142,7 @@ const migrations = [
|
||||
m066,
|
||||
m067,
|
||||
m068,
|
||||
m069,
|
||||
];
|
||||
|
||||
export default migrations;
|
||||
|
@ -45,6 +45,16 @@ export const MESSAGE_TYPE = {
|
||||
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 = {
|
||||
[ENVIRONMENT_TYPE_POPUP]: 'popupGasPollTokens',
|
||||
[ENVIRONMENT_TYPE_NOTIFICATION]: 'notificationGasPollTokens',
|
||||
|
Loading…
Reference in New Issue
Block a user