diff --git a/packages/gator-permissions-snap/snap.manifest.json b/packages/gator-permissions-snap/snap.manifest.json index 33bfb072..27c3ecc4 100644 --- a/packages/gator-permissions-snap/snap.manifest.json +++ b/packages/gator-permissions-snap/snap.manifest.json @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snap-7715-permissions.git" }, "source": { - "shasum": "fMzK/tguXx0jZMJ3Vu9z1OcEFZD3LDYPS8mL7LWSSc8=", + "shasum": "8PGSI83NMAkED4BCqQY6WUB6rSDGmHLx2PavNnyQm0s=", "location": { "npm": { "filePath": "dist/bundle.js", diff --git a/packages/gator-permissions-snap/src/index.ts b/packages/gator-permissions-snap/src/index.ts index 79b1ca6e..9303446f 100644 --- a/packages/gator-permissions-snap/src/index.ts +++ b/packages/gator-permissions-snap/src/index.ts @@ -173,6 +173,8 @@ const boundRpcHandlers: { rpcHandler.grantPermission.bind(rpcHandler), [RpcMethod.PermissionProviderGetPermissionOffers]: rpcHandler.getPermissionOffers.bind(rpcHandler), + [RpcMethod.PermissionProviderGetGrantedPermissions]: + rpcHandler.getGrantedPermissions.bind(rpcHandler), }; /** diff --git a/packages/gator-permissions-snap/src/rpc/permissions.ts b/packages/gator-permissions-snap/src/rpc/permissions.ts index 77ae8161..780149d1 100644 --- a/packages/gator-permissions-snap/src/rpc/permissions.ts +++ b/packages/gator-permissions-snap/src/rpc/permissions.ts @@ -7,6 +7,7 @@ const allowedPermissionsByOrigin: { [origin: string]: string[] } = { RpcMethod.PermissionProviderGrantPermissions, RpcMethod.PermissionProviderGetPermissionOffers, ], + metamask: [RpcMethod.PermissionProviderGetGrantedPermissions], }; /** diff --git a/packages/gator-permissions-snap/src/rpc/rpcHandler.ts b/packages/gator-permissions-snap/src/rpc/rpcHandler.ts index c5b7cf0b..b3ffbdf3 100644 --- a/packages/gator-permissions-snap/src/rpc/rpcHandler.ts +++ b/packages/gator-permissions-snap/src/rpc/rpcHandler.ts @@ -24,6 +24,13 @@ export type RpcHandler = { * @returns The permission offers. */ getPermissionOffers(): Promise; + + /** + * Handles get granted permissions requests. + * + * @returns The granted permissions. + */ + getGrantedPermissions(): Promise; }; /** @@ -87,8 +94,21 @@ export function createRpcHandler(config: { return DEFAULT_GATOR_PERMISSION_TO_OFFER; }; + /** + * Handles get granted permissions requests. + * + * @returns The granted permissions. + */ + const getGrantedPermissions = async (): Promise => { + logger.debug('getGrantedPermissions()'); + const grantedPermission = + await profileSyncManager.getAllGrantedPermissions(); + return grantedPermission as Json[]; + }; + return { grantPermission, getPermissionOffers, + getGrantedPermissions, }; } diff --git a/packages/gator-permissions-snap/src/rpc/rpcMethod.ts b/packages/gator-permissions-snap/src/rpc/rpcMethod.ts index debeadae..57dbe358 100644 --- a/packages/gator-permissions-snap/src/rpc/rpcMethod.ts +++ b/packages/gator-permissions-snap/src/rpc/rpcMethod.ts @@ -11,4 +11,9 @@ export enum RpcMethod { * This method is used by the kernel to request a permissions provider to grant attenuated permissions to a site. */ PermissionProviderGrantPermissions = 'permissionsProvider_grantPermissions', + + /** + * This method is used by Metamask clients to retrieve granted permissions for all sites. + */ + PermissionProviderGetGrantedPermissions = 'permissionsProvider_getGrantedPermissions', } diff --git a/packages/gator-permissions-snap/test/end-to-end/index.test.tsx b/packages/gator-permissions-snap/test/end-to-end/index.test.tsx index 190b37f1..0151ba05 100644 --- a/packages/gator-permissions-snap/test/end-to-end/index.test.tsx +++ b/packages/gator-permissions-snap/test/end-to-end/index.test.tsx @@ -29,6 +29,19 @@ describe('Kernel Snap', () => { stack: expect.any(String), }); }); + + it('throws an error if the origin is not metamask for getGrantedPermissions', async () => { + const response = await snapRequest({ + method: 'permissionsProvider_getGrantedPermissions', + origin: 'npm:@metamask/not-metamask', + }); + + expect(response).toRespondWithError({ + code: -32603, + message: `Origin 'npm:@metamask/not-metamask' is not allowed to call 'permissionsProvider_getGrantedPermissions'`, + stack: expect.any(String), + }); + }); }); }); }); diff --git a/packages/gator-permissions-snap/test/rpc/rpcHandler.test.ts b/packages/gator-permissions-snap/test/rpc/rpcHandler.test.ts index 0a7279ae..5279fc22 100644 --- a/packages/gator-permissions-snap/test/rpc/rpcHandler.test.ts +++ b/packages/gator-permissions-snap/test/rpc/rpcHandler.test.ts @@ -63,6 +63,7 @@ describe('RpcHandler', () => { let handler: RpcHandler; let mockHandler: jest.Mocked; let mockHandlerFactory: jest.Mocked; + let mockProfileSyncManager: jest.Mocked; beforeEach(() => { mockHandler = { @@ -72,7 +73,7 @@ describe('RpcHandler', () => { mockHandlerFactory = { createPermissionHandler: jest.fn().mockReturnValue(mockHandler), } as unknown as jest.Mocked; - const mockProfileSyncManager = { + mockProfileSyncManager = { revokeGrantedPermission: jest.fn(), storeGrantedPermission: jest.fn(), storeGrantedPermissionBatch: jest.fn(), @@ -375,4 +376,90 @@ describe('RpcHandler', () => { ]); }); }); + + describe('getGrantedPermissions', () => { + it('should return all granted permissions successfully', async () => { + const mockGrantedPermissions = [ + { + permissionResponse: { + chainId: TEST_CHAIN_ID, + expiry: TEST_EXPIRY, + signer: { + type: 'account' as const, + data: { address: TEST_ADDRESS }, + }, + permission: { + type: 'test-permission', + data: { justification: 'Testing permission request' }, + }, + context: TEST_CONTEXT, + accountMeta: [], + signerMeta: { + delegationManager: TEST_ADDRESS, + }, + }, + siteOrigin: TEST_SITE_ORIGIN, + }, + { + permissionResponse: { + chainId: '0x2' as const, + expiry: TEST_EXPIRY + 1000, + signer: { + type: 'account' as const, + data: { + address: '0x0987654321098765432109876543210987654321' as const, + }, + }, + permission: { + type: 'different-permission', + data: { justification: 'Another permission' }, + }, + context: '0xefgh' as const, + accountMeta: [], + signerMeta: { + delegationManager: + '0x0987654321098765432109876543210987654321' as const, + }, + }, + siteOrigin: 'https://another-example.com', + }, + ]; + + mockProfileSyncManager.getAllGrantedPermissions.mockResolvedValue( + mockGrantedPermissions, + ); + + const result = await handler.getGrantedPermissions(); + + expect( + mockProfileSyncManager.getAllGrantedPermissions, + ).toHaveBeenCalledTimes(1); + expect(result).toStrictEqual(mockGrantedPermissions); + }); + + it('should return empty array when no permissions are granted', async () => { + mockProfileSyncManager.getAllGrantedPermissions.mockResolvedValue([]); + + const result = await handler.getGrantedPermissions(); + + expect( + mockProfileSyncManager.getAllGrantedPermissions, + ).toHaveBeenCalledTimes(1); + expect(result).toStrictEqual([]); + }); + + it('should handle errors from profile sync manager', async () => { + const errorMessage = 'Failed to retrieve granted permissions'; + mockProfileSyncManager.getAllGrantedPermissions.mockRejectedValue( + new Error(errorMessage), + ); + + await expect(handler.getGrantedPermissions()).rejects.toThrow( + errorMessage, + ); + expect( + mockProfileSyncManager.getAllGrantedPermissions, + ).toHaveBeenCalledTimes(1); + }); + }); });