Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
147 commits
Select commit Hold shift + click to select a range
bc86edc
Begin work on PermissionsController refactor
rekmarks Jun 16, 2021
848f4c5
Update function generators, add caveat types test
rekmarks Jun 18, 2021
6ed5d49
Fixup caveat function gen. types and errors
rekmarks Jun 21, 2021
746f1bf
Use modifed ZCAP-LD interfaces for caveats and permissions
rekmarks Jun 27, 2021
d678853
Add errors, some basic functions
rekmarks Jul 1, 2021
ff38cce
Refactor caveats as middleware functions
rekmarks Jul 1, 2021
87d3a04
Implement state management primitives
rekmarks Jul 3, 2021
cb86f11
chore: lint, post-rebase fixup
rekmarks Jul 3, 2021
fa793c3
domain -> actor, update actions/events, register action handlers
rekmarks Jul 4, 2021
8a2f313
Add getCaveatValue static method
rekmarks Jul 13, 2021
724f8e1
Fixups: return types, comments, code clarity, etc.
rekmarks Jul 13, 2021
bd46ea5
Clean up events and actions
rekmarks Aug 5, 2021
dc19143
actors -> subjects
rekmarks Aug 5, 2021
718e8b7
Clean up caveat processing
rekmarks Aug 5, 2021
fc544c5
Add PermissionEnforcer
rekmarks Aug 5, 2021
3f7532a
Add consumer-specified caveat value validation
rekmarks Aug 6, 2021
741cb76
Add revokePermissionForAllSubjects
rekmarks Aug 6, 2021
8ea589f
Rename some stuff in the PermissionEnforcer
rekmarks Aug 9, 2021
e59568d
Post-rebase fixup
rekmarks Aug 17, 2021
7e1ea6d
DRY up some restricted method validation
rekmarks Aug 20, 2021
360fc34
Remove safelyExecuteRestrictedMethod
rekmarks Aug 20, 2021
f1b65bb
Re-implement caveat decoration per suggestion by @Gudahtt
rekmarks Aug 21, 2021
6c66ab6
Require that caveat functions are async
rekmarks Aug 21, 2021
4088421
Update caveat state management
rekmarks Aug 22, 2021
2024b51
Fixup / tweak
rekmarks Aug 22, 2021
747f120
Implement RPC methods, use types over interfaces
rekmarks Aug 23, 2021
3849fc1
Fix requestPermissions return value
rekmarks Aug 23, 2021
7e00e61
Add permission specifications and accept/reject hooks
rekmarks Aug 23, 2021
48289f7
Remove some cruft
rekmarks Aug 24, 2021
b517440
Add enums, export controller, refactor internal exports
rekmarks Aug 24, 2021
5fef679
Rename SubjectMetadata to PermissionsSubjectMetadata
rekmarks Aug 24, 2021
44ef0b6
fixup! Rename SubjectMetadata to PermissionsSubjectMetadata
rekmarks Aug 24, 2021
047e787
Assume that permission factories produce valid outputs
rekmarks Aug 24, 2021
2214b7c
Various tweaks and fixups
rekmarks Aug 31, 2021
19219b3
@metamask/controllers@15.0.0
rekmarks Aug 31, 2021
b262c88
Make PermissionEnforcer a PermissionController property
rekmarks Aug 31, 2021
14474a8
WIP: Tests
rekmarks Aug 31, 2021
4b4efbe
Remove temporary type declaration files
rekmarks Sep 1, 2021
a761273
Add permission system readme, rename some things
rekmarks Sep 1, 2021
db1fd30
Post-rebase fixup
rekmarks Sep 11, 2021
fc99e7d
Stop using 'in' operator
rekmarks Sep 21, 2021
8a07f64
WIP: Improved type safety 1 (unstable)
rekmarks Sep 22, 2021
fd98a4a
WIP: Improved type safety 2 (builds)
rekmarks Sep 22, 2021
d8cbb09
WIP: Improved type safety 3 (mostly cleanup left)
rekmarks Sep 23, 2021
27a7a36
Post-rebase fixup
rekmarks Sep 24, 2021
95e4580
WIP: Improved type safety 4 (fix permission specifications)
rekmarks Sep 24, 2021
5ab713f
Complete: Improved type safety
rekmarks Sep 25, 2021
4c15b7a
Unit tests: PermissionController.grantPermissions
rekmarks Sep 26, 2021
c912a4a
Fix caveat function type safety for namespaced permissions
rekmarks Sep 26, 2021
7cfd26b
Some type fixup
rekmarks Sep 26, 2021
c51f6d9
Add HasPermissions action
rekmarks Sep 27, 2021
d3b1984
Fix some parameters, rename an export
rekmarks Sep 28, 2021
e1a6242
Add back getCaveat, refactor permission revocation
rekmarks Sep 29, 2021
2deff4f
Unit tests: PermissionController, continued
rekmarks Sep 30, 2021
6d2e92b
Update permission requestData handling
rekmarks Sep 30, 2021
8860625
Unit tests: PermissionController, revocation complete
rekmarks Oct 1, 2021
0d4f5b4
Unit tests: PermissionController, caveats complete
rekmarks Oct 1, 2021
9fd6d67
Unit tests: PermissionController, caveats complete
rekmarks Oct 1, 2021
f7b2798
Unit tests: PermissionController, caveat errors
rekmarks Oct 1, 2021
ad34223
Fix target key validation
rekmarks Oct 1, 2021
9aeb9f2
Unit tests: PermissionController, actions
rekmarks Oct 1, 2021
fdda927
Remove some unused errors
rekmarks Oct 1, 2021
a22697e
fixup! Remove some unused errors
rekmarks Oct 1, 2021
a4fa33d
Add test helper docstrings, more test cases
rekmarks Oct 2, 2021
8326953
Add additional runtime validation to grantPermissions
rekmarks Oct 4, 2021
74e2fb7
Consolidate enforcement in PermissionController
rekmarks Oct 5, 2021
a8acab4
Fix restricted method params type
rekmarks Oct 5, 2021
0e216de
Unit tests: PermissionController.requestPermissions
rekmarks Oct 5, 2021
4a7f73f
Document requestPermissions and related methods
rekmarks Oct 5, 2021
04a8352
Unit tests: accept and rejectPermissionsRequest
rekmarks Oct 6, 2021
b9fbe47
Unit tests: begin executeRestrictedMethod
rekmarks Oct 6, 2021
4b74e21
WIP: Restricted method types
rekmarks Oct 7, 2021
3acb6f3
WIP: Restricted method types II
rekmarks Oct 7, 2021
531d5ad
WIP: Messy
rekmarks Oct 7, 2021
6e151ae
WIP: Refactor controller types (Builds)
rekmarks Oct 8, 2021
f5cd9d0
WIP: Refactor controller types II (Builds)
rekmarks Oct 9, 2021
eeb6812
WIP: Refactor controller types III (caveat specifications generic)
rekmarks Oct 9, 2021
fa61f4f
WIP: Refactor controller types IV (cleanup)
rekmarks Oct 9, 2021
cf0f547
WIP: Refactor controller types V (more cleanup)
rekmarks Oct 9, 2021
8383525
Refactor controller types 😭🙏
rekmarks Oct 9, 2021
d27dcee
Restore caveat method type safety
rekmarks Oct 9, 2021
b56b354
fixup! Restore caveat method type safety
rekmarks Oct 9, 2021
4078aee
Status quo ante type refactor
rekmarks Oct 10, 2021
99c8454
Unit tests: PermissionController, complete
rekmarks Oct 10, 2021
4df1d36
Unit tests: Complete Caveat, Permission, and errors files
rekmarks Oct 10, 2021
8977b19
Some comment / type fixup
rekmarks Oct 10, 2021
9b15377
Add PermissionController.updateAllCaveatsOfType
rekmarks Oct 10, 2021
8ef3113
Fix permission specification getting
rekmarks Oct 10, 2021
4c410be
Unit tests: permission middleware
rekmarks Oct 10, 2021
d80c31c
Unit tests: fin.
rekmarks Oct 11, 2021
f171a35
Update README
rekmarks Oct 11, 2021
0fed096
Minor test wording fixup
rekmarks Oct 13, 2021
c492759
Update packages/controllers/src/permissions/README.md
rekmarks Oct 13, 2021
33105e1
Update packages/controllers/src/permissions/README.md
rekmarks Oct 13, 2021
d724b56
Update packages/controllers/src/permissions/README.md
rekmarks Oct 13, 2021
0556f82
Simplify types
rekmarks Oct 17, 2021
6fed1c8
fixup! Simplify types
rekmarks Oct 18, 2021
ea899da
More documentation and type fixup
rekmarks Oct 18, 2021
4a1dd83
Fix parameter names of ExtractArrayMembers
rekmarks Oct 18, 2021
1d90813
Fix the ExtractPermission docstring, take II
rekmarks Oct 18, 2021
8fa50a5
Fix readme
rekmarks Oct 18, 2021
b1105f9
Replace GenericFunction with something more specific
rekmarks Oct 26, 2021
c3cbe8d
Remove GenericPermission, use PermissionConstraint
rekmarks Oct 26, 2021
fa86d30
Fixup
rekmarks Oct 26, 2021
b14367f
More type fixup
rekmarks Oct 26, 2021
8603b41
Rename more types from Validate* to Valid*
rekmarks Oct 26, 2021
9204cfc
Get rid of PermissionValidator generic
rekmarks Oct 26, 2021
3d3cdf2
Improve caveat specification type safety
rekmarks Oct 26, 2021
20298a6
Remove leftover comment
rekmarks Oct 26, 2021
3629734
Replace state metadata and default state consts with getters
rekmarks Oct 26, 2021
fca8ab5
Simplify PermissionControllerOptions
rekmarks Oct 26, 2021
a06545e
Improve PermissionControllerSubjects
rekmarks Oct 26, 2021
d42c119
Update packages/controllers/src/permissions/PermissionController.ts
rekmarks Oct 28, 2021
b0b7997
Address review feedback
rekmarks Oct 28, 2021
f2c8e49
Simplify permission-middleware types, add docs
rekmarks Oct 29, 2021
799f9fa
Verify that caveat values are JSON-compatible
rekmarks Oct 29, 2021
226ecaa
Apply suggestions from code review
rekmarks Oct 31, 2021
2aed963
fixup! Apply suggestions from code review
rekmarks Oct 31, 2021
37b75f9
Remove leftover typecast comments
rekmarks Oct 31, 2021
4d24c94
Rename Permission generic type to ValidPermission
rekmarks Oct 31, 2021
5ce5f80
Add NonEmptyArray type guard
rekmarks Nov 2, 2021
9307b5a
Apply suggestions from code review
rekmarks Nov 2, 2021
b8fbba7
fixup! Apply suggestions from code review
rekmarks Nov 2, 2021
11e318e
Improve some validation, address review feedback
rekmarks Nov 3, 2021
606ed4f
Apply suggestions from code review
rekmarks Nov 4, 2021
e1e50dd
Clean up some target name and key parameter types
rekmarks Nov 4, 2021
fb667ff
Rationalize getRestrictedMethod implementations
rekmarks Nov 4, 2021
47ff014
Fix a comment
rekmarks Nov 4, 2021
de43571
fixup! Fix a comment
rekmarks Nov 4, 2021
91cf3df
Address review feedback
rekmarks Nov 4, 2021
0655728
Apply suggestions from code review
rekmarks Nov 9, 2021
2bb2c55
fixup! Apply suggestions from code review
rekmarks Nov 10, 2021
c2fd150
Remove constructCaveat
rekmarks Nov 10, 2021
36efcf7
Improve requested permission / caveat validation
rekmarks Nov 10, 2021
28a6365
Make wallet_requestPermissions more readable
rekmarks Nov 10, 2021
04bcbc4
Improve variable name in wallet_getPermissions implementation
rekmarks Nov 10, 2021
72524c3
Improve KeyOfTargetName docstring
rekmarks Nov 10, 2021
ff0b6eb
Simplify ExtractArrayMembers
rekmarks Nov 10, 2021
bc7fdc3
Make ExtractArrayMembers resolve to never for the empty tuple
rekmarks Nov 10, 2021
9299cae
Simplify ExtractAllowedCaveatTypes
rekmarks Nov 10, 2021
2f67293
Remove unnecessary constructPermission cast
rekmarks Nov 10, 2021
1ccbaf7
Rename GenericTargetName to TargetName
rekmarks Nov 10, 2021
56ca078
Encapsulate ValidTargetName inside ExtractPermissionTargetNames
rekmarks Nov 10, 2021
14ec184
Simply CaveatSpecificationMap type
rekmarks Nov 10, 2021
3014a63
Add a TODO comment
rekmarks Nov 10, 2021
118bbc9
Remove unused default branches
rekmarks Nov 10, 2021
b4fae68
Change requestPermission origin parameter to subject metadata
rekmarks Nov 11, 2021
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
25 changes: 21 additions & 4 deletions packages/controllers/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,26 @@ module.exports = {
// statements: 100,
// },
// },
preset: 'ts-jest',
runner: '@jest-runner/electron',
testEnvironment: '@jest-runner/electron/environment',
testRegex: ['\\.test\\.ts$'],
silent: true,
testTimeout: 5000,
projects: [
{
displayName: 'runner: electron',
testMatch: [
'<rootDir>/src/plugins/**/*.test.ts',
'<rootDir>/src/services/**/*.test.ts',
],
preset: 'ts-jest',
runner: '@jest-runner/electron',
testEnvironment: '@jest-runner/electron/environment',
},
{
displayName: 'runner: default',
testPathIgnorePatterns: [
'<rootDir>/src/plugins/*',
'<rootDir>/src/services/*',
],
preset: 'ts-jest',
},
],
};
3 changes: 3 additions & 0 deletions packages/controllers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@
"@metamask/post-message-stream": "4.0.0",
"@metamask/safe-event-emitter": "^2.0.0",
"@metamask/snap-workers": "^0.2.0",
"@types/deep-freeze-strict": "^1.1.0",
"deep-freeze-strict": "^1.1.1",
"eth-rpc-errors": "^4.0.2",
"fast-deep-equal": "^3.1.3",
"immer": "^9.0.6",
"json-rpc-engine": "^6.1.0",
"json-rpc-middleware-stream": "^3.0.0",
Expand Down
8 changes: 4 additions & 4 deletions packages/controllers/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export * from './services/WebWorkerExecutionEnvironmentService';
export * from './plugins/PluginController';
export * from './resource/ExternalResourceController';
export * from './services/ExecutionEnvironmentService';
export * from './permissions';
export * from './plugins';
export * from './resource';
export * from './services';
60 changes: 60 additions & 0 deletions packages/controllers/src/permissions/Caveat.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import * as errors from './errors';
import {
CaveatSpecificationConstraint,
CaveatSpecificationMap,
decorateWithCaveats,
PermissionConstraint,
} from '.';

// This function is mostly tested through the PermissionController tests,
// we just add a couple here for completeness.
describe('decorateWithCaveats', () => {
it('decorates a method with caveat', async () => {
const methodImplementation = () => [1, 2, 3];

const caveatSpecifications = {
reverse: {
decorator: (method: any, _caveat: any) => async () => {
return (await method()).reverse();
},
},
} as unknown as CaveatSpecificationMap<CaveatSpecificationConstraint>;

const permission = {
caveats: [{ type: 'reverse', value: null }],
} as unknown as PermissionConstraint;

const decorated = decorateWithCaveats(
methodImplementation,
permission,
caveatSpecifications,
);

expect(methodImplementation()).toStrictEqual([1, 2, 3]);
expect(await decorated({} as any)).toStrictEqual([3, 2, 1]);
});

it('throws an error if the caveat type is unrecognized', () => {
const methodImplementation = () => [1, 2, 3];

const caveatSpecifications = {
reverse: {
decorator: (method: any, _caveat: any) => () =>
method().reverse() as any,
},
} as unknown as CaveatSpecificationMap<CaveatSpecificationConstraint>;

const permission = {
// This type doesn't exist
caveats: [{ type: 'kaplar', value: null }],
} as unknown as PermissionConstraint;

expect(() =>
decorateWithCaveats(
methodImplementation,
permission,
caveatSpecifications,
)({} as any),
).toThrow(new errors.UnrecognizedCaveatTypeError('kaplar'));
});
});
227 changes: 227 additions & 0 deletions packages/controllers/src/permissions/Caveat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
import { Json } from 'json-rpc-engine';

import { UnrecognizedCaveatTypeError } from './errors';
import {
AsyncRestrictedMethod,
RestrictedMethod,
PermissionConstraint,
RestrictedMethodParameters,
} from './Permission';

export type CaveatConstraint = {
/**
* The type of the caveat. The type is presumed to be meaningful in the
* context of the capability it is associated with.
*
* In MetaMask, every permission can only have one caveat of each type.
*/
readonly type: string;

/**
* Any additional data necessary to enforce the caveat.
*
* TODO:TS4.4 Make optional
*/
readonly value: Json;
};

/**
* TODO:docs
*
* @template Type - The type of the caveat.
* @template Value - The value associated with the caveat.
*/
export type Caveat<Type extends string, Value extends Json> = {
/**
* The type of the caveat. The type is presumed to be meaningful in the
* context of the capability it is associated with.
*
* In MetaMask, every permission can only have one caveat of each type.
*/
readonly type: Type;

/**
* Any additional data necessary to enforce the caveat.
*
* TODO:TS4.4 Make optional
*/
readonly value: Value;
};

// Next, we define types used for specifying caveats at the consumer layer,
// and a function for applying caveats to a restricted method request. This is
// Accomplished by decorating the restricted method implementation with the
// the corresponding caveat functions.

/**
* A function for applying caveats to a restricted method request.
*
* @template ParentCaveat - The caveat type associated with this decorator.
* @param decorated - The restricted method implementation to be decorated.
* The method may have already been decorated with other caveats.
* @param caveat - The caveat object.
* @returns The decorate restricted method implementation.
*/
export type CaveatDecorator<ParentCaveat extends CaveatConstraint> = (
decorated: AsyncRestrictedMethod<RestrictedMethodParameters, Json>,
caveat: ParentCaveat,
) => AsyncRestrictedMethod<RestrictedMethodParameters, Json>;

/**
* TODO:docs
*/
type ExtractCaveatValueFromDecorator<Decorator extends CaveatDecorator<any>> =
Decorator extends (
decorated: any,
caveat: infer ParentCaveat,
) => AsyncRestrictedMethod<any, any>
? ParentCaveat extends CaveatConstraint
? ParentCaveat['value']
: never
: never;

/**
* TODO:docs
*/
export type CaveatValidator<ParentCaveat extends CaveatConstraint> = (
caveat: { type: ParentCaveat['type']; value: unknown },
origin?: string,
target?: string,
) => void;

/**
* TODO:docs
*/
export type CaveatSpecificationConstraint = {
/**
* The string type of the caveat.
*/
type: string;

/**
* The decorator function used to apply the caveat to restricted method
* requests.
*/
decorator: CaveatDecorator<any>;
Comment thread
Gudahtt marked this conversation as resolved.

/**
* The validator function used to validate caveats of the associated type
* whenever they are instantiated. Caveat are instantiated whenever they are
* created or mutated.
*
* The validator should throw an appropriate JSON-RPC error if validation fails.
*
* If no validator is specified, no validation of caveat values will be
* performed. Although caveats can also be validated by permission validators,
* validating caveat values separately is strongly recommended.
*/
validator?: CaveatValidator<any>;
};

/**
* TODO:docs
*/
export type CaveatSpecification<SpecifiedCaveat extends CaveatConstraint> = {
Comment thread
Gudahtt marked this conversation as resolved.
/**
* The string type of the caveat.
*/
type: SpecifiedCaveat['type'];

/**
* The decorator function used to apply the caveat to restricted method
* requests.
*/
decorator: CaveatDecorator<SpecifiedCaveat>;

/**
* The validator function used to validate caveats of the associated type
* whenever they are instantiated. Caveat are instantiated whenever they are
* created or mutated.
*
* The validator should throw an appropriate JSON-RPC error if validation fails.
*
* If no validator is specified, no validation of caveat values will be
* performed. Although caveats can also be validated by permission validators,
* validating caveat values separately is strongly recommended.
*/
validator?: CaveatValidator<SpecifiedCaveat>;
};

/**
* TODO:docs
*/
export type CaveatSpecificationMap<
Specification extends CaveatSpecificationConstraint,
> = Record<Specification['type'], Specification>;

// TODO: Figure out why we have to extend "any" in this type.
/**
* TODO:docs
*/
export type ExtractCaveats<
Specification extends CaveatSpecification<CaveatConstraint>,
> = Specification extends any
Comment thread
rekmarks marked this conversation as resolved.
? Caveat<
Specification['type'],
ExtractCaveatValueFromDecorator<Specification['decorator']>
>
: never;

/**
* Extracts the type of a specific {@link Caveat} from a union of caveat
* specifications.
*
* @template CaveatSpecifications - The union of all caveat specifications.
* @template CaveatType - The type of the caveat to extract.
*/
export type ExtractCaveat<
CaveatSpecifications extends CaveatSpecification<CaveatConstraint>,
CaveatType extends string,
> = Extract<ExtractCaveats<CaveatSpecifications>, { type: CaveatType }>;

/**
* Extracts the value type of a specific {@link Caveat} from a union of caveat
* specifications.
*
* @template CaveatSpecifications - The union of all caveat specifications.
* @template CaveatType - The type of the caveat whose value to extract.
*/
export type ExtractCaveatValue<
CaveatSpecifications extends CaveatSpecification<CaveatConstraint>,
CaveatType extends string,
> = ExtractCaveat<CaveatSpecifications, CaveatType>['value'];

/**
* Decorate a restricted method implementation with its caveats.
*
* Note that all caveat functions (i.e. the argument and return value of the
* decorator) must be awaited.
*/
export function decorateWithCaveats<
CaveatSpecifications extends CaveatSpecification<CaveatConstraint>,
>(
methodImplementation: RestrictedMethod<RestrictedMethodParameters, Json>,
permission: Readonly<PermissionConstraint>, // bound to the requesting origin
caveatSpecifications: CaveatSpecificationMap<CaveatSpecifications>, // all caveat implementations
): RestrictedMethod<RestrictedMethodParameters, Json> {
const { caveats } = permission;
if (!caveats) {
return methodImplementation;
}

let decorated = async (
args: Parameters<RestrictedMethod<RestrictedMethodParameters, Json>>[0],
) => methodImplementation(args);

for (const caveat of caveats) {
const specification =
caveatSpecifications[caveat.type as CaveatSpecifications['type']];
if (!specification) {
throw new UnrecognizedCaveatTypeError(caveat.type);
}

decorated = specification.decorator(decorated, caveat);
}

return decorated;
}
45 changes: 45 additions & 0 deletions packages/controllers/src/permissions/Permission.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { constructPermission, CaveatConstraint } from '.';

describe('constructPermission', () => {
it('constructs a permission', () => {
const invoker = 'foo.io';
const target = 'wallet_bar';

expect(
constructPermission({
invoker,
target,
}),
).toMatchObject(
expect.objectContaining({
id: expect.any(String),
parentCapability: target,
invoker,
caveats: null,
date: expect.any(Number),
}),
);
});

it('constructs a permission with caveats', () => {
const invoker = 'foo.io';
const target = 'wallet_bar';
const caveats = [{ type: 'foo', value: 'bar' }] as [CaveatConstraint];

expect(
constructPermission({
invoker,
target,
caveats,
}),
).toMatchObject(
expect.objectContaining({
id: expect.any(String),
parentCapability: target,
invoker,
caveats: [...caveats],
date: expect.any(Number),
}),
);
});
});
Loading