Skip to content

Commit f941345

Browse files
rekmarksclaude
andauthored
refactor(permission-controller)!: decouple permission middleware via messenger actions (MetaMask#8532)
Advances MetaMask#4238 Reverts MetaMask#8502 - Rewrites `permission-middleware.ts` as a standalone `createPermissionMiddleware({ messenger, subject })` factory that dispatches through the `PermissionController:executeRestrictedMethod` and `PermissionController:hasUnrestrictedMethod` messenger actions instead of bound controller hooks. Removes the `createPermissionMiddleware` property from `PermissionController`. - Adds `createPermissionMiddlewareV2`, a `JsonRpcEngineV2` variant of the same factory. Consumers using the legacy `JsonRpcEngine` can continue to use the now-deprecated `createPermissionMiddleware`; new integrations should prefer V2. - Exposes `hasUnrestrictedMethod` as a public method / messenger action, and makes `getRestrictedMethod` `#`-private (it has no remaining external consumers now that the middleware goes through the messenger). - When a restricted method returns `undefined`, the middleware now propagates the plain `Error` thrown by `executeRestrictedMethod`; the JSON-RPC engine serializes it as a standard internal error response instead of a custom `internalError` with a `request` data payload. - If properly typed, it's impossible for restricted methods to return `undefined`. We nevertheless retain this check to minimize changes. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **High Risk** > High risk because it introduces a breaking API change by removing `PermissionController.createPermissionMiddleware`/`getRestrictedMethod` and rerouting enforcement through new messenger actions and a new `JsonRpcEngineV2` middleware, which could affect all RPC permissioning integrations. > > **Overview** > **Decouples permission enforcement middleware from `PermissionController`.** The `json-rpc-engine` permission middleware is removed from the controller and replaced with standalone exports `createPermissionMiddleware` (legacy, *deprecated*) and `createPermissionMiddlewareV2` (for `JsonRpcEngineV2`) that dispatch permission checks/execution via messenger actions. > > **Expands and reshapes the controller’s public surface via messenger.** Adds `PermissionController:hasUnrestrictedMethod` and `PermissionController:executeRestrictedMethod` action types/handlers, makes `getRestrictedMethod` private, and updates error behavior when restricted methods return no result (now a plain `Error`, which engines serialize as internal errors). Documentation and tests are updated to use the new middleware factories and the `JsonRpcEngineV2` path. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 257feaa. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 5801bc0 commit f941345

7 files changed

Lines changed: 622 additions & 219 deletions

File tree

packages/permission-controller/ARCHITECTURE.md

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -326,18 +326,29 @@ const permissionController = new PermissionController({
326326

327327
### Adding the permission middleware
328328

329+
The permission middleware is created via `createPermissionMiddlewareV2` for
330+
`JsonRpcEngineV2`, or via the deprecated `createPermissionMiddleware` for the
331+
legacy `JsonRpcEngine`. Both factories take a messenger with the
332+
`PermissionController:executeRestrictedMethod` and
333+
`PermissionController:hasUnrestrictedMethod` actions, typically obtained by
334+
delegating them from a root messenger to a subject-scoped messenger.
335+
329336
```typescript
330337
// This should take place where a middleware stack is created for a particular
331338
// subject.
332339

333340
// The subject could be a port, stream, socket, etc.
334341
const origin = getOrigin(subject);
335342

336-
const engine = new JsonRpcEngine();
337-
engine.push(/* your various middleware*/);
338-
engine.push(permissionController.createPermissionMiddleware({ origin }));
339-
// Your middleware stack is now permissioned
340-
engine.push(/* your other various middleware*/);
343+
// `messenger` is a messenger delegated the two actions listed above, e.g.
344+
// via `rootMessenger.delegate({ actions: [...], messenger: subjectMessenger })`.
345+
const engine = JsonRpcEngineV2.create({
346+
middleware: [
347+
/* your various middleware */
348+
createPermissionMiddlewareV2({ messenger, subject: { origin } }),
349+
/* your other various middleware */
350+
],
351+
});
341352
```
342353

343354
### Calling a restricted method internally

packages/permission-controller/CHANGELOG.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12-
- Expose `createPermissionMiddleware` through the messenger ([#8502](https://github.com/MetaMask/core/pull/8502))
12+
- Add `createPermissionMiddlewareV2`, a `JsonRpcEngineV2` variant of the standalone permission middleware factory ([#8532](https://github.com/MetaMask/core/pull/8532))
1313

1414
### Changed
1515

16+
- **BREAKING:** Decouple the permission middleware from `PermissionController` and expose it as a standalone function ([#8532](https://github.com/MetaMask/core/pull/8532))
17+
- The standalone `createPermissionMiddleware` replaces the former `PermissionController.createPermissionMiddleware`; it is imported from `@metamask/permission-controller` and called with a messenger and subject metadata, and targets the legacy `JsonRpcEngine`.
18+
- New integrations should prefer `createPermissionMiddlewareV2`, which targets `JsonRpcEngineV2`.
19+
- `PermissionController.getRestrictedMethod` no longer serves a purpose, and is removed. Restricted methods should be invoked via the `:executeRestrictedMethod` action instead.
1620
- Bump `@metamask/controller-utils` from `^11.19.0` to `^11.20.0` ([#8344](https://github.com/MetaMask/core/pull/8344))
1721
- Bump `@metamask/messenger` from `^1.0.0` to `^1.1.1` ([#8364](https://github.com/MetaMask/core/pull/8364), [#8373](https://github.com/MetaMask/core/pull/8373))
1822
- Bump `@metamask/base-controller` from `^9.0.1` to `^9.1.0` ([#8457](https://github.com/MetaMask/core/pull/8457))
1923

24+
### Deprecated
25+
26+
- Deprecate `createPermissionMiddleware` in favor of `createPermissionMiddlewareV2`, which targets `JsonRpcEngineV2` ([#8532](https://github.com/MetaMask/core/pull/8532))
27+
2028
## [12.3.0]
2129

2230
### Added
@@ -186,7 +194,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
186194
["Are the Types Wrong?"](https://arethetypeswrong.github.io/) tool as
187195
["masquerading as CJS"](https://github.com/arethetypeswrong/arethetypeswrong.github.io/blob/main/docs/problems/FalseCJS.md).
188196
All of the ATTW checks now pass.
189-
- Remove chunk files ([#4648](https://github.com/MetaMask/core/pull/4648)).
197+
- Remove chunk files ([#4648](https://github.com/MetaMask/core/pull/4648))
190198
- Previously, the build tool we used to generate JavaScript files extracted
191199
common code to "chunk" files. While this was intended to make this package
192200
more tree-shakeable, it also made debugging more difficult for our

packages/permission-controller/src/PermissionController-method-action-types.ts

Lines changed: 48 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,35 +6,25 @@
66
import type { PermissionController } from './PermissionController';
77

88
/**
9-
* Clears the state of the controller.
9+
* Checks whether the given method was declared as unrestricted at
10+
* construction time. Methods unknown to the controller return `false` and
11+
* would be treated as restricted by callers such as the permission
12+
* middleware.
13+
*
14+
* @param method - The name of the method to check.
15+
* @returns Whether the method is unrestricted.
1016
*/
11-
export type PermissionControllerClearStateAction = {
12-
type: `PermissionController:clearState`;
13-
handler: PermissionController['clearState'];
17+
export type PermissionControllerHasUnrestrictedMethodAction = {
18+
type: `PermissionController:hasUnrestrictedMethod`;
19+
handler: PermissionController['hasUnrestrictedMethod'];
1420
};
1521

1622
/**
17-
* Creates a permission middleware function. Like any {@link JsonRpcEngine}
18-
* middleware, each middleware will only receive requests from a particular
19-
* subject / origin.
20-
*
21-
* The middlewares returned will pass through requests for
22-
* unrestricted methods, and attempt to execute restricted methods. If a method
23-
* is neither restricted nor unrestricted, a "method not found" error will be
24-
* returned.
25-
* If a method is restricted, the middleware will first attempt to retrieve the
26-
* subject's permission for that method. If the permission is found, the method
27-
* will be executed. Otherwise, an "unauthorized" error will be returned.
28-
*
29-
* The middleware **must** be added in the correct place in the middleware
30-
* stack in order for it to work. See the README for an example.
31-
*
32-
* @param subject The permission subject.
33-
* @returns A `json-rpc-engine` middleware.
23+
* Clears the state of the controller.
3424
*/
35-
export type PermissionControllerCreatePermissionMiddlewareAction = {
36-
type: `PermissionController:createPermissionMiddleware`;
37-
handler: PermissionController['createPermissionMiddleware'];
25+
export type PermissionControllerClearStateAction = {
26+
type: `PermissionController:clearState`;
27+
handler: PermissionController['clearState'];
3828
};
3929

4030
/**
@@ -86,7 +76,7 @@ export type PermissionControllerHasPermissionsAction = {
8676
/**
8777
* Revokes all permissions from the specified origin.
8878
*
89-
* Throws an error of the origin has no permissions.
79+
* Throws an error if the origin has no permissions.
9080
*
9181
* @param origin - The origin whose permissions to revoke.
9282
*/
@@ -293,12 +283,42 @@ export type PermissionControllerGetEndowmentsAction = {
293283
handler: PermissionController['getEndowments'];
294284
};
295285

286+
/**
287+
* Executes a restricted method as the subject with the given origin.
288+
* The specified params, if any, will be passed to the method implementation.
289+
*
290+
* ATTN: Great caution should be exercised in the use of this method.
291+
* Methods that cause side effects or affect application state should
292+
* be avoided.
293+
*
294+
* This method will first attempt to retrieve the requested restricted method
295+
* implementation, throwing if it does not exist. The method will then be
296+
* invoked as though the subject with the specified origin had invoked it with
297+
* the specified parameters. This means that any existing caveats will be
298+
* applied to the restricted method, and this method will throw if the
299+
* restricted method or its caveat decorators throw.
300+
*
301+
* In addition, this method will throw if the subject does not have a
302+
* permission for the specified restricted method.
303+
*
304+
* @param origin - The origin of the subject to execute the method on behalf
305+
* of.
306+
* @param targetName - The name of the method to execute. This must be a valid
307+
* permission target name.
308+
* @param params - The parameters to pass to the method implementation.
309+
* @returns The result of the executed method.
310+
*/
311+
export type PermissionControllerExecuteRestrictedMethodAction = {
312+
type: `PermissionController:executeRestrictedMethod`;
313+
handler: PermissionController['executeRestrictedMethod'];
314+
};
315+
296316
/**
297317
* Union of all PermissionController action types.
298318
*/
299319
export type PermissionControllerMethodActions =
320+
| PermissionControllerHasUnrestrictedMethodAction
300321
| PermissionControllerClearStateAction
301-
| PermissionControllerCreatePermissionMiddlewareAction
302322
| PermissionControllerGetSubjectNamesAction
303323
| PermissionControllerGetPermissionsAction
304324
| PermissionControllerHasPermissionAction
@@ -312,4 +332,5 @@ export type PermissionControllerMethodActions =
312332
| PermissionControllerGrantPermissionsIncrementalAction
313333
| PermissionControllerRequestPermissionsAction
314334
| PermissionControllerRequestPermissionsIncrementalAction
315-
| PermissionControllerGetEndowmentsAction;
335+
| PermissionControllerGetEndowmentsAction
336+
| PermissionControllerExecuteRestrictedMethodAction;

0 commit comments

Comments
 (0)