thingsboard-aplcache
Changes
application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsSandboxService.java 44(+35 -9)
application/src/main/java/org/thingsboard/server/service/script/NashornJsSandboxService.java 8(+8 -0)
application/src/test/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngineTest.java 2(+1 -1)
application/src/test/java/org/thingsboard/server/service/script/TestNashornJsSandboxService.java 9(+8 -1)
ui/src/app/components/js-func.scss 13(+13 -0)
ui/src/app/components/json-content.scss 13(+13 -0)
ui/src/app/locale/locale.constant.js 3(+2 -1)
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();
ui/src/app/components/js-func.scss 13(+13 -0)
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();
ui/src/app/components/json-content.scss 13(+13 -0)
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">
ui/src/app/locale/locale.constant.js 3(+2 -1)
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">