thingsboard-aplcache

Enable/Disable Sandboxed JavaScript environment. UI: Tidy

5/18/2018 1:41:02 PM

Details

diff --git a/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsSandboxService.java b/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsSandboxService.java
index 58ad84c..7f274ec 100644
--- a/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsSandboxService.java
+++ b/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsSandboxService.java
@@ -20,10 +20,13 @@ import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import delight.nashornsandbox.NashornSandbox;
 import delight.nashornsandbox.NashornSandboxes;
+import jdk.nashorn.api.scripting.NashornScriptEngineFactory;
 import lombok.extern.slf4j.Slf4j;
 
 import javax.annotation.PostConstruct;
 import javax.annotation.PreDestroy;
+import javax.script.Invocable;
+import javax.script.ScriptEngine;
 import javax.script.ScriptException;
 import java.util.Map;
 import java.util.UUID;
@@ -35,7 +38,8 @@ import java.util.concurrent.atomic.AtomicInteger;
 @Slf4j
 public abstract class AbstractNashornJsSandboxService implements JsSandboxService {
 
-    private NashornSandbox sandbox = NashornSandboxes.create();
+    private NashornSandbox sandbox;
+    private ScriptEngine engine;
     private ExecutorService monitorExecutorService;
 
     private Map<UUID, String> functionsMap = new ConcurrentHashMap<>();
@@ -44,11 +48,17 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
 
     @PostConstruct
     public void init() {
-        monitorExecutorService = Executors.newFixedThreadPool(getMonitorThreadPoolSize());
-        sandbox.setExecutor(monitorExecutorService);
-        sandbox.setMaxCPUTime(getMaxCpuTime());
-        sandbox.allowNoBraces(false);
-        sandbox.setMaxPreparedStatements(30);
+        if (useJsSandbox()) {
+            sandbox = NashornSandboxes.create();
+            monitorExecutorService = Executors.newFixedThreadPool(getMonitorThreadPoolSize());
+            sandbox.setExecutor(monitorExecutorService);
+            sandbox.setMaxCPUTime(getMaxCpuTime());
+            sandbox.allowNoBraces(false);
+            sandbox.setMaxPreparedStatements(30);
+        } else {
+            NashornScriptEngineFactory factory = new NashornScriptEngineFactory();
+            engine = factory.getScriptEngine(new String[]{"--no-java"});
+        }
     }
 
     @PreDestroy
@@ -58,6 +68,8 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
         }
     }
 
+    protected abstract boolean useJsSandbox();
+
     protected abstract int getMonitorThreadPoolSize();
 
     protected abstract long getMaxCpuTime();
@@ -70,7 +82,11 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
         String functionName = "invokeInternal_" + scriptId.toString().replace('-','_');
         String jsScript = generateJsScript(scriptType, functionName, scriptBody, argNames);
         try {
-            sandbox.eval(jsScript);
+            if (useJsSandbox()) {
+                sandbox.eval(jsScript);
+            } else {
+                engine.eval(jsScript);
+            }
             functionsMap.put(scriptId, functionName);
         } catch (Exception e) {
             log.warn("Failed to compile JS script: {}", e.getMessage(), e);
@@ -87,7 +103,13 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
         }
         if (!isBlackListed(scriptId)) {
             try {
-                return Futures.immediateFuture(sandbox.getSandboxedInvocable().invokeFunction(functionName, args));
+                Object result;
+                if (useJsSandbox()) {
+                    result = sandbox.getSandboxedInvocable().invokeFunction(functionName, args);
+                } else {
+                    result = ((Invocable)engine).invokeFunction(functionName, args);
+                }
+                return Futures.immediateFuture(result);
             } catch (Exception e) {
                 blackListedFunctions.computeIfAbsent(scriptId, key -> new AtomicInteger(0)).incrementAndGet();
                 return Futures.immediateFailedFuture(e);
@@ -103,7 +125,11 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
         String functionName = functionsMap.get(scriptId);
         if (functionName != null) {
             try {
-                sandbox.eval(functionName + " = undefined;");
+                if (useJsSandbox()) {
+                    sandbox.eval(functionName + " = undefined;");
+                } else {
+                    engine.eval(functionName + " = undefined;");
+                }
                 functionsMap.remove(scriptId);
                 blackListedFunctions.remove(scriptId);
             } catch (ScriptException e) {
diff --git a/application/src/main/java/org/thingsboard/server/service/script/NashornJsSandboxService.java b/application/src/main/java/org/thingsboard/server/service/script/NashornJsSandboxService.java
index a08a1a8..3e8b4e9 100644
--- a/application/src/main/java/org/thingsboard/server/service/script/NashornJsSandboxService.java
+++ b/application/src/main/java/org/thingsboard/server/service/script/NashornJsSandboxService.java
@@ -24,6 +24,9 @@ import org.springframework.stereotype.Service;
 @Service
 public class NashornJsSandboxService extends AbstractNashornJsSandboxService {
 
+    @Value("${actors.rule.js_sandbox.use_js_sandbox}")
+    private boolean useJsSandbox;
+
     @Value("${actors.rule.js_sandbox.monitor_thread_pool_size}")
     private int monitorThreadPoolSize;
 
@@ -34,6 +37,11 @@ public class NashornJsSandboxService extends AbstractNashornJsSandboxService {
     private int maxErrors;
 
     @Override
+    protected boolean useJsSandbox() {
+        return useJsSandbox;
+    }
+
+    @Override
     protected int getMonitorThreadPoolSize() {
         return monitorThreadPoolSize;
     }
diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml
index 018b47e..9a10895 100644
--- a/application/src/main/resources/thingsboard.yml
+++ b/application/src/main/resources/thingsboard.yml
@@ -239,6 +239,8 @@ actors:
     # Specify thread pool size for external call service
     external_call_thread_pool_size: "${ACTORS_RULE_EXTERNAL_CALL_THREAD_POOL_SIZE:10}"
     js_sandbox:
+      # Use Sandboxed (secured) JavaScript environment
+      use_js_sandbox: "${ACTORS_RULE_JS_SANDBOX_USE_JS_SANDBOX:true}"
       # Specify thread pool size for JavaScript sandbox resource monitor
       monitor_thread_pool_size: "${ACTORS_RULE_JS_SANDBOX_MONITOR_THREAD_POOL_SIZE:4}"
       # Maximum CPU time in milliseconds allowed for script execution
diff --git a/application/src/test/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngineTest.java b/application/src/test/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngineTest.java
index e9ec6dd..ea70384 100644
--- a/application/src/test/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngineTest.java
+++ b/application/src/test/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngineTest.java
@@ -37,7 +37,7 @@ public class RuleNodeJsScriptEngineTest {
 
     @Before
     public void beforeTest() throws Exception {
-        jsSandboxService = new TestNashornJsSandboxService(1, 100, 3);
+        jsSandboxService = new TestNashornJsSandboxService(false, 1, 100, 3);
     }
 
     @After
diff --git a/application/src/test/java/org/thingsboard/server/service/script/TestNashornJsSandboxService.java b/application/src/test/java/org/thingsboard/server/service/script/TestNashornJsSandboxService.java
index f5f49e0..731c4bb 100644
--- a/application/src/test/java/org/thingsboard/server/service/script/TestNashornJsSandboxService.java
+++ b/application/src/test/java/org/thingsboard/server/service/script/TestNashornJsSandboxService.java
@@ -27,11 +27,13 @@ import java.util.concurrent.Executors;
 
 public class TestNashornJsSandboxService extends AbstractNashornJsSandboxService {
 
+    private boolean useJsSandbox;
     private final int monitorThreadPoolSize;
     private final long maxCpuTime;
     private final int maxErrors;
 
-    public TestNashornJsSandboxService(int monitorThreadPoolSize, long maxCpuTime, int maxErrors) {
+    public TestNashornJsSandboxService(boolean useJsSandbox, int monitorThreadPoolSize, long maxCpuTime, int maxErrors) {
+        this.useJsSandbox = useJsSandbox;
         this.monitorThreadPoolSize = monitorThreadPoolSize;
         this.maxCpuTime = maxCpuTime;
         this.maxErrors = maxErrors;
@@ -39,6 +41,11 @@ public class TestNashornJsSandboxService extends AbstractNashornJsSandboxService
     }
 
     @Override
+    protected boolean useJsSandbox() {
+        return useJsSandbox;
+    }
+
+    @Override
     protected int getMonitorThreadPoolSize() {
         return monitorThreadPoolSize;
     }
diff --git a/ui/src/app/components/js-func.directive.js b/ui/src/app/components/js-func.directive.js
index ef77df7..f13f13c 100644
--- a/ui/src/app/components/js-func.directive.js
+++ b/ui/src/app/components/js-func.directive.js
@@ -30,6 +30,10 @@ import jsFuncTemplate from './js-func.tpl.html';
 
 /* eslint-enable import/no-unresolved, import/default */
 
+import beautify from 'js-beautify';
+
+const js_beautify = beautify.js;
+
 /* eslint-disable angular/angularelement */
 
 export default angular.module('thingsboard.directives.jsFunc', [thingsboardToast, thingsboardUtils, thingsboardExpandFullscreen])
@@ -72,6 +76,11 @@ function JsFunc($compile, $templateCache, toast, utils, $translate) {
             updateEditorSize();
         };
 
+        scope.beautifyJs = function () {
+            var res = js_beautify(scope.functionBody, {indent_size: 4, wrap_line_length: 60});
+            scope.functionBody = res;
+        };
+
         function updateEditorSize() {
             if (scope.js_editor) {
                 scope.js_editor.resize();
diff --git a/ui/src/app/components/js-func.scss b/ui/src/app/components/js-func.scss
index d800d5f..ade2830 100644
--- a/ui/src/app/components/js-func.scss
+++ b/ui/src/app/components/js-func.scss
@@ -23,6 +23,19 @@ tb-js-func {
   }
 }
 
+.tb-js-func-toolbar {
+  .md-button.tidy {
+    color: #7B7B7B;
+    min-width: 32px;
+    min-height: 15px;
+    line-height: 15px;
+    font-size: 0.800rem;
+    margin: 0 5px 0 0;
+    padding: 4px;
+    background: rgba(220, 220, 220, 0.35);
+  }
+}
+
 .tb-js-func-panel {
   margin-left: 15px;
   border: 1px solid #C0C0C0;
diff --git a/ui/src/app/components/js-func.tpl.html b/ui/src/app/components/js-func.tpl.html
index d048598..58675cb 100644
--- a/ui/src/app/components/js-func.tpl.html
+++ b/ui/src/app/components/js-func.tpl.html
@@ -16,9 +16,12 @@
 
 -->
 <div style="background: #fff;" ng-class="{'tb-disabled': disabled, 'fill-height': fillHeight}" tb-expand-fullscreen fullscreen-zindex="100" expand-button-id="expand-button" on-fullscreen-changed="onFullscreenChanged()">
-	<div layout="row" layout-align="start center" style="height: 40px;">
+	<div layout="row" layout-align="start center" style="height: 40px;" class="tb-js-func-toolbar">
 		<label class="tb-title no-padding">function {{ functionName }}({{ functionArgsString }}) {</label>
 		<span flex></span>
+		<md-button ng-if="!disabled" class="tidy" aria-label="{{ 'js-func.tidy' | translate }}" ng-click="beautifyJs()">{{
+			'js-func.tidy' | translate }}
+		</md-button>
 		<div id="expand-button" layout="column" aria-label="Fullscreen" class="md-button md-icon-button tb-md-32 tb-fullscreen-button-style"></div>
 	</div>
 	<div id="tb-javascript-panel" class="tb-js-func-panel">
diff --git a/ui/src/app/components/json-content.directive.js b/ui/src/app/components/json-content.directive.js
index 84f8417..e945079 100644
--- a/ui/src/app/components/json-content.directive.js
+++ b/ui/src/app/components/json-content.directive.js
@@ -29,6 +29,10 @@ import jsonContentTemplate from './json-content.tpl.html';
 
 /* eslint-enable import/no-unresolved, import/default */
 
+import beautify from 'js-beautify';
+
+const js_beautify = beautify.js;
+
 export default angular.module('thingsboard.directives.jsonContent', [])
     .directive('tbJsonContent', JsonContent)
     .name;
@@ -52,6 +56,11 @@ function JsonContent($compile, $templateCache, toast, types, utils) {
             updateEditorSize();
         };
 
+        scope.beautifyJson = function () {
+            var res = js_beautify(scope.contentBody, {indent_size: 4, wrap_line_length: 60});
+            scope.contentBody = res;
+        };
+
         function updateEditorSize() {
             if (scope.json_editor) {
                 scope.json_editor.resize();
diff --git a/ui/src/app/components/json-content.scss b/ui/src/app/components/json-content.scss
index db57451..287c7e3 100644
--- a/ui/src/app/components/json-content.scss
+++ b/ui/src/app/components/json-content.scss
@@ -20,6 +20,19 @@ tb-json-content {
   }
 }
 
+.tb-json-content-toolbar {
+  .md-button.tidy {
+    color: #7B7B7B;
+    min-width: 32px;
+    min-height: 15px;
+    line-height: 15px;
+    font-size: 0.800rem;
+    margin: 0 5px 0 0;
+    padding: 4px;
+    background: rgba(220, 220, 220, 0.35);
+  }
+}
+
 .tb-json-content-panel {
   margin-left: 15px;
   border: 1px solid #C0C0C0;
diff --git a/ui/src/app/components/json-content.tpl.html b/ui/src/app/components/json-content.tpl.html
index 4fad30e..a902b99 100644
--- a/ui/src/app/components/json-content.tpl.html
+++ b/ui/src/app/components/json-content.tpl.html
@@ -16,9 +16,12 @@
 
 -->
 <div style="background: #fff;" ng-class="{'fill-height': fillHeight}" tb-expand-fullscreen fullscreen-zindex="100" expand-button-id="expand-button" on-fullscreen-changed="onFullscreenChanged()" layout="column">
-    <div layout="row" layout-align="start center">
+    <div layout="row" layout-align="start center" style="height: 40px;" class="tb-json-content-toolbar">
         <label class="tb-title no-padding">{{ label }}</label>
         <span flex></span>
+        <md-button ng-if="!readonly" class="tidy" aria-label="{{ 'js-func.tidy' | translate }}" ng-click="beautifyJson()">{{
+            'js-func.tidy' | translate }}
+        </md-button>
         <md-button id="expand-button" aria-label="Fullscreen" class="md-icon-button tb-md-32 tb-fullscreen-button-style"></md-button>
     </div>
     <div flex id="tb-json-panel" class="tb-json-content-panel" layout="column">
diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js
index 2a5eab4..b3dec38 100644
--- a/ui/src/app/locale/locale.constant.js
+++ b/ui/src/app/locale/locale.constant.js
@@ -991,7 +991,8 @@ export default angular.module('thingsboard.locale', [])
                 },
                 "js-func": {
                     "no-return-error": "Function must return value!",
-                    "return-type-mismatch": "Function must return value of '{{type}}' type!"
+                    "return-type-mismatch": "Function must return value of '{{type}}' type!",
+                    "tidy": "Tidy"
                 },
                 "key-val": {
                     "key": "Key",
diff --git a/ui/src/app/rulechain/script/node-script-test.scss b/ui/src/app/rulechain/script/node-script-test.scss
index 42124fb..a75ae59 100644
--- a/ui/src/app/rulechain/script/node-script-test.scss
+++ b/ui/src/app/rulechain/script/node-script-test.scss
@@ -76,9 +76,12 @@ md-dialog.tb-node-script-test-dialog {
     position: absolute;
     font-size: 0.800rem;
     font-weight: 500;
-    top: 10px;
+    top: 13px;
     right: 40px;
     z-index: 5;
+    &.tb-js-function {
+      right: 80px;
+    }
     label {
       color: #00acc1;
       background: rgba(220, 220, 220, 0.35);
diff --git a/ui/src/app/rulechain/script/node-script-test.tpl.html b/ui/src/app/rulechain/script/node-script-test.tpl.html
index 42fc885..0ce57e8 100644
--- a/ui/src/app/rulechain/script/node-script-test.tpl.html
+++ b/ui/src/app/rulechain/script/node-script-test.tpl.html
@@ -73,7 +73,7 @@
                 <div id="bottom_panel" class="tb-split tb-split-vertical">
                     <div id="bottom_left_panel" class="tb-split tb-content">
                         <div class="tb-resize-container">
-                            <div class="tb-editor-area-title-panel">
+                            <div class="tb-editor-area-title-panel tb-js-function">
                                 <label>{{ vm.functionTitle }}</label>
                             </div>
                             <ng-form name="funcBodyForm">