diff --git a/packages/gator-permissions-controller/src/GatorPermissionsController.test.ts b/packages/gator-permissions-controller/src/GatorPermissionsController.test.ts index 5802328d7e0..0e5ca320fad 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 = { + permissionContext: '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 = { + permissionContext: '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 = { + permissionContext: '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..198bd162e52 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. @@ -279,6 +289,8 @@ export default class GatorPermissionsController extends BaseController< } #registerMessageHandlers(): void { + console.log('[GatorPermissionsController] Registering message handlers...'); + this.messagingSystem.registerActionHandler( `${controllerName}:fetchAndUpdateGatorPermissions`, this.fetchAndUpdateGatorPermissions.bind(this), @@ -298,6 +310,19 @@ export default class GatorPermissionsController extends BaseController< `${controllerName}:decodePermissionFromPermissionContextForOrigin`, this.decodePermissionFromPermissionContextForOrigin.bind(this), ); + + const submitRevocationAction = `${controllerName}:submitRevocation`; + console.log( + '[GatorPermissionsController] Registering submitRevocation action:', + submitRevocationAction, + ); + this.messagingSystem.registerActionHandler( + submitRevocationAction, + this.submitRevocation.bind(this), + ); + console.log( + '[GatorPermissionsController] submitRevocation action registered successfully', + ); } /** @@ -598,4 +623,102 @@ export default class GatorPermissionsController extends BaseController< }); } } + + /** + * Submits a revocation to the gator permissions provider snap. + * + * @param revocationParams - The revocation parameters containing the permission context. + * @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 { + console.log( + '[GatorPermissionsController] submitRevocation called with permissionContext:', + revocationParams.permissionContext, + ); + controllerLog('submitRevocation method called', { + permissionContext: revocationParams.permissionContext, + }); + + console.log( + '[GatorPermissionsController] Checking if gator permissions are enabled...', + ); + console.log( + '[GatorPermissionsController] isGatorPermissionsEnabled:', + this.state.isGatorPermissionsEnabled, + ); + + try { + this.#assertGatorPermissionsEnabled(); + console.log( + '[GatorPermissionsController] Gator permissions are enabled, proceeding...', + ); + } catch (error) { + console.error( + '[GatorPermissionsController] Gator permissions not enabled:', + error, + ); + throw error; + } + + console.log('[GatorPermissionsController] Preparing snap request...'); + console.log( + '[GatorPermissionsController] snapId:', + this.state.gatorPermissionsProviderSnapId, + ); + console.log( + '[GatorPermissionsController] method:', + GatorPermissionsSnapRpcMethod.PermissionProviderSubmitRevocation, + ); + + try { + console.log('[GatorPermissionsController] Making snap request...'); + + const snapRequest = { + snapId: this.state.gatorPermissionsProviderSnapId, + origin: 'metamask', + handler: HandlerType.OnRpcRequest, + request: { + jsonrpc: '2.0', + method: + GatorPermissionsSnapRpcMethod.PermissionProviderSubmitRevocation, + params: revocationParams, + }, + }; + + console.log( + '[GatorPermissionsController] Snap request payload:', + JSON.stringify(snapRequest, null, 2), + ); + + const result = await this.messagingSystem.call( + 'SnapController:handleRequest', + snapRequest, + ); + + console.log( + '[GatorPermissionsController] Snap request successful, result:', + result, + ); + controllerLog('Successfully submitted revocation', { + permissionContext: revocationParams.permissionContext, + result, + }); + } catch (error) { + console.error('[GatorPermissionsController] Snap request failed:', error); + controllerLog('Failed to submit revocation', { + error, + permissionContext: revocationParams.permissionContext, + }); + + 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..bd78a2af4e5 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 permission context as a hex string that identifies the permission to revoke. + */ + permissionContext: Hex; +};