mirror of
https://github.com/node-red/node-red.git
synced 2023-10-10 13:36:53 +02:00
Implement allow/denyList when loading/installing modules
This commit is contained in:
parent
fc459be531
commit
aacb92a7ae
@ -50,13 +50,15 @@ module.exports = {
|
||||
// Nodes
|
||||
adminApp.get("/nodes",needsPermission("nodes.read"),nodes.getAll,apiUtil.errorHandler);
|
||||
|
||||
if (!settings.editorTheme || !settings.editorTheme.palette || settings.editorTheme.palette.upload !== false) {
|
||||
if (!settings.externalModules || !settings.externalModules.palette || settings.externalModules.palette.allowInstall !== false) {
|
||||
if (!settings.externalModules || !settings.externalModules.palette || settings.externalModules.palette.allowUpload !== false) {
|
||||
const multer = require('multer');
|
||||
const upload = multer({ storage: multer.memoryStorage() });
|
||||
adminApp.post("/nodes",needsPermission("nodes.write"),upload.single("tarball"),nodes.post,apiUtil.errorHandler);
|
||||
} else {
|
||||
adminApp.post("/nodes",needsPermission("nodes.write"),nodes.post,apiUtil.errorHandler);
|
||||
}
|
||||
}
|
||||
adminApp.get(/^\/nodes\/messages/,needsPermission("nodes.read"),nodes.getModuleCatalogs,apiUtil.errorHandler);
|
||||
adminApp.get(/^\/nodes\/((@[^\/]+\/)?[^\/]+\/[^\/]+)\/messages/,needsPermission("nodes.read"),nodes.getModuleCatalog,apiUtil.errorHandler);
|
||||
adminApp.get(/^\/nodes\/((@[^\/]+\/)?[^\/]+)$/,needsPermission("nodes.read"),nodes.getModule,apiUtil.errorHandler);
|
||||
|
@ -15,26 +15,42 @@
|
||||
**/
|
||||
|
||||
|
||||
var path = require("path");
|
||||
var os = require("os");
|
||||
var fs = require("fs-extra");
|
||||
var tar = require("tar");
|
||||
const path = require("path");
|
||||
const os = require("os");
|
||||
const fs = require("fs-extra");
|
||||
const tar = require("tar");
|
||||
|
||||
var registry = require("./registry");
|
||||
var library = require("./library");
|
||||
const registry = require("./registry");
|
||||
const registryUtil = require("./util");
|
||||
const library = require("./library");
|
||||
const {exec,log,events} = require("@node-red/util");
|
||||
var child_process = require('child_process');
|
||||
var npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
||||
var installerEnabled = false;
|
||||
const child_process = require('child_process');
|
||||
const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
||||
let installerEnabled = false;
|
||||
|
||||
var settings;
|
||||
let settings;
|
||||
const moduleRe = /^(@[^/@]+?[/])?[^/@]+?$/;
|
||||
const slashRe = process.platform === "win32" ? /\\|[/]/ : /[/]/;
|
||||
const pkgurlRe = /^(https?|git(|\+https?|\+ssh|\+file)):\/\//;
|
||||
const localtgzRe = /^([a-zA-Z]:|\/).+tgz$/;
|
||||
|
||||
// Default allow/deny lists
|
||||
let installAllowList = ['*'];
|
||||
let installDenyList = [];
|
||||
|
||||
|
||||
function init(_settings) {
|
||||
settings = _settings;
|
||||
// TODO: This is duplicated in localfilesystem.js
|
||||
// Should it *all* be managed by util?
|
||||
if (settings.externalModules && settings.externalModules.palette) {
|
||||
if (settings.externalModules.palette.allowList || settings.externalModules.palette.denyList) {
|
||||
installAllowList = settings.externalModules.palette.allowList;
|
||||
installDenyList = settings.externalModules.palette.denyList;
|
||||
}
|
||||
}
|
||||
installAllowList = registryUtil.parseModuleList(installAllowList);
|
||||
installDenyList = registryUtil.parseModuleList(installDenyList);
|
||||
}
|
||||
|
||||
var activePromise = Promise.resolve();
|
||||
@ -118,6 +134,12 @@ function installModule(module,version,url) {
|
||||
reject(e);
|
||||
return;
|
||||
}
|
||||
if (!registryUtil.checkModuleAllowed(module,version,installAllowList,installDenyList)) {
|
||||
const e = new Error("Install not allowed");
|
||||
e.code = "install_not_allowed";
|
||||
reject(e);
|
||||
return
|
||||
}
|
||||
isUpgrade = checkExistingModule(module,version);
|
||||
} catch(err) {
|
||||
return reject(err);
|
||||
@ -215,6 +237,10 @@ async function getExistingPackageVersion(moduleName) {
|
||||
}
|
||||
|
||||
async function installTarball(tarball) {
|
||||
if (settings.externalModules && settings.externalModules.palette && settings.externalModules.palette.allowUpload === false) {
|
||||
throw new Error("Module upload disabled")
|
||||
}
|
||||
|
||||
// Check this tarball contains a valid node-red module.
|
||||
// Get its module name/version
|
||||
const moduleInfo = await getTarballModuleInfo(tarball);
|
||||
|
@ -14,10 +14,16 @@
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
var fs = require("fs");
|
||||
var path = require("path");
|
||||
var log = require("@node-red/util").log;
|
||||
var i18n = require("@node-red/util").i18n;
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const log = require("@node-red/util").log;
|
||||
const i18n = require("@node-red/util").i18n;
|
||||
const registryUtil = require("./util");
|
||||
|
||||
// Default allow/deny lists
|
||||
let loadAllowList = ['*'];
|
||||
let loadDenyList = [];
|
||||
|
||||
|
||||
var settings;
|
||||
var disableNodePathScan = false;
|
||||
@ -25,6 +31,16 @@ var iconFileExtensions = [".png", ".gif", ".svg"];
|
||||
|
||||
function init(_settings) {
|
||||
settings = _settings;
|
||||
// TODO: This is duplicated in installer.js
|
||||
// Should it *all* be managed by util?
|
||||
if (settings.externalModules && settings.externalModules.palette) {
|
||||
if (settings.externalModules.palette.allowList || settings.externalModules.palette.denyList) {
|
||||
loadAllowList = settings.externalModules.palette.allowList;
|
||||
loadDenyList = settings.externalModules.palette.denyList;
|
||||
}
|
||||
}
|
||||
loadAllowList = registryUtil.parseModuleList(loadAllowList);
|
||||
loadDenyList = registryUtil.parseModuleList(loadDenyList);
|
||||
}
|
||||
|
||||
function isIncluded(name) {
|
||||
@ -137,9 +153,13 @@ function scanDirForNodesModules(dir,moduleName) {
|
||||
try {
|
||||
var pkg = require(pkgfn);
|
||||
if (pkg['node-red']) {
|
||||
if (!registryUtil.checkModuleAllowed(pkg.name,pkg.version,loadAllowList,loadDenyList)) {
|
||||
log.debug("! Module: "+pkg.name+" "+pkg.version+ " *ignored due to denyList*");
|
||||
} else {
|
||||
var moduleDir = path.join(dir,fn);
|
||||
results.push({dir:moduleDir,package:pkg});
|
||||
}
|
||||
}
|
||||
} catch(err) {
|
||||
if (err.code != "MODULE_NOT_FOUND") {
|
||||
// TODO: handle unexpected error
|
||||
@ -308,8 +328,7 @@ function getNodeFiles(disableNodePathScan) {
|
||||
} else {
|
||||
result = false;
|
||||
}
|
||||
log.debug("Module: "+mod.package.name+" "+mod.package.version+(result?"":" *ignored due to local copy*"));
|
||||
log.debug(" "+mod.dir);
|
||||
log.debug((result?"":"! ")+"Module: "+mod.package.name+" "+mod.package.version+" "+mod.dir+(result?"":" *ignored due to local copy*"));
|
||||
return result;
|
||||
});
|
||||
|
||||
|
@ -15,6 +15,7 @@
|
||||
**/
|
||||
|
||||
const path = require("path");
|
||||
const semver = require("semver");
|
||||
const {events,i18n,log} = require("@node-red/util");
|
||||
var runtime;
|
||||
|
||||
@ -104,9 +105,78 @@ function createNodeApi(node) {
|
||||
return red;
|
||||
}
|
||||
|
||||
|
||||
function checkAgainstList(module,version,list) {
|
||||
for (let i=0;i<list.length;i++) {
|
||||
let rule = list[i];
|
||||
if (rule.module.test(module)) {
|
||||
if (version && rule.version) {
|
||||
if (semver.satisfies(version,rule.version)) {
|
||||
return rule;
|
||||
}
|
||||
} else {
|
||||
return rule;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function checkModuleAllowed(module,version,allowList,denyList) {
|
||||
// console.log("checkModuleAllowed",module,version);//,allowList,denyList)
|
||||
if (!allowList && !denyList) {
|
||||
// Default to allow
|
||||
return true;
|
||||
}
|
||||
if (allowList.length === 0 && denyList.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var allowedRule = checkAgainstList(module,version,allowList);
|
||||
var deniedRule = checkAgainstList(module,version,denyList);
|
||||
// console.log("A",allowedRule)
|
||||
// console.log("D",deniedRule)
|
||||
|
||||
if (allowedRule && !deniedRule) {
|
||||
return true;
|
||||
}
|
||||
if (!allowedRule && deniedRule) {
|
||||
return false;
|
||||
}
|
||||
if (!allowedRule && !deniedRule) {
|
||||
return true;
|
||||
}
|
||||
if (allowedRule.wildcardPos !== deniedRule.wildcardPos) {
|
||||
return allowedRule.wildcardPos > deniedRule.wildcardPos
|
||||
} else {
|
||||
// First wildcard in same position.
|
||||
// Go with the longer matching rule. This isn't going to be 100%
|
||||
// right, but we are deep into edge cases at this point.
|
||||
return allowedRule.module.toString().length > deniedRule.module.toString().length
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function parseModuleList(list) {
|
||||
list = list || ["*"];
|
||||
return list.map(rule => {
|
||||
let m = /^(.+?)(?:@(.*))?$/.exec(rule);
|
||||
let wildcardPos = m[1].indexOf("*");
|
||||
wildcardPos = wildcardPos===-1?Infinity:wildcardPos;
|
||||
|
||||
return {
|
||||
module: new RegExp("^"+m[1].replace(/\*/g,".*")+"$"),
|
||||
version: m[2],
|
||||
wildcardPos: wildcardPos
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
init: function(_runtime) {
|
||||
runtime = _runtime;
|
||||
},
|
||||
createNodeApi: createNodeApi
|
||||
createNodeApi: createNodeApi,
|
||||
parseModuleList: parseModuleList,
|
||||
checkModuleAllowed: checkModuleAllowed
|
||||
}
|
||||
|
@ -164,7 +164,7 @@ var api = module.exports = {
|
||||
throw err;
|
||||
}
|
||||
if (opts.tarball) {
|
||||
if (runtime.settings.editorTheme && runtime.settings.editorTheme.palette && runtime.settings.editorTheme.palette.upload === false) {
|
||||
if (runtime.settings.externalModules && runtime.settings.externalModules.palette && runtime.settings.externalModules.palette.upload === false) {
|
||||
runtime.log.audit({event: "nodes.install",tarball:opts.tarball.file,error:"invalid_request"}, opts.req);
|
||||
var err = new Error("Invalid request");
|
||||
err.code = "invalid_request";
|
||||
|
@ -14,7 +14,54 @@
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
const should = require("should");
|
||||
const NR_TEST_UTILS = require("nr-test-utils");
|
||||
const registryUtil = NR_TEST_UTILS.require("@node-red/registry/lib/util");
|
||||
|
||||
|
||||
describe("red/nodes/registry/util",function() {
|
||||
it.skip("NEEDS TESTS");
|
||||
describe("createNodeApi", function() {
|
||||
it.skip("needs tests");
|
||||
});
|
||||
describe("checkModuleAllowed", function() {
|
||||
function checkList(module, version, allowList, denyList) {
|
||||
return registryUtil.checkModuleAllowed(
|
||||
module,
|
||||
version,
|
||||
registryUtil.parseModuleList(allowList),
|
||||
registryUtil.parseModuleList(denyList)
|
||||
)
|
||||
}
|
||||
|
||||
it("allows module with no allow/deny list provided", function() {
|
||||
checkList("abc","1.2.3",[],[]).should.be.true();
|
||||
})
|
||||
it("defaults allow to * when only deny list is provided", function() {
|
||||
checkList("abc","1.2.3",["*"],["def"]).should.be.true();
|
||||
checkList("def","1.2.3",["*"],["def"]).should.be.false();
|
||||
})
|
||||
it("uses most specific matching rule", function() {
|
||||
checkList("abc","1.2.3",["ab*"],["a*"]).should.be.true();
|
||||
checkList("def","1.2.3",["d*"],["de*"]).should.be.false();
|
||||
})
|
||||
it("checks version string using semver rules", function() {
|
||||
// Deny
|
||||
checkList("abc","1.2.3",["abc@1.2.2"],["*"]).should.be.false();
|
||||
checkList("abc","1.2.3",["abc@1.2.4"],["*"]).should.be.false();
|
||||
checkList("abc","1.2.3",["abc@>1.2.3"],["*"]).should.be.false();
|
||||
checkList("abc","1.2.3",["abc@>=1.2.3"],["abc"]).should.be.false();
|
||||
|
||||
|
||||
checkList("node-red-contrib-foo","1.2.3",["*"],["*contrib*"]).should.be.false();
|
||||
|
||||
|
||||
// Allow
|
||||
checkList("abc","1.2.3",["abc@1.2.3"],["*"]).should.be.true();
|
||||
checkList("abc","1.2.3",["abc@<1.2.4"],["*"]).should.be.true();
|
||||
checkList("abc","1.2.3",["abc"],["abc@>1.2.3"]).should.be.true();
|
||||
checkList("abc","1.2.3",["abc"],["abc@<1.2.3||>1.2.3"]).should.be.true();
|
||||
checkList("node-red-contrib-foo","1.2.3",["*contrib*"],["*"]).should.be.true();
|
||||
})
|
||||
|
||||
})
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user