Skip to content

Commit

Permalink
feat(compartment-mapper): add policy-related types
Browse files Browse the repository at this point in the history
This adds types for policies and exports them from the `compartment-mapper` entry point. It also updates some usages of existing and new types.
  • Loading branch information
boneskull committed Jan 9, 2024
1 parent b89e457 commit d3b49e8
Show file tree
Hide file tree
Showing 7 changed files with 216 additions and 88 deletions.
7 changes: 6 additions & 1 deletion packages/compartment-mapper/demo/policy/index.mjs
Expand Up @@ -8,7 +8,11 @@ lockdown({

import fs from 'fs';

import { importLocation, makeArchive, parseArchive } from '../../index.js';
import {
importLocation,
makeArchive,
parseArchive,
} from '@endo/compartment-mapper';

const readPower = async location =>
fs.promises.readFile(new URL(location).pathname);
Expand All @@ -18,6 +22,7 @@ const entrypointPath = new URL('./app.js', import.meta.url).href;
const ApiSubsetOfBuffer = harden({ from: Buffer.from });

const options = {
/** @type {import('@endo/compartment-mapper').Policy} */
policy: {
defaultAttenuator:
'@endo/compartment-mapper-demo-lavamoat-style-attenuator',
Expand Down
2 changes: 1 addition & 1 deletion packages/compartment-mapper/src/import.js
Expand Up @@ -115,7 +115,7 @@ export const loadLocation = async (readPowers, moduleLocation, options) => {
* @param {ReadFn | ReadPowers} readPowers
* @param {string} moduleLocation
* @param {ExecuteOptions & ArchiveOptions} [options]
* @returns {Promise<object>} the object of the imported modules exported
* @returns {Promise<import('./types.js').SomeObject>} the object of the imported modules exported
* names.
*/
export const importLocation = async (
Expand Down
2 changes: 1 addition & 1 deletion packages/compartment-mapper/src/node-modules.js
Expand Up @@ -631,7 +631,7 @@ const translateGraph = (
name,
path,
},
packagePolicy,
/** @type {import('./types.js').PackagePolicy} */ (packagePolicy),
)
) {
moduleDescriptors[localPath] = {
Expand Down
85 changes: 56 additions & 29 deletions packages/compartment-mapper/src/policy-format.js
@@ -1,23 +1,24 @@
// @ts-check

/** @typedef {import('./types.js').AttenuationDefinition} AttenuationDefinition */
/** @typedef {import('./types.js').UnifiedAttenuationDefinition} UnifiedAttenuationDefinition */

const { entries, keys } = Object;
const { isArray } = Array;
const q = JSON.stringify;

const ATTENUATOR_KEY = 'attenuate';
const ATTENUATOR_PARAMS_KEY = 'params';
const WILDCARD_POLICY_VALUE = 'any';
const POLICY_FIELDS_LOOKUP = ['builtins', 'globals', 'packages'];
const POLICY_FIELDS_LOOKUP = /** @type {const} */ ([
'builtins',
'globals',
'packages',
]);

/**
*
* @param {object} packagePolicy
* @param {string} field
* @param {import('./types.js').PackagePolicy} packagePolicy
* @param {'builtins'|'globals'|'packages'} field
* @param {string} itemName
* @returns {boolean | object}
* @returns {boolean | import('./types.js').AttenuationDefinition}
*/
export const policyLookupHelper = (packagePolicy, field, itemName) => {
if (!POLICY_FIELDS_LOOKUP.includes(field)) {
Expand All @@ -34,38 +35,42 @@ export const policyLookupHelper = (packagePolicy, field, itemName) => {
if (packagePolicy[field] === WILDCARD_POLICY_VALUE) {
return true;
}
if (packagePolicy[field][itemName]) {
return packagePolicy[field][itemName];

const value = /** @type {import('./types.js').AttenuationDefinition} */ (
packagePolicy[field]
);
if (itemName in value) {
return value[itemName];
}
return false;
};

/**
* Checks if the policy value is set to wildcard to allow everything
* Type guard; checks if the policy value is set to the wildcard value to allow everything
*
* @param {any} policyValue
* @returns {boolean}
* @param {unknown} policyValue
* @returns {policyValue is import('./types.js').WildcardPolicy}
*/
export const isAllowingEverything = policyValue =>
policyValue === WILDCARD_POLICY_VALUE;

/**
*
* @param {AttenuationDefinition} potentialDefinition
* @returns {boolean}
* Type guard for `AttenuationDefinition`
* @param {unknown} allegedDefinition
* @returns {allegedDefinition is import('./types.js').AttenuationDefinition}
*/
export const isAttenuationDefinition = potentialDefinition => {
export const isAttenuationDefinition = allegedDefinition => {
return (
(typeof potentialDefinition === 'object' &&
typeof potentialDefinition[ATTENUATOR_KEY] === 'string') || // object with attenuator name
isArray(potentialDefinition) // params for default attenuator
(typeof allegedDefinition === 'object' &&
typeof allegedDefinition?.[ATTENUATOR_KEY] === 'string') || // object with attenuator name
isArray(allegedDefinition) // params for default attenuator
);
};

/**
*
* @param {AttenuationDefinition} attenuationDefinition
* @returns {UnifiedAttenuationDefinition}
* @param {import('./types.js').AttenuationDefinition} attenuationDefinition
* @returns {import('./types.js').UnifiedAttenuationDefinition}
*/
export const getAttenuatorFromDefinition = attenuationDefinition => {
if (!isAttenuationDefinition(attenuationDefinition)) {
Expand All @@ -90,32 +95,49 @@ export const getAttenuatorFromDefinition = attenuationDefinition => {
}
};

// TODO: should be a type guard
const isRecordOf = (item, predicate) => {
if (typeof item !== 'object' || item === null || isArray(item)) {
return false;
}
return entries(item).every(([key, value]) => predicate(value, key));
};

/**
* Type guard for `boolean`
* @param {unknown} item
* @returns {item is boolean}
*/
const isBoolean = item => typeof item === 'boolean';

// TODO: should be a type guard
const predicateOr =
(...predicates) =>
item =>
predicates.some(p => p(item));

/**
* @param {unknown} item
* @returns {item is import('./types.js').PolicyItem}
*/
const isPolicyItem = item =>
item === undefined ||
item === WILDCARD_POLICY_VALUE ||
isRecordOf(item, isBoolean);

/**
* This asserts (i.e., throws) that `allegedPackagePolicy` is a valid `PackagePolicy`.
*
* Mild-mannered during the day, but fights crime at night as a type guard.
*
* @param {unknown} allegedPackagePolicy
* @param {string} path
* @param {string} [url]
* @returns {void}
* @param {unknown} allegedPackagePolicy - Alleged `PackagePolicy` to test
* @param {string} path - Path in the `Policy` object; used for error messages only
* @param {string} [url] - URL of the policy file; used for error messages only
* @returns {allegedPackagePolicy is import('./types.js').PackagePolicy}
*/
export const assertPackagePolicy = (allegedPackagePolicy, path, url) => {
if (allegedPackagePolicy === undefined) {
return;
return true;
}
const inUrl = url ? ` in ${q(url)}` : '';

Expand Down Expand Up @@ -169,16 +191,20 @@ export const assertPackagePolicy = (allegedPackagePolicy, path, url) => {
builtins,
})}${inUrl}`,
);
return true;
};

/**
* This asserts (i.e., throws) that `allegedPolicy` is a valid `Policy`
*
* It also moonlights as a type guard.
*
* @param {unknown} allegedPolicy
* @returns {void}
* @param {unknown} allegedPolicy - Alleged `Policy` to test
* @returns {allegedPolicy is import('./types.js').Policy}
*/
export const assertPolicy = allegedPolicy => {
if (allegedPolicy === undefined) {
return;
return true;
}
const policy = Object(allegedPolicy);
assert(
Expand Down Expand Up @@ -206,4 +232,5 @@ export const assertPolicy = allegedPolicy => {
for (const [key, value] of entries(resources)) {
assertPackagePolicy(value, `policy.resources["${key}"]`);
}
return true;
};

0 comments on commit d3b49e8

Please sign in to comment.