-
-
Notifications
You must be signed in to change notification settings - Fork 1
Added New RPC for Marking Delegation as Revoked #171
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
0befaa4
e89c045
83fa627
9630eb5
853f216
52417dc
1b22e64
e1a0c92
37937ad
d613dbc
11203b2
00ab24f
75316d9
b1ac613
ca62aaa
f36c5b0
168e4e7
5059b2b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,6 +21,7 @@ import { | |
| LimitExceededError, | ||
| ParseError, | ||
| UnsupportedMethodError, | ||
| type SnapsEthereumProvider, | ||
| } from '@metamask/snaps-sdk'; | ||
| import { z } from 'zod'; | ||
|
|
||
|
|
@@ -31,6 +32,7 @@ const MAX_STORAGE_SIZE_BYTES = 400 * 1024; // 400kb limit as documented | |
| const zStoredGrantedPermission = z.object({ | ||
| permissionResponse: zPermissionResponse, | ||
| siteOrigin: z.string().min(1, 'Site origin cannot be empty'), | ||
| isRevoked: z.boolean().default(false), | ||
| }); | ||
|
|
||
| /** | ||
|
|
@@ -100,17 +102,46 @@ export type ProfileSyncManager = { | |
| storeGrantedPermissionBatch: ( | ||
| storedGrantedPermission: StoredGrantedPermission[], | ||
| ) => Promise<void>; | ||
| updatePermissionRevocationStatus: ( | ||
| permissionContext: Hex, | ||
| isRevoked: boolean, | ||
| ) => Promise<void>; | ||
| updatePermissionRevocationStatusWithPermission: ( | ||
| existingPermission: StoredGrantedPermission, | ||
| isRevoked: boolean, | ||
| ) => Promise<void>; | ||
| checkDelegationDisabledOnChain: ( | ||
| delegationHash: Hex, | ||
| chainId: Hex, | ||
| delegationManagerAddress: Hex, | ||
| ) => Promise<boolean>; | ||
| }; | ||
|
|
||
| export type StoredGrantedPermission = { | ||
| permissionResponse: PermissionResponse; | ||
| siteOrigin: string; | ||
| isRevoked: boolean; | ||
| }; | ||
|
|
||
| /** | ||
| * Generates an object key for the permission response stored in profile sync. | ||
| * @param permissionContext - The encoded delegation(ie. permissions context). | ||
| * @returns The object key by concatenating the delegation hashes. | ||
| */ | ||
| export function generateObjectKey(permissionContext: Hex): Hex { | ||
| const delegations = decodeDelegations(permissionContext); | ||
| const hashes = delegations.map((delegation) => | ||
| hashDelegation(delegation).slice(2), | ||
| ); | ||
|
|
||
| return `0x${hashes.join('')}`; | ||
| } | ||
|
|
||
| export type ProfileSyncManagerConfig = { | ||
| auth: JwtBearerAuth; | ||
| userStorage: UserStorage; | ||
| isFeatureEnabled: boolean; | ||
| ethereumProvider: SnapsEthereumProvider; | ||
| }; | ||
|
|
||
| /** | ||
|
|
@@ -122,7 +153,7 @@ export function createProfileSyncManager( | |
| config: ProfileSyncManagerConfig, | ||
| ): ProfileSyncManager { | ||
| const FEATURE = 'gator_7715_permissions'; | ||
| const { auth, userStorage, isFeatureEnabled } = config; | ||
| const { auth, userStorage, isFeatureEnabled, ethereumProvider } = config; | ||
| const unConfiguredProfileSyncManager = { | ||
| getAllGrantedPermissions: async () => { | ||
| logger.debug('unConfiguredProfileSyncManager.getAllGrantedPermissions()'); | ||
|
|
@@ -143,22 +174,27 @@ export function createProfileSyncManager( | |
| 'unConfiguredProfileSyncManager.storeGrantedPermissionBatch()', | ||
| ); | ||
| }, | ||
| updatePermissionRevocationStatus: async (_: Hex, __: boolean) => { | ||
| logger.debug( | ||
| 'unConfiguredProfileSyncManager.updatePermissionRevocationStatus()', | ||
| ); | ||
| }, | ||
| updatePermissionRevocationStatusWithPermission: async ( | ||
| _: StoredGrantedPermission, | ||
| __: boolean, | ||
| ) => { | ||
| logger.debug( | ||
| 'unConfiguredProfileSyncManager.updatePermissionRevocationStatusWithPermission()', | ||
| ); | ||
| }, | ||
| checkDelegationDisabledOnChain: async (_: Hex, __: Hex, ___: Hex) => { | ||
| logger.debug( | ||
| 'unConfiguredProfileSyncManager.checkDelegationDisabledOnChain()', | ||
| ); | ||
| return false; // Default to not disabled when feature is disabled | ||
| }, | ||
| }; | ||
|
|
||
| /** | ||
| * Generates an object key for the permission response stored in profile sync. | ||
| * @param permissionContext - The encoded delegation(ie. permissions context). | ||
| * @returns The object key by concatenating the delegation hashes. | ||
| */ | ||
| function generateObjectKey(permissionContext: Hex): Hex { | ||
| const delegations = decodeDelegations(permissionContext); | ||
| const hashes = delegations.map((delegation) => | ||
| hashDelegation(delegation).slice(2), | ||
| ); | ||
|
|
||
| return `0x${hashes.join('')}`; | ||
| } | ||
|
|
||
| /** | ||
| * Authenticates the user with profile sync. | ||
| * | ||
|
|
@@ -167,7 +203,7 @@ export function createProfileSyncManager( | |
| try { | ||
| await auth.getAccessToken(); | ||
| } catch (error) { | ||
| logger.error('Error fetching access token'); | ||
| logger.error('Error fetching access token:', error); | ||
| throw error; | ||
| } | ||
| } | ||
|
|
@@ -306,6 +342,131 @@ export function createProfileSyncManager( | |
| } | ||
| } | ||
|
|
||
| /** | ||
| * Updates the revocation status of a granted permission in profile sync. | ||
| * | ||
| * @param permissionContext - The context of the granted permission to update. | ||
| * @param isRevoked - The new revocation status. | ||
| * @throws InvalidInputError if the permission is not found. | ||
| */ | ||
| async function updatePermissionRevocationStatus( | ||
| permissionContext: Hex, | ||
| isRevoked: boolean, | ||
| ): Promise<void> { | ||
| try { | ||
| await authenticate(); | ||
|
|
||
| const existingPermission = await getGrantedPermission(permissionContext); | ||
| if (!existingPermission) { | ||
| throw new InvalidInputError( | ||
| `Permission not found for permission context: ${permissionContext}`, | ||
| ); | ||
| } | ||
|
|
||
| await updatePermissionRevocationStatusWithPermission( | ||
| existingPermission, | ||
| isRevoked, | ||
| ); | ||
| } catch (error) { | ||
| logger.error('Error updating permission revocation status'); | ||
| throw error; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Updates the revocation status of a granted permission when you already have the permission object. | ||
| * This is an optimized version that avoids re-fetching the permission. | ||
| * | ||
| * @param existingPermission - The existing permission object. | ||
| * @param isRevoked - The new revocation status. | ||
| */ | ||
| async function updatePermissionRevocationStatusWithPermission( | ||
| existingPermission: StoredGrantedPermission, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we remove Consumers of the revocation flag feature are restricted to the MetaMask client and should only need to call updatePermissionRevocationStatus(permissionContext, isRevoked), which are provided as params on the When we allow the consumer to update a permission by providing an There is also no check to ensure the item is in the store, which morphs the expected behavior of an 'update' function into a proxy for creating a new store entry. The only field the consumer can update is the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have created a branch against your PR with my suggestions. Here is the commit: c628a5a |
||
| isRevoked: boolean, | ||
| ): Promise<void> { | ||
| try { | ||
| logger.debug('Profile Sync: Updating permission revocation status:', { | ||
| existingPermission, | ||
| isRevoked, | ||
| }); | ||
|
|
||
| await authenticate(); | ||
|
|
||
| const updatedPermission: StoredGrantedPermission = { | ||
| ...existingPermission, | ||
| isRevoked, | ||
| }; | ||
|
|
||
| logger.debug( | ||
| 'Profile Sync: Created updated permission object:', | ||
| updatedPermission, | ||
| ); | ||
|
|
||
| await storeGrantedPermission(updatedPermission); | ||
| logger.debug('Profile Sync: Successfully stored updated permission'); | ||
| } catch (error) { | ||
| logger.error( | ||
| 'Error updating permission revocation status with existing permission:', | ||
| error, | ||
| ); | ||
| throw error; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Checks if a delegation is disabled on-chain by calling the DelegationManager contract. | ||
| * @param delegationHash - The hash of the delegation to check. | ||
| * @param chainId - The chain ID in hex format. | ||
| * @param delegationManagerAddress - The address of the DelegationManager contract. | ||
| * @returns True if the delegation is disabled, false otherwise. | ||
| */ | ||
| async function checkDelegationDisabledOnChain( | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually maybe it would be better for this to be in: |
||
| delegationHash: Hex, | ||
| chainId: Hex, | ||
| delegationManagerAddress: Hex, | ||
| ): Promise<boolean> { | ||
| try { | ||
| logger.debug('Checking delegation disabled status on-chain', { | ||
| delegationHash, | ||
| chainId, | ||
| delegationManagerAddress, | ||
| }); | ||
|
|
||
| // Encode the function call data for disabledDelegations(bytes32) | ||
| const functionSelector = '0x2d40d052'; // keccak256("disabledDelegations(bytes32)").slice(0, 10) | ||
| const encodedParams = delegationHash.slice(2).padStart(64, '0'); // Remove 0x and pad to 32 bytes | ||
| const callData = `${functionSelector}${encodedParams}`; | ||
|
|
||
| const result = await ethereumProvider.request<Hex>({ | ||
| method: 'eth_call', | ||
| params: [ | ||
| { | ||
| to: delegationManagerAddress, | ||
| data: callData, | ||
| }, | ||
| 'latest', | ||
| ], | ||
| }); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: Invalid
|
||
|
|
||
| if (!result) { | ||
| logger.warn('No result from contract call'); | ||
| return false; | ||
| } | ||
|
|
||
| // Parse the boolean result (32 bytes, last byte is the boolean value) | ||
| const isDisabled = | ||
| result !== | ||
| '0x0000000000000000000000000000000000000000000000000000000000000000'; | ||
|
|
||
| logger.debug('Delegation disabled status result', { isDisabled }); | ||
| return isDisabled; | ||
| } catch (error) { | ||
| logger.error('Error checking delegation disabled status on-chain', error); | ||
| // In case of error, assume not disabled to avoid blocking legitimate operations | ||
| return false; | ||
| } | ||
| } | ||
This comment was marked as outdated.
Sorry, something went wrong. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: Chain ID Ignored in Delegation CheckThe There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: Chain ID Ignored in Delegation CheckThe There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
|
||
| /** | ||
| * Feature flag to disable profile sync feature until message-signing-snap v1.1.2 released in MM 12.18: https://github.com/MetaMask/metamask-extension/pull/32521. | ||
| */ | ||
|
|
@@ -315,6 +476,9 @@ export function createProfileSyncManager( | |
| getGrantedPermission, | ||
| storeGrantedPermission, | ||
| storeGrantedPermissionBatch, | ||
| updatePermissionRevocationStatus, | ||
| updatePermissionRevocationStatusWithPermission, | ||
| checkDelegationDisabledOnChain, | ||
| } | ||
| : unConfiguredProfileSyncManager; | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Permission Context Mislabeling
The error message in
updatePermissionRevocationStatusincorrectly labels thepermissionContextparameter as a "delegation hash." This is misleading, aspermissionContextrepresents an encoded permission context, and creates an inconsistency with other error messages that correctly use "permission context."