Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import type {
GatorPermissionsMap,
StoredGatorPermission,
PermissionTypesWithCustom,
RevocationParams,
} from './types';
import type {
ExtractAvailableAction,
Expand Down Expand Up @@ -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',
);
});
});
});

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
type PermissionTypesWithCustom,
type StoredGatorPermission,
type DelegationDetails,
type RevocationParams,
} from './types';
import {
deserializeGatorPermissionsMap,
Expand Down Expand Up @@ -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.
Expand All @@ -188,7 +197,8 @@ export type GatorPermissionsControllerActions =
| GatorPermissionsControllerFetchAndUpdateGatorPermissionsAction
| GatorPermissionsControllerEnableGatorPermissionsAction
| GatorPermissionsControllerDisableGatorPermissionsAction
| GatorPermissionsControllerDecodePermissionFromPermissionContextForOriginAction;
| GatorPermissionsControllerDecodePermissionFromPermissionContextForOriginAction
| GatorPermissionsControllerSubmitRevocationAction;

/**
* All actions that {@link GatorPermissionsController} calls internally.
Expand Down Expand Up @@ -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),
Expand All @@ -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',
);
}

/**
Expand Down Expand Up @@ -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<void> {
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,
});
}
}
}
2 changes: 2 additions & 0 deletions packages/gator-permissions-controller/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export type {
GatorPermissionsControllerFetchAndUpdateGatorPermissionsAction,
GatorPermissionsControllerEnableGatorPermissionsAction,
GatorPermissionsControllerDisableGatorPermissionsAction,
GatorPermissionsControllerSubmitRevocationAction,
GatorPermissionsControllerActions,
GatorPermissionsControllerEvents,
GatorPermissionsControllerStateChangeEvent,
Expand All @@ -31,6 +32,7 @@ export type {
GatorPermissionsMapByPermissionType,
GatorPermissionsListByPermissionTypeAndChainId,
DelegationDetails,
RevocationParams,
} from './types';

export type {
Expand Down
14 changes: 14 additions & 0 deletions packages/gator-permissions-controller/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
}

/**
Expand Down Expand Up @@ -220,3 +224,13 @@ export type DelegationDetails = Pick<
Delegation<Hex>,
'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;
};
Loading