@metamask/permission-controller 11.0.1 → 11.0.2

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.
Files changed (185) hide show
  1. package/CHANGELOG.md +20 -1
  2. package/dist/Caveat.cjs +48 -0
  3. package/dist/Caveat.cjs.map +1 -0
  4. package/dist/{types/Caveat.d.ts → Caveat.d.cts} +3 -3
  5. package/dist/Caveat.d.cts.map +1 -0
  6. package/dist/Caveat.d.mts +210 -0
  7. package/dist/Caveat.d.mts.map +1 -0
  8. package/dist/Caveat.mjs +42 -11
  9. package/dist/Caveat.mjs.map +1 -1
  10. package/dist/Permission.cjs +65 -0
  11. package/dist/Permission.cjs.map +1 -0
  12. package/dist/{types/Permission.d.ts → Permission.d.cts} +7 -7
  13. package/dist/Permission.d.cts.map +1 -0
  14. package/dist/Permission.d.mts +417 -0
  15. package/dist/Permission.d.mts.map +1 -0
  16. package/dist/Permission.mjs +58 -13
  17. package/dist/Permission.mjs.map +1 -1
  18. package/dist/PermissionController.cjs +1481 -0
  19. package/dist/{chunk-COGJLF22.mjs.map → PermissionController.cjs.map} +1 -1
  20. package/dist/{types/PermissionController.d.ts → PermissionController.d.cts} +10 -10
  21. package/dist/PermissionController.d.cts.map +1 -0
  22. package/dist/PermissionController.d.mts +986 -0
  23. package/dist/PermissionController.d.mts.map +1 -0
  24. package/dist/PermissionController.mjs +1479 -13
  25. package/dist/PermissionController.mjs.map +1 -1
  26. package/dist/SubjectMetadataController.cjs +152 -0
  27. package/dist/SubjectMetadataController.cjs.map +1 -0
  28. package/dist/{types/SubjectMetadataController.d.ts → SubjectMetadataController.d.cts} +5 -5
  29. package/dist/SubjectMetadataController.d.cts.map +1 -0
  30. package/dist/SubjectMetadataController.d.mts +107 -0
  31. package/dist/SubjectMetadataController.d.mts.map +1 -0
  32. package/dist/SubjectMetadataController.mjs +146 -8
  33. package/dist/SubjectMetadataController.mjs.map +1 -1
  34. package/dist/errors.cjs +217 -0
  35. package/dist/errors.cjs.map +1 -0
  36. package/dist/{types/errors.d.ts → errors.d.cts} +6 -6
  37. package/dist/errors.d.cts.map +1 -0
  38. package/dist/errors.d.mts +185 -0
  39. package/dist/errors.d.mts.map +1 -0
  40. package/dist/errors.mjs +187 -57
  41. package/dist/errors.mjs.map +1 -1
  42. package/dist/index.cjs +38 -0
  43. package/dist/index.cjs.map +1 -0
  44. package/dist/index.d.cts +9 -0
  45. package/dist/index.d.cts.map +1 -0
  46. package/dist/index.d.mts +9 -0
  47. package/dist/index.d.mts.map +1 -0
  48. package/dist/index.mjs +7 -97
  49. package/dist/index.mjs.map +1 -1
  50. package/dist/permission-middleware.cjs +55 -0
  51. package/dist/permission-middleware.cjs.map +1 -0
  52. package/dist/{types/permission-middleware.d.ts → permission-middleware.d.cts} +4 -4
  53. package/dist/permission-middleware.d.cts.map +1 -0
  54. package/dist/permission-middleware.d.mts +33 -0
  55. package/dist/permission-middleware.d.mts.map +1 -0
  56. package/dist/permission-middleware.mjs +50 -8
  57. package/dist/permission-middleware.mjs.map +1 -1
  58. package/dist/rpc-methods/getPermissions.cjs +27 -0
  59. package/dist/rpc-methods/getPermissions.cjs.map +1 -0
  60. package/dist/rpc-methods/getPermissions.d.cts +9 -0
  61. package/dist/rpc-methods/getPermissions.d.cts.map +1 -0
  62. package/dist/rpc-methods/getPermissions.d.mts +9 -0
  63. package/dist/rpc-methods/getPermissions.d.mts.map +1 -0
  64. package/dist/rpc-methods/getPermissions.mjs +22 -7
  65. package/dist/rpc-methods/getPermissions.mjs.map +1 -1
  66. package/dist/rpc-methods/index.cjs +12 -0
  67. package/dist/rpc-methods/index.cjs.map +1 -0
  68. package/dist/rpc-methods/index.d.cts +6 -0
  69. package/dist/rpc-methods/index.d.cts.map +1 -0
  70. package/dist/rpc-methods/index.d.mts +6 -0
  71. package/dist/rpc-methods/index.d.mts.map +1 -0
  72. package/dist/rpc-methods/index.mjs +8 -12
  73. package/dist/rpc-methods/index.mjs.map +1 -1
  74. package/dist/rpc-methods/requestPermissions.cjs +36 -0
  75. package/dist/rpc-methods/requestPermissions.cjs.map +1 -0
  76. package/dist/{types/rpc-methods/requestPermissions.d.ts → rpc-methods/requestPermissions.d.cts} +3 -3
  77. package/dist/rpc-methods/requestPermissions.d.cts.map +1 -0
  78. package/dist/rpc-methods/requestPermissions.d.mts +17 -0
  79. package/dist/rpc-methods/requestPermissions.d.mts.map +1 -0
  80. package/dist/rpc-methods/requestPermissions.mjs +31 -8
  81. package/dist/rpc-methods/requestPermissions.mjs.map +1 -1
  82. package/dist/rpc-methods/revokePermissions.cjs +41 -0
  83. package/dist/rpc-methods/revokePermissions.cjs.map +1 -0
  84. package/dist/{types/rpc-methods/revokePermissions.d.ts → rpc-methods/revokePermissions.d.cts} +4 -4
  85. package/dist/rpc-methods/revokePermissions.d.cts.map +1 -0
  86. package/dist/rpc-methods/revokePermissions.d.mts +11 -0
  87. package/dist/rpc-methods/revokePermissions.d.mts.map +1 -0
  88. package/dist/rpc-methods/revokePermissions.mjs +36 -8
  89. package/dist/rpc-methods/revokePermissions.mjs.map +1 -1
  90. package/dist/utils.cjs +44 -0
  91. package/dist/utils.cjs.map +1 -0
  92. package/dist/{types/utils.d.ts → utils.d.cts} +5 -5
  93. package/dist/utils.d.cts.map +1 -0
  94. package/dist/utils.d.mts +57 -0
  95. package/dist/utils.d.mts.map +1 -0
  96. package/dist/utils.mjs +39 -9
  97. package/dist/utils.mjs.map +1 -1
  98. package/package.json +16 -11
  99. package/dist/Caveat.js +0 -12
  100. package/dist/Caveat.js.map +0 -1
  101. package/dist/Permission.js +0 -14
  102. package/dist/Permission.js.map +0 -1
  103. package/dist/PermissionController.js +0 -15
  104. package/dist/PermissionController.js.map +0 -1
  105. package/dist/SubjectMetadataController.js +0 -10
  106. package/dist/SubjectMetadataController.js.map +0 -1
  107. package/dist/chunk-2L4QPE5A.mjs +0 -25
  108. package/dist/chunk-2L4QPE5A.mjs.map +0 -1
  109. package/dist/chunk-3WWJKO7P.mjs +0 -37
  110. package/dist/chunk-3WWJKO7P.mjs.map +0 -1
  111. package/dist/chunk-3YOPLPVY.js +0 -37
  112. package/dist/chunk-3YOPLPVY.js.map +0 -1
  113. package/dist/chunk-42QSJHWO.mjs +0 -155
  114. package/dist/chunk-42QSJHWO.mjs.map +0 -1
  115. package/dist/chunk-4FMYQC3Y.mjs +0 -29
  116. package/dist/chunk-4FMYQC3Y.mjs.map +0 -1
  117. package/dist/chunk-4NAVRO44.mjs +0 -35
  118. package/dist/chunk-4NAVRO44.mjs.map +0 -1
  119. package/dist/chunk-5RFW5THA.mjs +0 -17
  120. package/dist/chunk-5RFW5THA.mjs.map +0 -1
  121. package/dist/chunk-6PXDVUYM.js +0 -29
  122. package/dist/chunk-6PXDVUYM.js.map +0 -1
  123. package/dist/chunk-74H4CVH7.mjs +0 -34
  124. package/dist/chunk-74H4CVH7.mjs.map +0 -1
  125. package/dist/chunk-7A3VYLCK.js +0 -30
  126. package/dist/chunk-7A3VYLCK.js.map +0 -1
  127. package/dist/chunk-B6PDRQ7N.js +0 -1679
  128. package/dist/chunk-B6PDRQ7N.js.map +0 -1
  129. package/dist/chunk-COGJLF22.mjs +0 -1679
  130. package/dist/chunk-F5TBMVWC.js +0 -46
  131. package/dist/chunk-F5TBMVWC.js.map +0 -1
  132. package/dist/chunk-FYADAA2G.js +0 -220
  133. package/dist/chunk-FYADAA2G.js.map +0 -1
  134. package/dist/chunk-G4BWJ7EA.mjs +0 -220
  135. package/dist/chunk-G4BWJ7EA.mjs.map +0 -1
  136. package/dist/chunk-HYMS7IGB.mjs +0 -31
  137. package/dist/chunk-HYMS7IGB.mjs.map +0 -1
  138. package/dist/chunk-I62TTXZ6.mjs +0 -46
  139. package/dist/chunk-I62TTXZ6.mjs.map +0 -1
  140. package/dist/chunk-OCLNDUYO.mjs +0 -30
  141. package/dist/chunk-OCLNDUYO.mjs.map +0 -1
  142. package/dist/chunk-VBIZGGQL.js +0 -17
  143. package/dist/chunk-VBIZGGQL.js.map +0 -1
  144. package/dist/chunk-VQPP5PWS.js +0 -35
  145. package/dist/chunk-VQPP5PWS.js.map +0 -1
  146. package/dist/chunk-VSDHL2GQ.js +0 -155
  147. package/dist/chunk-VSDHL2GQ.js.map +0 -1
  148. package/dist/chunk-X4ZAW4QR.js +0 -34
  149. package/dist/chunk-X4ZAW4QR.js.map +0 -1
  150. package/dist/chunk-Z2XKIXLS.js +0 -25
  151. package/dist/chunk-Z2XKIXLS.js.map +0 -1
  152. package/dist/chunk-ZH4MLSXX.js +0 -31
  153. package/dist/chunk-ZH4MLSXX.js.map +0 -1
  154. package/dist/errors.js +0 -58
  155. package/dist/errors.js.map +0 -1
  156. package/dist/index.js +0 -98
  157. package/dist/index.js.map +0 -1
  158. package/dist/permission-middleware.js +0 -9
  159. package/dist/permission-middleware.js.map +0 -1
  160. package/dist/rpc-methods/getPermissions.js +0 -9
  161. package/dist/rpc-methods/getPermissions.js.map +0 -1
  162. package/dist/rpc-methods/index.js +0 -13
  163. package/dist/rpc-methods/index.js.map +0 -1
  164. package/dist/rpc-methods/requestPermissions.js +0 -10
  165. package/dist/rpc-methods/requestPermissions.js.map +0 -1
  166. package/dist/rpc-methods/revokePermissions.js +0 -10
  167. package/dist/rpc-methods/revokePermissions.js.map +0 -1
  168. package/dist/tsconfig.build.tsbuildinfo +0 -1
  169. package/dist/types/Caveat.d.ts.map +0 -1
  170. package/dist/types/Permission.d.ts.map +0 -1
  171. package/dist/types/PermissionController.d.ts.map +0 -1
  172. package/dist/types/SubjectMetadataController.d.ts.map +0 -1
  173. package/dist/types/errors.d.ts.map +0 -1
  174. package/dist/types/index.d.ts +0 -9
  175. package/dist/types/index.d.ts.map +0 -1
  176. package/dist/types/permission-middleware.d.ts.map +0 -1
  177. package/dist/types/rpc-methods/getPermissions.d.ts +0 -9
  178. package/dist/types/rpc-methods/getPermissions.d.ts.map +0 -1
  179. package/dist/types/rpc-methods/index.d.ts +0 -6
  180. package/dist/types/rpc-methods/index.d.ts.map +0 -1
  181. package/dist/types/rpc-methods/requestPermissions.d.ts.map +0 -1
  182. package/dist/types/rpc-methods/revokePermissions.d.ts.map +0 -1
  183. package/dist/types/utils.d.ts.map +0 -1
  184. package/dist/utils.js +0 -10
  185. package/dist/utils.js.map +0 -1
@@ -1,15 +1,1481 @@
1
- import {
2
- CaveatMutatorOperation,
3
- PermissionController
4
- } from "./chunk-COGJLF22.mjs";
5
- import "./chunk-3WWJKO7P.mjs";
6
- import "./chunk-HYMS7IGB.mjs";
7
- import "./chunk-I62TTXZ6.mjs";
8
- import "./chunk-G4BWJ7EA.mjs";
9
- import "./chunk-4NAVRO44.mjs";
10
- import "./chunk-2L4QPE5A.mjs";
11
- export {
12
- CaveatMutatorOperation,
13
- PermissionController
1
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
2
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
3
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
4
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
5
+ };
6
+ var _PermissionController_instances, _PermissionController_expectGetCaveatMerger, _PermissionController_applyGrantedPermissions, _PermissionController_mergeIncrementalPermissions, _PermissionController_mergePermission, _PermissionController_mergeCaveat, _PermissionController_handleApprovedPermissions;
7
+ function $importDefault(module) {
8
+ if (module?.__esModule) {
9
+ return module.default;
10
+ }
11
+ return module;
12
+ }
13
+ import { BaseController } from "@metamask/base-controller";
14
+ import { isNonEmptyArray, isPlainObject, isValidJson } from "@metamask/controller-utils";
15
+ import { JsonRpcError } from "@metamask/rpc-errors";
16
+ import { hasProperty } from "@metamask/utils";
17
+ import $deepFreeze from "deep-freeze-strict";
18
+ const deepFreeze = $importDefault($deepFreeze);
19
+ import { castDraft, produce as immerProduce } from "immer";
20
+ import { nanoid } from "nanoid";
21
+ import { decorateWithCaveats, isRestrictedMethodCaveatSpecification } from "./Caveat.mjs";
22
+ import { CaveatAlreadyExistsError, CaveatDoesNotExistError, CaveatInvalidJsonError, CaveatMergerDoesNotExistError, CaveatMergeTypeMismatchError, CaveatMissingValueError, CaveatSpecificationMismatchError, DuplicateCaveatError, EndowmentPermissionDoesNotExistError, ForbiddenCaveatError, internalError, InvalidApprovedPermissionError, InvalidCaveatError, InvalidCaveatFieldsError, InvalidCaveatsPropertyError, InvalidCaveatTypeError, InvalidMergedPermissionsError, invalidParams, InvalidSubjectIdentifierError, methodNotFound, PermissionDoesNotExistError, PermissionsRequestNotFoundError, unauthorized, UnrecognizedCaveatTypeError, UnrecognizedSubjectError, userRejectedRequest } from "./errors.mjs";
23
+ import { constructPermission, findCaveat, hasSpecificationType, PermissionType } from "./Permission.mjs";
24
+ import { getPermissionMiddlewareFactory } from "./permission-middleware.mjs";
25
+ import { collectUniqueAndPairedCaveats, MethodNames } from "./utils.mjs";
26
+ /**
27
+ * The name of the {@link PermissionController}.
28
+ */
29
+ const controllerName = 'PermissionController';
30
+ /**
31
+ * Get the state metadata of the {@link PermissionController}.
32
+ *
33
+ * @template Permission - The controller's permission type union.
34
+ * @returns The state metadata
35
+ */
36
+ function getStateMetadata() {
37
+ return { subjects: { anonymous: true, persist: true } };
38
+ }
39
+ /**
40
+ * Get the default state of the {@link PermissionController}.
41
+ *
42
+ * @template Permission - The controller's permission type union.
43
+ * @returns The default state of the controller
44
+ */
45
+ function getDefaultState() {
46
+ return { subjects: {} };
47
+ }
48
+ /**
49
+ * Describes the possible results of a {@link CaveatMutator} function.
50
+ */
51
+ export var CaveatMutatorOperation;
52
+ (function (CaveatMutatorOperation) {
53
+ CaveatMutatorOperation[CaveatMutatorOperation["Noop"] = 0] = "Noop";
54
+ CaveatMutatorOperation[CaveatMutatorOperation["UpdateValue"] = 1] = "UpdateValue";
55
+ CaveatMutatorOperation[CaveatMutatorOperation["DeleteCaveat"] = 2] = "DeleteCaveat";
56
+ CaveatMutatorOperation[CaveatMutatorOperation["RevokePermission"] = 3] = "RevokePermission";
57
+ })(CaveatMutatorOperation || (CaveatMutatorOperation = {}));
58
+ /**
59
+ * The permission controller. See the [Architecture](../ARCHITECTURE.md)
60
+ * document for details.
61
+ *
62
+ * Assumes the existence of an {@link ApprovalController} reachable via the
63
+ * {@link ControllerMessenger}.
64
+ *
65
+ * @template ControllerPermissionSpecification - A union of the types of all
66
+ * permission specifications available to the controller. Any referenced caveats
67
+ * must be included in the controller's caveat specifications.
68
+ * @template ControllerCaveatSpecification - A union of the types of all
69
+ * caveat specifications available to the controller.
70
+ */
71
+ export class PermissionController extends BaseController {
72
+ /**
73
+ * The names of all JSON-RPC methods that will be ignored by the controller.
74
+ *
75
+ * @returns The names of all unrestricted JSON-RPC methods
76
+ */
77
+ get unrestrictedMethods() {
78
+ return this._unrestrictedMethods;
79
+ }
80
+ /**
81
+ * Constructs the PermissionController.
82
+ *
83
+ * @param options - Permission controller options.
84
+ * @param options.caveatSpecifications - The specifications of all caveats
85
+ * available to the controller. See {@link CaveatSpecificationMap} and the
86
+ * documentation for more details.
87
+ * @param options.permissionSpecifications - The specifications of all
88
+ * permissions available to the controller. See
89
+ * {@link PermissionSpecificationMap} and the README for more details.
90
+ * @param options.unrestrictedMethods - The callable names of all JSON-RPC
91
+ * methods ignored by the new controller.
92
+ * @param options.messenger - The controller messenger. See
93
+ * {@link BaseController} for more information.
94
+ * @param options.state - Existing state to hydrate the controller with at
95
+ * initialization.
96
+ */
97
+ constructor(options) {
98
+ const { caveatSpecifications, permissionSpecifications, unrestrictedMethods, messenger, state = {}, } = options;
99
+ super({
100
+ name: controllerName,
101
+ metadata: getStateMetadata(),
102
+ messenger,
103
+ state: {
104
+ ...getDefaultState(),
105
+ ...state,
106
+ },
107
+ });
108
+ _PermissionController_instances.add(this);
109
+ this._unrestrictedMethods = new Set(unrestrictedMethods);
110
+ this._caveatSpecifications = deepFreeze({ ...caveatSpecifications });
111
+ this.validatePermissionSpecifications(permissionSpecifications, this._caveatSpecifications);
112
+ this._permissionSpecifications = deepFreeze({
113
+ ...permissionSpecifications,
114
+ });
115
+ this.registerMessageHandlers();
116
+ this.createPermissionMiddleware = getPermissionMiddlewareFactory({
117
+ executeRestrictedMethod: this._executeRestrictedMethod.bind(this),
118
+ getRestrictedMethod: this.getRestrictedMethod.bind(this),
119
+ isUnrestrictedMethod: this.unrestrictedMethods.has.bind(this.unrestrictedMethods),
120
+ });
121
+ }
122
+ /**
123
+ * Gets a permission specification.
124
+ *
125
+ * @param targetName - The name of the permission specification to get.
126
+ * @returns The permission specification with the specified target name.
127
+ */
128
+ getPermissionSpecification(targetName) {
129
+ return this._permissionSpecifications[targetName];
130
+ }
131
+ /**
132
+ * Gets a caveat specification.
133
+ *
134
+ * @param caveatType - The type of the caveat specification to get.
135
+ * @returns The caveat specification with the specified type.
136
+ */
137
+ getCaveatSpecification(caveatType) {
138
+ return this._caveatSpecifications[caveatType];
139
+ }
140
+ /**
141
+ * Constructor helper for validating permission specifications.
142
+ *
143
+ * Throws an error if validation fails.
144
+ *
145
+ * @param permissionSpecifications - The permission specifications passed to
146
+ * this controller's constructor.
147
+ * @param caveatSpecifications - The caveat specifications passed to this
148
+ * controller.
149
+ */
150
+ validatePermissionSpecifications(permissionSpecifications, caveatSpecifications) {
151
+ Object.entries(permissionSpecifications).forEach(([targetName, { permissionType, targetName: innerTargetName, allowedCaveats },]) => {
152
+ if (!permissionType || !hasProperty(PermissionType, permissionType)) {
153
+ throw new Error(`Invalid permission type: "${permissionType}"`);
154
+ }
155
+ if (!targetName) {
156
+ throw new Error(`Invalid permission target name: "${targetName}"`);
157
+ }
158
+ if (targetName !== innerTargetName) {
159
+ throw new Error(`Invalid permission specification: target name "${targetName}" must match specification.targetName value "${innerTargetName}".`);
160
+ }
161
+ if (allowedCaveats) {
162
+ allowedCaveats.forEach((caveatType) => {
163
+ if (!hasProperty(caveatSpecifications, caveatType)) {
164
+ throw new UnrecognizedCaveatTypeError(caveatType);
165
+ }
166
+ const specification = caveatSpecifications[caveatType];
167
+ const isRestrictedMethodCaveat = isRestrictedMethodCaveatSpecification(specification);
168
+ if ((permissionType === PermissionType.RestrictedMethod &&
169
+ !isRestrictedMethodCaveat) ||
170
+ (permissionType === PermissionType.Endowment &&
171
+ isRestrictedMethodCaveat)) {
172
+ throw new CaveatSpecificationMismatchError(specification, permissionType);
173
+ }
174
+ });
175
+ }
176
+ });
177
+ }
178
+ /**
179
+ * Constructor helper for registering the controller's messaging system
180
+ * actions.
181
+ */
182
+ registerMessageHandlers() {
183
+ this.messagingSystem.registerActionHandler(`${controllerName}:clearPermissions`, () => this.clearState());
184
+ this.messagingSystem.registerActionHandler(`${controllerName}:getEndowments`, (origin, targetName, requestData) => this.getEndowments(origin, targetName, requestData));
185
+ this.messagingSystem.registerActionHandler(`${controllerName}:getSubjectNames`, () => this.getSubjectNames());
186
+ this.messagingSystem.registerActionHandler(`${controllerName}:getPermissions`, (origin) => this.getPermissions(origin));
187
+ this.messagingSystem.registerActionHandler(`${controllerName}:hasPermission`, (origin, targetName) => this.hasPermission(origin, targetName));
188
+ this.messagingSystem.registerActionHandler(`${controllerName}:hasPermissions`, (origin) => this.hasPermissions(origin));
189
+ this.messagingSystem.registerActionHandler(`${controllerName}:grantPermissions`, this.grantPermissions.bind(this));
190
+ this.messagingSystem.registerActionHandler(`${controllerName}:grantPermissionsIncremental`, this.grantPermissionsIncremental.bind(this));
191
+ this.messagingSystem.registerActionHandler(`${controllerName}:requestPermissions`, (subject, permissions) => this.requestPermissions(subject, permissions));
192
+ this.messagingSystem.registerActionHandler(`${controllerName}:requestPermissionsIncremental`, (subject, permissions) => this.requestPermissionsIncremental(subject, permissions));
193
+ this.messagingSystem.registerActionHandler(`${controllerName}:revokeAllPermissions`, (origin) => this.revokeAllPermissions(origin));
194
+ this.messagingSystem.registerActionHandler(`${controllerName}:revokePermissionForAllSubjects`, (target) => this.revokePermissionForAllSubjects(target));
195
+ this.messagingSystem.registerActionHandler(`${controllerName}:revokePermissions`, this.revokePermissions.bind(this));
196
+ this.messagingSystem.registerActionHandler(`${controllerName}:updateCaveat`, (origin, target, caveatType, caveatValue) => {
197
+ this.updateCaveat(origin, target, caveatType, caveatValue);
198
+ });
199
+ }
200
+ /**
201
+ * Clears the state of the controller.
202
+ */
203
+ clearState() {
204
+ this.update((_draftState) => {
205
+ return {
206
+ ...getDefaultState(),
207
+ };
208
+ });
209
+ }
210
+ /**
211
+ * Gets the permission specification corresponding to the given permission
212
+ * type and target name. Throws an error if the target name does not
213
+ * correspond to a permission, or if the specification is not of the
214
+ * given permission type.
215
+ *
216
+ * @template Type - The type of the permission specification to get.
217
+ * @param permissionType - The type of the permission specification to get.
218
+ * @param targetName - The name of the permission whose specification to get.
219
+ * @param requestingOrigin - The origin of the requesting subject, if any.
220
+ * Will be added to any thrown errors.
221
+ * @returns The specification object corresponding to the given type and
222
+ * target name.
223
+ */
224
+ getTypedPermissionSpecification(permissionType, targetName, requestingOrigin) {
225
+ const failureError = permissionType === PermissionType.RestrictedMethod
226
+ ? methodNotFound(targetName, requestingOrigin ? { origin: requestingOrigin } : undefined)
227
+ : new EndowmentPermissionDoesNotExistError(targetName, requestingOrigin);
228
+ if (!this.targetExists(targetName)) {
229
+ throw failureError;
230
+ }
231
+ const specification = this.getPermissionSpecification(targetName);
232
+ if (!hasSpecificationType(specification, permissionType)) {
233
+ throw failureError;
234
+ }
235
+ return specification;
236
+ }
237
+ /**
238
+ * Gets the implementation of the specified restricted method.
239
+ *
240
+ * A JSON-RPC error is thrown if the method does not exist.
241
+ *
242
+ * @see {@link PermissionController.executeRestrictedMethod} and
243
+ * {@link PermissionController.createPermissionMiddleware} for internal usage.
244
+ * @param method - The name of the restricted method.
245
+ * @param origin - The origin associated with the request for the restricted
246
+ * method, if any.
247
+ * @returns The restricted method implementation.
248
+ */
249
+ getRestrictedMethod(method, origin) {
250
+ return this.getTypedPermissionSpecification(PermissionType.RestrictedMethod, method, origin).methodImplementation;
251
+ }
252
+ /**
253
+ * Gets a list of all origins of subjects.
254
+ *
255
+ * @returns The origins (i.e. IDs) of all subjects.
256
+ */
257
+ getSubjectNames() {
258
+ return Object.keys(this.state.subjects);
259
+ }
260
+ /**
261
+ * Gets the permission for the specified target of the subject corresponding
262
+ * to the specified origin.
263
+ *
264
+ * @param origin - The origin of the subject.
265
+ * @param targetName - The method name as invoked by a third party (i.e., not
266
+ * a method key).
267
+ * @returns The permission if it exists, or undefined otherwise.
268
+ */
269
+ getPermission(origin, targetName) {
270
+ return this.state.subjects[origin]?.permissions[targetName];
271
+ }
272
+ /**
273
+ * Gets all permissions for the specified subject, if any.
274
+ *
275
+ * @param origin - The origin of the subject.
276
+ * @returns The permissions of the subject, if any.
277
+ */
278
+ getPermissions(origin) {
279
+ return this.state.subjects[origin]?.permissions;
280
+ }
281
+ /**
282
+ * Checks whether the subject with the specified origin has the specified
283
+ * permission.
284
+ *
285
+ * @param origin - The origin of the subject.
286
+ * @param target - The target name of the permission.
287
+ * @returns Whether the subject has the permission.
288
+ */
289
+ hasPermission(origin, target) {
290
+ return Boolean(this.getPermission(origin, target));
291
+ }
292
+ /**
293
+ * Checks whether the subject with the specified origin has any permissions.
294
+ * Use this if you want to know if a subject "exists".
295
+ *
296
+ * @param origin - The origin of the subject to check.
297
+ * @returns Whether the subject has any permissions.
298
+ */
299
+ hasPermissions(origin) {
300
+ return Boolean(this.state.subjects[origin]);
301
+ }
302
+ /**
303
+ * Revokes all permissions from the specified origin.
304
+ *
305
+ * Throws an error of the origin has no permissions.
306
+ *
307
+ * @param origin - The origin whose permissions to revoke.
308
+ */
309
+ revokeAllPermissions(origin) {
310
+ this.update((draftState) => {
311
+ if (!draftState.subjects[origin]) {
312
+ throw new UnrecognizedSubjectError(origin);
313
+ }
314
+ delete draftState.subjects[origin];
315
+ });
316
+ }
317
+ /**
318
+ * Revokes the specified permission from the subject with the specified
319
+ * origin.
320
+ *
321
+ * Throws an error if the subject or the permission does not exist.
322
+ *
323
+ * @param origin - The origin of the subject whose permission to revoke.
324
+ * @param target - The target name of the permission to revoke.
325
+ */
326
+ revokePermission(origin, target) {
327
+ this.revokePermissions({ [origin]: [target] });
328
+ }
329
+ /**
330
+ * Revokes the specified permissions from the specified subjects.
331
+ *
332
+ * Throws an error if any of the subjects or permissions do not exist.
333
+ *
334
+ * @param subjectsAndPermissions - An object mapping subject origins
335
+ * to arrays of permission target names to revoke.
336
+ */
337
+ revokePermissions(subjectsAndPermissions) {
338
+ this.update((draftState) => {
339
+ Object.keys(subjectsAndPermissions).forEach((origin) => {
340
+ if (!hasProperty(draftState.subjects, origin)) {
341
+ throw new UnrecognizedSubjectError(origin);
342
+ }
343
+ subjectsAndPermissions[origin].forEach((target) => {
344
+ const { permissions } = draftState.subjects[origin];
345
+ if (!hasProperty(permissions, target)) {
346
+ throw new PermissionDoesNotExistError(origin, target);
347
+ }
348
+ this.deletePermission(draftState.subjects, origin, target);
349
+ });
350
+ });
351
+ });
352
+ }
353
+ /**
354
+ * Revokes all permissions corresponding to the specified target for all subjects.
355
+ * Does nothing if no subjects or no such permission exists.
356
+ *
357
+ * @param target - The name of the target to revoke all permissions for.
358
+ */
359
+ revokePermissionForAllSubjects(target) {
360
+ if (this.getSubjectNames().length === 0) {
361
+ return;
362
+ }
363
+ this.update((draftState) => {
364
+ Object.entries(draftState.subjects).forEach(([origin, subject]) => {
365
+ const { permissions } = subject;
366
+ if (hasProperty(permissions, target)) {
367
+ this.deletePermission(draftState.subjects, origin, target);
368
+ }
369
+ });
370
+ });
371
+ }
372
+ /**
373
+ * Deletes the permission identified by the given origin and target. If the
374
+ * permission is the single remaining permission of its subject, the subject
375
+ * is also deleted.
376
+ *
377
+ * @param subjects - The draft permission controller subjects.
378
+ * @param origin - The origin of the subject associated with the permission
379
+ * to delete.
380
+ * @param target - The target name of the permission to delete.
381
+ */
382
+ deletePermission(subjects, origin, target) {
383
+ const { permissions } = subjects[origin];
384
+ if (Object.keys(permissions).length > 1) {
385
+ delete permissions[target];
386
+ }
387
+ else {
388
+ delete subjects[origin];
389
+ }
390
+ }
391
+ /**
392
+ * Checks whether the permission of the subject corresponding to the given
393
+ * origin has a caveat of the specified type.
394
+ *
395
+ * Throws an error if the subject does not have a permission with the
396
+ * specified target name.
397
+ *
398
+ * @template TargetName - The permission target name. Should be inferred.
399
+ * @template CaveatType - The valid caveat types for the permission. Should
400
+ * be inferred.
401
+ * @param origin - The origin of the subject.
402
+ * @param target - The target name of the permission.
403
+ * @param caveatType - The type of the caveat to check for.
404
+ * @returns Whether the permission has the specified caveat.
405
+ */
406
+ hasCaveat(origin, target, caveatType) {
407
+ return Boolean(this.getCaveat(origin, target, caveatType));
408
+ }
409
+ /**
410
+ * Gets the caveat of the specified type, if any, for the permission of
411
+ * the subject corresponding to the given origin.
412
+ *
413
+ * Throws an error if the subject does not have a permission with the
414
+ * specified target name.
415
+ *
416
+ * @template TargetName - The permission target name. Should be inferred.
417
+ * @template CaveatType - The valid caveat types for the permission. Should
418
+ * be inferred.
419
+ * @param origin - The origin of the subject.
420
+ * @param target - The target name of the permission.
421
+ * @param caveatType - The type of the caveat to get.
422
+ * @returns The caveat, or `undefined` if no such caveat exists.
423
+ */
424
+ getCaveat(origin, target, caveatType) {
425
+ const permission = this.getPermission(origin, target);
426
+ if (!permission) {
427
+ throw new PermissionDoesNotExistError(origin, target);
428
+ }
429
+ return findCaveat(permission, caveatType);
430
+ }
431
+ /**
432
+ * Adds a caveat of the specified type, with the specified caveat value, to
433
+ * the permission corresponding to the given subject origin and permission
434
+ * target.
435
+ *
436
+ * For modifying existing caveats, use
437
+ * {@link PermissionController.updateCaveat}.
438
+ *
439
+ * Throws an error if no such permission exists, or if the caveat already
440
+ * exists.
441
+ *
442
+ * @template TargetName - The permission target name. Should be inferred.
443
+ * @template CaveatType - The valid caveat types for the permission. Should
444
+ * be inferred.
445
+ * @param origin - The origin of the subject.
446
+ * @param target - The target name of the permission.
447
+ * @param caveatType - The type of the caveat to add.
448
+ * @param caveatValue - The value of the caveat to add.
449
+ */
450
+ addCaveat(origin, target, caveatType, caveatValue) {
451
+ if (this.hasCaveat(origin, target, caveatType)) {
452
+ throw new CaveatAlreadyExistsError(origin, target, caveatType);
453
+ }
454
+ this.setCaveat(origin, target, caveatType, caveatValue);
455
+ }
456
+ /**
457
+ * Updates the value of the caveat of the specified type belonging to the
458
+ * permission corresponding to the given subject origin and permission
459
+ * target.
460
+ *
461
+ * For adding new caveats, use
462
+ * {@link PermissionController.addCaveat}.
463
+ *
464
+ * Throws an error if no such permission or caveat exists.
465
+ *
466
+ * @template TargetName - The permission target name. Should be inferred.
467
+ * @template CaveatType - The valid caveat types for the permission. Should
468
+ * be inferred.
469
+ * @param origin - The origin of the subject.
470
+ * @param target - The target name of the permission.
471
+ * @param caveatType - The type of the caveat to update.
472
+ * @param caveatValue - The new value of the caveat.
473
+ */
474
+ updateCaveat(origin, target, caveatType, caveatValue) {
475
+ if (!this.hasCaveat(origin, target, caveatType)) {
476
+ throw new CaveatDoesNotExistError(origin, target, caveatType);
477
+ }
478
+ this.setCaveat(origin, target, caveatType, caveatValue);
479
+ }
480
+ /**
481
+ * Sets the specified caveat on the specified permission. Overwrites existing
482
+ * caveats of the same type in-place (preserving array order), and adds the
483
+ * caveat to the end of the array otherwise.
484
+ *
485
+ * Throws an error if the permission does not exist or fails to validate after
486
+ * its caveats have been modified.
487
+ *
488
+ * @see {@link PermissionController.addCaveat}
489
+ * @see {@link PermissionController.updateCaveat}
490
+ * @template TargetName - The permission target name. Should be inferred.
491
+ * @template CaveatType - The valid caveat types for the permission. Should
492
+ * be inferred.
493
+ * @param origin - The origin of the subject.
494
+ * @param target - The target name of the permission.
495
+ * @param caveatType - The type of the caveat to set.
496
+ * @param caveatValue - The value of the caveat to set.
497
+ */
498
+ setCaveat(origin, target, caveatType, caveatValue) {
499
+ this.update((draftState) => {
500
+ const subject = draftState.subjects[origin];
501
+ // Unreachable because `hasCaveat` is always called before this, and it
502
+ // throws if permissions are missing. TypeScript needs this, however.
503
+ /* istanbul ignore if */
504
+ if (!subject) {
505
+ throw new UnrecognizedSubjectError(origin);
506
+ }
507
+ const permission = subject.permissions[target];
508
+ /* istanbul ignore if: practically impossible, but TypeScript wants it */
509
+ if (!permission) {
510
+ throw new PermissionDoesNotExistError(origin, target);
511
+ }
512
+ const caveat = {
513
+ type: caveatType,
514
+ value: caveatValue,
515
+ };
516
+ this.validateCaveat(caveat, origin, target);
517
+ if (permission.caveats) {
518
+ const caveatIndex = permission.caveats.findIndex((existingCaveat) => existingCaveat.type === caveat.type);
519
+ if (caveatIndex === -1) {
520
+ permission.caveats.push(caveat);
521
+ }
522
+ else {
523
+ permission.caveats.splice(caveatIndex, 1, caveat);
524
+ }
525
+ }
526
+ else {
527
+ // At this point, we don't know if the specific permission is allowed
528
+ // to have caveats, but it should be impossible to call this method
529
+ // for a permission that may not have any caveats. If all else fails,
530
+ // the permission validator is also called.
531
+ // @ts-expect-error See above comment
532
+ permission.caveats = [caveat];
533
+ }
534
+ this.validateModifiedPermission(permission, origin);
535
+ });
536
+ }
537
+ /**
538
+ * Updates all caveats with the specified type for all subjects and
539
+ * permissions by applying the specified mutator function to them.
540
+ *
541
+ * ATTN: Permissions can be revoked entirely by the action of this method,
542
+ * read on for details.
543
+ *
544
+ * Caveat mutators are functions that receive a caveat value and return a
545
+ * tuple consisting of a {@link CaveatMutatorOperation} and, optionally, a new
546
+ * value to update the existing caveat with.
547
+ *
548
+ * For each caveat, depending on the mutator result, this method will:
549
+ * - Do nothing ({@link CaveatMutatorOperation.Noop})
550
+ * - Update the value of the caveat ({@link CaveatMutatorOperation.UpdateValue}). The caveat specification validator, if any, will be called after updating the value.
551
+ * - Delete the caveat ({@link CaveatMutatorOperation.DeleteCaveat}). The permission specification validator, if any, will be called after deleting the caveat.
552
+ * - Revoke the parent permission ({@link CaveatMutatorOperation.RevokePermission})
553
+ *
554
+ * This method throws if the validation of any caveat or permission fails.
555
+ *
556
+ * @param targetCaveatType - The type of the caveats to update.
557
+ * @param mutator - The mutator function which will be applied to all caveat
558
+ * values.
559
+ */
560
+ updatePermissionsByCaveat(targetCaveatType, mutator) {
561
+ if (Object.keys(this.state.subjects).length === 0) {
562
+ return;
563
+ }
564
+ this.update((draftState) => {
565
+ Object.values(draftState.subjects).forEach((subject) => {
566
+ Object.values(subject.permissions).forEach((permission) => {
567
+ const { caveats } = permission;
568
+ const targetCaveat = caveats?.find(({ type }) => type === targetCaveatType);
569
+ if (!targetCaveat) {
570
+ return;
571
+ }
572
+ // The mutator may modify the caveat value in place, and must always
573
+ // return a valid mutation result.
574
+ const mutatorResult = mutator(targetCaveat.value);
575
+ const { operation } = mutatorResult;
576
+ switch (operation) {
577
+ case CaveatMutatorOperation.Noop:
578
+ break;
579
+ case CaveatMutatorOperation.UpdateValue:
580
+ // Typecast: `Mutable` is used here to assign to a readonly
581
+ // property. `targetConstraint` should already be mutable because
582
+ // it's part of a draft, but for some reason it's not. We can't
583
+ // use the more-correct `Draft` type here either because it
584
+ // results in an error.
585
+ targetCaveat.value =
586
+ mutatorResult.value;
587
+ this.validateCaveat(targetCaveat, subject.origin, permission.parentCapability);
588
+ break;
589
+ case CaveatMutatorOperation.DeleteCaveat:
590
+ this.deleteCaveat(permission, targetCaveatType, subject.origin);
591
+ break;
592
+ case CaveatMutatorOperation.RevokePermission:
593
+ this.deletePermission(draftState.subjects, subject.origin, permission.parentCapability);
594
+ break;
595
+ default: {
596
+ // Overriding as `never` is the expected result of exhaustiveness checking,
597
+ // and is intended to represent unchecked exception cases.
598
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
599
+ throw new Error(`Unrecognized mutation result: "${operation}"`);
600
+ }
601
+ }
602
+ });
603
+ });
604
+ });
605
+ }
606
+ /**
607
+ * Removes the caveat of the specified type from the permission corresponding
608
+ * to the given subject origin and target name.
609
+ *
610
+ * Throws an error if no such permission or caveat exists.
611
+ *
612
+ * @template TargetName - The permission target name. Should be inferred.
613
+ * @template CaveatType - The valid caveat types for the permission. Should
614
+ * be inferred.
615
+ * @param origin - The origin of the subject.
616
+ * @param target - The target name of the permission.
617
+ * @param caveatType - The type of the caveat to remove.
618
+ */
619
+ removeCaveat(origin, target, caveatType) {
620
+ this.update((draftState) => {
621
+ const permission = draftState.subjects[origin]?.permissions[target];
622
+ if (!permission) {
623
+ throw new PermissionDoesNotExistError(origin, target);
624
+ }
625
+ if (!permission.caveats) {
626
+ throw new CaveatDoesNotExistError(origin, target, caveatType);
627
+ }
628
+ this.deleteCaveat(permission, caveatType, origin);
629
+ });
630
+ }
631
+ /**
632
+ * Deletes the specified caveat from the specified permission. If no caveats
633
+ * remain after deletion, the permission's caveat property is set to `null`.
634
+ * The permission is validated after being modified.
635
+ *
636
+ * Throws an error if the permission does not have a caveat with the specified
637
+ * type.
638
+ *
639
+ * @param permission - The permission whose caveat to delete.
640
+ * @param caveatType - The type of the caveat to delete.
641
+ * @param origin - The origin the permission subject.
642
+ */
643
+ deleteCaveat(permission, caveatType, origin) {
644
+ /* istanbul ignore if: not possible in our usage */
645
+ if (!permission.caveats) {
646
+ throw new CaveatDoesNotExistError(origin, permission.parentCapability, caveatType);
647
+ }
648
+ const caveatIndex = permission.caveats.findIndex((existingCaveat) => existingCaveat.type === caveatType);
649
+ if (caveatIndex === -1) {
650
+ throw new CaveatDoesNotExistError(origin, permission.parentCapability, caveatType);
651
+ }
652
+ if (permission.caveats.length === 1) {
653
+ permission.caveats = null;
654
+ }
655
+ else {
656
+ permission.caveats.splice(caveatIndex, 1);
657
+ }
658
+ this.validateModifiedPermission(permission, origin);
659
+ }
660
+ /**
661
+ * Validates the specified modified permission. Should **always** be invoked
662
+ * on a permission after its caveats have been modified.
663
+ *
664
+ * Just like {@link PermissionController.validatePermission}, except that the
665
+ * corresponding target name and specification are retrieved first, and an
666
+ * error is thrown if the target name does not exist.
667
+ *
668
+ * @param permission - The modified permission to validate.
669
+ * @param origin - The origin associated with the permission.
670
+ */
671
+ validateModifiedPermission(permission, origin) {
672
+ /* istanbul ignore if: this should be impossible */
673
+ if (!this.targetExists(permission.parentCapability)) {
674
+ throw new Error(`Fatal: Existing permission target "${permission.parentCapability}" has no specification.`);
675
+ }
676
+ this.validatePermission(this.getPermissionSpecification(permission.parentCapability), permission, origin);
677
+ }
678
+ /**
679
+ * Verifies the existence the specified permission target, i.e. whether it has
680
+ * a specification.
681
+ *
682
+ * @param target - The requested permission target.
683
+ * @returns Whether the permission target exists.
684
+ */
685
+ targetExists(target) {
686
+ return hasProperty(this._permissionSpecifications, target);
687
+ }
688
+ /**
689
+ * Grants _approved_ permissions to the specified subject. Every permission and
690
+ * caveat is stringently validated—including by calling their specification
691
+ * validators—and an error is thrown if validation fails.
692
+ *
693
+ * ATTN: This method does **not** prompt the user for approval. User consent must
694
+ * first be obtained through some other means.
695
+ *
696
+ * @see {@link PermissionController.requestPermissions} For initiating a
697
+ * permissions request requiring user approval.
698
+ * @param options - Options bag.
699
+ * @param options.approvedPermissions - The requested permissions approved by
700
+ * the user.
701
+ * @param options.requestData - Permission request data. Passed to permission
702
+ * factory functions.
703
+ * @param options.preserveExistingPermissions - Whether to preserve the
704
+ * subject's existing permissions.
705
+ * @param options.subject - The subject to grant permissions to.
706
+ * @returns The subject's new permission state. It may or may not have changed.
707
+ */
708
+ grantPermissions({ approvedPermissions, requestData, preserveExistingPermissions = true, subject, }) {
709
+ return __classPrivateFieldGet(this, _PermissionController_instances, "m", _PermissionController_applyGrantedPermissions).call(this, {
710
+ approvedPermissions,
711
+ subject,
712
+ mergePermissions: false,
713
+ preserveExistingPermissions,
714
+ requestData,
715
+ });
716
+ }
717
+ /**
718
+ * Incrementally grants _approved_ permissions to the specified subject. Every
719
+ * permission and caveat is stringently validated—including by calling their
720
+ * specification validators—and an error is thrown if validation fails.
721
+ *
722
+ * ATTN: This method does **not** prompt the user for approval. User consent must
723
+ * first be obtained through some other means.
724
+ *
725
+ * @see {@link PermissionController.requestPermissionsIncremental} For initiating
726
+ * an incremental permissions request requiring user approval.
727
+ * @param options - Options bag.
728
+ * @param options.approvedPermissions - The requested permissions approved by
729
+ * the user.
730
+ * @param options.requestData - Permission request data. Passed to permission
731
+ * factory functions.
732
+ * @param options.subject - The subject to grant permissions to.
733
+ * @returns The subject's new permission state. It may or may not have changed.
734
+ */
735
+ grantPermissionsIncremental({ approvedPermissions, requestData, subject, }) {
736
+ return __classPrivateFieldGet(this, _PermissionController_instances, "m", _PermissionController_applyGrantedPermissions).call(this, {
737
+ approvedPermissions,
738
+ subject,
739
+ mergePermissions: true,
740
+ preserveExistingPermissions: true,
741
+ requestData,
742
+ });
743
+ }
744
+ /**
745
+ * Validates the specified permission by:
746
+ * - Ensuring that if `subjectTypes` is specified, the subject requesting the permission is of a type in the list.
747
+ * - Ensuring that its `caveats` property is either `null` or a non-empty array.
748
+ * - Ensuring that it only includes caveats allowed by its specification.
749
+ * - Ensuring that it includes no duplicate caveats (by caveat type).
750
+ * - Validating each caveat object, if `performCaveatValidation` is `true`.
751
+ * - Calling the validator of its specification, if one exists and `invokePermissionValidator` is `true`.
752
+ *
753
+ * An error is thrown if validation fails.
754
+ *
755
+ * @param specification - The specification of the permission.
756
+ * @param permission - The permission to validate.
757
+ * @param origin - The origin associated with the permission.
758
+ * @param validationOptions - Validation options.
759
+ * @param validationOptions.invokePermissionValidator - Whether to invoke the
760
+ * permission's consumer-specified validator function, if any.
761
+ * @param validationOptions.performCaveatValidation - Whether to invoke
762
+ * {@link PermissionController.validateCaveat} on each of the permission's
763
+ * caveats.
764
+ */
765
+ validatePermission(specification, permission, origin, { invokePermissionValidator, performCaveatValidation } = {
766
+ invokePermissionValidator: true,
767
+ performCaveatValidation: true,
768
+ }) {
769
+ const { allowedCaveats, validator, targetName } = specification;
770
+ if (specification.subjectTypes?.length &&
771
+ specification.subjectTypes.length > 0) {
772
+ const metadata = this.messagingSystem.call('SubjectMetadataController:getSubjectMetadata', origin);
773
+ if (!metadata ||
774
+ metadata.subjectType === null ||
775
+ !specification.subjectTypes.includes(metadata.subjectType)) {
776
+ throw specification.permissionType === PermissionType.RestrictedMethod
777
+ ? methodNotFound(targetName, { origin })
778
+ : new EndowmentPermissionDoesNotExistError(targetName, origin);
779
+ }
780
+ }
781
+ if (hasProperty(permission, 'caveats')) {
782
+ const { caveats } = permission;
783
+ if (caveats !== null && !(Array.isArray(caveats) && caveats.length > 0)) {
784
+ throw new InvalidCaveatsPropertyError(origin, targetName, caveats);
785
+ }
786
+ const seenCaveatTypes = new Set();
787
+ caveats?.forEach((caveat) => {
788
+ if (performCaveatValidation) {
789
+ this.validateCaveat(caveat, origin, targetName);
790
+ }
791
+ if (!allowedCaveats?.includes(caveat.type)) {
792
+ throw new ForbiddenCaveatError(caveat.type, origin, targetName);
793
+ }
794
+ if (seenCaveatTypes.has(caveat.type)) {
795
+ throw new DuplicateCaveatError(caveat.type, origin, targetName);
796
+ }
797
+ seenCaveatTypes.add(caveat.type);
798
+ });
799
+ }
800
+ if (invokePermissionValidator && validator) {
801
+ validator(permission, origin, targetName);
802
+ }
803
+ }
804
+ /**
805
+ * Assigns the specified permissions to the subject with the given origin.
806
+ * Overwrites all existing permissions, and creates a subject entry if it
807
+ * doesn't already exist.
808
+ *
809
+ * ATTN: Assumes that the new permissions have been validated.
810
+ *
811
+ * @param origin - The origin of the grantee subject.
812
+ * @param permissions - The new permissions for the grantee subject.
813
+ */
814
+ setValidatedPermissions(origin, permissions) {
815
+ this.update((draftState) => {
816
+ if (!draftState.subjects[origin]) {
817
+ draftState.subjects[origin] = { origin, permissions: {} };
818
+ }
819
+ draftState.subjects[origin].permissions = castDraft(permissions);
820
+ });
821
+ }
822
+ /**
823
+ * Validates the requested caveats for the permission of the specified
824
+ * subject origin and target name and returns the validated caveat array.
825
+ *
826
+ * Throws an error if validation fails.
827
+ *
828
+ * @param origin - The origin of the permission subject.
829
+ * @param target - The permission target name.
830
+ * @param requestedCaveats - The requested caveats to construct.
831
+ * @returns The constructed caveats.
832
+ */
833
+ constructCaveats(origin, target, requestedCaveats) {
834
+ const caveatArray = requestedCaveats?.map((requestedCaveat) => {
835
+ this.validateCaveat(requestedCaveat, origin, target);
836
+ // Reassign so that we have a fresh object.
837
+ const { type, value } = requestedCaveat;
838
+ return { type, value };
839
+ });
840
+ return caveatArray && isNonEmptyArray(caveatArray)
841
+ ? caveatArray
842
+ : undefined;
843
+ }
844
+ /**
845
+ * This methods validates that the specified caveat is an object with the
846
+ * expected properties and types. It also ensures that a caveat specification
847
+ * exists for the requested caveat type, and calls the specification
848
+ * validator, if it exists, on the caveat object.
849
+ *
850
+ * Throws an error if validation fails.
851
+ *
852
+ * @param caveat - The caveat object to validate.
853
+ * @param origin - The origin associated with the subject of the parent
854
+ * permission.
855
+ * @param target - The target name associated with the parent permission.
856
+ */
857
+ validateCaveat(caveat, origin, target) {
858
+ if (!isPlainObject(caveat)) {
859
+ // eslint-disable-next-line @typescript-eslint/no-throw-literal
860
+ throw new InvalidCaveatError(caveat, origin, target);
861
+ }
862
+ if (Object.keys(caveat).length !== 2) {
863
+ throw new InvalidCaveatFieldsError(caveat, origin, target);
864
+ }
865
+ if (typeof caveat.type !== 'string') {
866
+ throw new InvalidCaveatTypeError(caveat, origin, target);
867
+ }
868
+ const specification = this.getCaveatSpecification(caveat.type);
869
+ if (!specification) {
870
+ throw new UnrecognizedCaveatTypeError(caveat.type, origin, target);
871
+ }
872
+ if (!hasProperty(caveat, 'value') || caveat.value === undefined) {
873
+ throw new CaveatMissingValueError(caveat, origin, target);
874
+ }
875
+ if (!isValidJson(caveat.value)) {
876
+ throw new CaveatInvalidJsonError(caveat, origin, target);
877
+ }
878
+ // Typecast: TypeScript still believes that the caveat is a PlainObject.
879
+ specification.validator?.(caveat, origin, target);
880
+ }
881
+ /**
882
+ * Initiates a permission request that requires user approval.
883
+ *
884
+ * Either this or {@link PermissionController.requestPermissionsIncremental}
885
+ * should always be used to grant additional permissions to a subject,
886
+ * unless user approval has been obtained through some other means.
887
+ *
888
+ * Permissions are validated at every step of the approval process, and this
889
+ * method will reject if validation fails.
890
+ *
891
+ * @see {@link ApprovalController} For the user approval logic.
892
+ * @see {@link PermissionController.acceptPermissionsRequest} For the method
893
+ * that _accepts_ the request and resolves the user approval promise.
894
+ * @see {@link PermissionController.rejectPermissionsRequest} For the method
895
+ * that _rejects_ the request and the user approval promise.
896
+ * @param subject - The grantee subject.
897
+ * @param requestedPermissions - The requested permissions.
898
+ * @param options - Additional options.
899
+ * @param options.id - The id of the permissions request. Defaults to a unique
900
+ * id.
901
+ * @param options.preserveExistingPermissions - Whether to preserve the
902
+ * subject's existing permissions. Defaults to `true`.
903
+ * @param options.metadata - Additional metadata about the permission request.
904
+ * @returns The granted permissions and request metadata.
905
+ */
906
+ async requestPermissions(subject, requestedPermissions, options = {}) {
907
+ const { origin } = subject;
908
+ const { id = nanoid(), preserveExistingPermissions = true } = options;
909
+ this.validateRequestedPermissions(origin, requestedPermissions);
910
+ const metadata = {
911
+ ...options.metadata,
912
+ id,
913
+ origin,
914
+ };
915
+ const permissionsRequest = {
916
+ metadata,
917
+ permissions: requestedPermissions,
918
+ };
919
+ const approvedRequest = await this.requestUserApproval(permissionsRequest);
920
+ return await __classPrivateFieldGet(this, _PermissionController_instances, "m", _PermissionController_handleApprovedPermissions).call(this, {
921
+ subject,
922
+ metadata,
923
+ preserveExistingPermissions,
924
+ approvedRequest,
925
+ });
926
+ }
927
+ /**
928
+ * Initiates an incremental permission request that prompts for user approval.
929
+ * Incremental permission requests allow the caller to replace existing and/or
930
+ * add brand new permissions and caveats for the specified subject.
931
+ *
932
+ * Incremental permission request are merged with the subject's existing permissions
933
+ * through a right-biased union, where the incremental permission are the right-hand
934
+ * side of the merger. If both sides of the merger specify the same caveats for a
935
+ * given permission, the caveats are merged using their specification's caveat value
936
+ * merger property.
937
+ *
938
+ * Either this or {@link PermissionController.requestPermissions} should
939
+ * always be used to grant additional permissions to a subject, unless user
940
+ * approval has been obtained through some other means.
941
+ *
942
+ * Permissions are validated at every step of the approval process, and this
943
+ * method will reject if validation fails.
944
+ *
945
+ * @see {@link ApprovalController} For the user approval logic.
946
+ * @see {@link PermissionController.acceptPermissionsRequest} For the method
947
+ * that _accepts_ the request and resolves the user approval promise.
948
+ * @see {@link PermissionController.rejectPermissionsRequest} For the method
949
+ * that _rejects_ the request and the user approval promise.
950
+ * @param subject - The grantee subject.
951
+ * @param requestedPermissions - The requested permissions.
952
+ * @param options - Additional options.
953
+ * @param options.id - The id of the permissions request. Defaults to a unique
954
+ * id.
955
+ * @param options.metadata - Additional metadata about the permission request.
956
+ * @returns The granted permissions and request metadata.
957
+ */
958
+ async requestPermissionsIncremental(subject, requestedPermissions, options = {}) {
959
+ const { origin } = subject;
960
+ const { id = nanoid() } = options;
961
+ this.validateRequestedPermissions(origin, requestedPermissions);
962
+ const currentPermissions = this.getPermissions(origin) ?? {};
963
+ const [newPermissions, permissionDiffMap] = __classPrivateFieldGet(this, _PermissionController_instances, "m", _PermissionController_mergeIncrementalPermissions).call(this, currentPermissions, requestedPermissions);
964
+ // The second undefined check is just for type narrowing purposes. These values
965
+ // will always be jointly defined or undefined.
966
+ if (newPermissions === undefined || permissionDiffMap === undefined) {
967
+ return [];
968
+ }
969
+ try {
970
+ // It does not spark joy to run this validation again after the merger operation.
971
+ // But, optimizing this procedure is probably not worth it, especially considering
972
+ // that the worst-case scenario for validation degrades to the below function call.
973
+ this.validateRequestedPermissions(origin, newPermissions);
974
+ }
975
+ catch (error) {
976
+ if (error instanceof Error) {
977
+ throw new InvalidMergedPermissionsError(origin, error, permissionDiffMap);
978
+ }
979
+ /* istanbul ignore next: This should be impossible */
980
+ throw internalError('Unrecognized error type', { error });
981
+ }
982
+ const metadata = {
983
+ ...options.metadata,
984
+ id,
985
+ origin,
986
+ };
987
+ const permissionsRequest = {
988
+ metadata,
989
+ permissions: newPermissions,
990
+ diff: {
991
+ currentPermissions,
992
+ permissionDiffMap,
993
+ },
994
+ };
995
+ const approvedRequest = await this.requestUserApproval(permissionsRequest);
996
+ return await __classPrivateFieldGet(this, _PermissionController_instances, "m", _PermissionController_handleApprovedPermissions).call(this, {
997
+ subject,
998
+ metadata,
999
+ preserveExistingPermissions: false,
1000
+ approvedRequest,
1001
+ });
1002
+ }
1003
+ /**
1004
+ * Validates requested permissions. Throws if validation fails.
1005
+ *
1006
+ * This method ensures that the requested permissions are a properly
1007
+ * formatted {@link RequestedPermissions} object, and performs the same
1008
+ * validation as {@link PermissionController.grantPermissions}, except that
1009
+ * consumer-specified permission validator functions are not called, since
1010
+ * they are only called on fully constructed, approved permissions that are
1011
+ * otherwise completely valid.
1012
+ *
1013
+ * Unrecognzied properties on requested permissions are ignored.
1014
+ *
1015
+ * @param origin - The origin of the grantee subject.
1016
+ * @param requestedPermissions - The requested permissions.
1017
+ */
1018
+ validateRequestedPermissions(origin, requestedPermissions) {
1019
+ if (!isPlainObject(requestedPermissions)) {
1020
+ throw invalidParams({
1021
+ message: `Requested permissions for origin "${origin}" is not a plain object.`,
1022
+ data: { origin, requestedPermissions },
1023
+ });
1024
+ }
1025
+ if (Object.keys(requestedPermissions).length === 0) {
1026
+ throw invalidParams({
1027
+ message: `Permissions request for origin "${origin}" contains no permissions.`,
1028
+ data: { requestedPermissions },
1029
+ });
1030
+ }
1031
+ for (const targetName of Object.keys(requestedPermissions)) {
1032
+ const permission = requestedPermissions[targetName];
1033
+ if (!this.targetExists(targetName)) {
1034
+ throw methodNotFound(targetName, { origin, requestedPermissions });
1035
+ }
1036
+ if (!isPlainObject(permission) ||
1037
+ (permission.parentCapability !== undefined &&
1038
+ targetName !== permission.parentCapability)) {
1039
+ throw invalidParams({
1040
+ message: `Permissions request for origin "${origin}" contains invalid requested permission(s).`,
1041
+ data: { origin, requestedPermissions },
1042
+ });
1043
+ }
1044
+ // Here we validate the permission without invoking its validator, if any.
1045
+ // The validator will be invoked after the permission has been approved.
1046
+ this.validatePermission(this.getPermissionSpecification(targetName),
1047
+ // Typecast: The permission is still a "PlainObject" here.
1048
+ permission, origin, { invokePermissionValidator: false, performCaveatValidation: true });
1049
+ }
1050
+ }
1051
+ /**
1052
+ * Adds a request to the {@link ApprovalController} using the
1053
+ * {@link AddApprovalRequest} action. Also validates the resulting approved
1054
+ * permissions request, and throws an error if validation fails.
1055
+ *
1056
+ * @param permissionsRequest - The permissions request object.
1057
+ * @returns The approved permissions request object.
1058
+ */
1059
+ async requestUserApproval(permissionsRequest) {
1060
+ const { origin, id } = permissionsRequest.metadata;
1061
+ const approvedRequest = await this.messagingSystem.call('ApprovalController:addRequest', {
1062
+ id,
1063
+ origin,
1064
+ requestData: permissionsRequest,
1065
+ type: MethodNames.RequestPermissions,
1066
+ }, true);
1067
+ this.validateApprovedPermissions(approvedRequest, { id, origin });
1068
+ return approvedRequest;
1069
+ }
1070
+ /**
1071
+ * Reunites all the side-effects (onPermitted and onFailure) of the requested permissions inside a record of arrays.
1072
+ *
1073
+ * @param permissions - The approved permissions.
1074
+ * @returns The {@link SideEffects} object containing the handlers arrays.
1075
+ */
1076
+ getSideEffects(permissions) {
1077
+ return Object.keys(permissions).reduce((sideEffectList, targetName) => {
1078
+ if (this.targetExists(targetName)) {
1079
+ const specification = this.getPermissionSpecification(targetName);
1080
+ if (specification.sideEffect) {
1081
+ sideEffectList.permittedHandlers[targetName] =
1082
+ specification.sideEffect.onPermitted;
1083
+ if (specification.sideEffect.onFailure) {
1084
+ sideEffectList.failureHandlers[targetName] =
1085
+ specification.sideEffect.onFailure;
1086
+ }
1087
+ }
1088
+ }
1089
+ return sideEffectList;
1090
+ }, { permittedHandlers: {}, failureHandlers: {} });
1091
+ }
1092
+ /**
1093
+ * Executes the side-effects of the approved permissions while handling the errors if any.
1094
+ * It will pass an instance of the {@link messagingSystem} and the request data associated with the permission request to the handlers through its params.
1095
+ *
1096
+ * @param sideEffects - the side-effect record created by {@link getSideEffects}
1097
+ * @param requestData - the permissions requestData.
1098
+ * @returns the value returned by all the `onPermitted` handlers in an array.
1099
+ */
1100
+ async executeSideEffects(sideEffects, requestData) {
1101
+ const { permittedHandlers, failureHandlers } = sideEffects;
1102
+ const params = {
1103
+ requestData,
1104
+ messagingSystem: this.messagingSystem,
1105
+ };
1106
+ const promiseResults = await Promise.allSettled(Object.values(permittedHandlers).map((permittedHandler) => permittedHandler(params)));
1107
+ // lib.es2020.promise.d.ts does not export its types so we're using a simple type.
1108
+ const rejectedHandlers = promiseResults.filter((promise) => promise.status === 'rejected');
1109
+ if (rejectedHandlers.length > 0) {
1110
+ const failureHandlersList = Object.values(failureHandlers);
1111
+ if (failureHandlersList.length > 0) {
1112
+ try {
1113
+ await Promise.all(failureHandlersList.map((failureHandler) => failureHandler(params)));
1114
+ }
1115
+ catch (error) {
1116
+ throw internalError('Unexpected error in side-effects', { error });
1117
+ }
1118
+ }
1119
+ const reasons = rejectedHandlers.map((handler) => handler.reason);
1120
+ reasons.forEach((reason) => {
1121
+ console.error(reason);
1122
+ });
1123
+ throw reasons.length > 1
1124
+ ? internalError('Multiple errors occurred during side-effects execution', { errors: reasons })
1125
+ : reasons[0];
1126
+ }
1127
+ // lib.es2020.promise.d.ts does not export its types so we're using a simple type.
1128
+ return promiseResults.map(({ value }) => value);
1129
+ }
1130
+ /**
1131
+ * Validates an approved {@link PermissionsRequest} object. The approved
1132
+ * request must have the required `metadata` and `permissions` properties,
1133
+ * the `id` and `origin` of the `metadata` must match the original request
1134
+ * metadata, and the requested permissions must be valid per
1135
+ * {@link PermissionController.validateRequestedPermissions}. Any extra
1136
+ * metadata properties are ignored.
1137
+ *
1138
+ * An error is thrown if validation fails.
1139
+ *
1140
+ * @param approvedRequest - The approved permissions request object.
1141
+ * @param originalMetadata - The original request metadata.
1142
+ */
1143
+ validateApprovedPermissions(approvedRequest, originalMetadata) {
1144
+ const { id, origin } = originalMetadata;
1145
+ if (!isPlainObject(approvedRequest) ||
1146
+ !isPlainObject(approvedRequest.metadata)) {
1147
+ throw internalError(`Approved permissions request for subject "${origin}" is invalid.`, { data: { approvedRequest } });
1148
+ }
1149
+ const { metadata: { id: newId, origin: newOrigin }, permissions, } = approvedRequest;
1150
+ if (newId !== id) {
1151
+ throw internalError(`Approved permissions request for subject "${origin}" mutated its id.`, { originalId: id, mutatedId: newId });
1152
+ }
1153
+ if (newOrigin !== origin) {
1154
+ throw internalError(`Approved permissions request for subject "${origin}" mutated its origin.`, { originalOrigin: origin, mutatedOrigin: newOrigin });
1155
+ }
1156
+ try {
1157
+ this.validateRequestedPermissions(origin, permissions);
1158
+ }
1159
+ catch (error) {
1160
+ if (error instanceof Error) {
1161
+ // Re-throw as an internal error; we should never receive invalid approved
1162
+ // permissions.
1163
+ throw internalError(`Invalid approved permissions request: ${error.message}`, error instanceof JsonRpcError ? error.data : undefined);
1164
+ }
1165
+ /* istanbul ignore next: This should be impossible */
1166
+ throw internalError('Unrecognized error type', { error });
1167
+ }
1168
+ }
1169
+ /**
1170
+ * Accepts a permissions request created by
1171
+ * {@link PermissionController.requestPermissions}.
1172
+ *
1173
+ * @param request - The permissions request.
1174
+ */
1175
+ async acceptPermissionsRequest(request) {
1176
+ const { id } = request.metadata;
1177
+ if (!this.hasApprovalRequest({ id })) {
1178
+ throw new PermissionsRequestNotFoundError(id);
1179
+ }
1180
+ if (Object.keys(request.permissions).length === 0) {
1181
+ this._rejectPermissionsRequest(id, invalidParams({
1182
+ message: 'Must request at least one permission.',
1183
+ }));
1184
+ return;
1185
+ }
1186
+ try {
1187
+ await this.messagingSystem.call('ApprovalController:acceptRequest', id, request);
1188
+ }
1189
+ catch (error) {
1190
+ // If accepting unexpectedly fails, reject the request and re-throw the
1191
+ // error
1192
+ this._rejectPermissionsRequest(id, error);
1193
+ throw error;
1194
+ }
1195
+ }
1196
+ /**
1197
+ * Rejects a permissions request created by
1198
+ * {@link PermissionController.requestPermissions}.
1199
+ *
1200
+ * @param id - The id of the request to be rejected.
1201
+ */
1202
+ async rejectPermissionsRequest(id) {
1203
+ if (!this.hasApprovalRequest({ id })) {
1204
+ throw new PermissionsRequestNotFoundError(id);
1205
+ }
1206
+ this._rejectPermissionsRequest(id, userRejectedRequest());
1207
+ }
1208
+ /**
1209
+ * Checks whether the {@link ApprovalController} has a particular permissions
1210
+ * request.
1211
+ *
1212
+ * @see {@link PermissionController.acceptPermissionsRequest} and
1213
+ * {@link PermissionController.rejectPermissionsRequest} for usage.
1214
+ * @param options - The {@link HasApprovalRequest} options.
1215
+ * @param options.id - The id of the approval request to check for.
1216
+ * @returns Whether the specified request exists.
1217
+ */
1218
+ hasApprovalRequest(options) {
1219
+ return this.messagingSystem.call('ApprovalController:hasRequest', options);
1220
+ }
1221
+ /**
1222
+ * Rejects the permissions request with the specified id, with the specified
1223
+ * error as the reason. This method is effectively a wrapper around a
1224
+ * messenger call for the `ApprovalController:rejectRequest` action.
1225
+ *
1226
+ * @see {@link PermissionController.acceptPermissionsRequest} and
1227
+ * {@link PermissionController.rejectPermissionsRequest} for usage.
1228
+ * @param id - The id of the request to reject.
1229
+ * @param error - The error associated with the rejection.
1230
+ * @returns Nothing
1231
+ */
1232
+ _rejectPermissionsRequest(id, error) {
1233
+ return this.messagingSystem.call('ApprovalController:rejectRequest', id, error);
1234
+ }
1235
+ /**
1236
+ * Gets the subject's endowments per the specified endowment permission.
1237
+ * Throws if the subject does not have the required permission or if the
1238
+ * permission is not an endowment permission.
1239
+ *
1240
+ * @param origin - The origin of the subject whose endowments to retrieve.
1241
+ * @param targetName - The name of the endowment permission. This must be a
1242
+ * valid permission target name.
1243
+ * @param requestData - Additional data associated with the request, if any.
1244
+ * Forwarded to the endowment getter function for the permission.
1245
+ * @returns The endowments, if any.
1246
+ */
1247
+ async getEndowments(origin, targetName, requestData) {
1248
+ if (!this.hasPermission(origin, targetName)) {
1249
+ throw unauthorized({ data: { origin, targetName } });
1250
+ }
1251
+ return this.getTypedPermissionSpecification(PermissionType.Endowment, targetName, origin).endowmentGetter({ origin, requestData });
1252
+ }
1253
+ /**
1254
+ * Executes a restricted method as the subject with the given origin.
1255
+ * The specified params, if any, will be passed to the method implementation.
1256
+ *
1257
+ * ATTN: Great caution should be exercised in the use of this method.
1258
+ * Methods that cause side effects or affect application state should
1259
+ * be avoided.
1260
+ *
1261
+ * This method will first attempt to retrieve the requested restricted method
1262
+ * implementation, throwing if it does not exist. The method will then be
1263
+ * invoked as though the subject with the specified origin had invoked it with
1264
+ * the specified parameters. This means that any existing caveats will be
1265
+ * applied to the restricted method, and this method will throw if the
1266
+ * restricted method or its caveat decorators throw.
1267
+ *
1268
+ * In addition, this method will throw if the subject does not have a
1269
+ * permission for the specified restricted method.
1270
+ *
1271
+ * @param origin - The origin of the subject to execute the method on behalf
1272
+ * of.
1273
+ * @param targetName - The name of the method to execute. This must be a valid
1274
+ * permission target name.
1275
+ * @param params - The parameters to pass to the method implementation.
1276
+ * @returns The result of the executed method.
1277
+ */
1278
+ async executeRestrictedMethod(origin, targetName, params) {
1279
+ // Throws if the method does not exist
1280
+ const methodImplementation = this.getRestrictedMethod(targetName, origin);
1281
+ const result = await this._executeRestrictedMethod(methodImplementation, { origin }, targetName, params);
1282
+ if (result === undefined) {
1283
+ throw new Error(`Internal request for method "${targetName}" as origin "${origin}" returned no result.`);
1284
+ }
1285
+ return result;
1286
+ }
1287
+ /**
1288
+ * An internal method used in the controller's `json-rpc-engine` middleware
1289
+ * and {@link PermissionController.executeRestrictedMethod}. Calls the
1290
+ * specified restricted method implementation after decorating it with the
1291
+ * caveats of its permission. Throws if the subject does not have the
1292
+ * requisite permission.
1293
+ *
1294
+ * ATTN: Parameter validation is the responsibility of the caller, or
1295
+ * the restricted method implementation in the case of `params`.
1296
+ *
1297
+ * @see {@link PermissionController.executeRestrictedMethod} and
1298
+ * {@link PermissionController.createPermissionMiddleware} for usage.
1299
+ * @param methodImplementation - The implementation of the method to call.
1300
+ * @param subject - Metadata about the subject that made the request.
1301
+ * @param method - The method name
1302
+ * @param params - Params needed for executing the restricted method
1303
+ * @returns The result of the restricted method implementation
1304
+ */
1305
+ _executeRestrictedMethod(methodImplementation, subject, method, params = []) {
1306
+ const { origin } = subject;
1307
+ const permission = this.getPermission(origin, method);
1308
+ if (!permission) {
1309
+ throw unauthorized({ data: { origin, method } });
1310
+ }
1311
+ return decorateWithCaveats(methodImplementation, permission, this._caveatSpecifications)({ method, params, context: { origin } });
1312
+ }
1313
+ }
1314
+ _PermissionController_instances = new WeakSet(), _PermissionController_expectGetCaveatMerger = function _PermissionController_expectGetCaveatMerger(caveatType) {
1315
+ const { merger } = this.getCaveatSpecification(caveatType);
1316
+ if (merger === undefined) {
1317
+ throw new CaveatMergerDoesNotExistError(caveatType);
1318
+ }
1319
+ return merger;
1320
+ }, _PermissionController_applyGrantedPermissions = function _PermissionController_applyGrantedPermissions({ approvedPermissions, subject, mergePermissions, preserveExistingPermissions, requestData, }) {
1321
+ const { origin } = subject;
1322
+ if (!origin || typeof origin !== 'string') {
1323
+ throw new InvalidSubjectIdentifierError(origin);
1324
+ }
1325
+ const permissions = (preserveExistingPermissions
1326
+ ? {
1327
+ ...this.getPermissions(origin),
1328
+ }
1329
+ : {});
1330
+ for (const [requestedTarget, approvedPermission] of Object.entries(approvedPermissions)) {
1331
+ if (!this.targetExists(requestedTarget)) {
1332
+ throw methodNotFound(requestedTarget);
1333
+ }
1334
+ if (approvedPermission.parentCapability !== undefined &&
1335
+ requestedTarget !== approvedPermission.parentCapability) {
1336
+ throw new InvalidApprovedPermissionError(origin, requestedTarget, approvedPermission);
1337
+ }
1338
+ // We have verified that the target exists, and reassign it to change its
1339
+ // type.
1340
+ const targetName = requestedTarget;
1341
+ const specification = this.getPermissionSpecification(targetName);
1342
+ // The requested caveats are validated here.
1343
+ const caveats = this.constructCaveats(origin, targetName, approvedPermission.caveats);
1344
+ const permissionOptions = {
1345
+ caveats,
1346
+ invoker: origin,
1347
+ target: targetName,
1348
+ };
1349
+ let permission;
1350
+ let performCaveatValidation = true;
1351
+ if (specification.factory) {
1352
+ permission = specification.factory(permissionOptions, requestData);
1353
+ }
1354
+ else {
1355
+ permission = constructPermission(permissionOptions);
1356
+ // We do not need to validate caveats in this case, because the plain
1357
+ // permission constructor function does not modify the caveats, which
1358
+ // were already validated by `constructCaveats` above.
1359
+ performCaveatValidation = false;
1360
+ }
1361
+ if (mergePermissions) {
1362
+ permission = __classPrivateFieldGet(this, _PermissionController_instances, "m", _PermissionController_mergePermission).call(this, permissions[targetName], permission)[0];
1363
+ }
1364
+ this.validatePermission(specification, permission, origin, {
1365
+ invokePermissionValidator: true,
1366
+ performCaveatValidation,
1367
+ });
1368
+ permissions[targetName] = permission;
1369
+ }
1370
+ this.setValidatedPermissions(origin, permissions);
1371
+ return permissions;
1372
+ }, _PermissionController_mergeIncrementalPermissions = function _PermissionController_mergeIncrementalPermissions(existingPermissions, incrementalRequestedPermissions) {
1373
+ const permissionDiffMap = {};
1374
+ // Use immer's produce as a convenience for calculating the new permissions
1375
+ // without mutating the existing permissions or committing the results to state.
1376
+ const newPermissions = immerProduce(existingPermissions, (draftExistingPermissions) => {
1377
+ const leftPermissions = draftExistingPermissions;
1378
+ Object.entries(incrementalRequestedPermissions).forEach(([targetName, rightPermission]) => {
1379
+ const leftPermission = leftPermissions[targetName];
1380
+ const [newPermission, caveatsDiff] = __classPrivateFieldGet(this, _PermissionController_instances, "m", _PermissionController_mergePermission).call(this, leftPermission ?? {}, rightPermission);
1381
+ if (leftPermission === undefined ||
1382
+ Object.keys(caveatsDiff).length > 0) {
1383
+ leftPermissions[targetName] = newPermission;
1384
+ permissionDiffMap[targetName] = caveatsDiff;
1385
+ }
1386
+ // Otherwise, leave the left permission as-is; its authority has
1387
+ // not changed.
1388
+ });
1389
+ });
1390
+ if (Object.keys(permissionDiffMap).length === 0) {
1391
+ return [];
1392
+ }
1393
+ return [newPermissions, permissionDiffMap];
1394
+ }, _PermissionController_mergePermission = function _PermissionController_mergePermission(leftPermission, rightPermission) {
1395
+ const { caveatPairs, leftUniqueCaveats, rightUniqueCaveats } = collectUniqueAndPairedCaveats(leftPermission, rightPermission);
1396
+ const [mergedCaveats, caveatDiffMap] = caveatPairs.reduce(([caveats, diffMap], [leftCaveat, rightCaveat]) => {
1397
+ const [newCaveat, diff] = __classPrivateFieldGet(this, _PermissionController_instances, "m", _PermissionController_mergeCaveat).call(this, leftCaveat, rightCaveat);
1398
+ if (newCaveat !== undefined && diff !== undefined) {
1399
+ caveats.push(newCaveat);
1400
+ diffMap[newCaveat.type] = diff;
1401
+ }
1402
+ else {
1403
+ caveats.push(leftCaveat);
1404
+ }
1405
+ return [caveats, diffMap];
1406
+ }, [[], {}]);
1407
+ const mergedRightUniqueCaveats = rightUniqueCaveats.map((caveat) => {
1408
+ const [newCaveat, diff] = __classPrivateFieldGet(this, _PermissionController_instances, "m", _PermissionController_mergeCaveat).call(this, undefined, caveat);
1409
+ caveatDiffMap[newCaveat.type] = diff;
1410
+ return newCaveat;
1411
+ });
1412
+ const allCaveats = [
1413
+ ...mergedCaveats,
1414
+ ...leftUniqueCaveats,
1415
+ ...mergedRightUniqueCaveats,
1416
+ ];
1417
+ const newPermission = {
1418
+ ...leftPermission,
1419
+ ...rightPermission,
1420
+ ...(allCaveats.length > 0
1421
+ ? { caveats: allCaveats }
1422
+ : {}),
1423
+ };
1424
+ return [newPermission, caveatDiffMap];
1425
+ }, _PermissionController_mergeCaveat = function _PermissionController_mergeCaveat(leftCaveat, rightCaveat) {
1426
+ /* istanbul ignore if: This should be impossible */
1427
+ if (leftCaveat !== undefined && leftCaveat.type !== rightCaveat.type) {
1428
+ throw new CaveatMergeTypeMismatchError(leftCaveat.type, rightCaveat.type);
1429
+ }
1430
+ const merger = __classPrivateFieldGet(this, _PermissionController_instances, "m", _PermissionController_expectGetCaveatMerger).call(this, rightCaveat.type);
1431
+ if (leftCaveat === undefined) {
1432
+ return [
1433
+ {
1434
+ ...rightCaveat,
1435
+ },
1436
+ rightCaveat.value,
1437
+ ];
1438
+ }
1439
+ const [newValue, diff] = merger(leftCaveat.value, rightCaveat.value);
1440
+ return newValue !== undefined && diff !== undefined
1441
+ ? [
1442
+ {
1443
+ type: rightCaveat.type,
1444
+ value: newValue,
1445
+ },
1446
+ diff,
1447
+ ]
1448
+ : [];
1449
+ }, _PermissionController_handleApprovedPermissions =
1450
+ /**
1451
+ * Accepts a permissions request that has been approved by the user. This
1452
+ * method should be called after the user has approved the request and the
1453
+ * {@link ApprovalController} has resolved the user approval promise.
1454
+ *
1455
+ * @param options - Options bag.
1456
+ * @param options.subject - The subject to grant permissions to.
1457
+ * @param options.metadata - The metadata of the approved permissions request.
1458
+ * @param options.preserveExistingPermissions - Whether to preserve the
1459
+ * subject's existing permissions.
1460
+ * @param options.approvedRequest - The approved permissions request to handle.
1461
+ * @returns The granted permissions and request metadata.
1462
+ */
1463
+ async function _PermissionController_handleApprovedPermissions({ subject, metadata, preserveExistingPermissions, approvedRequest, }) {
1464
+ const { permissions: approvedPermissions, ...requestData } = approvedRequest;
1465
+ const approvedMetadata = { ...metadata };
1466
+ const sideEffects = this.getSideEffects(approvedPermissions);
1467
+ if (Object.values(sideEffects.permittedHandlers).length > 0) {
1468
+ const sideEffectsData = await this.executeSideEffects(sideEffects, approvedRequest);
1469
+ approvedMetadata.data = Object.keys(sideEffects.permittedHandlers).reduce((acc, permission, i) => ({ [permission]: sideEffectsData[i], ...acc }), {});
1470
+ }
1471
+ return [
1472
+ this.grantPermissions({
1473
+ subject,
1474
+ approvedPermissions,
1475
+ preserveExistingPermissions,
1476
+ requestData,
1477
+ }),
1478
+ approvedMetadata,
1479
+ ];
14
1480
  };
15
1481
  //# sourceMappingURL=PermissionController.mjs.map