From efbc65e11fdd81a5f3485552f3eeaa9ae4d2871b Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 18 May 2018 19:41:02 +0300 Subject: [PATCH] Enable/Disable Sandboxed JavaScript environment. UI: Tidy button to format java scripts. --- .../AbstractNashornJsSandboxService.java | 44 +++++++++++++++---- .../script/NashornJsSandboxService.java | 8 ++++ .../src/main/resources/thingsboard.yml | 2 + .../script/RuleNodeJsScriptEngineTest.java | 2 +- .../script/TestNashornJsSandboxService.java | 9 +++- ui/src/app/components/js-func.directive.js | 9 ++++ ui/src/app/components/js-func.scss | 13 ++++++ ui/src/app/components/js-func.tpl.html | 5 ++- .../app/components/json-content.directive.js | 9 ++++ ui/src/app/components/json-content.scss | 13 ++++++ ui/src/app/components/json-content.tpl.html | 5 ++- ui/src/app/locale/locale.constant.js | 3 +- .../rulechain/script/node-script-test.scss | 5 ++- .../script/node-script-test.tpl.html | 2 +- 14 files changed, 113 insertions(+), 16 deletions(-) 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 58ad84c508..7f274ec6d7 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 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 a08a1a8488..3e8b4e9c55 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; @@ -33,6 +36,11 @@ public class NashornJsSandboxService extends AbstractNashornJsSandboxService { @Value("${actors.rule.js_sandbox.max_errors}") 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 018b47ee61..9a1089549e 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 e9ec6dd9ed..ea7038442d 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 f5f49e0fcf..731c4bb147 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,17 +27,24 @@ 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; init(); } + @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 ef77df713e..f13f13c838 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 d800d5f4e3..ade2830714 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 d0485984a9..58675cb761 100644 --- a/ui/src/app/components/js-func.tpl.html +++ b/ui/src/app/components/js-func.tpl.html @@ -16,9 +16,12 @@ -->
-
+
+ {{ + 'js-func.tidy' | translate }} +
diff --git a/ui/src/app/components/json-content.directive.js b/ui/src/app/components/json-content.directive.js index 84f84175be..e945079273 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 db57451f76..287c7e3ecf 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 4fad30eaed..a902b99bcc 100644 --- a/ui/src/app/components/json-content.tpl.html +++ b/ui/src/app/components/json-content.tpl.html @@ -16,9 +16,12 @@ -->
-
+
+ {{ + 'js-func.tidy' | translate }} +
diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js index 2a5eab4825..b3dec38af8 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 42124fb409..a75ae59a79 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 42fc8857fc..0ce57e8e95 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 @@
-
+