Commit efbc65e11fdd81a5f3485552f3eeaa9ae4d2871b

Authored by Igor Kulikov
1 parent af5791ab

Enable/Disable Sandboxed JavaScript environment. UI: Tidy button to format java scripts.

... ... @@ -20,10 +20,13 @@ import com.google.common.util.concurrent.Futures;
20 20 import com.google.common.util.concurrent.ListenableFuture;
21 21 import delight.nashornsandbox.NashornSandbox;
22 22 import delight.nashornsandbox.NashornSandboxes;
  23 +import jdk.nashorn.api.scripting.NashornScriptEngineFactory;
23 24 import lombok.extern.slf4j.Slf4j;
24 25
25 26 import javax.annotation.PostConstruct;
26 27 import javax.annotation.PreDestroy;
  28 +import javax.script.Invocable;
  29 +import javax.script.ScriptEngine;
27 30 import javax.script.ScriptException;
28 31 import java.util.Map;
29 32 import java.util.UUID;
... ... @@ -35,7 +38,8 @@ import java.util.concurrent.atomic.AtomicInteger;
35 38 @Slf4j
36 39 public abstract class AbstractNashornJsSandboxService implements JsSandboxService {
37 40
38   - private NashornSandbox sandbox = NashornSandboxes.create();
  41 + private NashornSandbox sandbox;
  42 + private ScriptEngine engine;
39 43 private ExecutorService monitorExecutorService;
40 44
41 45 private Map<UUID, String> functionsMap = new ConcurrentHashMap<>();
... ... @@ -44,11 +48,17 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
44 48
45 49 @PostConstruct
46 50 public void init() {
47   - monitorExecutorService = Executors.newFixedThreadPool(getMonitorThreadPoolSize());
48   - sandbox.setExecutor(monitorExecutorService);
49   - sandbox.setMaxCPUTime(getMaxCpuTime());
50   - sandbox.allowNoBraces(false);
51   - sandbox.setMaxPreparedStatements(30);
  51 + if (useJsSandbox()) {
  52 + sandbox = NashornSandboxes.create();
  53 + monitorExecutorService = Executors.newFixedThreadPool(getMonitorThreadPoolSize());
  54 + sandbox.setExecutor(monitorExecutorService);
  55 + sandbox.setMaxCPUTime(getMaxCpuTime());
  56 + sandbox.allowNoBraces(false);
  57 + sandbox.setMaxPreparedStatements(30);
  58 + } else {
  59 + NashornScriptEngineFactory factory = new NashornScriptEngineFactory();
  60 + engine = factory.getScriptEngine(new String[]{"--no-java"});
  61 + }
52 62 }
53 63
54 64 @PreDestroy
... ... @@ -58,6 +68,8 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
58 68 }
59 69 }
60 70
  71 + protected abstract boolean useJsSandbox();
  72 +
61 73 protected abstract int getMonitorThreadPoolSize();
62 74
63 75 protected abstract long getMaxCpuTime();
... ... @@ -70,7 +82,11 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
70 82 String functionName = "invokeInternal_" + scriptId.toString().replace('-','_');
71 83 String jsScript = generateJsScript(scriptType, functionName, scriptBody, argNames);
72 84 try {
73   - sandbox.eval(jsScript);
  85 + if (useJsSandbox()) {
  86 + sandbox.eval(jsScript);
  87 + } else {
  88 + engine.eval(jsScript);
  89 + }
74 90 functionsMap.put(scriptId, functionName);
75 91 } catch (Exception e) {
76 92 log.warn("Failed to compile JS script: {}", e.getMessage(), e);
... ... @@ -87,7 +103,13 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
87 103 }
88 104 if (!isBlackListed(scriptId)) {
89 105 try {
90   - return Futures.immediateFuture(sandbox.getSandboxedInvocable().invokeFunction(functionName, args));
  106 + Object result;
  107 + if (useJsSandbox()) {
  108 + result = sandbox.getSandboxedInvocable().invokeFunction(functionName, args);
  109 + } else {
  110 + result = ((Invocable)engine).invokeFunction(functionName, args);
  111 + }
  112 + return Futures.immediateFuture(result);
91 113 } catch (Exception e) {
92 114 blackListedFunctions.computeIfAbsent(scriptId, key -> new AtomicInteger(0)).incrementAndGet();
93 115 return Futures.immediateFailedFuture(e);
... ... @@ -103,7 +125,11 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
103 125 String functionName = functionsMap.get(scriptId);
104 126 if (functionName != null) {
105 127 try {
106   - sandbox.eval(functionName + " = undefined;");
  128 + if (useJsSandbox()) {
  129 + sandbox.eval(functionName + " = undefined;");
  130 + } else {
  131 + engine.eval(functionName + " = undefined;");
  132 + }
107 133 functionsMap.remove(scriptId);
108 134 blackListedFunctions.remove(scriptId);
109 135 } catch (ScriptException e) {
... ...
... ... @@ -24,6 +24,9 @@ import org.springframework.stereotype.Service;
24 24 @Service
25 25 public class NashornJsSandboxService extends AbstractNashornJsSandboxService {
26 26
  27 + @Value("${actors.rule.js_sandbox.use_js_sandbox}")
  28 + private boolean useJsSandbox;
  29 +
27 30 @Value("${actors.rule.js_sandbox.monitor_thread_pool_size}")
28 31 private int monitorThreadPoolSize;
29 32
... ... @@ -34,6 +37,11 @@ public class NashornJsSandboxService extends AbstractNashornJsSandboxService {
34 37 private int maxErrors;
35 38
36 39 @Override
  40 + protected boolean useJsSandbox() {
  41 + return useJsSandbox;
  42 + }
  43 +
  44 + @Override
37 45 protected int getMonitorThreadPoolSize() {
38 46 return monitorThreadPoolSize;
39 47 }
... ...
... ... @@ -239,6 +239,8 @@ actors:
239 239 # Specify thread pool size for external call service
240 240 external_call_thread_pool_size: "${ACTORS_RULE_EXTERNAL_CALL_THREAD_POOL_SIZE:10}"
241 241 js_sandbox:
  242 + # Use Sandboxed (secured) JavaScript environment
  243 + use_js_sandbox: "${ACTORS_RULE_JS_SANDBOX_USE_JS_SANDBOX:true}"
242 244 # Specify thread pool size for JavaScript sandbox resource monitor
243 245 monitor_thread_pool_size: "${ACTORS_RULE_JS_SANDBOX_MONITOR_THREAD_POOL_SIZE:4}"
244 246 # Maximum CPU time in milliseconds allowed for script execution
... ...
... ... @@ -37,7 +37,7 @@ public class RuleNodeJsScriptEngineTest {
37 37
38 38 @Before
39 39 public void beforeTest() throws Exception {
40   - jsSandboxService = new TestNashornJsSandboxService(1, 100, 3);
  40 + jsSandboxService = new TestNashornJsSandboxService(false, 1, 100, 3);
41 41 }
42 42
43 43 @After
... ...
... ... @@ -27,11 +27,13 @@ import java.util.concurrent.Executors;
27 27
28 28 public class TestNashornJsSandboxService extends AbstractNashornJsSandboxService {
29 29
  30 + private boolean useJsSandbox;
30 31 private final int monitorThreadPoolSize;
31 32 private final long maxCpuTime;
32 33 private final int maxErrors;
33 34
34   - public TestNashornJsSandboxService(int monitorThreadPoolSize, long maxCpuTime, int maxErrors) {
  35 + public TestNashornJsSandboxService(boolean useJsSandbox, int monitorThreadPoolSize, long maxCpuTime, int maxErrors) {
  36 + this.useJsSandbox = useJsSandbox;
35 37 this.monitorThreadPoolSize = monitorThreadPoolSize;
36 38 this.maxCpuTime = maxCpuTime;
37 39 this.maxErrors = maxErrors;
... ... @@ -39,6 +41,11 @@ public class TestNashornJsSandboxService extends AbstractNashornJsSandboxService
39 41 }
40 42
41 43 @Override
  44 + protected boolean useJsSandbox() {
  45 + return useJsSandbox;
  46 + }
  47 +
  48 + @Override
42 49 protected int getMonitorThreadPoolSize() {
43 50 return monitorThreadPoolSize;
44 51 }
... ...
... ... @@ -30,6 +30,10 @@ import jsFuncTemplate from './js-func.tpl.html';
30 30
31 31 /* eslint-enable import/no-unresolved, import/default */
32 32
  33 +import beautify from 'js-beautify';
  34 +
  35 +const js_beautify = beautify.js;
  36 +
33 37 /* eslint-disable angular/angularelement */
34 38
35 39 export default angular.module('thingsboard.directives.jsFunc', [thingsboardToast, thingsboardUtils, thingsboardExpandFullscreen])
... ... @@ -72,6 +76,11 @@ function JsFunc($compile, $templateCache, toast, utils, $translate) {
72 76 updateEditorSize();
73 77 };
74 78
  79 + scope.beautifyJs = function () {
  80 + var res = js_beautify(scope.functionBody, {indent_size: 4, wrap_line_length: 60});
  81 + scope.functionBody = res;
  82 + };
  83 +
75 84 function updateEditorSize() {
76 85 if (scope.js_editor) {
77 86 scope.js_editor.resize();
... ...
... ... @@ -23,6 +23,19 @@ tb-js-func {
23 23 }
24 24 }
25 25
  26 +.tb-js-func-toolbar {
  27 + .md-button.tidy {
  28 + color: #7B7B7B;
  29 + min-width: 32px;
  30 + min-height: 15px;
  31 + line-height: 15px;
  32 + font-size: 0.800rem;
  33 + margin: 0 5px 0 0;
  34 + padding: 4px;
  35 + background: rgba(220, 220, 220, 0.35);
  36 + }
  37 +}
  38 +
26 39 .tb-js-func-panel {
27 40 margin-left: 15px;
28 41 border: 1px solid #C0C0C0;
... ...
... ... @@ -16,9 +16,12 @@
16 16
17 17 -->
18 18 <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()">
19   - <div layout="row" layout-align="start center" style="height: 40px;">
  19 + <div layout="row" layout-align="start center" style="height: 40px;" class="tb-js-func-toolbar">
20 20 <label class="tb-title no-padding">function {{ functionName }}({{ functionArgsString }}) {</label>
21 21 <span flex></span>
  22 + <md-button ng-if="!disabled" class="tidy" aria-label="{{ 'js-func.tidy' | translate }}" ng-click="beautifyJs()">{{
  23 + 'js-func.tidy' | translate }}
  24 + </md-button>
22 25 <div id="expand-button" layout="column" aria-label="Fullscreen" class="md-button md-icon-button tb-md-32 tb-fullscreen-button-style"></div>
23 26 </div>
24 27 <div id="tb-javascript-panel" class="tb-js-func-panel">
... ...
... ... @@ -29,6 +29,10 @@ import jsonContentTemplate from './json-content.tpl.html';
29 29
30 30 /* eslint-enable import/no-unresolved, import/default */
31 31
  32 +import beautify from 'js-beautify';
  33 +
  34 +const js_beautify = beautify.js;
  35 +
32 36 export default angular.module('thingsboard.directives.jsonContent', [])
33 37 .directive('tbJsonContent', JsonContent)
34 38 .name;
... ... @@ -52,6 +56,11 @@ function JsonContent($compile, $templateCache, toast, types, utils) {
52 56 updateEditorSize();
53 57 };
54 58
  59 + scope.beautifyJson = function () {
  60 + var res = js_beautify(scope.contentBody, {indent_size: 4, wrap_line_length: 60});
  61 + scope.contentBody = res;
  62 + };
  63 +
55 64 function updateEditorSize() {
56 65 if (scope.json_editor) {
57 66 scope.json_editor.resize();
... ...
... ... @@ -20,6 +20,19 @@ tb-json-content {
20 20 }
21 21 }
22 22
  23 +.tb-json-content-toolbar {
  24 + .md-button.tidy {
  25 + color: #7B7B7B;
  26 + min-width: 32px;
  27 + min-height: 15px;
  28 + line-height: 15px;
  29 + font-size: 0.800rem;
  30 + margin: 0 5px 0 0;
  31 + padding: 4px;
  32 + background: rgba(220, 220, 220, 0.35);
  33 + }
  34 +}
  35 +
23 36 .tb-json-content-panel {
24 37 margin-left: 15px;
25 38 border: 1px solid #C0C0C0;
... ...
... ... @@ -16,9 +16,12 @@
16 16
17 17 -->
18 18 <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">
19   - <div layout="row" layout-align="start center">
  19 + <div layout="row" layout-align="start center" style="height: 40px;" class="tb-json-content-toolbar">
20 20 <label class="tb-title no-padding">{{ label }}</label>
21 21 <span flex></span>
  22 + <md-button ng-if="!readonly" class="tidy" aria-label="{{ 'js-func.tidy' | translate }}" ng-click="beautifyJson()">{{
  23 + 'js-func.tidy' | translate }}
  24 + </md-button>
22 25 <md-button id="expand-button" aria-label="Fullscreen" class="md-icon-button tb-md-32 tb-fullscreen-button-style"></md-button>
23 26 </div>
24 27 <div flex id="tb-json-panel" class="tb-json-content-panel" layout="column">
... ...
... ... @@ -991,7 +991,8 @@ export default angular.module('thingsboard.locale', [])
991 991 },
992 992 "js-func": {
993 993 "no-return-error": "Function must return value!",
994   - "return-type-mismatch": "Function must return value of '{{type}}' type!"
  994 + "return-type-mismatch": "Function must return value of '{{type}}' type!",
  995 + "tidy": "Tidy"
995 996 },
996 997 "key-val": {
997 998 "key": "Key",
... ...
... ... @@ -76,9 +76,12 @@ md-dialog.tb-node-script-test-dialog {
76 76 position: absolute;
77 77 font-size: 0.800rem;
78 78 font-weight: 500;
79   - top: 10px;
  79 + top: 13px;
80 80 right: 40px;
81 81 z-index: 5;
  82 + &.tb-js-function {
  83 + right: 80px;
  84 + }
82 85 label {
83 86 color: #00acc1;
84 87 background: rgba(220, 220, 220, 0.35);
... ...
... ... @@ -73,7 +73,7 @@
73 73 <div id="bottom_panel" class="tb-split tb-split-vertical">
74 74 <div id="bottom_left_panel" class="tb-split tb-content">
75 75 <div class="tb-resize-container">
76   - <div class="tb-editor-area-title-panel">
  76 + <div class="tb-editor-area-title-panel tb-js-function">
77 77 <label>{{ vm.functionTitle }}</label>
78 78 </div>
79 79 <ng-form name="funcBodyForm">
... ...