-
Notifications
You must be signed in to change notification settings - Fork 570
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add generic caveat validation utility (#2122)
Add a utility function for validating caveat types for a permission. This lets us generically validate permissions with multiple caveat types without duplicating validation code across the permission specifications. This is useful long-term as we add more permissions that have more than 1 caveat type. This is a prerequisite for #2113 and #2098
- Loading branch information
1 parent
cf1fa9a
commit f1903db
Showing
4 changed files
with
161 additions
and
4 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
{ | ||
"branches": 90.77, | ||
"functions": 96.6, | ||
"lines": 97.84, | ||
"statements": 97.54 | ||
"branches": 90.87, | ||
"functions": 96.7, | ||
"lines": 97.86, | ||
"statements": 97.57 | ||
} |
96 changes: 96 additions & 0 deletions
96
packages/snaps-controllers/src/snaps/endowments/caveats/generic.test.ts
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,96 @@ | ||
import type { PermissionConstraint } from '@metamask/permission-controller'; | ||
import { SnapCaveatType } from '@metamask/snaps-utils'; | ||
import { MOCK_ORIGIN } from '@metamask/snaps-utils/test-utils'; | ||
|
||
import { createGenericPermissionValidator } from './generic'; | ||
|
||
const MOCK_PERMISSION: PermissionConstraint = { | ||
caveats: null, | ||
date: 1664187844588, | ||
id: 'izn0WGUO8cvq_jqvLQuQP', | ||
invoker: MOCK_ORIGIN, | ||
parentCapability: 'snap_dialog', | ||
}; | ||
|
||
describe('createGenericPermissionValidator', () => { | ||
it('fails if required caveats are not specified', () => { | ||
const validator = createGenericPermissionValidator([ | ||
{ type: SnapCaveatType.ChainIds }, | ||
{ type: SnapCaveatType.RpcOrigin }, | ||
]); | ||
|
||
expect(() => | ||
validator({ | ||
...MOCK_PERMISSION, | ||
caveats: [{ type: SnapCaveatType.ChainIds, value: null }], | ||
}), | ||
).toThrow('Expected the following caveats: "chainIds", "rpcOrigin".'); | ||
}); | ||
|
||
it('fails if caveats are of the wrong type', () => { | ||
const validator = createGenericPermissionValidator([ | ||
{ type: SnapCaveatType.ChainIds }, | ||
{ type: SnapCaveatType.RpcOrigin }, | ||
]); | ||
|
||
expect(() => | ||
validator({ | ||
...MOCK_PERMISSION, | ||
caveats: [ | ||
{ type: SnapCaveatType.KeyringOrigin, value: null }, | ||
{ type: SnapCaveatType.SnapIds, value: null }, | ||
], | ||
}), | ||
).toThrow( | ||
'Expected the following caveats: "chainIds", "rpcOrigin", received "keyringOrigin", "snapIds".', | ||
); | ||
}); | ||
|
||
it('fails if too many caveats specified', () => { | ||
const validator = createGenericPermissionValidator([ | ||
{ type: SnapCaveatType.ChainIds }, | ||
{ type: SnapCaveatType.RpcOrigin }, | ||
]); | ||
|
||
expect(() => | ||
validator({ | ||
...MOCK_PERMISSION, | ||
caveats: [ | ||
{ type: SnapCaveatType.ChainIds, value: null }, | ||
{ type: SnapCaveatType.ChainIds, value: null }, | ||
{ type: SnapCaveatType.RpcOrigin, value: null }, | ||
], | ||
}), | ||
).toThrow('Duplicate caveats are not allowed.'); | ||
}); | ||
|
||
it('does not fail if optional caveat is missing', () => { | ||
const validator = createGenericPermissionValidator([ | ||
{ type: SnapCaveatType.ChainIds }, | ||
{ type: SnapCaveatType.RpcOrigin, optional: true }, | ||
]); | ||
|
||
expect(() => | ||
validator({ | ||
...MOCK_PERMISSION, | ||
caveats: [{ type: SnapCaveatType.ChainIds, value: null }], | ||
}), | ||
).not.toThrow(); | ||
}); | ||
|
||
it('does not fail if optional caveats is missing', () => { | ||
const validator = createGenericPermissionValidator([ | ||
{ type: SnapCaveatType.ChainIds, optional: true }, | ||
{ type: SnapCaveatType.RpcOrigin, optional: true }, | ||
]); | ||
|
||
expect(() => | ||
validator({ | ||
...MOCK_PERMISSION, | ||
caveats: [{ type: SnapCaveatType.ChainIds, value: null }], | ||
}), | ||
).not.toThrow(); | ||
|
||
expect(() => validator(MOCK_PERMISSION)).not.toThrow(); | ||
}); | ||
}); |
60 changes: 60 additions & 0 deletions
60
packages/snaps-controllers/src/snaps/endowments/caveats/generic.ts
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,60 @@ | ||
import type { PermissionValidatorConstraint } from '@metamask/permission-controller'; | ||
import { rpcErrors } from '@metamask/rpc-errors'; | ||
|
||
/** | ||
* Create a generic permission validator that validates the presence of certain caveats. | ||
* | ||
* This validator only validates the types of the caveats, not the values. | ||
* | ||
* @param caveatsToValidate - A list of objects that represent caveats. | ||
* @param caveatsToValidate.type - The string defining the caveat type. | ||
* @param caveatsToValidate.optional - An optional boolean flag that defines | ||
* whether the caveat is optional or not. | ||
* @returns A function that validates a permission. | ||
*/ | ||
export function createGenericPermissionValidator( | ||
caveatsToValidate: { | ||
type: string; | ||
optional?: boolean; | ||
}[], | ||
): PermissionValidatorConstraint { | ||
const validCaveatTypes = new Set( | ||
caveatsToValidate.map((caveat) => caveat.type), | ||
); | ||
const requiredCaveats = caveatsToValidate.filter( | ||
(caveat) => !caveat.optional, | ||
); | ||
|
||
return function ({ caveats }) { | ||
const actualCaveats = caveats ?? []; | ||
const passedCaveatTypes = actualCaveats.map((caveat) => caveat.type); | ||
const passedCaveatsSet = new Set(passedCaveatTypes); | ||
|
||
// Disallow duplicates | ||
if (passedCaveatsSet.size !== passedCaveatTypes.length) { | ||
throw rpcErrors.invalidParams({ | ||
message: 'Duplicate caveats are not allowed.', | ||
}); | ||
} | ||
|
||
// Disallow caveats that don't match expected types | ||
if (!actualCaveats.every((caveat) => validCaveatTypes.has(caveat.type))) { | ||
throw rpcErrors.invalidParams({ | ||
message: `Expected the following caveats: ${caveatsToValidate | ||
.map((caveat) => `"${caveat.type}"`) | ||
.join(', ')}, received ${actualCaveats | ||
.map((caveat) => `"${caveat.type}"`) | ||
.join(', ')}.`, | ||
}); | ||
} | ||
|
||
// Fail if not all required caveats are specified | ||
if (!requiredCaveats.every((caveat) => passedCaveatsSet.has(caveat.type))) { | ||
throw rpcErrors.invalidParams({ | ||
message: `Expected the following caveats: ${requiredCaveats | ||
.map((caveat) => `"${caveat.type}"`) | ||
.join(', ')}.`, | ||
}); | ||
} | ||
}; | ||
} |
1 change: 1 addition & 0 deletions
1
packages/snaps-controllers/src/snaps/endowments/caveats/index.ts
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 @@ | ||
export * from './generic'; |