diff --git a/packages/gator-permissions-controller/src/GatorPermissionsController.test.ts b/packages/gator-permissions-controller/src/GatorPermissionsController.test.ts index 5802328d7e0..48653a61e42 100644 --- a/packages/gator-permissions-controller/src/GatorPermissionsController.test.ts +++ b/packages/gator-permissions-controller/src/GatorPermissionsController.test.ts @@ -29,6 +29,7 @@ import type { GatorPermissionsMap, StoredGatorPermission, PermissionTypesWithCustom, + RevocationParams, } from './types'; import type { ExtractAvailableAction, @@ -688,6 +689,91 @@ describe('GatorPermissionsController', () => { ).toThrow('Failed to decode permission'); }); }); + + describe('submitRevocation', () => { + it('should successfully submit a revocation when gator permissions are enabled', async () => { + const mockHandleRequestHandler = jest.fn().mockResolvedValue(undefined); + const messenger = getMessenger( + getRootMessenger({ + snapControllerHandleRequestActionHandler: mockHandleRequestHandler, + }), + ); + + const controller = new GatorPermissionsController({ + messenger, + state: { + isGatorPermissionsEnabled: true, + gatorPermissionsProviderSnapId: + MOCK_GATOR_PERMISSIONS_PROVIDER_SNAP_ID, + }, + }); + + const revocationParams: RevocationParams = { + delegationHash: '0x1234567890abcdef1234567890abcdef12345678', + }; + + await controller.submitRevocation(revocationParams); + + expect(mockHandleRequestHandler).toHaveBeenCalledWith({ + snapId: MOCK_GATOR_PERMISSIONS_PROVIDER_SNAP_ID, + origin: 'metamask', + handler: 'onRpcRequest', + request: { + jsonrpc: '2.0', + method: 'permissionsProvider_submitRevocation', + params: revocationParams, + }, + }); + }); + + it('should throw GatorPermissionsNotEnabledError when gator permissions are disabled', async () => { + const messenger = getMessenger(); + const controller = new GatorPermissionsController({ + messenger, + state: { + isGatorPermissionsEnabled: false, + }, + }); + + const revocationParams: RevocationParams = { + delegationHash: '0x1234567890abcdef1234567890abcdef12345678', + }; + + await expect( + controller.submitRevocation(revocationParams), + ).rejects.toThrow('Gator permissions are not enabled'); + }); + + it('should throw GatorPermissionsProviderError when snap request fails', async () => { + const mockHandleRequestHandler = jest + .fn() + .mockRejectedValue(new Error('Snap request failed')); + const messenger = getMessenger( + getRootMessenger({ + snapControllerHandleRequestActionHandler: mockHandleRequestHandler, + }), + ); + + const controller = new GatorPermissionsController({ + messenger, + state: { + isGatorPermissionsEnabled: true, + gatorPermissionsProviderSnapId: + MOCK_GATOR_PERMISSIONS_PROVIDER_SNAP_ID, + }, + }); + + const revocationParams: RevocationParams = { + delegationHash: '0x1234567890abcdef1234567890abcdef12345678', + }; + + await expect( + controller.submitRevocation(revocationParams), + ).rejects.toThrow( + 'Failed to handle snap request to gator permissions provider for method permissionsProvider_submitRevocation', + ); + }); + }); }); /** diff --git a/packages/gator-permissions-controller/src/GatorPermissionsController.ts b/packages/gator-permissions-controller/src/GatorPermissionsController.ts index aeaa9886681..d5d38fa3e1f 100644 --- a/packages/gator-permissions-controller/src/GatorPermissionsController.ts +++ b/packages/gator-permissions-controller/src/GatorPermissionsController.ts @@ -32,6 +32,7 @@ import { type PermissionTypesWithCustom, type StoredGatorPermission, type DelegationDetails, + type RevocationParams, } from './types'; import { deserializeGatorPermissionsMap, @@ -179,6 +180,14 @@ export type GatorPermissionsControllerDecodePermissionFromPermissionContextForOr handler: GatorPermissionsController['decodePermissionFromPermissionContextForOrigin']; }; +/** + * The action which can be used to submit a revocation. + */ +export type GatorPermissionsControllerSubmitRevocationAction = { + type: `${typeof controllerName}:submitRevocation`; + handler: GatorPermissionsController['submitRevocation']; +}; + /** * All actions that {@link GatorPermissionsController} registers, to be called * externally. @@ -188,7 +197,8 @@ export type GatorPermissionsControllerActions = | GatorPermissionsControllerFetchAndUpdateGatorPermissionsAction | GatorPermissionsControllerEnableGatorPermissionsAction | GatorPermissionsControllerDisableGatorPermissionsAction - | GatorPermissionsControllerDecodePermissionFromPermissionContextForOriginAction; + | GatorPermissionsControllerDecodePermissionFromPermissionContextForOriginAction + | GatorPermissionsControllerSubmitRevocationAction; /** * All actions that {@link GatorPermissionsController} calls internally. @@ -298,6 +308,11 @@ export default class GatorPermissionsController extends BaseController< `${controllerName}:decodePermissionFromPermissionContextForOrigin`, this.decodePermissionFromPermissionContextForOrigin.bind(this), ); + + this.messagingSystem.registerActionHandler( + `${controllerName}:submitRevocation`, + this.submitRevocation.bind(this), + ); } /** @@ -598,4 +613,41 @@ export default class GatorPermissionsController extends BaseController< }); } } + + /** + * Submits a revocation to the gator permissions provider snap. + * + * @param revocationParams - The revocation parameters containing the delegation hash. + * @returns A promise that resolves when the revocation is submitted successfully. + * @throws {GatorPermissionsNotEnabledError} If the gator permissions are not enabled. + * @throws {GatorPermissionsProviderError} If the snap request fails. + */ + public async submitRevocation( + revocationParams: RevocationParams, + ): Promise { + this.#assertGatorPermissionsEnabled(); + + try { + await this.messagingSystem.call('SnapController:handleRequest', { + snapId: this.state.gatorPermissionsProviderSnapId, + origin: 'metamask', + handler: HandlerType.OnRpcRequest, + request: { + jsonrpc: '2.0', + method: + GatorPermissionsSnapRpcMethod.PermissionProviderSubmitRevocation, + params: revocationParams, + }, + }); + + controllerLog('Successfully submitted revocation', revocationParams); + } catch (error) { + controllerLog('Failed to submit revocation', error); + throw new GatorPermissionsProviderError({ + method: + GatorPermissionsSnapRpcMethod.PermissionProviderSubmitRevocation, + cause: error as Error, + }); + } + } } diff --git a/packages/gator-permissions-controller/src/index.ts b/packages/gator-permissions-controller/src/index.ts index c2170783ff2..b753a64ec74 100644 --- a/packages/gator-permissions-controller/src/index.ts +++ b/packages/gator-permissions-controller/src/index.ts @@ -11,6 +11,7 @@ export type { GatorPermissionsControllerFetchAndUpdateGatorPermissionsAction, GatorPermissionsControllerEnableGatorPermissionsAction, GatorPermissionsControllerDisableGatorPermissionsAction, + GatorPermissionsControllerSubmitRevocationAction, GatorPermissionsControllerActions, GatorPermissionsControllerEvents, GatorPermissionsControllerStateChangeEvent, @@ -31,6 +32,7 @@ export type { GatorPermissionsMapByPermissionType, GatorPermissionsListByPermissionTypeAndChainId, DelegationDetails, + RevocationParams, } from './types'; export type { diff --git a/packages/gator-permissions-controller/src/types.ts b/packages/gator-permissions-controller/src/types.ts index 6d875cc37f6..930f91c3de9 100644 --- a/packages/gator-permissions-controller/src/types.ts +++ b/packages/gator-permissions-controller/src/types.ts @@ -32,6 +32,10 @@ export enum GatorPermissionsSnapRpcMethod { * This method is used by the metamask to request a permissions provider to get granted permissions for all sites. */ PermissionProviderGetGrantedPermissions = 'permissionsProvider_getGrantedPermissions', + /** + * This method is used by the metamask to submit a revocation to the permissions provider. + */ + PermissionProviderSubmitRevocation = 'permissionsProvider_submitRevocation', } /** @@ -220,3 +224,13 @@ export type DelegationDetails = Pick< Delegation, 'caveats' | 'delegator' | 'delegate' | 'authority' >; + +/** + * Represents the parameters for submitting a revocation. + */ +export type RevocationParams = { + /** + * The delegation hash as a hex string that identifies the permission to revoke. + */ + delegationHash: Hex; +};