From 5743a5f91dd5ead0c4d2b511c4073542092f3b29 Mon Sep 17 00:00:00 2001
From: Nick O'Leary <knolleary@users.noreply.github.com>
Date: Sun, 27 Dec 2020 21:34:21 +0000
Subject: [PATCH] Filter palette manager nodes based on allow/deny list

---
 .../editor-client/src/js/ui/palette-editor.js | 109 +++++++++++++++---
 1 file changed, 95 insertions(+), 14 deletions(-)

diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js
index 147789940..3dbff87a3 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js
@@ -329,21 +329,26 @@ RED.palette.editor = (function() {
         catalogueLoadStatus.push(err||v);
         if (!err) {
             if (v.modules) {
-                v.modules.forEach(function(m) {
-                    loadedIndex[m.id] = m;
-                    m.index = [m.id];
-                    if (m.keywords) {
-                        m.index = m.index.concat(m.keywords);
+                var a = false;
+                v.modules = v.modules.filter(function(m) {
+                    if (checkModuleAllowed(m.id,m.version,installAllowList,installDenyList)) {
+                        loadedIndex[m.id] = m;
+                        m.index = [m.id];
+                        if (m.keywords) {
+                            m.index = m.index.concat(m.keywords);
+                        }
+                        if (m.types) {
+                            m.index = m.index.concat(m.types);
+                        }
+                        if (m.updated_at) {
+                            m.timestamp = new Date(m.updated_at).getTime();
+                        } else {
+                            m.timestamp = 0;
+                        }
+                        m.index = m.index.join(",").toLowerCase();
+                        return true;
                     }
-                    if (m.types) {
-                        m.index = m.index.concat(m.types);
-                    }
-                    if (m.updated_at) {
-                        m.timestamp = new Date(m.updated_at).getTime();
-                    } else {
-                        m.timestamp = 0;
-                    }
-                    m.index = m.index.join(",").toLowerCase();
+                    return false;
                 })
                 loadedList = loadedList.concat(v.modules);
             }
@@ -437,11 +442,87 @@ RED.palette.editor = (function() {
         return -1 * (A.info.timestamp-B.info.timestamp);
     }
 
+    var installAllowList = ['*'];
+    var installDenyList = [];
+
+    function parseModuleList(list) {
+        list = list || ["*"];
+        return list.map(rule => {
+            var m = /^(.+?)(?:@(.*))?$/.exec(rule);
+            var wildcardPos = m[1].indexOf("*");
+            wildcardPos = wildcardPos===-1?Infinity:wildcardPos;
+
+            return {
+                module: new RegExp("^"+m[1].replace(/\*/g,".*")+"$"),
+                version: m[2],
+                wildcardPos: wildcardPos
+            }
+        })
+    }
+
+    function checkAgainstList(module,version,list) {
+        for (var i=0;i<list.length;i++) {
+            var rule = list[i];
+            if (rule.module.test(module)) {
+                // Without a full semver library in the editor,
+                // we skip the version check.
+                // Not ideal - but will get caught in the runtime
+                // if the user tries to install.
+                return rule;
+            }
+        }
+    }
+
+    function 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 init() {
         if (RED.settings.get('externalModules.palette.allowInstall', true) === false) {
             return;
         }
+        var settingsAllowList = RED.settings.get("externalModules.palette.allowList")
+        var settingsDenyList = RED.settings.get("externalModules.palette.denyList")
+        if (settingsAllowList || settingsDenyList) {
+            installAllowList = settingsAllowList;
+            installDenyList = settingsDenyList
+        }
+        installAllowList = parseModuleList(installAllowList);
+        installDenyList = parseModuleList(installDenyList);
+
+        console.log(installAllowList);
+        console.log(installDenyList);
+
         createSettingsPane();
 
         RED.userSettings.add({