UNPKG

@endo/compartment-mapper

Version:

The compartment mapper assembles Node applications in a sandbox

458 lines (423 loc) 11.4 kB
/* Validates a compartment map against its schema. */ import { assertPackagePolicy } from './policy-format.js'; /** @import {CompartmentMapDescriptor} from './types.js' */ // TODO convert to the new `||` assert style. // Deferred because this file pervasively uses simple template strings rather than // template strings tagged with `assert.details` (aka `X`), and uses // this definition of `q` rather than `assert.quote` const q = JSON.stringify; /** @type {(a: string, b: string) => number} */ // eslint-disable-next-line no-nested-ternary export const stringCompare = (a, b) => (a === b ? 0 : a < b ? -1 : 1); /** * @template T * @param {Iterable<T>} iterable */ function* enumerate(iterable) { let index = 0; for (const value of iterable) { yield [index, value]; index += 1; } } /** * @param {Record<string, unknown>} object * @param {string} message */ const assertEmptyObject = (object, message) => { assert(Object.keys(object).length === 0, message); }; /** * @param {unknown} conditions * @param {string} url */ const assertConditions = (conditions, url) => { if (conditions === undefined) return; assert( Array.isArray(conditions), `conditions must be an array, got ${conditions} in ${q(url)}`, ); for (const [index, value] of enumerate(conditions)) { assert.typeof( value, 'string', `conditions[${index}] must be a string, got ${value} in ${q(url)}`, ); } }; /** * @param {Record<string, unknown>} allegedModule * @param {string} path * @param {string} url */ const assertCompartmentModule = (allegedModule, path, url) => { const { compartment, module, retained, ...extra } = allegedModule; assertEmptyObject( extra, `${path} must not have extra properties, got ${q({ extra, compartment, })} in ${q(url)}`, ); assert.typeof( compartment, 'string', `${path}.compartment must be a string, got ${q(compartment)} in ${q(url)}`, ); assert.typeof( module, 'string', `${path}.module must be a string, got ${q(module)} in ${q(url)}`, ); if (retained !== undefined) { assert.typeof( retained, 'boolean', `${path}.retained must be a boolean, got ${q(retained)} in ${q(url)}`, ); } }; /** * @param {Record<string, unknown>} allegedModule * @param {string} path * @param {string} url */ const assertFileModule = (allegedModule, path, url) => { const { location, parser, sha512, ...extra } = allegedModule; assertEmptyObject( extra, `${path} must not have extra properties, got ${q( Object.keys(extra), )} in ${q(url)}`, ); assert.typeof( location, 'string', `${path}.location must be a string, got ${q(location)} in ${q(url)}`, ); assert.typeof( parser, 'string', `${path}.parser must be a string, got ${q(parser)} in ${q(url)}`, ); if (sha512 !== undefined) { assert.typeof( sha512, 'string', `${path}.sha512 must be a string, got ${q(sha512)} in ${q(url)}`, ); } }; /** * @param {Record<string, unknown>} allegedModule * @param {string} path * @param {string} url */ const assertExitModule = (allegedModule, path, url) => { const { exit, ...extra } = allegedModule; assertEmptyObject( extra, `${path} must not have extra properties, got ${q( Object.keys(extra), )} in ${q(url)}`, ); assert.typeof( exit, 'string', `${path}.exit must be a string, got ${q(exit)} in ${q(url)}`, ); }; /** * @param {unknown} allegedModule * @param {string} path * @param {string} url */ const assertModule = (allegedModule, path, url) => { const moduleDescriptor = Object(allegedModule); assert( allegedModule === moduleDescriptor && !Array.isArray(moduleDescriptor), `${path} must be an object, got ${allegedModule} in ${q(url)}`, ); const { compartment, module, location, parser, exit, deferredError } = moduleDescriptor; if (compartment !== undefined || module !== undefined) { assertCompartmentModule(moduleDescriptor, path, url); } else if (location !== undefined || parser !== undefined) { assertFileModule(moduleDescriptor, path, url); } else if (exit !== undefined) { assertExitModule(moduleDescriptor, path, url); } else if (deferredError !== undefined) { assert.typeof( deferredError, 'string', `${path}.deferredError must be a string contaiing an error message`, ); } else { assert.fail( `${path} is not a valid module descriptor, got ${q(allegedModule)} in ${q( url, )}`, ); } }; /** * @param {unknown} allegedModules * @param {string} path * @param {string} url */ const assertModules = (allegedModules, path, url) => { const modules = Object(allegedModules); assert( allegedModules === modules || !Array.isArray(modules), `modules must be an object, got ${q(allegedModules)} in ${q(url)}`, ); for (const [key, value] of Object.entries(modules)) { assertModule(value, `${path}.modules[${q(key)}]`, url); } }; /** * @param {unknown} allegedParsers * @param {string} path * @param {string} url */ const assertParsers = (allegedParsers, path, url) => { if (allegedParsers === undefined) { return; } const parsers = Object(allegedParsers); assert( allegedParsers === parsers && !Array.isArray(parsers), `${path}.parsers must be an object, got ${allegedParsers} in ${q(url)}`, ); for (const [key, value] of Object.entries(parsers)) { assert.typeof( key, 'string', `all keys of ${path}.parsers must be strings, got ${key} in ${q(url)}`, ); assert.typeof( value, 'string', `${path}.parsers[${q(key)}] must be a string, got ${value} in ${q(url)}`, ); } }; /** * @param {unknown} allegedScope * @param {string} path * @param {string} url */ const assertScope = (allegedScope, path, url) => { const scope = Object(allegedScope); assert( allegedScope === scope && !Array.isArray(scope), `${path} must be an object, got ${allegedScope} in ${q(url)}`, ); const { compartment, ...extra } = scope; assertEmptyObject( extra, `${path} must not have extra properties, got ${q( Object.keys(extra), )} in ${q(url)}`, ); assert.typeof( compartment, 'string', `${path}.compartment must be a string, got ${q(compartment)} in ${q(url)}`, ); }; /** * @param {unknown} allegedScopes * @param {string} path * @param {string} url */ const assertScopes = (allegedScopes, path, url) => { if (allegedScopes === undefined) { return; } const scopes = Object(allegedScopes); assert( allegedScopes === scopes && !Array.isArray(scopes), `${path}.scopes must be an object, got ${q(allegedScopes)} in ${q(url)}`, ); for (const [key, value] of Object.entries(scopes)) { assert.typeof( key, 'string', `all keys of ${path}.scopes must be strings, got ${key} in ${q(url)}`, ); assertScope(value, `${path}.scopes[${q(key)}]`, url); } }; /** * @param {unknown} allegedTypes * @param {string} path * @param {string} url */ const assertTypes = (allegedTypes, path, url) => { if (allegedTypes === undefined) { return; } const types = Object(allegedTypes); assert( allegedTypes === types && !Array.isArray(types), `${path}.types must be an object, got ${allegedTypes} in ${q(url)}`, ); for (const [key, value] of Object.entries(types)) { assert.typeof( key, 'string', `all keys of ${path}.types must be strings, got ${key} in ${q(url)}`, ); assert.typeof( value, 'string', `${path}.types[${q(key)}] must be a string, got ${value} in ${q(url)}`, ); } }; /** * @param {unknown} allegedPolicy * @param {string} path * @param {string} [url] */ const assertPolicy = ( allegedPolicy, path, url = '<unknown-compartment-map.json>', ) => { assertPackagePolicy(allegedPolicy, `${path}.policy`, url); }; /** * @param {unknown} allegedCompartment * @param {string} path * @param {string} url */ const assertCompartment = (allegedCompartment, path, url) => { const compartment = Object(allegedCompartment); assert( allegedCompartment === compartment && !Array.isArray(compartment), `${path} must be an object, got ${allegedCompartment} in ${q(url)}`, ); const { location, name, label, parsers, types, scopes, modules, policy, ...extra } = compartment; assertEmptyObject( extra, `${path} must not have extra properties, got ${q( Object.keys(extra), )} in ${q(url)}`, ); assert.typeof( location, 'string', `${path}.location in ${q(url)} must be string, got ${q(location)}`, ); assert.typeof( name, 'string', `${path}.name in ${q(url)} must be string, got ${q(name)}`, ); assert.typeof( label, 'string', `${path}.label in ${q(url)} must be string, got ${q(label)}`, ); assertModules(modules, path, url); assertParsers(parsers, path, url); assertScopes(scopes, path, url); assertTypes(types, path, url); assertPolicy(policy, path, url); }; /** * @param {unknown} allegedCompartments * @param {string} url */ const assertCompartments = (allegedCompartments, url) => { const compartments = Object(allegedCompartments); assert( allegedCompartments === compartments || !Array.isArray(compartments), `compartments must be an object, got ${q(allegedCompartments)} in ${q( url, )}`, ); for (const [key, value] of Object.entries(compartments)) { assertCompartment(value, `compartments[${q(key)}]`, url); } }; /** * @param {unknown} allegedEntry * @param {string} url */ const assertEntry = (allegedEntry, url) => { const entry = Object(allegedEntry); assert( allegedEntry === entry && !Array.isArray(entry), `"entry" must be an object in compartment map, got ${allegedEntry} in ${q( url, )}`, ); const { compartment, module, ...extra } = entry; assertEmptyObject( extra, `"entry" must not have extra properties in compartment map, got ${q( Object.keys(extra), )} in ${q(url)}`, ); assert.typeof( compartment, 'string', `entry.compartment must be a string in compartment map, got ${compartment} in ${q( url, )}`, ); assert.typeof( module, 'string', `entry.module must be a string in compartment map, got ${module} in ${q( url, )}`, ); }; /** * @param {unknown} allegedCompartmentMap * @param {string} [url] * @returns {asserts allegedCompartmentMap is CompartmentMapDescriptor} */ export const assertCompartmentMap = ( allegedCompartmentMap, url = '<unknown-compartment-map.json>', ) => { const compartmentMap = Object(allegedCompartmentMap); assert( allegedCompartmentMap === compartmentMap && !Array.isArray(compartmentMap), `Compartment map must be an object, got ${allegedCompartmentMap} in ${q( url, )}`, ); const { // TODO migrate tags to conditions // https://github.com/endojs/endo/issues/2388 tags: conditions, entry, compartments, ...extra } = Object(compartmentMap); assertEmptyObject( extra, `Compartment map must not have extra properties, got ${q( Object.keys(extra), )} in ${q(url)}`, ); assertConditions(conditions, url); assertEntry(entry, url); assertCompartments(compartments, url); };