Socket
Socket
Sign inDemoInstall

@metamask/permission-controller

Package Overview
Dependencies
Maintainers
12
Versions
29
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@metamask/permission-controller - npm Package Compare versions

Comparing version 8.0.1 to 9.0.0

dist/Caveat.mjs

24

CHANGELOG.md

@@ -10,2 +10,23 @@ # Changelog

## [9.0.0]
### Added
- **BREAKING**: Add ESM build ([#3998](https://github.com/MetaMask/core/pull/3998))
- It's no longer possible to import files from `./dist` directly.
### Changed
- **BREAKING:** Bump peer dependency on `@metamask/approval-controller` to `^6.0.0` ([#4039](https://github.com/MetaMask/core/pull/4039))
- **BREAKING:** Bump `@metamask/base-controller` to `^5.0.0` ([#4039](https://github.com/MetaMask/core/pull/4039))
- This version has a number of breaking changes. See the changelog for more.
- Bump `@metamask/controller-utils` to `^9.0.0` ([#4039](https://github.com/MetaMask/core/pull/4039))
- Bump `@metamask/json-rpc-engine` to `^8.0.0` ([#4039](https://github.com/MetaMask/core/pull/4039))
### Fixed
- **BREAKING:** Fix `SideEffectMessenger` so that it's defined with a `RestrictedControllerMessenger` that has access to `PermissionController` allowed actions ([#4031](https://github.com/MetaMask/core/pull/4031))
- The messenger's `Action` generic parameter is widened to include the `PermissionController` actions allowlist.
- The messenger's `AllowedAction` generic parameter is narrowed from `string` to the `PermissionController` actions allowlist.
## [8.0.1]

@@ -189,3 +210,4 @@

[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/permission-controller@8.0.1...HEAD
[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/permission-controller@9.0.0...HEAD
[9.0.0]: https://github.com/MetaMask/core/compare/@metamask/permission-controller@8.0.1...@metamask/permission-controller@9.0.0
[8.0.1]: https://github.com/MetaMask/core/compare/@metamask/permission-controller@8.0.0...@metamask/permission-controller@8.0.1

@@ -192,0 +214,0 @@ [8.0.0]: https://github.com/MetaMask/core/compare/@metamask/permission-controller@7.1.0...@metamask/permission-controller@8.0.0

67

dist/Caveat.js

@@ -1,57 +0,12 @@

"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.decorateWithCaveats = exports.isRestrictedMethodCaveatSpecification = void 0;
const utils_1 = require("@metamask/utils");
const errors_1 = require("./errors");
const Permission_1 = require("./Permission");
/**
* Determines whether a caveat specification is a restricted method caveat specification.
*
* @param specification - The caveat specification.
* @returns True if the caveat specification is a restricted method caveat specification, otherwise false.
*/
function isRestrictedMethodCaveatSpecification(specification) {
return (0, utils_1.hasProperty)(specification, 'decorator');
}
exports.isRestrictedMethodCaveatSpecification = isRestrictedMethodCaveatSpecification;
/**
* Decorate a restricted method implementation with its caveats.
*
* Note that all caveat functions (i.e. the argument and return value of the
* decorator) must be awaited.
*
* @param methodImplementation - The restricted method implementation
* @param permission - The origin's potential permission
* @param caveatSpecifications - All caveat implementations
* @returns The decorated method implementation
*/
function decorateWithCaveats(methodImplementation, permission, // bound to the requesting origin
caveatSpecifications) {
const { caveats } = permission;
if (!caveats) {
return methodImplementation;
}
let decorated = (args) => __awaiter(this, void 0, void 0, function* () { return methodImplementation(args); });
for (const caveat of caveats) {
const specification = caveatSpecifications[caveat.type];
if (!specification) {
throw new errors_1.UnrecognizedCaveatTypeError(caveat.type);
}
if (!isRestrictedMethodCaveatSpecification(specification)) {
throw new errors_1.CaveatSpecificationMismatchError(specification, Permission_1.PermissionType.RestrictedMethod);
}
decorated = specification.decorator(decorated, caveat);
}
return decorated;
}
exports.decorateWithCaveats = decorateWithCaveats;
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
var _chunkEGNDXGRGjs = require('./chunk-EGNDXGRG.js');
require('./chunk-6CID6TBW.js');
require('./chunk-U5LTEXSU.js');
require('./chunk-CSAU5B4Q.js');
exports.decorateWithCaveats = _chunkEGNDXGRGjs.decorateWithCaveats; exports.isRestrictedMethodCaveatSpecification = _chunkEGNDXGRGjs.isRestrictedMethodCaveatSpecification;
//# sourceMappingURL=Caveat.js.map

@@ -1,196 +0,52 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PermissionsRequestNotFoundError = exports.CaveatSpecificationMismatchError = exports.DuplicateCaveatError = exports.ForbiddenCaveatError = exports.InvalidCaveatFieldsError = exports.CaveatInvalidJsonError = exports.CaveatMissingValueError = exports.InvalidCaveatTypeError = exports.InvalidCaveatError = exports.CaveatAlreadyExistsError = exports.CaveatDoesNotExistError = exports.InvalidCaveatsPropertyError = exports.UnrecognizedCaveatTypeError = exports.EndowmentPermissionDoesNotExistError = exports.PermissionDoesNotExistError = exports.InvalidApprovedPermissionError = exports.UnrecognizedSubjectError = exports.InvalidSubjectIdentifierError = exports.internalError = exports.userRejectedRequest = exports.invalidParams = exports.methodNotFound = exports.unauthorized = void 0;
const rpc_errors_1 = require("@metamask/rpc-errors");
/**
* Utility function for building an "unauthorized" error.
*
* @param opts - Optional arguments that add extra context
* @returns The built error
*/
function unauthorized(opts) {
return rpc_errors_1.providerErrors.unauthorized({
message: 'Unauthorized to perform action. Try requesting the required permission(s) first. For more information, see: https://docs.metamask.io/guide/rpc-api.html#permissions',
data: opts.data,
});
}
exports.unauthorized = unauthorized;
/**
* Utility function for building a "method not found" error.
*
* @param method - The method in question.
* @param data - Optional data for context.
* @returns The built error
*/
function methodNotFound(method, data) {
const message = `The method "${method}" does not exist / is not available.`;
const opts = { message };
if (data !== undefined) {
opts.data = data;
}
return rpc_errors_1.rpcErrors.methodNotFound(opts);
}
exports.methodNotFound = methodNotFound;
/**
* Utility function for building an "invalid params" error.
*
* @param opts - Optional arguments that add extra context
* @returns The built error
*/
function invalidParams(opts) {
return rpc_errors_1.rpcErrors.invalidParams({
data: opts.data,
message: opts.message,
});
}
exports.invalidParams = invalidParams;
/**
* Utility function for building an "user rejected request" error.
*
* @param data - Optional data to add extra context
* @returns The built error
*/
function userRejectedRequest(data) {
return rpc_errors_1.providerErrors.userRejectedRequest({ data });
}
exports.userRejectedRequest = userRejectedRequest;
/**
* Utility function for building an internal error.
*
* @param message - The error message
* @param data - Optional data to add extra context
* @returns The built error
*/
function internalError(message, data) {
return rpc_errors_1.rpcErrors.internal({ message, data });
}
exports.internalError = internalError;
class InvalidSubjectIdentifierError extends Error {
constructor(origin) {
super(`Invalid subject identifier: "${typeof origin === 'string' ? origin : typeof origin}"`);
}
}
exports.InvalidSubjectIdentifierError = InvalidSubjectIdentifierError;
class UnrecognizedSubjectError extends Error {
constructor(origin) {
super(`Unrecognized subject: "${origin}" has no permissions.`);
}
}
exports.UnrecognizedSubjectError = UnrecognizedSubjectError;
class InvalidApprovedPermissionError extends Error {
constructor(origin, target, approvedPermission) {
super(`Invalid approved permission for origin "${origin}" and target "${target}".`);
this.data = { origin, target, approvedPermission };
}
}
exports.InvalidApprovedPermissionError = InvalidApprovedPermissionError;
class PermissionDoesNotExistError extends Error {
constructor(origin, target) {
super(`Subject "${origin}" has no permission for "${target}".`);
}
}
exports.PermissionDoesNotExistError = PermissionDoesNotExistError;
class EndowmentPermissionDoesNotExistError extends Error {
constructor(target, origin) {
super(`Subject "${origin}" has no permission for "${target}".`);
if (origin) {
this.data = { origin };
}
}
}
exports.EndowmentPermissionDoesNotExistError = EndowmentPermissionDoesNotExistError;
class UnrecognizedCaveatTypeError extends Error {
constructor(caveatType, origin, target) {
super(`Unrecognized caveat type: "${caveatType}"`);
this.data = { caveatType };
if (origin !== undefined) {
this.data.origin = origin;
}
if (target !== undefined) {
this.data.target = target;
}
}
}
exports.UnrecognizedCaveatTypeError = UnrecognizedCaveatTypeError;
class InvalidCaveatsPropertyError extends Error {
constructor(origin, target, caveatsProperty) {
super(`The "caveats" property of permission for "${target}" of subject "${origin}" is invalid. It must be a non-empty array if specified.`);
this.data = { origin, target, caveatsProperty };
}
}
exports.InvalidCaveatsPropertyError = InvalidCaveatsPropertyError;
class CaveatDoesNotExistError extends Error {
constructor(origin, target, caveatType) {
super(`Permission for "${target}" of subject "${origin}" has no caveat of type "${caveatType}".`);
}
}
exports.CaveatDoesNotExistError = CaveatDoesNotExistError;
class CaveatAlreadyExistsError extends Error {
constructor(origin, target, caveatType) {
super(`Permission for "${target}" of subject "${origin}" already has a caveat of type "${caveatType}".`);
}
}
exports.CaveatAlreadyExistsError = CaveatAlreadyExistsError;
class InvalidCaveatError extends rpc_errors_1.JsonRpcError {
constructor(receivedCaveat, origin, target) {
super(rpc_errors_1.errorCodes.rpc.invalidParams, `Invalid caveat. Caveats must be plain objects.`, { receivedCaveat });
this.data = { origin, target };
}
}
exports.InvalidCaveatError = InvalidCaveatError;
class InvalidCaveatTypeError extends Error {
constructor(caveat, origin, target) {
super(`Caveat types must be strings. Received: "${typeof caveat.type}"`);
this.data = { caveat, origin, target };
}
}
exports.InvalidCaveatTypeError = InvalidCaveatTypeError;
class CaveatMissingValueError extends Error {
constructor(caveat, origin, target) {
super(`Caveat is missing "value" field.`);
this.data = { caveat, origin, target };
}
}
exports.CaveatMissingValueError = CaveatMissingValueError;
class CaveatInvalidJsonError extends Error {
constructor(caveat, origin, target) {
super(`Caveat "value" is invalid JSON.`);
this.data = { caveat, origin, target };
}
}
exports.CaveatInvalidJsonError = CaveatInvalidJsonError;
class InvalidCaveatFieldsError extends Error {
constructor(caveat, origin, target) {
super(`Caveat has unexpected number of fields: "${Object.keys(caveat).length}"`);
this.data = { caveat, origin, target };
}
}
exports.InvalidCaveatFieldsError = InvalidCaveatFieldsError;
class ForbiddenCaveatError extends Error {
constructor(caveatType, origin, targetName) {
super(`Permissions for target "${targetName}" may not have caveats of type "${caveatType}".`);
this.data = { caveatType, origin, target: targetName };
}
}
exports.ForbiddenCaveatError = ForbiddenCaveatError;
class DuplicateCaveatError extends Error {
constructor(caveatType, origin, targetName) {
super(`Permissions for target "${targetName}" contains multiple caveats of type "${caveatType}".`);
this.data = { caveatType, origin, target: targetName };
}
}
exports.DuplicateCaveatError = DuplicateCaveatError;
class CaveatSpecificationMismatchError extends Error {
constructor(caveatSpec, permissionType) {
super(`Caveat specification uses a mismatched type. Expected caveats for ${permissionType}`);
this.data = { caveatSpec, permissionType };
}
}
exports.CaveatSpecificationMismatchError = CaveatSpecificationMismatchError;
class PermissionsRequestNotFoundError extends Error {
constructor(id) {
super(`Permissions request with id "${id}" not found.`);
}
}
exports.PermissionsRequestNotFoundError = PermissionsRequestNotFoundError;
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
var _chunkU5LTEXSUjs = require('./chunk-U5LTEXSU.js');
require('./chunk-CSAU5B4Q.js');
exports.CaveatAlreadyExistsError = _chunkU5LTEXSUjs.CaveatAlreadyExistsError; exports.CaveatDoesNotExistError = _chunkU5LTEXSUjs.CaveatDoesNotExistError; exports.CaveatInvalidJsonError = _chunkU5LTEXSUjs.CaveatInvalidJsonError; exports.CaveatMissingValueError = _chunkU5LTEXSUjs.CaveatMissingValueError; exports.CaveatSpecificationMismatchError = _chunkU5LTEXSUjs.CaveatSpecificationMismatchError; exports.DuplicateCaveatError = _chunkU5LTEXSUjs.DuplicateCaveatError; exports.EndowmentPermissionDoesNotExistError = _chunkU5LTEXSUjs.EndowmentPermissionDoesNotExistError; exports.ForbiddenCaveatError = _chunkU5LTEXSUjs.ForbiddenCaveatError; exports.InvalidApprovedPermissionError = _chunkU5LTEXSUjs.InvalidApprovedPermissionError; exports.InvalidCaveatError = _chunkU5LTEXSUjs.InvalidCaveatError; exports.InvalidCaveatFieldsError = _chunkU5LTEXSUjs.InvalidCaveatFieldsError; exports.InvalidCaveatTypeError = _chunkU5LTEXSUjs.InvalidCaveatTypeError; exports.InvalidCaveatsPropertyError = _chunkU5LTEXSUjs.InvalidCaveatsPropertyError; exports.InvalidSubjectIdentifierError = _chunkU5LTEXSUjs.InvalidSubjectIdentifierError; exports.PermissionDoesNotExistError = _chunkU5LTEXSUjs.PermissionDoesNotExistError; exports.PermissionsRequestNotFoundError = _chunkU5LTEXSUjs.PermissionsRequestNotFoundError; exports.UnrecognizedCaveatTypeError = _chunkU5LTEXSUjs.UnrecognizedCaveatTypeError; exports.UnrecognizedSubjectError = _chunkU5LTEXSUjs.UnrecognizedSubjectError; exports.internalError = _chunkU5LTEXSUjs.internalError; exports.invalidParams = _chunkU5LTEXSUjs.invalidParams; exports.methodNotFound = _chunkU5LTEXSUjs.methodNotFound; exports.unauthorized = _chunkU5LTEXSUjs.unauthorized; exports.userRejectedRequest = _chunkU5LTEXSUjs.userRejectedRequest;
//# sourceMappingURL=errors.js.map

@@ -1,37 +0,92 @@

"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.permissionRpcMethods = void 0;
__exportStar(require("./Caveat"), exports);
__exportStar(require("./errors"), exports);
__exportStar(require("./Permission"), exports);
__exportStar(require("./PermissionController"), exports);
__exportStar(require("./utils"), exports);
exports.permissionRpcMethods = __importStar(require("./rpc-methods"));
__exportStar(require("./SubjectMetadataController"), exports);
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
var _chunk7CTPRFQ3js = require('./chunk-7CTPRFQ3.js');
require('./chunk-HRDKMOYS.js');
require('./chunk-YRNH4R3G.js');
require('./chunk-ZVO26XPN.js');
var _chunk5L2IOZE2js = require('./chunk-5L2IOZE2.js');
var _chunkEGNDXGRGjs = require('./chunk-EGNDXGRG.js');
var _chunk6CID6TBWjs = require('./chunk-6CID6TBW.js');
var _chunkSFKE5HHKjs = require('./chunk-SFKE5HHK.js');
require('./chunk-AQ35E2HU.js');
var _chunkU5LTEXSUjs = require('./chunk-U5LTEXSU.js');
var _chunkK5R57Y57js = require('./chunk-K5R57Y57.js');
require('./chunk-CSAU5B4Q.js');
exports.CaveatAlreadyExistsError = _chunkU5LTEXSUjs.CaveatAlreadyExistsError; exports.CaveatDoesNotExistError = _chunkU5LTEXSUjs.CaveatDoesNotExistError; exports.CaveatInvalidJsonError = _chunkU5LTEXSUjs.CaveatInvalidJsonError; exports.CaveatMissingValueError = _chunkU5LTEXSUjs.CaveatMissingValueError; exports.CaveatMutatorOperation = _chunk5L2IOZE2js.CaveatMutatorOperation; exports.CaveatSpecificationMismatchError = _chunkU5LTEXSUjs.CaveatSpecificationMismatchError; exports.DuplicateCaveatError = _chunkU5LTEXSUjs.DuplicateCaveatError; exports.EndowmentPermissionDoesNotExistError = _chunkU5LTEXSUjs.EndowmentPermissionDoesNotExistError; exports.ForbiddenCaveatError = _chunkU5LTEXSUjs.ForbiddenCaveatError; exports.InvalidApprovedPermissionError = _chunkU5LTEXSUjs.InvalidApprovedPermissionError; exports.InvalidCaveatError = _chunkU5LTEXSUjs.InvalidCaveatError; exports.InvalidCaveatFieldsError = _chunkU5LTEXSUjs.InvalidCaveatFieldsError; exports.InvalidCaveatTypeError = _chunkU5LTEXSUjs.InvalidCaveatTypeError; exports.InvalidCaveatsPropertyError = _chunkU5LTEXSUjs.InvalidCaveatsPropertyError; exports.InvalidSubjectIdentifierError = _chunkU5LTEXSUjs.InvalidSubjectIdentifierError; exports.MethodNames = _chunkK5R57Y57js.MethodNames; exports.PermissionController = _chunk5L2IOZE2js.PermissionController; exports.PermissionDoesNotExistError = _chunkU5LTEXSUjs.PermissionDoesNotExistError; exports.PermissionType = _chunk6CID6TBWjs.PermissionType; exports.PermissionsRequestNotFoundError = _chunkU5LTEXSUjs.PermissionsRequestNotFoundError; exports.SubjectMetadataController = _chunkSFKE5HHKjs.SubjectMetadataController; exports.SubjectType = _chunkSFKE5HHKjs.SubjectType; exports.UnrecognizedCaveatTypeError = _chunkU5LTEXSUjs.UnrecognizedCaveatTypeError; exports.UnrecognizedSubjectError = _chunkU5LTEXSUjs.UnrecognizedSubjectError; exports.constructPermission = _chunk6CID6TBWjs.constructPermission; exports.decorateWithCaveats = _chunkEGNDXGRGjs.decorateWithCaveats; exports.findCaveat = _chunk6CID6TBWjs.findCaveat; exports.hasSpecificationType = _chunk6CID6TBWjs.hasSpecificationType; exports.internalError = _chunkU5LTEXSUjs.internalError; exports.invalidParams = _chunkU5LTEXSUjs.invalidParams; exports.isRestrictedMethodCaveatSpecification = _chunkEGNDXGRGjs.isRestrictedMethodCaveatSpecification; exports.methodNotFound = _chunkU5LTEXSUjs.methodNotFound; exports.permissionRpcMethods = _chunk7CTPRFQ3js.rpc_methods_exports; exports.unauthorized = _chunkU5LTEXSUjs.unauthorized; exports.userRejectedRequest = _chunkU5LTEXSUjs.userRejectedRequest;
//# sourceMappingURL=index.js.map

@@ -1,64 +0,9 @@

"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getPermissionMiddlewareFactory = void 0;
const json_rpc_engine_1 = require("@metamask/json-rpc-engine");
const errors_1 = require("./errors");
/**
* Creates a permission middleware function factory. Intended for internal use
* in the {@link PermissionController}. Like any {@link JsonRpcEngine}
* middleware, each middleware will only receive requests from a particular
* subject / origin. However, each middleware also requires access to some
* `PermissionController` internals, which is why this "factory factory" exists.
*
* The middlewares returned by the factory will pass through requests for
* unrestricted methods, and attempt to execute restricted methods. If a method
* is neither restricted nor unrestricted, a "method not found" error will be
* returned.
* If a method is restricted, the middleware will first attempt to retrieve the
* subject's permission for that method. If the permission is found, the method
* will be executed. Otherwise, an "unauthorized" error will be returned.
*
* @param options - Options bag.
* @param options.executeRestrictedMethod - {@link PermissionController._executeRestrictedMethod}.
* @param options.getRestrictedMethod - {@link PermissionController.getRestrictedMethod}.
* @param options.isUnrestrictedMethod - A function that checks whether a
* particular method is unrestricted.
* @returns A permission middleware factory function.
*/
function getPermissionMiddlewareFactory({ executeRestrictedMethod, getRestrictedMethod, isUnrestrictedMethod, }) {
return function createPermissionMiddleware(subject) {
const { origin } = subject;
if (typeof origin !== 'string' || !origin) {
throw new Error('The subject "origin" must be a non-empty string.');
}
const permissionsMiddleware = (req, res, next) => __awaiter(this, void 0, void 0, function* () {
const { method, params } = req;
// Skip registered unrestricted methods.
if (isUnrestrictedMethod(method)) {
return next();
}
// This will throw if no restricted method implementation is found.
const methodImplementation = getRestrictedMethod(method, origin);
// This will throw if the permission does not exist.
const result = yield executeRestrictedMethod(methodImplementation, subject, method, params);
if (result === undefined) {
res.error = (0, errors_1.internalError)(`Request for method "${req.method}" returned undefined result.`, { request: req });
return undefined;
}
res.result = result;
return undefined;
});
return (0, json_rpc_engine_1.createAsyncMiddleware)(permissionsMiddleware);
};
}
exports.getPermissionMiddlewareFactory = getPermissionMiddlewareFactory;
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
var _chunkAQ35E2HUjs = require('./chunk-AQ35E2HU.js');
require('./chunk-U5LTEXSU.js');
require('./chunk-CSAU5B4Q.js');
exports.getPermissionMiddlewareFactory = _chunkAQ35E2HUjs.getPermissionMiddlewareFactory;
//# sourceMappingURL=permission-middleware.js.map

@@ -1,66 +0,14 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.hasSpecificationType = exports.PermissionType = exports.findCaveat = exports.constructPermission = void 0;
const nanoid_1 = require("nanoid");
/**
* The default permission factory function. Naively constructs a permission from
* the inputs. Sets a default, random `id` if none is provided.
*
* @see {@link Permission} For more details.
* @template TargetPermission- - The {@link Permission} that will be constructed.
* @param options - The options for the permission.
* @returns The new permission object.
*/
function constructPermission(options) {
const { caveats = null, invoker, target } = options;
return {
id: (0, nanoid_1.nanoid)(),
parentCapability: target,
invoker,
caveats,
date: new Date().getTime(),
};
}
exports.constructPermission = constructPermission;
/**
* Gets the caveat of the specified type belonging to the specified permission.
*
* @param permission - The permission whose caveat to retrieve.
* @param caveatType - The type of the caveat to retrieve.
* @returns The caveat, or undefined if no such caveat exists.
*/
function findCaveat(permission, caveatType) {
var _a;
return (_a = permission.caveats) === null || _a === void 0 ? void 0 : _a.find((caveat) => caveat.type === caveatType);
}
exports.findCaveat = findCaveat;
/**
* The different possible types of permissions.
*/
var PermissionType;
(function (PermissionType) {
/**
* A restricted JSON-RPC method. A subject must have the requisite permission
* to call a restricted JSON-RPC method.
*/
PermissionType["RestrictedMethod"] = "RestrictedMethod";
/**
* An "endowment" granted to subjects that possess the requisite permission,
* such as a global environment variable exposing a restricted API, etc.
*/
PermissionType["Endowment"] = "Endowment";
})(PermissionType = exports.PermissionType || (exports.PermissionType = {}));
/**
* Checks that the specification has the expected permission type.
*
* @param specification - The specification to check.
* @param expectedType - The expected permission type.
* @template Specification - The specification to check.
* @template Type - The expected permission type.
* @returns Whether or not the specification is of the expected type.
*/
function hasSpecificationType(specification, expectedType) {
return specification.permissionType === expectedType;
}
exports.hasSpecificationType = hasSpecificationType;
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
var _chunk6CID6TBWjs = require('./chunk-6CID6TBW.js');
require('./chunk-CSAU5B4Q.js');
exports.PermissionType = _chunk6CID6TBWjs.PermissionType; exports.constructPermission = _chunk6CID6TBWjs.constructPermission; exports.findCaveat = _chunk6CID6TBWjs.findCaveat; exports.hasSpecificationType = _chunk6CID6TBWjs.hasSpecificationType;
//# sourceMappingURL=Permission.js.map

@@ -1,1295 +0,15 @@

"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PermissionController = exports.CaveatMutatorOperation = void 0;
const base_controller_1 = require("@metamask/base-controller");
const controller_utils_1 = require("@metamask/controller-utils");
const rpc_errors_1 = require("@metamask/rpc-errors");
const utils_1 = require("@metamask/utils");
const deep_freeze_strict_1 = __importDefault(require("deep-freeze-strict"));
const immer_1 = require("immer");
const nanoid_1 = require("nanoid");
const Caveat_1 = require("./Caveat");
const errors_1 = require("./errors");
const Permission_1 = require("./Permission");
const permission_middleware_1 = require("./permission-middleware");
const utils_2 = require("./utils");
/**
* The name of the {@link PermissionController}.
*/
const controllerName = 'PermissionController';
/**
* Get the state metadata of the {@link PermissionController}.
*
* @template Permission - The controller's permission type union.
* @returns The state metadata
*/
function getStateMetadata() {
return { subjects: { anonymous: true, persist: true } };
}
/**
* Get the default state of the {@link PermissionController}.
*
* @template Permission - The controller's permission type union.
* @returns The default state of the controller
*/
function getDefaultState() {
return { subjects: {} };
}
/**
* Describes the possible results of a {@link CaveatMutator} function.
*/
var CaveatMutatorOperation;
(function (CaveatMutatorOperation) {
CaveatMutatorOperation[CaveatMutatorOperation["noop"] = 0] = "noop";
CaveatMutatorOperation[CaveatMutatorOperation["updateValue"] = 1] = "updateValue";
CaveatMutatorOperation[CaveatMutatorOperation["deleteCaveat"] = 2] = "deleteCaveat";
CaveatMutatorOperation[CaveatMutatorOperation["revokePermission"] = 3] = "revokePermission";
})(CaveatMutatorOperation = exports.CaveatMutatorOperation || (exports.CaveatMutatorOperation = {}));
/**
* The permission controller. See the [Architecture](../ARCHITECTURE.md)
* document for details.
*
* Assumes the existence of an {@link ApprovalController} reachable via the
* {@link ControllerMessenger}.
*
* @template ControllerPermissionSpecification - A union of the types of all
* permission specifications available to the controller. Any referenced caveats
* must be included in the controller's caveat specifications.
* @template ControllerCaveatSpecification - A union of the types of all
* caveat specifications available to the controller.
*/
class PermissionController extends base_controller_1.BaseController {
/**
* Constructs the PermissionController.
*
* @param options - Permission controller options.
* @param options.caveatSpecifications - The specifications of all caveats
* available to the controller. See {@link CaveatSpecificationMap} and the
* documentation for more details.
* @param options.permissionSpecifications - The specifications of all
* permissions available to the controller. See
* {@link PermissionSpecificationMap} and the README for more details.
* @param options.unrestrictedMethods - The callable names of all JSON-RPC
* methods ignored by the new controller.
* @param options.messenger - The controller messenger. See
* {@link BaseController} for more information.
* @param options.state - Existing state to hydrate the controller with at
* initialization.
*/
constructor(options) {
const { caveatSpecifications, permissionSpecifications, unrestrictedMethods, messenger, state = {}, } = options;
super({
name: controllerName,
metadata: getStateMetadata(),
messenger,
state: Object.assign(Object.assign({}, getDefaultState()), state),
});
this._unrestrictedMethods = new Set(unrestrictedMethods);
this._caveatSpecifications = (0, deep_freeze_strict_1.default)(Object.assign({}, caveatSpecifications));
this.validatePermissionSpecifications(permissionSpecifications, this._caveatSpecifications);
this._permissionSpecifications = (0, deep_freeze_strict_1.default)(Object.assign({}, permissionSpecifications));
this.registerMessageHandlers();
this.createPermissionMiddleware = (0, permission_middleware_1.getPermissionMiddlewareFactory)({
executeRestrictedMethod: this._executeRestrictedMethod.bind(this),
getRestrictedMethod: this.getRestrictedMethod.bind(this),
isUnrestrictedMethod: this.unrestrictedMethods.has.bind(this.unrestrictedMethods),
});
}
/**
* The names of all JSON-RPC methods that will be ignored by the controller.
*
* @returns The names of all unrestricted JSON-RPC methods
*/
get unrestrictedMethods() {
return this._unrestrictedMethods;
}
/**
* Gets a permission specification.
*
* @param targetName - The name of the permission specification to get.
* @returns The permission specification with the specified target name.
*/
getPermissionSpecification(targetName) {
return this._permissionSpecifications[targetName];
}
/**
* Gets a caveat specification.
*
* @param caveatType - The type of the caveat specification to get.
* @returns The caveat specification with the specified type.
*/
getCaveatSpecification(caveatType) {
return this._caveatSpecifications[caveatType];
}
/**
* Constructor helper for validating permission specifications.
*
* Throws an error if validation fails.
*
* @param permissionSpecifications - The permission specifications passed to
* this controller's constructor.
* @param caveatSpecifications - The caveat specifications passed to this
* controller.
*/
validatePermissionSpecifications(permissionSpecifications, caveatSpecifications) {
Object.entries(permissionSpecifications).forEach(([targetName, { permissionType, targetName: innerTargetName, allowedCaveats },]) => {
if (!permissionType || !(0, utils_1.hasProperty)(Permission_1.PermissionType, permissionType)) {
throw new Error(`Invalid permission type: "${permissionType}"`);
}
if (!targetName) {
throw new Error(`Invalid permission target name: "${targetName}"`);
}
if (targetName !== innerTargetName) {
throw new Error(`Invalid permission specification: target name "${targetName}" must match specification.targetName value "${innerTargetName}".`);
}
if (allowedCaveats) {
allowedCaveats.forEach((caveatType) => {
if (!(0, utils_1.hasProperty)(caveatSpecifications, caveatType)) {
throw new errors_1.UnrecognizedCaveatTypeError(caveatType);
}
const specification = caveatSpecifications[caveatType];
const isRestrictedMethodCaveat = (0, Caveat_1.isRestrictedMethodCaveatSpecification)(specification);
if ((permissionType === Permission_1.PermissionType.RestrictedMethod &&
!isRestrictedMethodCaveat) ||
(permissionType === Permission_1.PermissionType.Endowment &&
isRestrictedMethodCaveat)) {
throw new errors_1.CaveatSpecificationMismatchError(specification, permissionType);
}
});
}
});
}
/**
* Constructor helper for registering the controller's messaging system
* actions.
*/
registerMessageHandlers() {
this.messagingSystem.registerActionHandler(`${controllerName}:clearPermissions`, () => this.clearState());
this.messagingSystem.registerActionHandler(`${controllerName}:getEndowments`, (origin, targetName, requestData) => this.getEndowments(origin, targetName, requestData));
this.messagingSystem.registerActionHandler(`${controllerName}:getSubjectNames`, () => this.getSubjectNames());
this.messagingSystem.registerActionHandler(`${controllerName}:getPermissions`, (origin) => this.getPermissions(origin));
this.messagingSystem.registerActionHandler(`${controllerName}:hasPermission`, (origin, targetName) => this.hasPermission(origin, targetName));
this.messagingSystem.registerActionHandler(`${controllerName}:hasPermissions`, (origin) => this.hasPermissions(origin));
this.messagingSystem.registerActionHandler(`${controllerName}:grantPermissions`, this.grantPermissions.bind(this));
this.messagingSystem.registerActionHandler(`${controllerName}:requestPermissions`, (subject, permissions) => this.requestPermissions(subject, permissions));
this.messagingSystem.registerActionHandler(`${controllerName}:revokeAllPermissions`, (origin) => this.revokeAllPermissions(origin));
this.messagingSystem.registerActionHandler(`${controllerName}:revokePermissionForAllSubjects`, (target) => this.revokePermissionForAllSubjects(target));
this.messagingSystem.registerActionHandler(`${controllerName}:revokePermissions`, this.revokePermissions.bind(this));
this.messagingSystem.registerActionHandler(`${controllerName}:updateCaveat`, (origin, target, caveatType, caveatValue) => {
this.updateCaveat(origin, target, caveatType, caveatValue);
});
}
/**
* Clears the state of the controller.
*/
clearState() {
this.update((_draftState) => {
return Object.assign({}, getDefaultState());
});
}
/**
* Gets the permission specification corresponding to the given permission
* type and target name. Throws an error if the target name does not
* correspond to a permission, or if the specification is not of the
* given permission type.
*
* @template Type - The type of the permission specification to get.
* @param permissionType - The type of the permission specification to get.
* @param targetName - The name of the permission whose specification to get.
* @param requestingOrigin - The origin of the requesting subject, if any.
* Will be added to any thrown errors.
* @returns The specification object corresponding to the given type and
* target name.
*/
getTypedPermissionSpecification(permissionType, targetName, requestingOrigin) {
const failureError = permissionType === Permission_1.PermissionType.RestrictedMethod
? (0, errors_1.methodNotFound)(targetName, requestingOrigin ? { origin: requestingOrigin } : undefined)
: new errors_1.EndowmentPermissionDoesNotExistError(targetName, requestingOrigin);
if (!this.targetExists(targetName)) {
throw failureError;
}
const specification = this.getPermissionSpecification(targetName);
if (!(0, Permission_1.hasSpecificationType)(specification, permissionType)) {
throw failureError;
}
return specification;
}
/**
* Gets the implementation of the specified restricted method.
*
* A JSON-RPC error is thrown if the method does not exist.
*
* @see {@link PermissionController.executeRestrictedMethod} and
* {@link PermissionController.createPermissionMiddleware} for internal usage.
* @param method - The name of the restricted method.
* @param origin - The origin associated with the request for the restricted
* method, if any.
* @returns The restricted method implementation.
*/
getRestrictedMethod(method, origin) {
return this.getTypedPermissionSpecification(Permission_1.PermissionType.RestrictedMethod, method, origin).methodImplementation;
}
/**
* Gets a list of all origins of subjects.
*
* @returns The origins (i.e. IDs) of all subjects.
*/
getSubjectNames() {
return Object.keys(this.state.subjects);
}
/**
* Gets the permission for the specified target of the subject corresponding
* to the specified origin.
*
* @param origin - The origin of the subject.
* @param targetName - The method name as invoked by a third party (i.e., not
* a method key).
* @returns The permission if it exists, or undefined otherwise.
*/
getPermission(origin, targetName) {
var _a;
return (_a = this.state.subjects[origin]) === null || _a === void 0 ? void 0 : _a.permissions[targetName];
}
/**
* Gets all permissions for the specified subject, if any.
*
* @param origin - The origin of the subject.
* @returns The permissions of the subject, if any.
*/
getPermissions(origin) {
var _a;
return (_a = this.state.subjects[origin]) === null || _a === void 0 ? void 0 : _a.permissions;
}
/**
* Checks whether the subject with the specified origin has the specified
* permission.
*
* @param origin - The origin of the subject.
* @param target - The target name of the permission.
* @returns Whether the subject has the permission.
*/
hasPermission(origin, target) {
return Boolean(this.getPermission(origin, target));
}
/**
* Checks whether the subject with the specified origin has any permissions.
* Use this if you want to know if a subject "exists".
*
* @param origin - The origin of the subject to check.
* @returns Whether the subject has any permissions.
*/
hasPermissions(origin) {
return Boolean(this.state.subjects[origin]);
}
/**
* Revokes all permissions from the specified origin.
*
* Throws an error of the origin has no permissions.
*
* @param origin - The origin whose permissions to revoke.
*/
revokeAllPermissions(origin) {
this.update((draftState) => {
if (!draftState.subjects[origin]) {
throw new errors_1.UnrecognizedSubjectError(origin);
}
delete draftState.subjects[origin];
});
}
/**
* Revokes the specified permission from the subject with the specified
* origin.
*
* Throws an error if the subject or the permission does not exist.
*
* @param origin - The origin of the subject whose permission to revoke.
* @param target - The target name of the permission to revoke.
*/
revokePermission(origin, target) {
this.revokePermissions({ [origin]: [target] });
}
/**
* Revokes the specified permissions from the specified subjects.
*
* Throws an error if any of the subjects or permissions do not exist.
*
* @param subjectsAndPermissions - An object mapping subject origins
* to arrays of permission target names to revoke.
*/
revokePermissions(subjectsAndPermissions) {
this.update((draftState) => {
Object.keys(subjectsAndPermissions).forEach((origin) => {
if (!(0, utils_1.hasProperty)(draftState.subjects, origin)) {
throw new errors_1.UnrecognizedSubjectError(origin);
}
subjectsAndPermissions[origin].forEach((target) => {
const { permissions } = draftState.subjects[origin];
if (!(0, utils_1.hasProperty)(permissions, target)) {
throw new errors_1.PermissionDoesNotExistError(origin, target);
}
this.deletePermission(draftState.subjects, origin, target);
});
});
});
}
/**
* Revokes all permissions corresponding to the specified target for all subjects.
* Does nothing if no subjects or no such permission exists.
*
* @param target - The name of the target to revoke all permissions for.
*/
revokePermissionForAllSubjects(target) {
if (this.getSubjectNames().length === 0) {
return;
}
this.update((draftState) => {
Object.entries(draftState.subjects).forEach(([origin, subject]) => {
const { permissions } = subject;
if ((0, utils_1.hasProperty)(permissions, target)) {
this.deletePermission(draftState.subjects, origin, target);
}
});
});
}
/**
* Deletes the permission identified by the given origin and target. If the
* permission is the single remaining permission of its subject, the subject
* is also deleted.
*
* @param subjects - The draft permission controller subjects.
* @param origin - The origin of the subject associated with the permission
* to delete.
* @param target - The target name of the permission to delete.
*/
deletePermission(subjects, origin, target) {
const { permissions } = subjects[origin];
if (Object.keys(permissions).length > 1) {
delete permissions[target];
}
else {
delete subjects[origin];
}
}
/**
* Checks whether the permission of the subject corresponding to the given
* origin has a caveat of the specified type.
*
* Throws an error if the subject does not have a permission with the
* specified target name.
*
* @template TargetName - The permission target name. Should be inferred.
* @template CaveatType - The valid caveat types for the permission. Should
* be inferred.
* @param origin - The origin of the subject.
* @param target - The target name of the permission.
* @param caveatType - The type of the caveat to check for.
* @returns Whether the permission has the specified caveat.
*/
hasCaveat(origin, target, caveatType) {
return Boolean(this.getCaveat(origin, target, caveatType));
}
/**
* Gets the caveat of the specified type, if any, for the permission of
* the subject corresponding to the given origin.
*
* Throws an error if the subject does not have a permission with the
* specified target name.
*
* @template TargetName - The permission target name. Should be inferred.
* @template CaveatType - The valid caveat types for the permission. Should
* be inferred.
* @param origin - The origin of the subject.
* @param target - The target name of the permission.
* @param caveatType - The type of the caveat to get.
* @returns The caveat, or `undefined` if no such caveat exists.
*/
getCaveat(origin, target, caveatType) {
const permission = this.getPermission(origin, target);
if (!permission) {
throw new errors_1.PermissionDoesNotExistError(origin, target);
}
return (0, Permission_1.findCaveat)(permission, caveatType);
}
/**
* Adds a caveat of the specified type, with the specified caveat value, to
* the permission corresponding to the given subject origin and permission
* target.
*
* For modifying existing caveats, use
* {@link PermissionController.updateCaveat}.
*
* Throws an error if no such permission exists, or if the caveat already
* exists.
*
* @template TargetName - The permission target name. Should be inferred.
* @template CaveatType - The valid caveat types for the permission. Should
* be inferred.
* @param origin - The origin of the subject.
* @param target - The target name of the permission.
* @param caveatType - The type of the caveat to add.
* @param caveatValue - The value of the caveat to add.
*/
addCaveat(origin, target, caveatType, caveatValue) {
if (this.hasCaveat(origin, target, caveatType)) {
throw new errors_1.CaveatAlreadyExistsError(origin, target, caveatType);
}
this.setCaveat(origin, target, caveatType, caveatValue);
}
/**
* Updates the value of the caveat of the specified type belonging to the
* permission corresponding to the given subject origin and permission
* target.
*
* For adding new caveats, use
* {@link PermissionController.addCaveat}.
*
* Throws an error if no such permission or caveat exists.
*
* @template TargetName - The permission target name. Should be inferred.
* @template CaveatType - The valid caveat types for the permission. Should
* be inferred.
* @param origin - The origin of the subject.
* @param target - The target name of the permission.
* @param caveatType - The type of the caveat to update.
* @param caveatValue - The new value of the caveat.
*/
updateCaveat(origin, target, caveatType, caveatValue) {
if (!this.hasCaveat(origin, target, caveatType)) {
throw new errors_1.CaveatDoesNotExistError(origin, target, caveatType);
}
this.setCaveat(origin, target, caveatType, caveatValue);
}
/**
* Sets the specified caveat on the specified permission. Overwrites existing
* caveats of the same type in-place (preserving array order), and adds the
* caveat to the end of the array otherwise.
*
* Throws an error if the permission does not exist or fails to validate after
* its caveats have been modified.
*
* @see {@link PermissionController.addCaveat}
* @see {@link PermissionController.updateCaveat}
* @template TargetName - The permission target name. Should be inferred.
* @template CaveatType - The valid caveat types for the permission. Should
* be inferred.
* @param origin - The origin of the subject.
* @param target - The target name of the permission.
* @param caveatType - The type of the caveat to set.
* @param caveatValue - The value of the caveat to set.
*/
setCaveat(origin, target, caveatType, caveatValue) {
this.update((draftState) => {
const subject = draftState.subjects[origin];
// Unreachable because `hasCaveat` is always called before this, and it
// throws if permissions are missing. TypeScript needs this, however.
/* istanbul ignore if */
if (!subject) {
throw new errors_1.UnrecognizedSubjectError(origin);
}
const permission = subject.permissions[target];
/* istanbul ignore if: practically impossible, but TypeScript wants it */
if (!permission) {
throw new errors_1.PermissionDoesNotExistError(origin, target);
}
const caveat = {
type: caveatType,
value: caveatValue,
};
this.validateCaveat(caveat, origin, target);
if (permission.caveats) {
const caveatIndex = permission.caveats.findIndex((existingCaveat) => existingCaveat.type === caveat.type);
if (caveatIndex === -1) {
permission.caveats.push(caveat);
}
else {
permission.caveats.splice(caveatIndex, 1, caveat);
}
}
else {
// Typecast: At this point, we don't know if the specific permission
// is allowed to have caveats, but it should be impossible to call
// this method for a permission that may not have any caveats.
// If all else fails, the permission validator is also called.
// TODO: Replace `any` with type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
permission.caveats = [caveat];
}
this.validateModifiedPermission(permission, origin);
});
}
/**
* Updates all caveats with the specified type for all subjects and
* permissions by applying the specified mutator function to them.
*
* ATTN: Permissions can be revoked entirely by the action of this method,
* read on for details.
*
* Caveat mutators are functions that receive a caveat value and return a
* tuple consisting of a {@link CaveatMutatorOperation} and, optionally, a new
* value to update the existing caveat with.
*
* For each caveat, depending on the mutator result, this method will:
* - Do nothing ({@link CaveatMutatorOperation.noop})
* - Update the value of the caveat ({@link CaveatMutatorOperation.updateValue}). The caveat specification validator, if any, will be called after updating the value.
* - Delete the caveat ({@link CaveatMutatorOperation.deleteCaveat}). The permission specification validator, if any, will be called after deleting the caveat.
* - Revoke the parent permission ({@link CaveatMutatorOperation.revokePermission})
*
* This method throws if the validation of any caveat or permission fails.
*
* @param targetCaveatType - The type of the caveats to update.
* @param mutator - The mutator function which will be applied to all caveat
* values.
*/
updatePermissionsByCaveat(targetCaveatType, mutator) {
if (Object.keys(this.state.subjects).length === 0) {
return;
}
this.update((draftState) => {
Object.values(draftState.subjects).forEach((subject) => {
Object.values(subject.permissions).forEach((permission) => {
const { caveats } = permission;
const targetCaveat = caveats === null || caveats === void 0 ? void 0 : caveats.find(({ type }) => type === targetCaveatType);
if (!targetCaveat) {
return;
}
// The mutator may modify the caveat value in place, and must always
// return a valid mutation result.
const mutatorResult = mutator(targetCaveat.value);
switch (mutatorResult.operation) {
case CaveatMutatorOperation.noop:
break;
case CaveatMutatorOperation.updateValue:
// Typecast: `Mutable` is used here to assign to a readonly
// property. `targetConstraint` should already be mutable because
// it's part of a draft, but for some reason it's not. We can't
// use the more-correct `Draft` type here either because it
// results in an error.
targetCaveat.value =
mutatorResult.value;
this.validateCaveat(targetCaveat, subject.origin, permission.parentCapability);
break;
case CaveatMutatorOperation.deleteCaveat:
this.deleteCaveat(permission, targetCaveatType, subject.origin);
break;
case CaveatMutatorOperation.revokePermission:
this.deletePermission(draftState.subjects, subject.origin, permission.parentCapability);
break;
default: {
// This type check ensures that the switch statement is
// exhaustive.
const _exhaustiveCheck = mutatorResult;
throw new Error(`Unrecognized mutation result: "${
// TODO: Replace `any` with type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
_exhaustiveCheck.operation}"`);
}
}
});
});
});
}
/**
* Removes the caveat of the specified type from the permission corresponding
* to the given subject origin and target name.
*
* Throws an error if no such permission or caveat exists.
*
* @template TargetName - The permission target name. Should be inferred.
* @template CaveatType - The valid caveat types for the permission. Should
* be inferred.
* @param origin - The origin of the subject.
* @param target - The target name of the permission.
* @param caveatType - The type of the caveat to remove.
*/
removeCaveat(origin, target, caveatType) {
this.update((draftState) => {
var _a;
const permission = (_a = draftState.subjects[origin]) === null || _a === void 0 ? void 0 : _a.permissions[target];
if (!permission) {
throw new errors_1.PermissionDoesNotExistError(origin, target);
}
if (!permission.caveats) {
throw new errors_1.CaveatDoesNotExistError(origin, target, caveatType);
}
this.deleteCaveat(permission, caveatType, origin);
});
}
/**
* Deletes the specified caveat from the specified permission. If no caveats
* remain after deletion, the permission's caveat property is set to `null`.
* The permission is validated after being modified.
*
* Throws an error if the permission does not have a caveat with the specified
* type.
*
* @param permission - The permission whose caveat to delete.
* @param caveatType - The type of the caveat to delete.
* @param origin - The origin the permission subject.
*/
deleteCaveat(permission, caveatType, origin) {
/* istanbul ignore if: not possible in our usage */
if (!permission.caveats) {
throw new errors_1.CaveatDoesNotExistError(origin, permission.parentCapability, caveatType);
}
const caveatIndex = permission.caveats.findIndex((existingCaveat) => existingCaveat.type === caveatType);
if (caveatIndex === -1) {
throw new errors_1.CaveatDoesNotExistError(origin, permission.parentCapability, caveatType);
}
if (permission.caveats.length === 1) {
permission.caveats = null;
}
else {
permission.caveats.splice(caveatIndex, 1);
}
this.validateModifiedPermission(permission, origin);
}
/**
* Validates the specified modified permission. Should **always** be invoked
* on a permission after its caveats have been modified.
*
* Just like {@link PermissionController.validatePermission}, except that the
* corresponding target name and specification are retrieved first, and an
* error is thrown if the target name does not exist.
*
* @param permission - The modified permission to validate.
* @param origin - The origin associated with the permission.
*/
validateModifiedPermission(permission, origin) {
/* istanbul ignore if: this should be impossible */
if (!this.targetExists(permission.parentCapability)) {
throw new Error(`Fatal: Existing permission target "${permission.parentCapability}" has no specification.`);
}
this.validatePermission(this.getPermissionSpecification(permission.parentCapability), permission, origin);
}
/**
* Verifies the existence the specified permission target, i.e. whether it has
* a specification.
*
* @param target - The requested permission target.
* @returns Whether the permission target exists.
*/
targetExists(target) {
return (0, utils_1.hasProperty)(this._permissionSpecifications, target);
}
/**
* Grants _approved_ permissions to the specified subject. Every permission and
* caveat is stringently validated – including by calling every specification
* validator – and an error is thrown if any validation fails.
*
* ATTN: This method does **not** prompt the user for approval.
*
* @see {@link PermissionController.requestPermissions} For initiating a
* permissions request requiring user approval.
* @param options - Options bag.
* @param options.approvedPermissions - The requested permissions approved by
* the user.
* @param options.requestData - Permission request data. Passed to permission
* factory functions.
* @param options.preserveExistingPermissions - Whether to preserve the
* subject's existing permissions.
* @param options.subject - The subject to grant permissions to.
* @returns The granted permissions.
*/
grantPermissions({ approvedPermissions, requestData, preserveExistingPermissions = true, subject, }) {
const { origin } = subject;
if (!origin || typeof origin !== 'string') {
throw new errors_1.InvalidSubjectIdentifierError(origin);
}
const permissions = (preserveExistingPermissions
? Object.assign({}, this.getPermissions(origin)) : {});
for (const [requestedTarget, approvedPermission] of Object.entries(approvedPermissions)) {
if (!this.targetExists(requestedTarget)) {
throw (0, errors_1.methodNotFound)(requestedTarget);
}
if (approvedPermission.parentCapability !== undefined &&
requestedTarget !== approvedPermission.parentCapability) {
throw new errors_1.InvalidApprovedPermissionError(origin, requestedTarget, approvedPermission);
}
// We have verified that the target exists, and reassign it to change its
// type.
const targetName = requestedTarget;
const specification = this.getPermissionSpecification(targetName);
// The requested caveats are validated here.
const caveats = this.constructCaveats(origin, targetName, approvedPermission.caveats);
const permissionOptions = {
caveats,
invoker: origin,
target: targetName,
};
let permission;
if (specification.factory) {
permission = specification.factory(permissionOptions, requestData);
// Full caveat and permission validation is performed here since the
// factory function can arbitrarily modify the entire permission object,
// including its caveats.
this.validatePermission(specification, permission, origin);
}
else {
permission = (0, Permission_1.constructPermission)(permissionOptions);
// We do not need to validate caveats in this case, because the plain
// permission constructor function does not modify the caveats, which
// were already validated by `constructCaveats` above.
this.validatePermission(specification, permission, origin, {
invokePermissionValidator: true,
performCaveatValidation: false,
});
}
permissions[targetName] = permission;
}
this.setValidatedPermissions(origin, permissions);
return permissions;
}
/**
* Validates the specified permission by:
* - Ensuring that if `subjectTypes` is specified, the subject requesting the permission is of a type in the list.
* - Ensuring that its `caveats` property is either `null` or a non-empty array.
* - Ensuring that it only includes caveats allowed by its specification.
* - Ensuring that it includes no duplicate caveats (by caveat type).
* - Validating each caveat object, if `performCaveatValidation` is `true`.
* - Calling the validator of its specification, if one exists and `invokePermissionValidator` is `true`.
*
* An error is thrown if validation fails.
*
* @param specification - The specification of the permission.
* @param permission - The permission to validate.
* @param origin - The origin associated with the permission.
* @param validationOptions - Validation options.
* @param validationOptions.invokePermissionValidator - Whether to invoke the
* permission's consumer-specified validator function, if any.
* @param validationOptions.performCaveatValidation - Whether to invoke
* {@link PermissionController.validateCaveat} on each of the permission's
* caveats.
*/
validatePermission(specification, permission, origin, { invokePermissionValidator, performCaveatValidation } = {
invokePermissionValidator: true,
performCaveatValidation: true,
}) {
var _a;
const { allowedCaveats, validator, targetName } = specification;
if (((_a = specification.subjectTypes) === null || _a === void 0 ? void 0 : _a.length) &&
specification.subjectTypes.length > 0) {
const metadata = this.messagingSystem.call('SubjectMetadataController:getSubjectMetadata', origin);
if (!metadata ||
metadata.subjectType === null ||
!specification.subjectTypes.includes(metadata.subjectType)) {
throw specification.permissionType === Permission_1.PermissionType.RestrictedMethod
? (0, errors_1.methodNotFound)(targetName, { origin })
: new errors_1.EndowmentPermissionDoesNotExistError(targetName, origin);
}
}
if ((0, utils_1.hasProperty)(permission, 'caveats')) {
const { caveats } = permission;
if (caveats !== null && !(Array.isArray(caveats) && caveats.length > 0)) {
throw new errors_1.InvalidCaveatsPropertyError(origin, targetName, caveats);
}
const seenCaveatTypes = new Set();
caveats === null || caveats === void 0 ? void 0 : caveats.forEach((caveat) => {
if (performCaveatValidation) {
this.validateCaveat(caveat, origin, targetName);
}
if (!(allowedCaveats === null || allowedCaveats === void 0 ? void 0 : allowedCaveats.includes(caveat.type))) {
throw new errors_1.ForbiddenCaveatError(caveat.type, origin, targetName);
}
if (seenCaveatTypes.has(caveat.type)) {
throw new errors_1.DuplicateCaveatError(caveat.type, origin, targetName);
}
seenCaveatTypes.add(caveat.type);
});
}
if (invokePermissionValidator && validator) {
validator(permission, origin, targetName);
}
}
/**
* Assigns the specified permissions to the subject with the given origin.
* Overwrites all existing permissions, and creates a subject entry if it
* doesn't already exist.
*
* ATTN: Assumes that the new permissions have been validated.
*
* @param origin - The origin of the grantee subject.
* @param permissions - The new permissions for the grantee subject.
*/
setValidatedPermissions(origin, permissions) {
this.update((draftState) => {
if (!draftState.subjects[origin]) {
draftState.subjects[origin] = { origin, permissions: {} };
}
draftState.subjects[origin].permissions = (0, immer_1.castDraft)(permissions);
});
}
/**
* Validates the requested caveats for the permission of the specified
* subject origin and target name and returns the validated caveat array.
*
* Throws an error if validation fails.
*
* @param origin - The origin of the permission subject.
* @param target - The permission target name.
* @param requestedCaveats - The requested caveats to construct.
* @returns The constructed caveats.
*/
constructCaveats(origin, target, requestedCaveats) {
const caveatArray = requestedCaveats === null || requestedCaveats === void 0 ? void 0 : requestedCaveats.map((requestedCaveat) => {
this.validateCaveat(requestedCaveat, origin, target);
// Reassign so that we have a fresh object.
const { type, value } = requestedCaveat;
return { type, value };
});
return caveatArray && (0, controller_utils_1.isNonEmptyArray)(caveatArray)
? caveatArray
: undefined;
}
/**
* This methods validates that the specified caveat is an object with the
* expected properties and types. It also ensures that a caveat specification
* exists for the requested caveat type, and calls the specification
* validator, if it exists, on the caveat object.
*
* Throws an error if validation fails.
*
* @param caveat - The caveat object to validate.
* @param origin - The origin associated with the subject of the parent
* permission.
* @param target - The target name associated with the parent permission.
*/
validateCaveat(caveat, origin, target) {
var _a;
if (!(0, controller_utils_1.isPlainObject)(caveat)) {
// eslint-disable-next-line @typescript-eslint/no-throw-literal
throw new errors_1.InvalidCaveatError(caveat, origin, target);
}
if (Object.keys(caveat).length !== 2) {
throw new errors_1.InvalidCaveatFieldsError(caveat, origin, target);
}
if (typeof caveat.type !== 'string') {
throw new errors_1.InvalidCaveatTypeError(caveat, origin, target);
}
const specification = this.getCaveatSpecification(caveat.type);
if (!specification) {
throw new errors_1.UnrecognizedCaveatTypeError(caveat.type, origin, target);
}
if (!(0, utils_1.hasProperty)(caveat, 'value') || caveat.value === undefined) {
throw new errors_1.CaveatMissingValueError(caveat, origin, target);
}
if (!(0, controller_utils_1.isValidJson)(caveat.value)) {
throw new errors_1.CaveatInvalidJsonError(caveat, origin, target);
}
// Typecast: TypeScript still believes that the caveat is a PlainObject.
(_a = specification.validator) === null || _a === void 0 ? void 0 : _a.call(specification, caveat, origin, target);
}
/**
* Initiates a permission request that requires user approval. This should
* always be used to grant additional permissions to a subject, unless user
* approval has been obtained through some other means.
*
* Permissions are validated at every step of the approval process, and this
* method will reject if validation fails.
*
* @see {@link ApprovalController} For the user approval logic.
* @see {@link PermissionController.acceptPermissionsRequest} For the method
* that _accepts_ the request and resolves the user approval promise.
* @see {@link PermissionController.rejectPermissionsRequest} For the method
* that _rejects_ the request and the user approval promise.
* @param subject - The grantee subject.
* @param requestedPermissions - The requested permissions.
* @param options - Additional options.
* @param options.id - The id of the permissions request. Defaults to a unique
* id.
* @param options.preserveExistingPermissions - Whether to preserve the
* subject's existing permissions. Defaults to `true`.
* @returns The granted permissions and request metadata.
*/
requestPermissions(subject, requestedPermissions, options = {}) {
return __awaiter(this, void 0, void 0, function* () {
const { origin } = subject;
const { id = (0, nanoid_1.nanoid)(), preserveExistingPermissions = true } = options;
this.validateRequestedPermissions(origin, requestedPermissions);
const metadata = {
id,
origin,
};
const permissionsRequest = {
metadata,
permissions: requestedPermissions,
};
const approvedRequest = yield this.requestUserApproval(permissionsRequest);
const { permissions: approvedPermissions } = approvedRequest, requestData = __rest(approvedRequest, ["permissions"]);
const sideEffects = this.getSideEffects(approvedPermissions);
if (Object.values(sideEffects.permittedHandlers).length > 0) {
const sideEffectsData = yield this.executeSideEffects(sideEffects, approvedRequest);
const mappedData = Object.keys(sideEffects.permittedHandlers).reduce((acc, permission, i) => (Object.assign({ [permission]: sideEffectsData[i] }, acc)), {});
return [
this.grantPermissions({
subject,
approvedPermissions,
preserveExistingPermissions,
requestData,
}),
Object.assign({ data: mappedData }, metadata),
];
}
return [
this.grantPermissions({
subject,
approvedPermissions,
preserveExistingPermissions,
requestData,
}),
metadata,
];
});
}
/**
* Validates requested permissions. Throws if validation fails.
*
* This method ensures that the requested permissions are a properly
* formatted {@link RequestedPermissions} object, and performs the same
* validation as {@link PermissionController.grantPermissions}, except that
* consumer-specified permission validator functions are not called, since
* they are only called on fully constructed, approved permissions that are
* otherwise completely valid.
*
* Unrecognzied properties on requested permissions are ignored.
*
* @param origin - The origin of the grantee subject.
* @param requestedPermissions - The requested permissions.
*/
validateRequestedPermissions(origin, requestedPermissions) {
if (!(0, controller_utils_1.isPlainObject)(requestedPermissions)) {
throw (0, errors_1.invalidParams)({
message: `Requested permissions for origin "${origin}" is not a plain object.`,
data: { origin, requestedPermissions },
});
}
if (Object.keys(requestedPermissions).length === 0) {
throw (0, errors_1.invalidParams)({
message: `Permissions request for origin "${origin}" contains no permissions.`,
data: { requestedPermissions },
});
}
for (const targetName of Object.keys(requestedPermissions)) {
const permission = requestedPermissions[targetName];
if (!this.targetExists(targetName)) {
throw (0, errors_1.methodNotFound)(targetName, { origin, requestedPermissions });
}
if (!(0, controller_utils_1.isPlainObject)(permission) ||
(permission.parentCapability !== undefined &&
targetName !== permission.parentCapability)) {
throw (0, errors_1.invalidParams)({
message: `Permissions request for origin "${origin}" contains invalid requested permission(s).`,
data: { origin, requestedPermissions },
});
}
// Here we validate the permission without invoking its validator, if any.
// The validator will be invoked after the permission has been approved.
this.validatePermission(this.getPermissionSpecification(targetName),
// Typecast: The permission is still a "PlainObject" here.
permission, origin, { invokePermissionValidator: false, performCaveatValidation: true });
}
}
/**
* Adds a request to the {@link ApprovalController} using the
* {@link AddApprovalRequest} action. Also validates the resulting approved
* permissions request, and throws an error if validation fails.
*
* @param permissionsRequest - The permissions request object.
* @returns The approved permissions request object.
*/
requestUserApproval(permissionsRequest) {
return __awaiter(this, void 0, void 0, function* () {
const { origin, id } = permissionsRequest.metadata;
const approvedRequest = yield this.messagingSystem.call('ApprovalController:addRequest', {
id,
origin,
requestData: permissionsRequest,
type: utils_2.MethodNames.requestPermissions,
}, true);
this.validateApprovedPermissions(approvedRequest, { id, origin });
return approvedRequest;
});
}
/**
* Reunites all the side-effects (onPermitted and onFailure) of the requested permissions inside a record of arrays.
*
* @param permissions - The approved permissions.
* @returns The {@link SideEffects} object containing the handlers arrays.
*/
getSideEffects(permissions) {
return Object.keys(permissions).reduce((sideEffectList, targetName) => {
if (this.targetExists(targetName)) {
const specification = this.getPermissionSpecification(targetName);
if (specification.sideEffect) {
sideEffectList.permittedHandlers[targetName] =
specification.sideEffect.onPermitted;
if (specification.sideEffect.onFailure) {
sideEffectList.failureHandlers[targetName] =
specification.sideEffect.onFailure;
}
}
}
return sideEffectList;
}, { permittedHandlers: {}, failureHandlers: {} });
}
/**
* Executes the side-effects of the approved permissions while handling the errors if any.
* It will pass an instance of the {@link messagingSystem} and the request data associated with the permission request to the handlers through its params.
*
* @param sideEffects - the side-effect record created by {@link getSideEffects}
* @param requestData - the permissions requestData.
* @returns the value returned by all the `onPermitted` handlers in an array.
*/
executeSideEffects(sideEffects, requestData) {
return __awaiter(this, void 0, void 0, function* () {
const { permittedHandlers, failureHandlers } = sideEffects;
const params = {
requestData,
messagingSystem: this.messagingSystem,
};
const promiseResults = yield Promise.allSettled(Object.values(permittedHandlers).map((permittedHandler) => permittedHandler(params)));
// lib.es2020.promise.d.ts does not export its types so we're using a simple type.
const rejectedHandlers = promiseResults.filter((promise) => promise.status === 'rejected');
if (rejectedHandlers.length > 0) {
const failureHandlersList = Object.values(failureHandlers);
if (failureHandlersList.length > 0) {
try {
yield Promise.all(failureHandlersList.map((failureHandler) => failureHandler(params)));
}
catch (error) {
throw (0, errors_1.internalError)('Unexpected error in side-effects', { error });
}
}
const reasons = rejectedHandlers.map((handler) => handler.reason);
reasons.forEach((reason) => {
console.error(reason);
});
throw reasons.length > 1
? (0, errors_1.internalError)('Multiple errors occurred during side-effects execution', { errors: reasons })
: reasons[0];
}
// lib.es2020.promise.d.ts does not export its types so we're using a simple type.
return promiseResults.map(({ value }) => value);
});
}
/**
* Validates an approved {@link PermissionsRequest} object. The approved
* request must have the required `metadata` and `permissions` properties,
* the `id` and `origin` of the `metadata` must match the original request
* metadata, and the requested permissions must be valid per
* {@link PermissionController.validateRequestedPermissions}. Any extra
* metadata properties are ignored.
*
* An error is thrown if validation fails.
*
* @param approvedRequest - The approved permissions request object.
* @param originalMetadata - The original request metadata.
*/
validateApprovedPermissions(approvedRequest, originalMetadata) {
const { id, origin } = originalMetadata;
if (!(0, controller_utils_1.isPlainObject)(approvedRequest) ||
!(0, controller_utils_1.isPlainObject)(approvedRequest.metadata)) {
throw (0, errors_1.internalError)(`Approved permissions request for subject "${origin}" is invalid.`, { data: { approvedRequest } });
}
const { metadata: { id: newId, origin: newOrigin }, permissions, } = approvedRequest;
if (newId !== id) {
throw (0, errors_1.internalError)(`Approved permissions request for subject "${origin}" mutated its id.`, { originalId: id, mutatedId: newId });
}
if (newOrigin !== origin) {
throw (0, errors_1.internalError)(`Approved permissions request for subject "${origin}" mutated its origin.`, { originalOrigin: origin, mutatedOrigin: newOrigin });
}
try {
this.validateRequestedPermissions(origin, permissions);
}
catch (error) {
if (error instanceof rpc_errors_1.JsonRpcError) {
// Re-throw as an internal error; we should never receive invalid approved
// permissions.
throw (0, errors_1.internalError)(`Invalid approved permissions request: ${error.message}`, error.data);
}
throw (0, errors_1.internalError)('Unrecognized error type', { error });
}
}
/**
* Accepts a permissions request created by
* {@link PermissionController.requestPermissions}.
*
* @param request - The permissions request.
*/
acceptPermissionsRequest(request) {
return __awaiter(this, void 0, void 0, function* () {
const { id } = request.metadata;
if (!this.hasApprovalRequest({ id })) {
throw new errors_1.PermissionsRequestNotFoundError(id);
}
if (Object.keys(request.permissions).length === 0) {
this._rejectPermissionsRequest(id, (0, errors_1.invalidParams)({
message: 'Must request at least one permission.',
}));
return;
}
try {
this.messagingSystem.call('ApprovalController:acceptRequest', id, request);
}
catch (error) {
// If accepting unexpectedly fails, reject the request and re-throw the
// error
this._rejectPermissionsRequest(id, error);
throw error;
}
});
}
/**
* Rejects a permissions request created by
* {@link PermissionController.requestPermissions}.
*
* @param id - The id of the request to be rejected.
*/
rejectPermissionsRequest(id) {
return __awaiter(this, void 0, void 0, function* () {
if (!this.hasApprovalRequest({ id })) {
throw new errors_1.PermissionsRequestNotFoundError(id);
}
this._rejectPermissionsRequest(id, (0, errors_1.userRejectedRequest)());
});
}
/**
* Checks whether the {@link ApprovalController} has a particular permissions
* request.
*
* @see {@link PermissionController.acceptPermissionsRequest} and
* {@link PermissionController.rejectPermissionsRequest} for usage.
* @param options - The {@link HasApprovalRequest} options.
* @param options.id - The id of the approval request to check for.
* @returns Whether the specified request exists.
*/
hasApprovalRequest(options) {
return this.messagingSystem.call('ApprovalController:hasRequest',
// Typecast: For some reason, the type here expects all of the possible
// HasApprovalRequest options to be specified, when they're actually all
// optional. Passing just the id is definitely valid, so we just cast it.
// TODO: Replace `any` with type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
options);
}
/**
* Rejects the permissions request with the specified id, with the specified
* error as the reason. This method is effectively a wrapper around a
* messenger call for the `ApprovalController:rejectRequest` action.
*
* @see {@link PermissionController.acceptPermissionsRequest} and
* {@link PermissionController.rejectPermissionsRequest} for usage.
* @param id - The id of the request to reject.
* @param error - The error associated with the rejection.
* @returns Nothing
*/
_rejectPermissionsRequest(id, error) {
return this.messagingSystem.call('ApprovalController:rejectRequest', id, error);
}
/**
* Gets the subject's endowments per the specified endowment permission.
* Throws if the subject does not have the required permission or if the
* permission is not an endowment permission.
*
* @param origin - The origin of the subject whose endowments to retrieve.
* @param targetName - The name of the endowment permission. This must be a
* valid permission target name.
* @param requestData - Additional data associated with the request, if any.
* Forwarded to the endowment getter function for the permission.
* @returns The endowments, if any.
*/
getEndowments(origin, targetName, requestData) {
return __awaiter(this, void 0, void 0, function* () {
if (!this.hasPermission(origin, targetName)) {
throw (0, errors_1.unauthorized)({ data: { origin, targetName } });
}
return this.getTypedPermissionSpecification(Permission_1.PermissionType.Endowment, targetName, origin).endowmentGetter({ origin, requestData });
});
}
/**
* Executes a restricted method as the subject with the given origin.
* The specified params, if any, will be passed to the method implementation.
*
* ATTN: Great caution should be exercised in the use of this method.
* Methods that cause side effects or affect application state should
* be avoided.
*
* This method will first attempt to retrieve the requested restricted method
* implementation, throwing if it does not exist. The method will then be
* invoked as though the subject with the specified origin had invoked it with
* the specified parameters. This means that any existing caveats will be
* applied to the restricted method, and this method will throw if the
* restricted method or its caveat decorators throw.
*
* In addition, this method will throw if the subject does not have a
* permission for the specified restricted method.
*
* @param origin - The origin of the subject to execute the method on behalf
* of.
* @param targetName - The name of the method to execute. This must be a valid
* permission target name.
* @param params - The parameters to pass to the method implementation.
* @returns The result of the executed method.
*/
executeRestrictedMethod(origin, targetName, params) {
return __awaiter(this, void 0, void 0, function* () {
// Throws if the method does not exist
const methodImplementation = this.getRestrictedMethod(targetName, origin);
const result = yield this._executeRestrictedMethod(methodImplementation, { origin }, targetName, params);
if (result === undefined) {
throw new Error(`Internal request for method "${targetName}" as origin "${origin}" returned no result.`);
}
return result;
});
}
/**
* An internal method used in the controller's `json-rpc-engine` middleware
* and {@link PermissionController.executeRestrictedMethod}. Calls the
* specified restricted method implementation after decorating it with the
* caveats of its permission. Throws if the subject does not have the
* requisite permission.
*
* ATTN: Parameter validation is the responsibility of the caller, or
* the restricted method implementation in the case of `params`.
*
* @see {@link PermissionController.executeRestrictedMethod} and
* {@link PermissionController.createPermissionMiddleware} for usage.
* @param methodImplementation - The implementation of the method to call.
* @param subject - Metadata about the subject that made the request.
* @param method - The method name
* @param params - Params needed for executing the restricted method
* @returns The result of the restricted method implementation
*/
_executeRestrictedMethod(methodImplementation, subject, method, params = []) {
const { origin } = subject;
const permission = this.getPermission(origin, method);
if (!permission) {
throw (0, errors_1.unauthorized)({ data: { origin, method } });
}
return (0, Caveat_1.decorateWithCaveats)(methodImplementation, permission, this._caveatSpecifications)({ method, params, context: { origin } });
}
}
exports.PermissionController = PermissionController;
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
var _chunk5L2IOZE2js = require('./chunk-5L2IOZE2.js');
require('./chunk-EGNDXGRG.js');
require('./chunk-6CID6TBW.js');
require('./chunk-AQ35E2HU.js');
require('./chunk-U5LTEXSU.js');
require('./chunk-K5R57Y57.js');
require('./chunk-CSAU5B4Q.js');
exports.CaveatMutatorOperation = _chunk5L2IOZE2js.CaveatMutatorOperation; exports.PermissionController = _chunk5L2IOZE2js.PermissionController;
//# sourceMappingURL=PermissionController.js.map

@@ -1,38 +0,9 @@

"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getPermissionsHandler = void 0;
const utils_1 = require("../utils");
exports.getPermissionsHandler = {
methodNames: [utils_1.MethodNames.getPermissions],
implementation: getPermissionsImplementation,
hookNames: {
getPermissionsForOrigin: true,
},
};
/**
* Get Permissions implementation to be used in JsonRpcEngine middleware.
*
* @param _req - The JsonRpcEngine request - unused
* @param res - The JsonRpcEngine result object
* @param _next - JsonRpcEngine next() callback - unused
* @param end - JsonRpcEngine end() callback
* @param options - Method hooks passed to the method implementation
* @param options.getPermissionsForOrigin - The specific method hook needed for this method implementation
* @returns A promise that resolves to nothing
*/
function getPermissionsImplementation(_req, res, _next, end, { getPermissionsForOrigin }) {
return __awaiter(this, void 0, void 0, function* () {
res.result = Object.values(getPermissionsForOrigin() || {});
return end();
});
}
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
var _chunkHRDKMOYSjs = require('../chunk-HRDKMOYS.js');
require('../chunk-K5R57Y57.js');
require('../chunk-CSAU5B4Q.js');
exports.getPermissionsHandler = _chunkHRDKMOYSjs.getPermissionsHandler;
//# sourceMappingURL=getPermissions.js.map

@@ -1,12 +0,13 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.handlers = void 0;
const getPermissions_1 = require("./getPermissions");
const requestPermissions_1 = require("./requestPermissions");
const revokePermissions_1 = require("./revokePermissions");
exports.handlers = [
requestPermissions_1.requestPermissionsHandler,
getPermissions_1.getPermissionsHandler,
revokePermissions_1.revokePermissionsHandler,
];
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
var _chunk7CTPRFQ3js = require('../chunk-7CTPRFQ3.js');
require('../chunk-HRDKMOYS.js');
require('../chunk-YRNH4R3G.js');
require('../chunk-ZVO26XPN.js');
require('../chunk-U5LTEXSU.js');
require('../chunk-K5R57Y57.js');
require('../chunk-CSAU5B4Q.js');
exports.handlers = _chunk7CTPRFQ3js.handlers;
//# sourceMappingURL=index.js.map

@@ -1,47 +0,10 @@

"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.requestPermissionsHandler = void 0;
const controller_utils_1 = require("@metamask/controller-utils");
const errors_1 = require("../errors");
const utils_1 = require("../utils");
exports.requestPermissionsHandler = {
methodNames: [utils_1.MethodNames.requestPermissions],
implementation: requestPermissionsImplementation,
hookNames: {
requestPermissionsForOrigin: true,
},
};
/**
* Request Permissions implementation to be used in JsonRpcEngine middleware.
*
* @param req - The JsonRpcEngine request
* @param res - The JsonRpcEngine result object
* @param _next - JsonRpcEngine next() callback - unused
* @param end - JsonRpcEngine end() callback
* @param options - Method hooks passed to the method implementation
* @param options.requestPermissionsForOrigin - The specific method hook needed for this method implementation
* @returns A promise that resolves to nothing
*/
function requestPermissionsImplementation(req, res, _next, end, { requestPermissionsForOrigin }) {
return __awaiter(this, void 0, void 0, function* () {
const { params } = req;
if (!Array.isArray(params) || !(0, controller_utils_1.isPlainObject)(params[0])) {
return end((0, errors_1.invalidParams)({ data: { request: req } }));
}
const [requestedPermissions] = params;
const [grantedPermissions] = yield requestPermissionsForOrigin(requestedPermissions);
// `wallet_requestPermission` is specified to return an array.
res.result = Object.values(grantedPermissions);
return end();
});
}
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
var _chunkYRNH4R3Gjs = require('../chunk-YRNH4R3G.js');
require('../chunk-U5LTEXSU.js');
require('../chunk-K5R57Y57.js');
require('../chunk-CSAU5B4Q.js');
exports.requestPermissionsHandler = _chunkYRNH4R3Gjs.requestPermissionsHandler;
//# sourceMappingURL=requestPermissions.js.map

@@ -1,52 +0,10 @@

"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.revokePermissionsHandler = void 0;
const utils_1 = require("@metamask/utils");
const errors_1 = require("../errors");
const utils_2 = require("../utils");
exports.revokePermissionsHandler = {
methodNames: [utils_2.MethodNames.revokePermissions],
implementation: revokePermissionsImplementation,
hookNames: {
revokePermissionsForOrigin: true,
},
};
/**
* Revoke Permissions implementation to be used in JsonRpcEngine middleware.
*
* @param req - The JsonRpcEngine request
* @param res - The JsonRpcEngine result object
* @param _next - JsonRpcEngine next() callback - unused
* @param end - JsonRpcEngine end() callback
* @param options - Method hooks passed to the method implementation
* @param options.revokePermissionsForOrigin - A hook that revokes given permission keys for an origin
* @returns A promise that resolves to nothing
*/
function revokePermissionsImplementation(req, res, _next, end, { revokePermissionsForOrigin }) {
return __awaiter(this, void 0, void 0, function* () {
const { params } = req;
const param = params === null || params === void 0 ? void 0 : params[0];
if (!param) {
return end((0, errors_1.invalidParams)({ data: { request: req } }));
}
// For now, this API revokes the entire permission key
// even if caveats are specified.
const permissionKeys = Object.keys(param);
if (!(0, utils_1.isNonEmptyArray)(permissionKeys)) {
return end((0, errors_1.invalidParams)({ data: { request: req } }));
}
revokePermissionsForOrigin(permissionKeys);
res.result = null;
return end();
});
}
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
var _chunkZVO26XPNjs = require('../chunk-ZVO26XPN.js');
require('../chunk-U5LTEXSU.js');
require('../chunk-K5R57Y57.js');
require('../chunk-CSAU5B4Q.js');
exports.revokePermissionsHandler = _chunkZVO26XPNjs.revokePermissionsHandler;
//# sourceMappingURL=revokePermissions.js.map

@@ -1,144 +0,10 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SubjectMetadataController = exports.SubjectType = void 0;
const base_controller_1 = require("@metamask/base-controller");
const controllerName = 'SubjectMetadataController';
/**
* The different kinds of subjects that MetaMask may interact with, including
* third parties and itself (e.g., when the background communicated with the UI).
*/
var SubjectType;
(function (SubjectType) {
SubjectType["Extension"] = "extension";
SubjectType["Internal"] = "internal";
SubjectType["Unknown"] = "unknown";
SubjectType["Website"] = "website";
SubjectType["Snap"] = "snap";
})(SubjectType = exports.SubjectType || (exports.SubjectType = {}));
const stateMetadata = {
subjectMetadata: { persist: true, anonymous: false },
};
const defaultState = {
subjectMetadata: {},
};
/**
* A controller for storing metadata associated with permission subjects. More
* or less, a cache.
*/
class SubjectMetadataController extends base_controller_1.BaseController {
constructor({ messenger, subjectCacheLimit, state = {}, }) {
if (!Number.isInteger(subjectCacheLimit) || subjectCacheLimit < 1) {
throw new Error(`subjectCacheLimit must be a positive integer. Received: "${subjectCacheLimit}"`);
}
const hasPermissions = (origin) => {
return messenger.call('PermissionController:hasPermissions', origin);
};
super({
name: controllerName,
metadata: stateMetadata,
messenger,
state: Object.assign({}, SubjectMetadataController.getTrimmedState(state, hasPermissions)),
});
this.subjectHasPermissions = hasPermissions;
this.subjectCacheLimit = subjectCacheLimit;
this.subjectsWithoutPermissionsEncounteredSinceStartup = new Set();
this.messagingSystem.registerActionHandler(`${this.name}:getSubjectMetadata`, this.getSubjectMetadata.bind(this));
this.messagingSystem.registerActionHandler(`${this.name}:addSubjectMetadata`, this.addSubjectMetadata.bind(this));
}
/**
* Clears the state of this controller. Also resets the cache of subjects
* encountered since startup, so as to not prematurely reach the cache limit.
*/
clearState() {
this.subjectsWithoutPermissionsEncounteredSinceStartup.clear();
this.update((_draftState) => {
return Object.assign({}, defaultState);
});
}
/**
* Stores domain metadata for the given origin (subject). Deletes metadata for
* subjects without permissions in a FIFO manner once more than
* {@link SubjectMetadataController.subjectCacheLimit} distinct origins have
* been added since boot.
*
* In order to prevent a degraded user experience,
* metadata is never deleted for subjects with permissions, since metadata
* cannot yet be requested on demand.
*
* @param metadata - The subject metadata to store.
*/
addSubjectMetadata(metadata) {
const { origin } = metadata;
const newMetadata = Object.assign(Object.assign({}, metadata), { extensionId: metadata.extensionId || null, iconUrl: metadata.iconUrl || null, name: metadata.name || null, subjectType: metadata.subjectType || null });
let originToForget = null;
// We only delete the oldest encountered subject from the cache, again to
// ensure that the user's experience isn't degraded by missing icons etc.
if (this.subjectsWithoutPermissionsEncounteredSinceStartup.size >=
this.subjectCacheLimit) {
const cachedOrigin = this.subjectsWithoutPermissionsEncounteredSinceStartup
.values()
.next().value;
this.subjectsWithoutPermissionsEncounteredSinceStartup.delete(cachedOrigin);
if (!this.subjectHasPermissions(cachedOrigin)) {
originToForget = cachedOrigin;
}
}
this.subjectsWithoutPermissionsEncounteredSinceStartup.add(origin);
this.update((draftState) => {
// Typecast: ts(2589)
// TODO: Replace `any` with type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
draftState.subjectMetadata[origin] = newMetadata;
if (typeof originToForget === 'string') {
delete draftState.subjectMetadata[originToForget];
}
});
}
/**
* Gets the subject metadata for the given origin, if any.
*
* @param origin - The origin for which to get the subject metadata.
* @returns The subject metadata, if any, or `undefined` otherwise.
*/
getSubjectMetadata(origin) {
return this.state.subjectMetadata[origin];
}
/**
* Deletes all subjects without permissions from the controller's state.
*/
trimMetadataState() {
this.update((draftState) => {
return SubjectMetadataController.getTrimmedState(
// Typecast: ts(2589)
// TODO: Replace `any` with type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
draftState, this.subjectHasPermissions);
});
}
/**
* Returns a new state object that only includes subjects with permissions.
* This method is static because we want to call it in the constructor, before
* the controller's state is initialized.
*
* @param state - The state object to trim.
* @param hasPermissions - A function that returns a boolean indicating
* whether a particular subject (identified by its origin) has any
* permissions.
* @returns The new state object. If the specified `state` object has no
* subject metadata, the returned object will be equivalent to the default
* state of this controller.
*/
static getTrimmedState(state, hasPermissions) {
const { subjectMetadata = {} } = state;
return {
subjectMetadata: Object.keys(subjectMetadata).reduce((newSubjectMetadata, origin) => {
if (hasPermissions(origin)) {
newSubjectMetadata[origin] = subjectMetadata[origin];
}
return newSubjectMetadata;
}, {}),
};
}
}
exports.SubjectMetadataController = SubjectMetadataController;
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
var _chunkSFKE5HHKjs = require('./chunk-SFKE5HHK.js');
require('./chunk-CSAU5B4Q.js');
exports.SubjectMetadataController = _chunkSFKE5HHKjs.SubjectMetadataController; exports.SubjectType = _chunkSFKE5HHKjs.SubjectType;
//# sourceMappingURL=SubjectMetadataController.js.map

@@ -1,10 +0,8 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MethodNames = void 0;
var MethodNames;
(function (MethodNames) {
MethodNames["requestPermissions"] = "wallet_requestPermissions";
MethodNames["getPermissions"] = "wallet_getPermissions";
MethodNames["revokePermissions"] = "wallet_revokePermissions";
})(MethodNames = exports.MethodNames || (exports.MethodNames = {}));
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
var _chunkK5R57Y57js = require('./chunk-K5R57Y57.js');
require('./chunk-CSAU5B4Q.js');
exports.MethodNames = _chunkK5R57Y57js.MethodNames;
//# sourceMappingURL=utils.js.map
{
"name": "@metamask/permission-controller",
"version": "8.0.1",
"version": "9.0.0",
"description": "Mediates access to JSON-RPC methods, used to interact with pieces of the MetaMask stack, via middleware for json-rpc-engine",

@@ -18,2 +18,11 @@ "keywords": [

"license": "MIT",
"sideEffects": false,
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.js",
"types": "./dist/types/index.d.ts"
},
"./package.json": "./package.json"
},
"main": "./dist/index.js",

@@ -25,2 +34,3 @@ "types": "./dist/index.d.ts",

"scripts": {
"build": "tsup --config ../../tsup.config.ts --tsconfig ./tsconfig.build.json --clean",
"build:docs": "typedoc",

@@ -36,5 +46,5 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/permission-controller",

"dependencies": {
"@metamask/base-controller": "^4.1.1",
"@metamask/controller-utils": "^8.0.3",
"@metamask/json-rpc-engine": "^7.3.2",
"@metamask/base-controller": "^5.0.0",
"@metamask/controller-utils": "^9.0.0",
"@metamask/json-rpc-engine": "^8.0.0",
"@metamask/rpc-errors": "^6.2.1",

@@ -48,3 +58,3 @@ "@metamask/utils": "^8.3.0",

"devDependencies": {
"@metamask/approval-controller": "^5.1.2",
"@metamask/approval-controller": "^6.0.0",
"@metamask/auto-changelog": "^3.4.4",

@@ -60,3 +70,3 @@ "@types/jest": "^27.4.1",

"peerDependencies": {
"@metamask/approval-controller": "^5.1.2"
"@metamask/approval-controller": "^6.0.0"
},

@@ -63,0 +73,0 @@ "engines": {

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc