@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
JavaScript
;
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;