@jsreport/jsreport-core 3.5.0 → 3.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,14 +1,17 @@
1
1
  const LRU = require('lru-cache')
2
2
  const stackTrace = require('stack-trace')
3
3
  const { customAlphabet } = require('nanoid')
4
- const safeSandbox = require('./safeSandbox')
4
+ const createSandbox = require('./createSandbox')
5
5
  const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz', 10)
6
6
 
7
7
  module.exports = (reporter) => {
8
- return ({
8
+ const functionsCache = LRU(reporter.options.sandbox.cache)
9
+
10
+ return async ({
9
11
  manager = {},
10
12
  context,
11
13
  userCode,
14
+ initFn,
12
15
  executionFn,
13
16
  currentPath,
14
17
  onRequire,
@@ -19,7 +22,8 @@ module.exports = (reporter) => {
19
22
 
20
23
  // we use dynamic name because of the potential nested vm2 execution in the jsreportProxy.assets.require
21
24
  // it may turn out it is a bad approach in assets so we gonna delete it here
22
- const executionFnName = nanoid() + '_executionFn'
25
+ const executionFnName = `${nanoid()}_executionFn`
26
+
23
27
  context[executionFnName] = executionFn
24
28
  context.__appDirectory = reporter.options.appDirectory
25
29
  context.__rootDirectory = reporter.options.rootDirectory
@@ -28,13 +32,16 @@ module.exports = (reporter) => {
28
32
  context.__topLevelFunctions = {}
29
33
  context.__handleError = (err) => handleError(reporter, err)
30
34
 
31
- const { sourceFilesInfo, run, restore, sandbox, safeRequire } = safeSandbox(context, {
35
+ const { sourceFilesInfo, run, compileScript, restore, sandbox, sandboxRequire } = createSandbox(context, {
32
36
  onLog: (log) => {
33
- reporter.logger[log.level](log.message, { ...req, timestamp: log.timestamp })
37
+ // we mark any log done in sandbox as userLevel: true, this allows us to detect which logs belongs to user
38
+ // and can potentially contain sensitive information
39
+ reporter.logger[log.level](log.message, { ...req, timestamp: log.timestamp, userLevel: true })
34
40
  },
35
41
  formatError: (error, moduleName) => {
36
- error.message += ` To be able to require custom modules you need to add to configuration { "allowLocalFilesAccess": true } or enable just specific module using { sandbox: { allowedModules": ["${moduleName}"] }`
42
+ error.message += ` To be able to require custom modules you need to add to configuration { "trustUserCode": true } or enable just specific module using { sandbox: { allowedModules": ["${moduleName}"] }`
37
43
  },
44
+ safeExecution: reporter.options.trustUserCode === false,
38
45
  modulesCache: reporter.requestModulesCache.get(req.context.rootId),
39
46
  globalModules: reporter.options.sandbox.nativeModules || [],
40
47
  allowedModules: reporter.options.sandbox.allowedModules,
@@ -61,7 +68,17 @@ module.exports = (reporter) => {
61
68
  }
62
69
  })
63
70
 
64
- jsreportProxy = reporter.createProxy({ req, runInSandbox: run, context: sandbox, getTopLevelFunctions, safeRequire })
71
+ const _getTopLevelFunctions = (code) => {
72
+ return getTopLevelFunctions(functionsCache, code)
73
+ }
74
+
75
+ jsreportProxy = reporter.createProxy({
76
+ req,
77
+ runInSandbox: run,
78
+ context: sandbox,
79
+ getTopLevelFunctions: _getTopLevelFunctions,
80
+ sandboxRequire
81
+ })
65
82
 
66
83
  jsreportProxy.currentPath = async () => {
67
84
  // we get the current path by throwing an error, which give us a stack trace
@@ -114,7 +131,18 @@ module.exports = (reporter) => {
114
131
  // be passed in options
115
132
  manager.restore = restore
116
133
 
117
- const functionNames = getTopLevelFunctions(userCode)
134
+ if (typeof initFn === 'function') {
135
+ const initScriptInfo = await initFn(_getTopLevelFunctions, compileScript)
136
+
137
+ if (initScriptInfo) {
138
+ await run(initScriptInfo.script, {
139
+ filename: initScriptInfo.filename || 'sandbox-init.js',
140
+ source: initScriptInfo.source
141
+ })
142
+ }
143
+ }
144
+
145
+ const functionNames = getTopLevelFunctions(functionsCache, userCode)
118
146
  const functionsCode = `return {${functionNames.map(h => `"${h}": ${h}`).join(',')}}`
119
147
  const executionCode = `;(async () => { ${userCode} \n\n;${functionsCode} })()
120
148
  .then((topLevelFunctions) => {
@@ -180,12 +208,11 @@ function handleError (reporter, errValue) {
180
208
  })
181
209
  }
182
210
 
183
- const functionsCache = LRU({ max: 100 })
184
- function getTopLevelFunctions (code) {
211
+ function getTopLevelFunctions (cache, code) {
185
212
  const key = `functions:${code}`
186
213
 
187
- if (functionsCache.has(key)) {
188
- return functionsCache.get(key)
214
+ if (cache.has(key)) {
215
+ return cache.get(key)
189
216
  }
190
217
 
191
218
  // lazy load to speed up boot
@@ -223,6 +250,6 @@ function getTopLevelFunctions (code) {
223
250
  return []
224
251
  }
225
252
 
226
- functionsCache.set(key, names)
253
+ cache.set(key, names)
227
254
  return names
228
255
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsreport/jsreport-core",
3
- "version": "3.5.0",
3
+ "version": "3.7.0",
4
4
  "description": "javascript based business reporting",
5
5
  "keywords": [
6
6
  "report",
@@ -17,12 +17,19 @@
17
17
  "name": "Jan Blaha",
18
18
  "email": "jan.blaha@hotmail.com"
19
19
  },
20
+ "maintainers": [
21
+ {
22
+ "name": "pofider",
23
+ "email": "jan.blaha@hotmail.com"
24
+ }
25
+ ],
20
26
  "main": "index.js",
21
27
  "files": [
22
28
  "lib",
23
29
  "index.js",
24
30
  "test/store/common.js",
25
- "test/blobStorage/common.js"
31
+ "test/blobStorage/common.js",
32
+ "test/extensions/validExtensions/listeners"
26
33
  ],
27
34
  "scripts": {
28
35
  "test": "mocha --timeout 5000 --recursive test --exit && standard",
@@ -32,10 +39,12 @@
32
39
  "@babel/code-frame": "7.12.13",
33
40
  "@babel/parser": "7.14.4",
34
41
  "@babel/traverse": "7.12.9",
35
- "@jsreport/advanced-workers": "1.2.1",
42
+ "@colors/colors": "1.5.0",
43
+ "@jsreport/advanced-workers": "1.2.3",
36
44
  "@jsreport/mingo": "2.4.1",
45
+ "@jsreport/reap": "0.1.0",
37
46
  "ajv": "6.12.6",
38
- "app-root-path": "2.0.1",
47
+ "app-root-path": "3.0.0",
39
48
  "bytes": "3.1.2",
40
49
  "camelcase": "5.0.0",
41
50
  "debug": "4.3.2",
@@ -56,16 +65,15 @@
56
65
  "nanoid": "3.2.0",
57
66
  "nconf": "0.12.0",
58
67
  "node.extend.without.arrays": "1.1.6",
59
- "reap2": "1.0.1",
60
68
  "semver": "7.3.5",
61
69
  "serializator": "1.0.2",
62
70
  "stack-trace": "0.0.10",
63
71
  "triple-beam": "1.3.0",
64
72
  "unset-value": "1.0.0",
65
73
  "uuid": "8.3.2",
66
- "vm2": "3.9.7",
67
- "winston": "3.3.3",
68
- "winston-transport": "4.4.0"
74
+ "vm2": "3.9.9",
75
+ "winston": "3.8.1",
76
+ "winston-transport": "4.5.0"
69
77
  },
70
78
  "devDependencies": {
71
79
  "mocha": "9.2.2",
@@ -77,12 +85,6 @@
77
85
  "engines": {
78
86
  "node": ">=16.11"
79
87
  },
80
- "maintainers": [
81
- {
82
- "name": "pofider",
83
- "email": "jan.blaha@hotmail.com"
84
- }
85
- ],
86
88
  "standard": {
87
89
  "env": {
88
90
  "mocha": true
@@ -0,0 +1,9 @@
1
+ module.exports = {
2
+ name: 'listeners',
3
+ main: 'main.js',
4
+ worker: 'worker.js',
5
+ dependencies: ['a', 'b', 'c', 'd', 'e', 'f'],
6
+ requires: {},
7
+ hasPublicPart: false,
8
+ directory: __dirname
9
+ }
@@ -0,0 +1,51 @@
1
+
2
+ module.exports = (reporter, definition) => {
3
+ reporter.tests = reporter.tests || {}
4
+ reporter.tests.beforeRenderListeners = reporter.createListenerCollection()
5
+ reporter.tests.afterRenderListeners = reporter.createListenerCollection()
6
+ reporter.tests.validateRenderListeners = reporter.createListenerCollection()
7
+ reporter.tests.afterTemplatingEnginesExecutedListeners = reporter.createListenerCollection()
8
+
9
+ reporter.registerMainAction('test-beforeRender-listeners', async (data, req) => {
10
+ data.req = reporter.Request(data.req)
11
+ await reporter.tests.beforeRenderListeners.fire(data.req, data.res)
12
+ return { req: data.req, res: data.res }
13
+ })
14
+ reporter.registerMainAction('test-afterRender-listeners', async (data, req) => {
15
+ data.req = reporter.Request(data.req)
16
+ await reporter.tests.afterRenderListeners.fire(data.req, data.res)
17
+ return { req: data.req, res: data.res }
18
+ })
19
+ reporter.registerMainAction('test-validateRender-listeners', async (data, req) => {
20
+ data.req = reporter.Request(data.req)
21
+ await reporter.tests.validateRenderListeners.fire(data.req, data.res)
22
+ return { req: data.req, res: data.res }
23
+ })
24
+ reporter.registerMainAction('test-afterTemplatingEnginesExecuted-listeners', async (data, req) => {
25
+ data.req = reporter.Request(data.req)
26
+ await reporter.tests.afterTemplatingEnginesExecutedListeners.fire(data.req, data.res)
27
+ return { req: data.req, res: data.res }
28
+ })
29
+
30
+ let beforeRenderEval
31
+ reporter.tests.beforeRenderEval = (fn) => {
32
+ beforeRenderEval = fn
33
+ }
34
+ reporter.registerMainAction('test-beforeRenderEval', async (data, req) => {
35
+ if (beforeRenderEval == null) {
36
+ return
37
+ }
38
+ return beforeRenderEval.toString()
39
+ })
40
+
41
+ let afterRenderEval
42
+ reporter.tests.afterRenderEval = (fn) => {
43
+ afterRenderEval = fn
44
+ }
45
+ reporter.registerMainAction('test-afterRenderEval', async (data, req) => {
46
+ if (afterRenderEval == null) {
47
+ return
48
+ }
49
+ return afterRenderEval.toString()
50
+ })
51
+ }
@@ -0,0 +1,68 @@
1
+ const extend = require('node.extend.without.arrays')
2
+ const vm = require('vm')
3
+ const path = require('path')
4
+ const process = require('process')
5
+
6
+ module.exports = (reporter, definition) => {
7
+ reporter.initializeListeners.add('test-listeners', () => {
8
+ reporter.beforeRenderListeners.add('listeners', async (req, res) => {
9
+ const result = await reporter.executeMainAction('test-beforeRender-listeners', { req, res }, req)
10
+ extend(true, req, result.req)
11
+ extend(true, res, result.res)
12
+ })
13
+
14
+ reporter.afterRenderListeners.add('listeners', async (req, res) => {
15
+ const result = await reporter.executeMainAction('test-afterRender-listeners', { req, res }, req)
16
+ extend(true, req, result.req)
17
+ extend(true, res, result.res)
18
+ })
19
+
20
+ reporter.validateRenderListeners.add('listeners', async (req, res) => {
21
+ const result = await reporter.executeMainAction('test-validateRender-listeners', { req, res }, req)
22
+ extend(true, req, result.req)
23
+ extend(true, res, result.res)
24
+ })
25
+
26
+ reporter.afterTemplatingEnginesExecutedListeners.add('listeners', async (req, res) => {
27
+ const result = await reporter.executeMainAction('test-afterTemplatingEnginesExecuted-listeners', { req, res }, req)
28
+ extend(true, req, result.req)
29
+ extend(true, res, result.res)
30
+ })
31
+
32
+ const evalInWorker = (code, req, res) => {
33
+ const script = new vm.Script(`
34
+ ;(function () {
35
+ return ${code}
36
+ })()
37
+ `)
38
+
39
+ return script.runInThisContext({
40
+ displayErrors: true
41
+ })(req, res, {
42
+ require: (m) => {
43
+ try {
44
+ return require(path.join(process.cwd(), 'node_modules', m))
45
+ } catch (e) {
46
+ // hack, make it working in monorepo as well as normal extension
47
+ return require(path.join(process.cwd(), '../../node_modules', m))
48
+ }
49
+ },
50
+ reporter
51
+ })
52
+ }
53
+
54
+ reporter.afterRenderListeners.add('eval-listeners', async (req, res) => {
55
+ const code = await reporter.executeMainAction('test-afterRenderEval', {}, req)
56
+ if (code) {
57
+ return evalInWorker(code, req, res)
58
+ }
59
+ })
60
+
61
+ reporter.beforeRenderListeners.insert(0, 'eval-listeners', async (req, res) => {
62
+ const code = await reporter.executeMainAction('test-beforeRenderEval', {}, req)
63
+ if (code) {
64
+ return evalInWorker(code, req, res)
65
+ }
66
+ })
67
+ })
68
+ }
@@ -228,7 +228,7 @@ function collectionTests (store, isInternal, runTransactions) {
228
228
  const colName = !isInternal ? 'templates' : 'internalTemplates'
229
229
 
230
230
  await getCollection(colName).insert({ name: '1', engine: 'none', recipe: 'a' })
231
- await getCollection(colName).insert({ name: '2', engine: 'none', recipe: 'a' })
231
+ await getCollection(colName).insert({ name: '2', engine: 'test2', recipe: 'a' })
232
232
  const res = await getCollection(colName).update({ recipe: 'a' }, { $set: { engine: 'test2' } })
233
233
  res.should.be.eql(2)
234
234
  })