UNPKG

@openfga/syntax-transformer

Version:

Javascript implementation of ANTLR Grammar for the OpenFGA DSL and parser from and to the OpenFGA JSON Syntax

125 lines (124 loc) 5.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.transformModFileToJSON = void 0; const yaml_1 = require("yaml"); const errors_1 = require("../../errors"); /** * Gets the line and column data from the `linePos` returned from parsing a yaml file. * @param linePos - The `linePos` property. * @returns Line and column data. */ function getLineAndColumnFromLinePos(linePos) { if (linePos === undefined) { return { line: { start: 0, end: 0 }, column: { start: 0, end: 0 } }; } // eslint-disable-next-line prefer-const let [start, end] = linePos; if (end === undefined) { end = start; } return { line: { start: start.line, end: end.line, }, column: { start: start.col, end: end.col, }, }; } /** * Gets the line and column data for a node in a yaml doc. * @param node - The node from the yaml doc. * @param counter - The instance of LineCounter that was passed to `parseDocument`. * @returns Line and column data. */ function getLineAndColumnFromNode(node, counter) { if (!(0, yaml_1.isNode)(node) || !node.range) { return { line: { start: 0, end: 0 }, column: { start: 0, end: 0 } }; } const start = counter.linePos(node.range[0]); const end = counter.linePos(node.range[1]); return { line: { start: start.line - 1, end: end.line - 1, }, column: { start: start.col - 1, end: end.col - 1, }, }; } /** * Transforms an `fga.mod` file into the JSON representation and validate the fields are correct. * @param {string} modFile - The `fga.mod` file * @returns {ModFile} The jSON representation of the `fga.mod` file. */ const transformModFileToJSON = (modFile) => { const lineCounter = new yaml_1.LineCounter(); const yamlDoc = (0, yaml_1.parseDocument)(modFile, { lineCounter, keepSourceTokens: true, }); const errors = []; // Copy over any general yaml parsing errors for (const error of yamlDoc.errors) { errors.push(new errors_1.FGAModFileValidationSingleError(Object.assign({ msg: error.message }, getLineAndColumnFromLinePos(error.linePos)))); } const parsedModFile = {}; const schemaNode = yamlDoc.get("schema", true); if (!schemaNode) { errors.push(new errors_1.FGAModFileValidationSingleError(Object.assign({ msg: "missing schema field" }, getLineAndColumnFromLinePos()))); } else if (typeof schemaNode.value !== "string") { errors.push(new errors_1.FGAModFileValidationSingleError(Object.assign({ msg: `unexpected schema type, expected string got value ${schemaNode.value}` }, getLineAndColumnFromNode(schemaNode, lineCounter)))); } else if (schemaNode.value !== "1.2") { errors.push(new errors_1.FGAModFileValidationSingleError(Object.assign({ msg: "unsupported schema version, fga.mod only supported in version `1.2`" }, getLineAndColumnFromNode(schemaNode, lineCounter)))); } else { parsedModFile.schema = Object.assign({ value: schemaNode.value }, getLineAndColumnFromNode(schemaNode, lineCounter)); } const contentsNode = yamlDoc.get("contents", true); if (!contentsNode) { errors.push(new errors_1.FGAModFileValidationSingleError(Object.assign({ msg: "missing contents field" }, getLineAndColumnFromLinePos()))); } else if (!(0, yaml_1.isSeq)(contentsNode)) { const node = yamlDoc.get("contents", true); const contents = yamlDoc.get("contents"); errors.push(new errors_1.FGAModFileValidationSingleError(Object.assign({ msg: `unexpected contents type, expected list of strings got value ${contents}` }, getLineAndColumnFromNode(node, lineCounter)))); } else { const contents = yamlDoc.get("contents"); const contentsValue = []; for (const file of contents.items) { if (typeof file.value !== "string") { errors.push(new errors_1.FGAModFileValidationSingleError(Object.assign({ msg: `unexpected contents item type, expected string got value ${file.value}` }, getLineAndColumnFromNode(file, lineCounter)))); continue; } // Replace URI encoded characters to handle different path variants let filename = file.value.replace(/%2e/gi, "."); filename = filename.replace(/%2f/gi, "/"); filename = filename.replace(/%5c/gi, "\\"); // Check for directory traversal patterns or absolute paths if (filename.includes("../") || filename.includes("..\\") || filename.startsWith("/")) { errors.push(new errors_1.FGAModFileValidationSingleError(Object.assign({ msg: `invalid contents item ${file.value}` }, getLineAndColumnFromNode(file, lineCounter)))); continue; } if (!filename.endsWith(".fga")) { errors.push(new errors_1.FGAModFileValidationSingleError(Object.assign({ msg: `contents items should use fga file extension, got ${file.value}` }, getLineAndColumnFromNode(file, lineCounter)))); continue; } contentsValue.push(Object.assign({ value: filename }, getLineAndColumnFromNode(file, lineCounter))); } const node = yamlDoc.get("contents", true); parsedModFile.contents = Object.assign({ value: contentsValue }, getLineAndColumnFromNode(node, lineCounter)); } if (errors.length) { throw new errors_1.FGAModFileValidationError(errors); } return parsedModFile; }; exports.transformModFileToJSON = transformModFileToJSON;