@metamask/permission-controller 3.2.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -24,17 +24,19 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
24
24
  };
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
26
  exports.PermissionController = exports.CaveatMutatorOperation = void 0;
27
+ /* eslint-enable @typescript-eslint/no-unused-vars */
27
28
  const deep_freeze_strict_1 = __importDefault(require("deep-freeze-strict"));
28
29
  const immer_1 = require("immer");
29
30
  const nanoid_1 = require("nanoid");
30
31
  const eth_rpc_errors_1 = require("eth-rpc-errors");
32
+ const utils_1 = require("@metamask/utils");
31
33
  const base_controller_1 = require("@metamask/base-controller");
32
34
  const controller_utils_1 = require("@metamask/controller-utils");
33
35
  const Caveat_1 = require("./Caveat");
34
36
  const errors_1 = require("./errors");
35
37
  const Permission_1 = require("./Permission");
36
38
  const permission_middleware_1 = require("./permission-middleware");
37
- const utils_1 = require("./utils");
39
+ const utils_2 = require("./utils");
38
40
  /**
39
41
  * The name of the {@link PermissionController}.
40
42
  */
@@ -128,11 +130,11 @@ class PermissionController extends base_controller_1.BaseControllerV2 {
128
130
  /**
129
131
  * Gets a permission specification.
130
132
  *
131
- * @param targetKey - The target key of the permission specification to get.
132
- * @returns The permission specification with the specified target key.
133
+ * @param targetName - The name of the permission specification to get.
134
+ * @returns The permission specification with the specified target name.
133
135
  */
134
- getPermissionSpecification(targetKey) {
135
- return this._permissionSpecifications[targetKey];
136
+ getPermissionSpecification(targetName) {
137
+ return this._permissionSpecifications[targetName];
136
138
  }
137
139
  /**
138
140
  * Gets a caveat specification.
@@ -144,9 +146,7 @@ class PermissionController extends base_controller_1.BaseControllerV2 {
144
146
  return this._caveatSpecifications[caveatType];
145
147
  }
146
148
  /**
147
- * Constructor helper for validating permission specifications. This is
148
- * intended to prevent the use of invalid target keys which, while impossible
149
- * to add in TypeScript, could rather easily occur in plain JavaScript.
149
+ * Constructor helper for validating permission specifications.
150
150
  *
151
151
  * Throws an error if validation fails.
152
152
  *
@@ -156,21 +156,19 @@ class PermissionController extends base_controller_1.BaseControllerV2 {
156
156
  * controller.
157
157
  */
158
158
  validatePermissionSpecifications(permissionSpecifications, caveatSpecifications) {
159
- Object.entries(permissionSpecifications).forEach(([targetKey, { permissionType, targetKey: innerTargetKey, allowedCaveats },]) => {
160
- if (!permissionType || !(0, controller_utils_1.hasProperty)(Permission_1.PermissionType, permissionType)) {
159
+ Object.entries(permissionSpecifications).forEach(([targetName, { permissionType, targetName: innerTargetName, allowedCaveats },]) => {
160
+ if (!permissionType || !(0, utils_1.hasProperty)(Permission_1.PermissionType, permissionType)) {
161
161
  throw new Error(`Invalid permission type: "${permissionType}"`);
162
162
  }
163
- // Check if the target key is the empty string, ends with "_", or ends
164
- // with "*" but not "_*"
165
- if (!targetKey || /_$/u.test(targetKey) || /[^_]\*$/u.test(targetKey)) {
166
- throw new Error(`Invalid permission target key: "${targetKey}"`);
163
+ if (!targetName) {
164
+ throw new Error(`Invalid permission target name: "${targetName}"`);
167
165
  }
168
- if (targetKey !== innerTargetKey) {
169
- throw new Error(`Invalid permission specification: key "${targetKey}" must match specification.target value "${innerTargetKey}".`);
166
+ if (targetName !== innerTargetName) {
167
+ throw new Error(`Invalid permission specification: target name "${targetName}" must match specification.targetName value "${innerTargetName}".`);
170
168
  }
171
169
  if (allowedCaveats) {
172
170
  allowedCaveats.forEach((caveatType) => {
173
- if (!(0, controller_utils_1.hasProperty)(caveatSpecifications, caveatType)) {
171
+ if (!(0, utils_1.hasProperty)(caveatSpecifications, caveatType)) {
174
172
  throw new errors_1.UnrecognizedCaveatTypeError(caveatType);
175
173
  }
176
174
  const specification = caveatSpecifications[caveatType];
@@ -231,11 +229,10 @@ class PermissionController extends base_controller_1.BaseControllerV2 {
231
229
  const failureError = permissionType === Permission_1.PermissionType.RestrictedMethod
232
230
  ? (0, errors_1.methodNotFound)(targetName, requestingOrigin ? { origin: requestingOrigin } : undefined)
233
231
  : new errors_1.EndowmentPermissionDoesNotExistError(targetName, requestingOrigin);
234
- const targetKey = this.getTargetKey(targetName);
235
- if (!targetKey) {
232
+ if (!this.targetExists(targetName)) {
236
233
  throw failureError;
237
234
  }
238
- const specification = this.getPermissionSpecification(targetKey);
235
+ const specification = this.getPermissionSpecification(targetName);
239
236
  if (!(0, Permission_1.hasSpecificationType)(specification, permissionType)) {
240
237
  throw failureError;
241
238
  }
@@ -346,12 +343,12 @@ class PermissionController extends base_controller_1.BaseControllerV2 {
346
343
  revokePermissions(subjectsAndPermissions) {
347
344
  this.update((draftState) => {
348
345
  Object.keys(subjectsAndPermissions).forEach((origin) => {
349
- if (!(0, controller_utils_1.hasProperty)(draftState.subjects, origin)) {
346
+ if (!(0, utils_1.hasProperty)(draftState.subjects, origin)) {
350
347
  throw new errors_1.UnrecognizedSubjectError(origin);
351
348
  }
352
349
  subjectsAndPermissions[origin].forEach((target) => {
353
350
  const { permissions } = draftState.subjects[origin];
354
- if (!(0, controller_utils_1.hasProperty)(permissions, target)) {
351
+ if (!(0, utils_1.hasProperty)(permissions, target)) {
355
352
  throw new errors_1.PermissionDoesNotExistError(origin, target);
356
353
  }
357
354
  this.deletePermission(draftState.subjects, origin, target);
@@ -372,7 +369,7 @@ class PermissionController extends base_controller_1.BaseControllerV2 {
372
369
  this.update((draftState) => {
373
370
  Object.entries(draftState.subjects).forEach(([origin, subject]) => {
374
371
  const { permissions } = subject;
375
- if ((0, controller_utils_1.hasProperty)(permissions, target)) {
372
+ if ((0, utils_1.hasProperty)(permissions, target)) {
376
373
  this.deletePermission(draftState.subjects, origin, target);
377
374
  }
378
375
  });
@@ -539,7 +536,7 @@ class PermissionController extends base_controller_1.BaseControllerV2 {
539
536
  // If all else fails, the permission validator is also called.
540
537
  permission.caveats = [caveat];
541
538
  }
542
- this.validateModifiedPermission(permission, origin, target);
539
+ this.validateModifiedPermission(permission, origin);
543
540
  });
544
541
  }
545
542
  /**
@@ -594,7 +591,7 @@ class PermissionController extends base_controller_1.BaseControllerV2 {
594
591
  this.validateCaveat(targetCaveat, subject.origin, permission.parentCapability);
595
592
  break;
596
593
  case CaveatMutatorOperation.deleteCaveat:
597
- this.deleteCaveat(permission, targetCaveatType, subject.origin, permission.parentCapability);
594
+ this.deleteCaveat(permission, targetCaveatType, subject.origin);
598
595
  break;
599
596
  case CaveatMutatorOperation.revokePermission:
600
597
  this.deletePermission(draftState.subjects, subject.origin, permission.parentCapability);
@@ -633,7 +630,7 @@ class PermissionController extends base_controller_1.BaseControllerV2 {
633
630
  if (!permission.caveats) {
634
631
  throw new errors_1.CaveatDoesNotExistError(origin, target, caveatType);
635
632
  }
636
- this.deleteCaveat(permission, caveatType, origin, target);
633
+ this.deleteCaveat(permission, caveatType, origin);
637
634
  });
638
635
  }
639
636
  /**
@@ -647,16 +644,15 @@ class PermissionController extends base_controller_1.BaseControllerV2 {
647
644
  * @param permission - The permission whose caveat to delete.
648
645
  * @param caveatType - The type of the caveat to delete.
649
646
  * @param origin - The origin the permission subject.
650
- * @param target - The name of the permission target.
651
647
  */
652
- deleteCaveat(permission, caveatType, origin, target) {
648
+ deleteCaveat(permission, caveatType, origin) {
653
649
  /* istanbul ignore if: not possible in our usage */
654
650
  if (!permission.caveats) {
655
- throw new errors_1.CaveatDoesNotExistError(origin, target, caveatType);
651
+ throw new errors_1.CaveatDoesNotExistError(origin, permission.parentCapability, caveatType);
656
652
  }
657
653
  const caveatIndex = permission.caveats.findIndex((existingCaveat) => existingCaveat.type === caveatType);
658
654
  if (caveatIndex === -1) {
659
- throw new errors_1.CaveatDoesNotExistError(origin, target, caveatType);
655
+ throw new errors_1.CaveatDoesNotExistError(origin, permission.parentCapability, caveatType);
660
656
  }
661
657
  if (permission.caveats.length === 1) {
662
658
  permission.caveats = null;
@@ -664,62 +660,35 @@ class PermissionController extends base_controller_1.BaseControllerV2 {
664
660
  else {
665
661
  permission.caveats.splice(caveatIndex, 1);
666
662
  }
667
- this.validateModifiedPermission(permission, origin, target);
663
+ this.validateModifiedPermission(permission, origin);
668
664
  }
669
665
  /**
670
666
  * Validates the specified modified permission. Should **always** be invoked
671
667
  * on a permission after its caveats have been modified.
672
668
  *
673
669
  * Just like {@link PermissionController.validatePermission}, except that the
674
- * corresponding target key and specification are retrieved first, and an
675
- * error is thrown if the target key does not exist.
670
+ * corresponding target name and specification are retrieved first, and an
671
+ * error is thrown if the target name does not exist.
676
672
  *
677
673
  * @param permission - The modified permission to validate.
678
674
  * @param origin - The origin associated with the permission.
679
- * @param targetName - The target name name of the permission.
680
675
  */
681
- validateModifiedPermission(permission, origin, targetName) {
682
- const targetKey = this.getTargetKey(permission.parentCapability);
676
+ validateModifiedPermission(permission, origin) {
683
677
  /* istanbul ignore if: this should be impossible */
684
- if (!targetKey) {
685
- throw new Error(`Fatal: Existing permission target key "${targetKey}" has no specification.`);
678
+ if (!this.targetExists(permission.parentCapability)) {
679
+ throw new Error(`Fatal: Existing permission target "${permission.parentCapability}" has no specification.`);
686
680
  }
687
- this.validatePermission(this.getPermissionSpecification(targetKey), permission, origin, targetName);
681
+ this.validatePermission(this.getPermissionSpecification(permission.parentCapability), permission, origin);
688
682
  }
689
683
  /**
690
- * Gets the key for the specified permission target.
691
- *
692
- * Used to support our namespaced permission target feature, which is used
693
- * to implement namespaced restricted JSON-RPC methods.
684
+ * Verifies the existence the specified permission target, i.e. whether it has
685
+ * a specification.
694
686
  *
695
687
  * @param target - The requested permission target.
696
- * @returns The internal key of the permission target.
688
+ * @returns Whether the permission target exists.
697
689
  */
698
- getTargetKey(target) {
699
- if ((0, controller_utils_1.hasProperty)(this._permissionSpecifications, target)) {
700
- return target;
701
- }
702
- const namespacedTargetsWithoutWildcard = {};
703
- for (const targetKey of Object.keys(this._permissionSpecifications)) {
704
- const wildCardMatch = targetKey.match(/(.+)\*$/u);
705
- if (wildCardMatch) {
706
- namespacedTargetsWithoutWildcard[wildCardMatch[1]] = true;
707
- }
708
- }
709
- // Check for potentially nested namespaces:
710
- // Ex: wildzone_
711
- // Ex: eth_plugin_
712
- const segments = target.split('_');
713
- let targetKey = '';
714
- while (segments.length > 0 &&
715
- !(0, controller_utils_1.hasProperty)(this._permissionSpecifications, targetKey) &&
716
- !namespacedTargetsWithoutWildcard[targetKey]) {
717
- targetKey += `${segments.shift()}_`;
718
- }
719
- if (namespacedTargetsWithoutWildcard[targetKey]) {
720
- return `${targetKey}*`;
721
- }
722
- return undefined;
690
+ targetExists(target) {
691
+ return (0, utils_1.hasProperty)(this._permissionSpecifications, target);
723
692
  }
724
693
  /**
725
694
  * Grants _approved_ permissions to the specified subject. Every permission and
@@ -748,18 +717,17 @@ class PermissionController extends base_controller_1.BaseControllerV2 {
748
717
  const permissions = (preserveExistingPermissions
749
718
  ? Object.assign({}, this.getPermissions(origin)) : {});
750
719
  for (const [requestedTarget, approvedPermission] of Object.entries(approvedPermissions)) {
751
- const targetKey = this.getTargetKey(requestedTarget);
752
- if (!targetKey) {
720
+ if (!this.targetExists(requestedTarget)) {
753
721
  throw (0, errors_1.methodNotFound)(requestedTarget);
754
722
  }
755
723
  if (approvedPermission.parentCapability !== undefined &&
756
724
  requestedTarget !== approvedPermission.parentCapability) {
757
725
  throw new errors_1.InvalidApprovedPermissionError(origin, requestedTarget, approvedPermission);
758
726
  }
759
- // The requested target must be a valid target name if we found its key.
760
- // We reassign it to change its type.
727
+ // We have verified that the target exists, and reassign it to change its
728
+ // type.
761
729
  const targetName = requestedTarget;
762
- const specification = this.getPermissionSpecification(targetKey);
730
+ const specification = this.getPermissionSpecification(targetName);
763
731
  // The requested caveats are validated here.
764
732
  const caveats = this.constructCaveats(origin, targetName, approvedPermission.caveats);
765
733
  const permissionOptions = {
@@ -773,14 +741,14 @@ class PermissionController extends base_controller_1.BaseControllerV2 {
773
741
  // Full caveat and permission validation is performed here since the
774
742
  // factory function can arbitrarily modify the entire permission object,
775
743
  // including its caveats.
776
- this.validatePermission(specification, permission, origin, targetName);
744
+ this.validatePermission(specification, permission, origin);
777
745
  }
778
746
  else {
779
747
  permission = (0, Permission_1.constructPermission)(permissionOptions);
780
748
  // We do not need to validate caveats in this case, because the plain
781
749
  // permission constructor function does not modify the caveats, which
782
750
  // were already validated by `constructCaveats` above.
783
- this.validatePermission(specification, permission, origin, targetName, {
751
+ this.validatePermission(specification, permission, origin, {
784
752
  invokePermissionValidator: true,
785
753
  performCaveatValidation: false,
786
754
  });
@@ -804,7 +772,6 @@ class PermissionController extends base_controller_1.BaseControllerV2 {
804
772
  * @param specification - The specification of the permission.
805
773
  * @param permission - The permission to validate.
806
774
  * @param origin - The origin associated with the permission.
807
- * @param targetName - The target name of the permission.
808
775
  * @param validationOptions - Validation options.
809
776
  * @param validationOptions.invokePermissionValidator - Whether to invoke the
810
777
  * permission's consumer-specified validator function, if any.
@@ -812,12 +779,12 @@ class PermissionController extends base_controller_1.BaseControllerV2 {
812
779
  * {@link PermissionController.validateCaveat} on each of the permission's
813
780
  * caveats.
814
781
  */
815
- validatePermission(specification, permission, origin, targetName, { invokePermissionValidator, performCaveatValidation } = {
782
+ validatePermission(specification, permission, origin, { invokePermissionValidator, performCaveatValidation } = {
816
783
  invokePermissionValidator: true,
817
784
  performCaveatValidation: true,
818
785
  }) {
819
786
  var _a;
820
- const { allowedCaveats, validator } = specification;
787
+ const { allowedCaveats, validator, targetName } = specification;
821
788
  if (((_a = specification.subjectTypes) === null || _a === void 0 ? void 0 : _a.length) &&
822
789
  specification.subjectTypes.length > 0) {
823
790
  const metadata = this.messagingSystem.call('SubjectMetadataController:getSubjectMetadata', origin);
@@ -829,7 +796,7 @@ class PermissionController extends base_controller_1.BaseControllerV2 {
829
796
  : new errors_1.EndowmentPermissionDoesNotExistError(targetName, origin);
830
797
  }
831
798
  }
832
- if ((0, controller_utils_1.hasProperty)(permission, 'caveats')) {
799
+ if ((0, utils_1.hasProperty)(permission, 'caveats')) {
833
800
  const { caveats } = permission;
834
801
  if (caveats !== null && !(Array.isArray(caveats) && caveats.length > 0)) {
835
802
  throw new errors_1.InvalidCaveatsPropertyError(origin, targetName, caveats);
@@ -920,7 +887,7 @@ class PermissionController extends base_controller_1.BaseControllerV2 {
920
887
  if (!specification) {
921
888
  throw new errors_1.UnrecognizedCaveatTypeError(caveat.type, origin, target);
922
889
  }
923
- if (!(0, controller_utils_1.hasProperty)(caveat, 'value') || caveat.value === undefined) {
890
+ if (!(0, utils_1.hasProperty)(caveat, 'value') || caveat.value === undefined) {
924
891
  throw new errors_1.CaveatMissingValueError(caveat, origin, target);
925
892
  }
926
893
  if (!(0, controller_utils_1.isValidJson)(caveat.value)) {
@@ -1021,8 +988,7 @@ class PermissionController extends base_controller_1.BaseControllerV2 {
1021
988
  }
1022
989
  for (const targetName of Object.keys(requestedPermissions)) {
1023
990
  const permission = requestedPermissions[targetName];
1024
- const targetKey = this.getTargetKey(targetName);
1025
- if (!targetKey) {
991
+ if (!this.targetExists(targetName)) {
1026
992
  throw (0, errors_1.methodNotFound)(targetName, { origin, requestedPermissions });
1027
993
  }
1028
994
  if (!(0, controller_utils_1.isPlainObject)(permission) ||
@@ -1035,9 +1001,9 @@ class PermissionController extends base_controller_1.BaseControllerV2 {
1035
1001
  }
1036
1002
  // Here we validate the permission without invoking its validator, if any.
1037
1003
  // The validator will be invoked after the permission has been approved.
1038
- this.validatePermission(this.getPermissionSpecification(targetKey),
1004
+ this.validatePermission(this.getPermissionSpecification(targetName),
1039
1005
  // Typecast: The permission is still a "PlainObject" here.
1040
- permission, origin, targetName, { invokePermissionValidator: false, performCaveatValidation: true });
1006
+ permission, origin, { invokePermissionValidator: false, performCaveatValidation: true });
1041
1007
  }
1042
1008
  }
1043
1009
  /**
@@ -1055,7 +1021,7 @@ class PermissionController extends base_controller_1.BaseControllerV2 {
1055
1021
  id,
1056
1022
  origin,
1057
1023
  requestData: permissionsRequest,
1058
- type: utils_1.MethodNames.requestPermissions,
1024
+ type: utils_2.MethodNames.requestPermissions,
1059
1025
  }, true);
1060
1026
  this.validateApprovedPermissions(approvedRequest, { id, origin });
1061
1027
  return approvedRequest;
@@ -1069,9 +1035,8 @@ class PermissionController extends base_controller_1.BaseControllerV2 {
1069
1035
  */
1070
1036
  getSideEffects(permissions) {
1071
1037
  return Object.keys(permissions).reduce((sideEffectList, targetName) => {
1072
- const targetKey = this.getTargetKey(targetName);
1073
- if (targetKey) {
1074
- const specification = this.getPermissionSpecification(targetKey);
1038
+ if (this.targetExists(targetName)) {
1039
+ const specification = this.getPermissionSpecification(targetName);
1075
1040
  if (specification.sideEffect) {
1076
1041
  sideEffectList.permittedHandlers[targetName] =
1077
1042
  specification.sideEffect.onPermitted;