diff --git a/packages/node_modules/@node-red/registry/lib/installer.js b/packages/node_modules/@node-red/registry/lib/installer.js index c5588bb95..c7cd0d5e0 100644 --- a/packages/node_modules/@node-red/registry/lib/installer.js +++ b/packages/node_modules/@node-red/registry/lib/installer.js @@ -37,7 +37,8 @@ const localtgzRe = /^([a-zA-Z]:|\/).+tgz$/; // Default allow/deny lists let installAllowList = ['*']; let installDenyList = []; - +let installAllAllowed = true; +let installVersionRestricted = false; function init(_settings) { settings = _settings; @@ -51,6 +52,18 @@ function init(_settings) { } installAllowList = registryUtil.parseModuleList(installAllowList); installDenyList = registryUtil.parseModuleList(installDenyList); + installAllAllowed = installDenyList.length === 0; + if (!installAllAllowed) { + installAllowList.forEach(function(rule) { + installVersionRestricted = installVersionRestricted || (!!rule.version); + }) + if (!installVersionRestricted) { + installDenyList.forEach(function(rule) { + installVersionRestricted = installVersionRestricted || (!!rule.version); + }) + + } + } } var activePromise = Promise.resolve(); @@ -95,99 +108,101 @@ function checkExistingModule(module,version) { return false; } -function installModule(module,version,url) { +async function installModule(module,version,url) { if (Buffer.isBuffer(module)) { return installTarball(module) } module = module || ""; - activePromise = activePromise.then(() => { + activePromise = activePromise.then(async function() { //TODO: ensure module is 'safe' - return new Promise((resolve,reject) => { - var installName = module; - var isUpgrade = false; - try { - if (url) { - if (pkgurlRe.test(url) || localtgzRe.test(url)) { - // Git remote url or Tarball url - check the valid package url - installName = url; - } else { - log.warn(log._("server.install.install-failed-url",{name:module,url:url})); - const e = new Error("Invalid url"); - e.code = "invalid_module_url"; - reject(e); - return; - } - } else if (moduleRe.test(module)) { - // Simple module name - assume it can be npm installed - if (version) { - installName += "@"+version; - } - } else if (slashRe.test(module)) { - // A path - check if there's a valid package.json - installName = module; - let info = checkModulePath(module); - module = info.name; - } else { - log.warn(log._("server.install.install-failed-name",{name:module})); - const e = new Error("Invalid module name"); - e.code = "invalid_module_name"; - 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); - } - if (!isUpgrade) { - log.info(log._("server.install.installing",{name: module,version: version||"latest"})); + var installName = module; + let isRegistryPackage = true; + var isUpgrade = false; + if (url) { + if (pkgurlRe.test(url) || localtgzRe.test(url)) { + // Git remote url or Tarball url - check the valid package url + installName = url; + isRegistryPackage = false; } else { - log.info(log._("server.install.upgrading",{name: module,version: version||"latest"})); + log.warn(log._("server.install.install-failed-url",{name:module,url:url})); + const e = new Error("Invalid url"); + e.code = "invalid_module_url"; + throw e; + } + } else if (moduleRe.test(module)) { + // Simple module name - assume it can be npm installed + if (version) { + installName += "@"+version; + } + } else if (slashRe.test(module)) { + // A path - check if there's a valid package.json + installName = module; + let info = checkModulePath(module); + module = info.name; + isRegistryPackage = false; + } else { + log.warn(log._("server.install.install-failed-name",{name:module})); + const e = new Error("Invalid module name"); + e.code = "invalid_module_name"; + throw e; + } + if (!installAllAllowed) { + let installVersion = version; + if (installVersionRestricted && isRegistryPackage) { + installVersion = await getModuleVersionFromNPM(module, version); } - var installDir = settings.userDir || process.env.NODE_RED_HOME || "."; - var args = ['install','--no-audit','--no-update-notifier','--no-fund','--save','--save-prefix=~','--production',installName]; - log.trace(npmCommand + JSON.stringify(args)); - exec.run(npmCommand,args,{ - cwd: installDir - }, true).then(result => { - if (!isUpgrade) { - log.info(log._("server.install.installed",{name:module})); - resolve(require("./index").addModule(module).then(reportAddedModules)); - } else { - log.info(log._("server.install.upgraded",{name:module, version:version})); - events.emit("runtime-event",{id:"restart-required",payload:{type:"warning",text:"notification.warnings.restartRequired"},retain:true}); - resolve(require("./registry").setModulePendingUpdated(module,version)); - } - }).catch(result => { - var output = result.stderr; - var e; - var lookFor404 = new RegExp(" 404 .*"+module,"m"); - var lookForVersionNotFound = new RegExp("version not found: "+module+"@"+version,"m"); - if (lookFor404.test(output)) { - log.warn(log._("server.install.install-failed-not-found",{name:module})); - e = new Error("Module not found"); - e.code = 404; - reject(e); - } else if (isUpgrade && lookForVersionNotFound.test(output)) { - log.warn(log._("server.install.upgrade-failed-not-found",{name:module})); - e = new Error("Module not found"); - e.code = 404; - reject(e); - } else { - log.warn(log._("server.install.install-failed-long",{name:module})); - log.warn("------------------------------------------"); - log.warn(output); - log.warn("------------------------------------------"); - reject(new Error(log._("server.install.install-failed"))); - } - }) - }); + if (!registryUtil.checkModuleAllowed(module,installVersion,installAllowList,installDenyList)) { + const e = new Error("Install not allowed"); + e.code = "install_not_allowed"; + throw e; + } + } + isUpgrade = checkExistingModule(module,version); + + if (!isUpgrade) { + log.info(log._("server.install.installing",{name: module,version: version||"latest"})); + } else { + log.info(log._("server.install.upgrading",{name: module,version: version||"latest"})); + } + + var installDir = settings.userDir || process.env.NODE_RED_HOME || "."; + var args = ['install','--no-audit','--no-update-notifier','--no-fund','--save','--save-prefix=~','--production',installName]; + log.trace(npmCommand + JSON.stringify(args)); + return exec.run(npmCommand,args,{ + cwd: installDir + }, true).then(result => { + if (!isUpgrade) { + log.info(log._("server.install.installed",{name:module})); + return require("./index").addModule(module).then(reportAddedModules); + } else { + log.info(log._("server.install.upgraded",{name:module, version:version})); + events.emit("runtime-event",{id:"restart-required",payload:{type:"warning",text:"notification.warnings.restartRequired"},retain:true}); + return require("./registry").setModulePendingUpdated(module,version); + } + }).catch(result => { + var output = result.stderr; + var e; + var lookFor404 = new RegExp(" 404 .*"+module,"m"); + var lookForVersionNotFound = new RegExp("version not found: "+module+"@"+version,"m"); + if (lookFor404.test(output)) { + log.warn(log._("server.install.install-failed-not-found",{name:module})); + e = new Error("Module not found"); + e.code = 404; + throw e; + } else if (isUpgrade && lookForVersionNotFound.test(output)) { + log.warn(log._("server.install.upgrade-failed-not-found",{name:module})); + e = new Error("Module not found"); + e.code = 404; + throw e; + } else { + log.warn(log._("server.install.install-failed-long",{name:module})); + log.warn("------------------------------------------"); + log.warn(output); + log.warn("------------------------------------------"); + throw new Error(log._("server.install.install-failed")); + } + }) }).catch(err => { // In case of error, reset activePromise to be resolvable activePromise = Promise.resolve(); @@ -236,6 +251,58 @@ async function getExistingPackageVersion(moduleName) { return null; } +async function getModuleVersionFromNPM(module, version) { + let installName = module; + if (version) { + installName += "@" + version; + } + + return new Promise((resolve, reject) => { + child_process.execFile(npmCommand,['info','--json',installName],function(err,stdout,stderr) { + try { + if (!stdout) { + log.warn(log._("server.install.install-failed-not-found",{name:module})); + e = new Error("Version not found"); + e.code = 404; + reject(e); + return; + } + const response = JSON.parse(stdout); + if (response.error) { + if (response.error.code === "E404") { + log.warn(log._("server.install.install-failed-not-found",{name:module})); + e = new Error("Module not found"); + e.code = 404; + reject(e); + } else { + log.warn(log._("server.install.install-failed-long",{name:module})); + log.warn("------------------------------------------"); + log.warn(response.error.summary); + log.warn("------------------------------------------"); + reject(new Error(log._("server.install.install-failed"))); + } + return; + } else { + resolve(response.version); + } + } catch(err) { + log.warn(log._("server.install.install-failed-long",{name:module})); + log.warn("------------------------------------------"); + if (stdout) { + log.warn(stdout); + } + if (stderr) { + log.warn(stderr); + } + log.warn(err); + log.warn("------------------------------------------"); + reject(new Error(log._("server.install.install-failed"))); + } + }); + }) +} + + async function installTarball(tarball) { if (settings.externalModules && settings.externalModules.palette && settings.externalModules.palette.allowUpload === false) { throw new Error("Module upload disabled")