-
Notifications
You must be signed in to change notification settings - Fork 27
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add permission boundary (#448)
* feat: add permission boundary * chore: add mock for index test
- Loading branch information
1 parent
5d569a8
commit 1e80070
Showing
4 changed files
with
201 additions
and
42 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import { originPermissions, validateOrigin, RpcMethod } from './permission'; | ||
|
||
describe('validateOrigin', () => { | ||
const walletUIDappPermissions = Array.from( | ||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
originPermissions.get('https://snaps.consensys.io')!, | ||
); | ||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
const publicPermissions = Array.from(originPermissions.get('*')!); | ||
const restrictedPermissions = [ | ||
RpcMethod.DeployCario0Account, | ||
RpcMethod.ListAccounts, | ||
RpcMethod.GetTransactions, | ||
RpcMethod.UpgradeAccContract, | ||
RpcMethod.GetStarkName, | ||
RpcMethod.ReadContract, | ||
RpcMethod.GetStoredErc20Tokens, | ||
]; | ||
|
||
it.each(walletUIDappPermissions)( | ||
"pass the validation with a valid Wallet UI Dapp's origin and a whitelisted method. method - %s", | ||
(method: string) => { | ||
expect(() => | ||
validateOrigin('https://snaps.consensys.io', method), | ||
).not.toThrow(); | ||
}, | ||
); | ||
|
||
it.each(publicPermissions)( | ||
'pass the validation with any origin and a whitelisted method. method - %s', | ||
(method: string) => { | ||
expect(() => validateOrigin('https://any.io', method)).not.toThrow(); | ||
}, | ||
); | ||
|
||
it.each(restrictedPermissions)( | ||
'throw a `Permission denied` if the method is restricted for public. method - %s', | ||
(method: string) => { | ||
expect(() => validateOrigin('https://any.io', method)).toThrow( | ||
'Permission denied', | ||
); | ||
}, | ||
); | ||
|
||
it('throw a `Permission denied` if the method is not exist.', () => { | ||
expect(() => validateOrigin('https://any.io', 'method_not_exist')).toThrow( | ||
'Permission denied', | ||
); | ||
expect(() => | ||
validateOrigin('https://snaps.consensys.io', 'method_not_exist'), | ||
).toThrow('Permission denied'); | ||
}); | ||
|
||
it('throw a `Origin not found` if the orgin is not given or empty.', () => { | ||
expect(() => validateOrigin('', 'method_not_exist')).toThrow( | ||
'Origin not found', | ||
); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import { UnauthorizedError } from '@metamask/snaps-sdk'; | ||
|
||
export enum RpcMethod { | ||
ExtractPublicKey = 'starkNet_extractPublicKey', | ||
GetCurrentNetwork = 'starkNet_getCurrentNetwork', | ||
GetStoredNetworks = 'starkNet_getStoredNetworks', | ||
SwitchNetwork = 'starkNet_switchNetwork', | ||
AddErc20Token = 'starkNet_addErc20Token', | ||
RecoverAccounts = 'starkNet_recoverAccounts', | ||
ExecuteTxn = 'starkNet_executeTxn', | ||
DeclareContract = 'starkNet_declareContract', | ||
GetDeploymentData = 'starkNet_getDeploymentData', | ||
SignMessage = 'starkNet_signMessage', | ||
SignTransaction = 'starkNet_signTransaction', | ||
SignDeclareTransaction = 'starkNet_signDeclareTransaction', | ||
SignDeployAccountTransaction = 'starkNet_signDeployAccountTransaction', | ||
|
||
CreateAccount = 'starkNet_createAccount', | ||
DisplayPrivateKey = 'starkNet_displayPrivateKey', | ||
GetErc20TokenBalance = 'starkNet_getErc20TokenBalance', | ||
GetTransactionStatus = 'starkNet_getTransactionStatus', | ||
EstimateFee = 'starkNet_estimateFee', | ||
VerifySignedMessage = 'starkNet_verifySignedMessage', | ||
DeployCario0Account = 'starkNet_createAccountLegacy', | ||
ListAccounts = 'starkNet_getStoredUserAccounts', | ||
GetTransactions = 'starkNet_getTransactions', | ||
UpgradeAccContract = 'starkNet_upgradeAccContract', | ||
GetStarkName = 'starkNet_getStarkName', | ||
ReadContract = 'starkNet_getValue', | ||
GetStoredErc20Tokens = 'starkNet_getStoredErc20Tokens', | ||
} | ||
// RpcMethod that are allowed to be called by any origin | ||
const publicPermissions = [ | ||
RpcMethod.ExtractPublicKey, | ||
RpcMethod.GetCurrentNetwork, | ||
RpcMethod.GetStoredNetworks, | ||
RpcMethod.SwitchNetwork, | ||
RpcMethod.AddErc20Token, | ||
RpcMethod.RecoverAccounts, | ||
RpcMethod.ExecuteTxn, | ||
RpcMethod.DeclareContract, | ||
RpcMethod.GetDeploymentData, | ||
RpcMethod.SignMessage, | ||
RpcMethod.SignTransaction, | ||
RpcMethod.SignDeclareTransaction, | ||
RpcMethod.SignDeployAccountTransaction, | ||
RpcMethod.CreateAccount, | ||
RpcMethod.DisplayPrivateKey, | ||
RpcMethod.GetErc20TokenBalance, | ||
RpcMethod.GetTransactionStatus, | ||
RpcMethod.EstimateFee, | ||
RpcMethod.VerifySignedMessage, | ||
]; | ||
// RpcMethod that are restricted to be called by wallet UI origins | ||
const walletUIDappPermissions = publicPermissions.concat([ | ||
RpcMethod.DeployCario0Account, | ||
RpcMethod.ListAccounts, | ||
RpcMethod.GetTransactions, | ||
RpcMethod.UpgradeAccContract, | ||
RpcMethod.GetStarkName, | ||
RpcMethod.ReadContract, | ||
RpcMethod.GetStoredErc20Tokens, | ||
]); | ||
|
||
const publicPermissionsSet = new Set(publicPermissions); | ||
const walletUIDappPermissionsSet = new Set(walletUIDappPermissions); | ||
|
||
const walletUIDappOrigins = [ | ||
'http://localhost:3000', | ||
'https://snaps.consensys.io', | ||
'https://dev.snaps.consensys.io', | ||
'https://staging.snaps.consensys.io', | ||
]; | ||
|
||
export const originPermissions = new Map<string, Set<string>>([]); | ||
for (const origin of walletUIDappOrigins) { | ||
originPermissions.set(origin, walletUIDappPermissionsSet); | ||
} | ||
originPermissions.set('*', publicPermissionsSet); | ||
|
||
/** | ||
* Validate the origin and method pair. | ||
* If the origin is not found or the method is not allowed, throw an error. | ||
* | ||
* @param origin - The origin of the request. | ||
* @param method - The method of the request. | ||
* @throws {UnauthorizedError} If the origin is not found or the method is not allowed. | ||
*/ | ||
export function validateOrigin(origin: string, method: string): void { | ||
if (!origin) { | ||
// eslint-disable-next-line @typescript-eslint/no-throw-literal | ||
throw new UnauthorizedError('Origin not found'); | ||
} | ||
// As public permissions are a subset of wallet UI Dapp permissions, | ||
// If the origin and method pair are not in the wallet UI Dapp permissions, | ||
// then fallback and validate whether it hits the common permission. | ||
if ( | ||
!originPermissions.get(origin)?.has(method) && | ||
!originPermissions.get('*')?.has(method) | ||
) { | ||
// eslint-disable-next-line @typescript-eslint/no-throw-literal | ||
throw new UnauthorizedError(`Permission denied`); | ||
} | ||
} |