Merge branch 'dev' into function-modules

This commit is contained in:
Nick O'Leary
2021-02-13 00:21:27 +00:00
122 changed files with 3732 additions and 545 deletions

View File

@@ -28,6 +28,7 @@ var api = module.exports = {
api.library.init(runtime);
api.projects.init(runtime);
api.context.init(runtime);
api.plugins.init(runtime);
},
comms: require("./comms"),
@@ -37,6 +38,7 @@ var api = module.exports = {
settings: require("./settings"),
projects: require("./projects"),
context: require("./context"),
plugins: require("./plugins"),
isStarted: async function(opts) {
return runtime.isStarted();

View File

@@ -94,6 +94,10 @@ var api = module.exports = {
getNodeConfig: async function(opts) {
var id = opts.id;
var lang = opts.lang;
if (/[^a-z\-\*]/i.test(opts.lang)) {
reject(new Error("Invalid language: "+opts.lang));
return
}
var result = runtime.nodes.getNodeConfig(id,lang);
if (result) {
runtime.log.audit({event: "nodes.config.get",id:id}, opts.req);
@@ -116,6 +120,10 @@ var api = module.exports = {
* @memberof @node-red/runtime_nodes
*/
getNodeConfigs: async function(opts) {
if (/[^a-z\-\*]/i.test(opts.lang)) {
reject(new Error("Invalid language: "+opts.lang));
return
}
runtime.log.audit({event: "nodes.configs.get"}, opts.req);
return runtime.nodes.getNodeConfigs(opts.lang);
},
@@ -374,6 +382,10 @@ var api = module.exports = {
getModuleCatalogs: async function(opts) {
var namespace = opts.module;
var lang = opts.lang;
if (/[^a-z\-\*]/i.test(lang)) {
reject(new Error("Invalid language: "+lang));
return
}
var prevLang = runtime.i18n.i.language;
// Trigger a load from disk of the language if it is not the default
return new Promise( (resolve,reject) => {
@@ -404,6 +416,10 @@ var api = module.exports = {
getModuleCatalog: async function(opts) {
var namespace = opts.module;
var lang = opts.lang;
if (/[^a-z\-\*]/i.test(lang)) {
reject(new Error("Invalid language: "+lang));
return
}
var prevLang = runtime.i18n.i.language;
// Trigger a load from disk of the language if it is not the default
return new Promise(resolve => {

View File

@@ -0,0 +1,95 @@
/**
* @mixin @node-red/runtime_plugins
*/
var runtime;
var api = module.exports = {
init: function(_runtime) {
runtime = _runtime;
},
/**
* Gets a plugin definition from the registry
* @param {Object} opts
* @param {String} opts.id - the id of the plugin to get
* @param {User} opts.user - the user calling the api
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<PluginDefinition>} - the plugin definition
* @memberof @node-red/runtime_plugins
*/
getPlugin: async function(opts) {
return runtime.plugins.getPlugin(opts.id);
},
/**
* Gets all plugin definitions of a given type
* @param {Object} opts
* @param {String} opts.type - the type of the plugins to get
* @param {User} opts.user - the user calling the api
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<Array>} - the plugin definitions
* @memberof @node-red/runtime_plugins
*/
getPluginsByType: async function(opts) {
return runtime.plugins.getPluginsByType(opts.type);
},
/**
* Gets the editor content for an individual plugin
* @param {String} opts.lang - the locale language to return
* @param {User} opts.user - the user calling the api
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<NodeInfo>} - the node information
* @memberof @node-red/runtime_plugins
*/
getPluginList: async function(opts) {
runtime.log.audit({event: "plugins.list.get"}, opts.req);
return runtime.plugins.getPluginList();
},
/**
* Gets the editor content for all registered plugins
* @param {Object} opts
* @param {User} opts.user - the user calling the api
* @param {User} opts.user - the user calling the api
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<NodeInfo>} - the node information
* @memberof @node-red/runtime_plugins
*/
getPluginConfigs: async function(opts) {
if (/[^a-z\-]/i.test(opts.lang)) {
throw new Error("Invalid language: "+opts.lang)
return;
}
runtime.log.audit({event: "plugins.configs.get"}, opts.req);
return runtime.plugins.getPluginConfigs(opts.lang);
},
/**
* Gets all registered module message catalogs
* @param {Object} opts
* @param {User} opts.user - the user calling the api
* @param {User} opts.lang - the i18n language to return. If not set, uses runtime default (en-US)
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<Object>} - the message catalogs
* @memberof @node-red/runtime_plugins
*/
getPluginCatalogs: async function(opts) {
var lang = opts.lang;
var prevLang = runtime.i18n.i.language;
// Trigger a load from disk of the language if it is not the default
return new Promise( (resolve,reject) => {
runtime.i18n.i.changeLanguage(lang, function(){
var nodeList = runtime.plugins.getPluginList();
var result = {};
nodeList.forEach(function(n) {
if (n.module !== "node-red") {
result[n.id] = runtime.i18n.i.getResourceBundle(lang, n.id)||{};
}
});
runtime.i18n.i.changeLanguage(prevLang);
resolve(result);
});
});
},
}

View File

@@ -21,6 +21,7 @@ var flows = require("./flows");
var storage = require("./storage");
var library = require("./library");
var hooks = require("./hooks");
var plugins = require("./plugins");
var settings = require("./settings");
var express = require("express");
@@ -281,6 +282,7 @@ var runtime = {
storage: storage,
hooks: hooks,
nodes: redNodes,
plugins: plugins,
flows: flows,
library: library,
exec: exec,
@@ -342,6 +344,12 @@ module.exports = {
*/
context: externalAPI.context,
/**
* @memberof @node-red/runtime
* @mixes @node-red/runtime_plugins
*/
plugins: externalAPI.plugins,
/**
* Returns whether the runtime is started
* @param {Object} opts

View File

@@ -0,0 +1,10 @@
const registry = require("@node-red/registry");
module.exports = {
init: function() {},
registerPlugin: registry.registerPlugin,
getPlugin: registry.getPlugin,
getPluginsByType: registry.getPluginsByType,
getPluginList: registry.getPluginList,
getPluginConfigs: registry.getPluginConfigs,
}

View File

@@ -305,6 +305,9 @@ Project.prototype.update = async function (user, data) {
return new Error("Invalid package file: "+data.files.package)
}
var root = data.files.package.substring(0,data.files.package.length-12);
if (/^\.\./.test(fspath.relative(this.path,fspath.join(this.path,data.files.package)))) {
return Promise.reject("Invalid package file: "+data.files.package)
}
this.paths.root = root;
this.paths['package.json'] = data.files.package;
globalProjectSettings.projects[this.name].rootPath = root;
@@ -322,12 +325,18 @@ Project.prototype.update = async function (user, data) {
}
if (data.files.hasOwnProperty('flow') && this.package['node-red'].settings.flowFile !== data.files.flow.substring(this.paths.root.length)) {
if (/^\.\./.test(fspath.relative(this.path,fspath.join(this.path,data.files.flow)))) {
return Promise.reject("Invalid flow file: "+data.files.flow)
}
this.paths.flowFile = data.files.flow;
this.package['node-red'].settings.flowFile = data.files.flow.substring(this.paths.root.length);
savePackage = true;
flowFilesChanged = true;
}
if (data.files.hasOwnProperty('credentials') && this.package['node-red'].settings.credentialsFile !== data.files.credentials.substring(this.paths.root.length)) {
if (/^\.\./.test(fspath.relative(this.path,fspath.join(this.path,data.files.credentials)))) {
return Promise.reject("Invalid credentials file: "+data.files.credentials)
}
this.paths.credentialsFile = data.files.credentials;
this.package['node-red'].settings.credentialsFile = data.files.credentials.substring(this.paths.root.length);
// Don't know if the credSecret is invalid or not so clear the flag
@@ -490,6 +499,10 @@ Project.prototype.getFile = function (filePath,treeish) {
if (treeish !== "_") {
return gitTools.getFile(this.path, filePath, treeish);
} else {
let fullPath = fspath.join(this.path,filePath);
if (/^\.\./.test(fspath.relative(this.path,fullPath))) {
throw new Error("Invalid file name")
}
return fs.readFile(fspath.join(this.path,filePath),"utf8");
}
};
@@ -639,6 +652,11 @@ Project.prototype.pull = function (user,remoteBranchName,setRemote,allowUnrelate
Project.prototype.resolveMerge = function (file,resolutions) {
var filePath = fspath.join(this.path,file);
if (/^\.\./.test(fspath.relative(this.path,filePath))) {
throw new Error("Invalid file name")
}
var self = this;
if (typeof resolutions === 'string') {
return util.writeFile(filePath, resolutions).then(function() {
@@ -1047,7 +1065,10 @@ function loadProject(projectPath) {
function init(_settings, _runtime) {
settings = _settings;
runtime = _runtime;
projectsDir = fspath.join(settings.userDir,"projects");
projectsDir = fspath.resolve(fspath.join(settings.userDir,"projects"));
if(settings.editorTheme.projects.path) {
projectsDir = settings.editorTheme.projects.path;
}
authCache.init();
}

View File

@@ -113,7 +113,13 @@ function init(_settings, _runtime) {
globalGitUser = gitConfig.user;
Projects.init(settings,runtime);
sshTools.init(settings);
projectsDir = fspath.join(settings.userDir,"projects");
projectsDir = fspath.resolve(fspath.join(settings.userDir,"projects"));
if(settings.editorTheme.projects.path) {
projectsDir = settings.editorTheme.projects.path;
}
if (!settings.readOnly) {
return fs.ensureDir(projectsDir)
//TODO: this is accessing settings from storage directly as settings
@@ -210,9 +216,16 @@ function getBackupFilename(filename) {
}
function loadProject(name) {
let fullPath = fspath.resolve(fspath.join(projectsDir,name));
var projectPath = name;
if (projectPath.indexOf(fspath.sep) === -1) {
projectPath = fspath.join(projectsDir,name);
projectPath = fullPath;
} else {
// Ensure this project dir is under projectsDir;
let relativePath = fspath.relative(projectsDir,fullPath);
if (/^\.\./.test(relativePath)) {
throw new Error("Invalid project name")
}
}
return Projects.load(projectPath).then(function(project) {
activeProject = project;
@@ -236,6 +249,10 @@ function deleteProject(user, name) {
throw e;
}
var projectPath = fspath.join(projectsDir,name);
let relativePath = fspath.relative(projectsDir,projectPath);
if (/^\.\./.test(relativePath)) {
throw new Error("Invalid project name")
}
return Projects.delete(user, projectPath);
}
@@ -394,6 +411,10 @@ function createProject(user, metadata) {
metadata.files.credentialSecret = currentEncryptionKey;
}
metadata.path = fspath.join(projectsDir,metadata.name);
if (/^\.\./.test(fspath.relative(projectsDir,metadata.path))) {
throw new Error("Invalid project name")
}
return Projects.create(user, metadata).then(function(p) {
return setActiveProject(user, p.name);
}).then(function() {
@@ -501,6 +522,11 @@ async function getFlows() {
if (!initialFlowLoadComplete) {
initialFlowLoadComplete = true;
log.info(log._("storage.localfilesystem.user-dir",{path:settings.userDir}));
if (projectsEnabled) {
log.info(log._("storage.localfilesystem.projects.projects-directory", {projectsDirectory: projectsDir}));
}
if (activeProject) {
// At this point activeProject will be a string, so go load it and
// swap in an instance of Project

View File

@@ -156,6 +156,7 @@
"projects": {
"changing-project": "Setting active project : __project__",
"active-project": "Active project : __project__",
"projects-directory": "Projects directory: __projectsDirectory__",
"project-not-found": "Project not found : __project__",
"no-active-project": "No active project : using default flows file",
"disabled": "Projects disabled : editorTheme.projects.enabled=false",