@sap/cds 6.4.0 → 6.5.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.
- package/CHANGELOG.md +59 -3
- package/apis/cds.d.ts +2 -0
- package/apis/cqn.d.ts +14 -3
- package/apis/ql.d.ts +12 -8
- package/apis/services.d.ts +39 -64
- package/apis/test.d.ts +7 -0
- package/bin/build/buildTaskEngine.js +9 -12
- package/bin/build/buildTaskHandler.js +3 -14
- package/bin/build/index.js +8 -2
- package/bin/build/provider/buildTaskProviderInternal.js +8 -7
- package/bin/build/provider/hana/template/package.json +3 -0
- package/bin/build/provider/mtx/resourcesTarBuilder.js +13 -4
- package/bin/build/provider/mtx-extension/index.js +41 -38
- package/bin/build/util.js +17 -0
- package/bin/deploy/to-hana/hdiDeployUtil.js +11 -5
- package/bin/serve.js +6 -2
- package/common.cds +7 -0
- package/lib/auth/index.js +17 -15
- package/lib/auth/jwt-auth.js +4 -3
- package/lib/compile/for/lean_drafts.js +1 -1
- package/lib/compile/minify.js +3 -3
- package/lib/core/index.js +1 -0
- package/lib/dbs/cds-deploy.js +13 -10
- package/lib/env/cds-requires.js +1 -1
- package/lib/env/defaults.js +5 -1
- package/lib/env/schemas/cds-rc.json +74 -3
- package/lib/lazy.js +6 -8
- package/lib/log/cds-error.js +2 -2
- package/lib/ql/Whereable.js +22 -11
- package/lib/ql/cds-ql.js +1 -1
- package/lib/req/response.js +8 -3
- package/lib/req/user.js +12 -2
- package/lib/srv/middlewares/cds-context.js +0 -2
- package/lib/srv/middlewares/ctx-auth.js +11 -0
- package/lib/srv/middlewares/ctx-model.js +22 -20
- package/lib/srv/middlewares/index.js +7 -9
- package/lib/srv/protocols/_legacy.js +4 -0
- package/lib/srv/protocols/graphql.js +2 -2
- package/lib/srv/protocols/index.js +7 -3
- package/lib/srv/srv-api.js +1 -0
- package/lib/srv/srv-models.js +6 -1
- package/lib/utils/cds-utils.js +3 -1
- package/lib/utils/data.js +2 -2
- package/lib/utils/tar.js +37 -12
- package/libx/_runtime/auth/strategies/JWT.js +1 -0
- package/libx/_runtime/auth/strategies/ias-auth.js +2 -1
- package/libx/_runtime/auth/strategies/mock.js +12 -1
- package/libx/_runtime/auth/strategies/xssecUtils.js +7 -8
- package/libx/_runtime/auth/strategies/xsuaa.js +1 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -2
- package/libx/_runtime/cds-services/services/Service.js +3 -0
- package/libx/_runtime/cds-services/services/utils/columns.js +35 -36
- package/libx/_runtime/common/code-ext/WorkerReq.js +79 -0
- package/libx/_runtime/common/code-ext/config.js +13 -0
- package/libx/_runtime/common/code-ext/execute.js +106 -0
- package/libx/_runtime/common/code-ext/handlers.js +49 -0
- package/libx/_runtime/common/code-ext/worker.js +36 -0
- package/libx/_runtime/common/code-ext/workerQuery.js +45 -0
- package/libx/_runtime/common/code-ext/workerQueryExecutor.js +33 -0
- package/libx/_runtime/common/generic/crud.js +5 -1
- package/libx/_runtime/common/generic/paging.js +8 -7
- package/libx/_runtime/common/i18n/index.js +1 -1
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +47 -11
- package/libx/_runtime/common/utils/path.js +5 -25
- package/libx/_runtime/common/utils/resolveView.js +2 -0
- package/libx/_runtime/common/utils/search2cqn4sql.js +13 -9
- package/libx/_runtime/db/expand/expandCQNToJoin.js +2 -1
- package/libx/_runtime/db/sql-builder/InsertBuilder.js +5 -1
- package/libx/_runtime/db/sql-builder/UpsertBuilder.js +9 -32
- package/libx/_runtime/db/sql-builder/annotations.js +6 -3
- package/libx/_runtime/db/utils/localized.js +1 -1
- package/libx/_runtime/fiori/generic/activate.js +4 -0
- package/libx/_runtime/fiori/generic/before.js +8 -1
- package/libx/_runtime/fiori/generic/edit.js +5 -0
- package/libx/_runtime/fiori/generic/read.js +8 -3
- package/libx/_runtime/fiori/lean-draft.js +12 -1
- package/libx/_runtime/hana/Service.js +1 -1
- package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +5 -5
- package/libx/_runtime/hana/execute.js +5 -5
- package/libx/_runtime/hana/pool.js +1 -1
- package/libx/_runtime/hana/search2cqn4sql.js +51 -51
- package/libx/_runtime/sqlite/Service.js +1 -1
- package/libx/_runtime/sqlite/customBuilder/CustomUpsertBuilder.js +20 -38
- package/libx/odata/afterburner.js +6 -3
- package/libx/odata/cqn2odata.js +1 -1
- package/libx/rest/middleware/parse.js +26 -4
- package/package.json +1 -1
- package/server.js +2 -20
package/lib/req/user.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const PSEUDO_ROLES = ['system-user', 'internal-user']
|
|
2
|
+
|
|
1
3
|
class User {
|
|
2
4
|
|
|
3
5
|
constructor (_) {
|
|
@@ -9,14 +11,22 @@ class User {
|
|
|
9
11
|
if (typeof _ === 'string') { this.id = _; return }
|
|
10
12
|
for (let each in _) super[each === '_roles' ? 'roles' : each] = _[each] // overrides getters
|
|
11
13
|
const roles = this.hasOwnProperty('roles') && this.roles // eslint-disable-line no-prototype-builtins
|
|
12
|
-
if (Array.isArray(roles)) this.roles = roles.reduce ((p,n)=>{p[n]=1; return p},{})
|
|
14
|
+
if (Array.isArray(roles)) this.roles = roles.filter(r => !PSEUDO_ROLES.includes(r)).reduce ((p,n)=>{p[n]=1; return p},{})
|
|
15
|
+
else PSEUDO_ROLES.forEach(r => delete this.roles[r])
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
get attr() { return super.attr = {} }
|
|
16
19
|
get roles(){ return super.roles = {} }
|
|
17
20
|
get _roles(){ return this.roles } // compatibility
|
|
18
21
|
|
|
19
|
-
is (role) {
|
|
22
|
+
is (role) {
|
|
23
|
+
return role === 'any' ||
|
|
24
|
+
role === 'identified-user' ||
|
|
25
|
+
role === 'system-user' && this._is_system ||
|
|
26
|
+
role === 'internal-user' && this._is_internal ||
|
|
27
|
+
role === 'authenticated-user' && this.authLevel !== 'weak' ||
|
|
28
|
+
!!this.roles[role]
|
|
29
|
+
}
|
|
20
30
|
valueOf() { return this.id }
|
|
21
31
|
|
|
22
32
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const cds = require ('../../index')
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Propagates auth results to cds.context
|
|
5
|
+
*/
|
|
6
|
+
module.exports = ()=> function cds_context_auth (req, res, next) {
|
|
7
|
+
const ctx = cds.context
|
|
8
|
+
ctx.user = req.user
|
|
9
|
+
ctx.tenant = req.tenant || ctx.user?.tenant
|
|
10
|
+
next()
|
|
11
|
+
}
|
|
@@ -1,24 +1,26 @@
|
|
|
1
|
-
module.exports = ()=>{
|
|
1
|
+
module.exports = ()=> {
|
|
2
|
+
|
|
2
3
|
const cds = require ('../../index')
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
4
|
+
const context_model_required = cds.requires.extensibility || cds.requires.toggles || cds.mtx
|
|
5
|
+
if (!context_model_required) return []
|
|
6
|
+
|
|
7
|
+
const { model4 } = require('../srv-models')
|
|
8
|
+
return async function cds_context_model (req,res, next) {
|
|
9
|
+
if (req.baseUrl.startsWith('/-/')) return next() //> our own tech services cannot be extended
|
|
10
|
+
const ctx = cds.context
|
|
11
|
+
if (ctx.tenant) try {
|
|
12
|
+
// if (req.headers.features) ctx.user.features = req.headers.features //> currently done in basic-auth only
|
|
13
|
+
ctx.model = req.__model = await model4 (ctx.tenant, ctx.features) // REVISIT: req.__model is because of Okra
|
|
14
|
+
} catch (e) {
|
|
15
|
+
console.error(e)
|
|
16
|
+
return res.status(503) .json ({ // REVISIT: we should throw a simple error, nothing else! -> this is overly OData-specific!
|
|
17
|
+
error: { code: '503', message:
|
|
18
|
+
process.env.NODE_ENV === 'production' ? 'Service Unavailable' :
|
|
19
|
+
'Unable to get context-specific model due to: ' + e.message
|
|
20
|
+
}
|
|
21
|
+
})
|
|
21
22
|
}
|
|
23
|
+
next()
|
|
22
24
|
}
|
|
23
|
-
|
|
25
|
+
|
|
24
26
|
}
|
|
@@ -1,22 +1,20 @@
|
|
|
1
1
|
const auth = exports.auth = require('../../auth')
|
|
2
2
|
const context = exports.context = require('./cds-context')
|
|
3
|
+
const ctx_auth = exports.ctx_auth = require('./ctx-auth')
|
|
3
4
|
const ctx_model = exports.ctx_model = require('./ctx-model')
|
|
4
5
|
const errors = exports.errors = require('./errors')
|
|
5
6
|
const trace = exports.trace = require('./trace')
|
|
6
7
|
|
|
7
8
|
// middlewares running before protocol adapters
|
|
8
9
|
exports.before = [
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
context(), // provides cds.context
|
|
11
|
+
trace(), // provides detailed trace logs when DEBUG=trace
|
|
12
|
+
auth(), // provides req.user & tenant
|
|
13
|
+
ctx_auth(), // propagates auth results to cds.context
|
|
14
|
+
ctx_model(), // fills in cds.context.model, in case of extensibility
|
|
13
15
|
]
|
|
14
16
|
|
|
17
|
+
// middlewares running after protocol adapters -> usually error middlewares
|
|
15
18
|
exports.after = [
|
|
16
|
-
// usually error middlewares
|
|
17
19
|
errors(),
|
|
18
20
|
]
|
|
19
|
-
|
|
20
|
-
exports.bootstrap = ()=>{
|
|
21
|
-
require('../protocols')()
|
|
22
|
-
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
const libx = require('../../../libx/_runtime')
|
|
2
2
|
const cds_context_model = require('../srv-models')
|
|
3
|
+
const cds_context = require('../middlewares/cds-context')()
|
|
4
|
+
const ctx_auth = require('../middlewares/ctx-auth')()
|
|
3
5
|
const { ProtocolAdapter } = require('.')
|
|
4
6
|
|
|
5
7
|
class LegacyProtocolAdapter extends ProtocolAdapter {
|
|
@@ -14,9 +16,11 @@ class LegacyProtocolAdapter extends ProtocolAdapter {
|
|
|
14
16
|
static serve (srv, /* in: */ app) {
|
|
15
17
|
return super.serve (srv, app, { before: [
|
|
16
18
|
// async (req, res, next) => { await 1; next() }, // REVISIT: AsyncResource.bind() -> enable to break cds/tests/_runtime/odata/__tests__/integration/crud-with-mtx.test.js with existing, non-middleware mode, *w/o* fix to BufferedWriter
|
|
19
|
+
cds_context,
|
|
17
20
|
cap_req_logger,
|
|
18
21
|
libx.perf,
|
|
19
22
|
libx.auth(srv),
|
|
23
|
+
ctx_auth,
|
|
20
24
|
cds_context_model.middleware4(srv)
|
|
21
25
|
], after:[] })
|
|
22
26
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const cds = require ('../../index'), { decodeURIComponent } = cds.utils
|
|
2
2
|
const LOG = cds.log('graphql')
|
|
3
3
|
|
|
4
|
-
const GraphQLAdapter = require('@
|
|
4
|
+
const GraphQLAdapter = require('@cap-js/graphql') // eslint-disable-line cds/no-missing-dependencies
|
|
5
5
|
const express = require ('express') // eslint-disable-line cds/no-missing-dependencies
|
|
6
6
|
|
|
7
7
|
function CDSGraphQLAdapter (options) {
|
|
@@ -33,7 +33,7 @@ function CDSGraphQLAdapter (options) {
|
|
|
33
33
|
})
|
|
34
34
|
|
|
35
35
|
/** The global /graphql route */
|
|
36
|
-
.use (new GraphQLAdapter (
|
|
36
|
+
.use (new GraphQLAdapter (options))
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
module.exports = CDSGraphQLAdapter
|
|
@@ -10,7 +10,6 @@ class ProtocolAdapter {
|
|
|
10
10
|
for (let [k,o] of Object.entries(protocols)) if (typeof o === 'string') protocols[k] = {path:o}
|
|
11
11
|
if (!protocols.odata) protocols.odata = { impl: join(__dirname,'odata-v4') }
|
|
12
12
|
if (!protocols.rest) protocols.rest = { impl: join(__dirname,'rest') }
|
|
13
|
-
|
|
14
13
|
// odata must always be first for fallback
|
|
15
14
|
return this.protocols = { odata: protocols.odata, ...protocols }
|
|
16
15
|
}
|
|
@@ -84,5 +83,10 @@ const protocols = Object.keys(ProtocolAdapter.init())
|
|
|
84
83
|
const protocol4 = (def, _default = protocols[0]) => def['@protocol'] || protocols.find(p => def['@'+p]) || _default
|
|
85
84
|
const is_global = adapter => adapter.length === 1 && !/^(function )?(\w+\s+)?\((srv|service)/.test(adapter)
|
|
86
85
|
|
|
87
|
-
module.exports =
|
|
88
|
-
if (
|
|
86
|
+
module.exports = { ProtocolAdapter, protocol4 }
|
|
87
|
+
if (cds.env.protocols) {
|
|
88
|
+
cds.middlewares = require('../middlewares')
|
|
89
|
+
ProtocolAdapter.serveAll()
|
|
90
|
+
} else if (!cds.requires.middlewares) {
|
|
91
|
+
module.exports.ProtocolAdapter = require('./_legacy')
|
|
92
|
+
}
|
package/lib/srv/srv-api.js
CHANGED
|
@@ -76,6 +76,7 @@ class Service extends require('./srv-handlers') {
|
|
|
76
76
|
insert (...args) { return INSERT(...args).bind(this) }
|
|
77
77
|
create (...args) { return INSERT.into(...args).bind(this) }
|
|
78
78
|
update (...args) { return UPDATE.entity(...args).bind(this) }
|
|
79
|
+
upsert (...args) { return UPSERT(...args).bind(this) }
|
|
79
80
|
exists (...args) { return SELECT.one([1]).from(...args).bind(this) }
|
|
80
81
|
|
|
81
82
|
/**
|
package/lib/srv/srv-models.js
CHANGED
|
@@ -57,7 +57,12 @@ class ExtendedModels {
|
|
|
57
57
|
else return cache[key] = (async()=>{ // temporarily add promise to cache to avoid race conditions...
|
|
58
58
|
|
|
59
59
|
// If tenant doesn't have extensions check cache with tenant = undefined
|
|
60
|
-
|
|
60
|
+
let _has_extensions = false
|
|
61
|
+
try {
|
|
62
|
+
_has_extensions = tenant && extensibility && await _is_extended(tenant)
|
|
63
|
+
} catch (error) {
|
|
64
|
+
LOG.error('`extensibility: true` is configured but table "cds.xt.Extensions" does not exist. Please redeploy.', error)
|
|
65
|
+
}
|
|
61
66
|
if (!_has_extensions) {
|
|
62
67
|
let k = cache.key4 (tenant = undefined, features)
|
|
63
68
|
let cached = cache.at(k); if (cached) return cached
|
package/lib/utils/cds-utils.js
CHANGED
|
@@ -63,6 +63,7 @@ exports.exists = function exists (x) {
|
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
// REVISIT naming: doesn't return boolean
|
|
66
67
|
exports.isdir = function isdir (x) {
|
|
67
68
|
if (x) try {
|
|
68
69
|
const y = resolve (cds.root,x)
|
|
@@ -72,6 +73,7 @@ exports.isdir = function isdir (x) {
|
|
|
72
73
|
} catch(e){/* ignore */}
|
|
73
74
|
}
|
|
74
75
|
|
|
76
|
+
// REVISIT naming: doesn't return boolean
|
|
75
77
|
exports.isfile = function isfile (x) {
|
|
76
78
|
if (x) try {
|
|
77
79
|
const y = resolve (cds.root,x)
|
|
@@ -100,7 +102,7 @@ exports.read = async function read (file, _encoding) {
|
|
|
100
102
|
exports.write = function write (file, data, o) {
|
|
101
103
|
if (arguments.length === 1) return {to:(...path) => write(join(...path),file)}
|
|
102
104
|
if (typeof data === 'object' && !Buffer.isBuffer(data))
|
|
103
|
-
data = JSON.stringify(data, null, ' '.repeat(o && o.spaces))
|
|
105
|
+
data = JSON.stringify(data, null, ' '.repeat(o && o.spaces)) + require('os').EOL
|
|
104
106
|
const f = resolve (cds.root,file)
|
|
105
107
|
return fs.mkdirp (dirname(f)).then (()=> fs.promises.writeFile (f,data,o))
|
|
106
108
|
}
|
package/lib/utils/data.js
CHANGED
|
@@ -13,8 +13,8 @@ class DataUtil {
|
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
15
|
if (this._deletes.length > 0) {
|
|
16
|
-
const
|
|
17
|
-
if (
|
|
16
|
+
const LOG = cds.log('deploy')
|
|
17
|
+
if (!this._autoReset) LOG.info('Deleting all data for', db.model.each('entity'))
|
|
18
18
|
await db.run(this._deletes)
|
|
19
19
|
}
|
|
20
20
|
}
|
package/lib/utils/tar.js
CHANGED
|
@@ -15,16 +15,37 @@ const win = path => {
|
|
|
15
15
|
if (Array.isArray(path)) return path.map(el => win(el))
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
async function copyDir(src, dest) {
|
|
19
|
+
if ((await fs.promises.stat(src)).isDirectory()) {
|
|
20
|
+
const entries = await fs.promises.readdir(src)
|
|
21
|
+
return Promise.all(entries.map(async each => copyDir(path.join(src, each), path.join(dest, each))))
|
|
22
|
+
} else {
|
|
23
|
+
await fs.promises.mkdir(path.dirname(dest), { recursive: true })
|
|
24
|
+
return fs.promises.copyFile(src, dest)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Copy resources containing files and folders to temp dir on Windows and pack temp dir.
|
|
19
29
|
// cli tar has a size limit on Windows.
|
|
20
|
-
const createTemp = async (root,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
await
|
|
30
|
+
const createTemp = async (root, resources) => {
|
|
31
|
+
// Asynchronously copies the entire content from src to dest.
|
|
32
|
+
const temp = await fs.promises.mkdtemp(`${fs.realpathSync(require('os').tmpdir())}${path.sep}tar-`)
|
|
33
|
+
for (let resource of resources) {
|
|
34
|
+
const destination = path.join(temp, path.relative(root, resource))
|
|
35
|
+
if ((await fs.promises.stat(resource)).isFile()) {
|
|
36
|
+
const dirName = path.dirname(destination)
|
|
37
|
+
if (!await exists(dirName)) {
|
|
38
|
+
await fs.promises.mkdir(dirName, { recursive: true })
|
|
39
|
+
}
|
|
40
|
+
await fs.promises.copyFile(resource, destination)
|
|
41
|
+
} else {
|
|
42
|
+
if (fs.promises.cp) {
|
|
43
|
+
await fs.promises.cp(resource, destination, { recursive: true })
|
|
44
|
+
} else {
|
|
45
|
+
// node < 16
|
|
46
|
+
await copyDir(resource, destination)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
28
49
|
}
|
|
29
50
|
|
|
30
51
|
return temp
|
|
@@ -60,6 +81,7 @@ exports.create = async (dir='.', ...args) => {
|
|
|
60
81
|
if (Array.isArray(dir)) [ dir, ...args ] = [ cds.root, dir, ...args ]
|
|
61
82
|
|
|
62
83
|
let c, temp
|
|
84
|
+
args = args.filter(el => el)
|
|
63
85
|
if (process.platform === 'win32') {
|
|
64
86
|
const spawnDir = (dir, args) => {
|
|
65
87
|
if (args.some(arg => arg === '-f')) return spawn ('tar', ['c', '-C', win(dir), ...win(args)])
|
|
@@ -74,8 +96,11 @@ exports.create = async (dir='.', ...args) => {
|
|
|
74
96
|
c = spawnDir(dir, args)
|
|
75
97
|
}
|
|
76
98
|
} else {
|
|
77
|
-
if (Array.isArray(args[0]))
|
|
78
|
-
|
|
99
|
+
if (Array.isArray(args[0])) {
|
|
100
|
+
args.push (...args.shift().map (f => path.isAbsolute(f) ? path.relative(dir,f) : f))
|
|
101
|
+
} else {
|
|
102
|
+
args.push('.')
|
|
103
|
+
}
|
|
79
104
|
|
|
80
105
|
c = spawn ('tar', ['c', '-C', dir, ...args])
|
|
81
106
|
}
|
|
@@ -214,5 +239,5 @@ exports.t = tar.tf = tar.list
|
|
|
214
239
|
// ---------------------------------------------------------------------------------
|
|
215
240
|
// Compatibility...
|
|
216
241
|
|
|
217
|
-
exports.packTarArchive = (
|
|
242
|
+
exports.packTarArchive = (resources,d) => d ? tar.cz (d,resources) : tar.cz (resources)
|
|
218
243
|
exports.unpackTarArchive = (x,dir) => tar.xz(x).to(dir)
|
|
@@ -24,6 +24,7 @@ class JWTStrategy extends JS {
|
|
|
24
24
|
roles: xssecUtils.getRoles(['any', 'identified-user'], info, credentials),
|
|
25
25
|
attr: xssecUtils.getAttrForJWT(info)
|
|
26
26
|
})
|
|
27
|
+
xssecUtils.addRolesFromGrantType(user, info, credentials)
|
|
27
28
|
const tenant = xssecUtils.getTenant(info)
|
|
28
29
|
if (tenant) user.tenant = tenant
|
|
29
30
|
// call "super.success"
|
|
@@ -42,9 +42,10 @@ module.exports = function ias_auth(config) {
|
|
|
42
42
|
if (req.tokenInfo.getClientId() === req.tokenInfo.getSubject()) {
|
|
43
43
|
req.user = new cds.User({
|
|
44
44
|
id: 'system',
|
|
45
|
-
roles: ['
|
|
45
|
+
roles: ['authenticated-user'],
|
|
46
46
|
attr: {}
|
|
47
47
|
})
|
|
48
|
+
req.user._is_system = true
|
|
48
49
|
} else {
|
|
49
50
|
// add all unknown attributes to req.user.attr in order to keep public API small
|
|
50
51
|
const payload = req.tokenInfo.getPayload()
|
|
@@ -21,6 +21,17 @@ class MockStrategy {
|
|
|
21
21
|
if (user.password && user.password !== password) return this.fail(CHALLENGE)
|
|
22
22
|
|
|
23
23
|
const { features } = req.headers
|
|
24
|
+
// Only in the mock strategy the pseudo roles are kept in the role list.
|
|
25
|
+
// In all other cases pseudo roles are filtered out.
|
|
26
|
+
if (user.roles) {
|
|
27
|
+
if (Array.isArray(user.roles)) {
|
|
28
|
+
if (user.roles.includes('system-user')) user._is_system = true
|
|
29
|
+
if (user.roles.includes('internal-user')) user._is_internal = true
|
|
30
|
+
} else {
|
|
31
|
+
if ('system-user' in user.roles) user._is_system = true
|
|
32
|
+
if ('internal-user' in user.roles) user._is_internal = true
|
|
33
|
+
}
|
|
34
|
+
}
|
|
24
35
|
this.success(new cds.User(features ? { ...user, features } : user))
|
|
25
36
|
}
|
|
26
37
|
}
|
|
@@ -44,7 +55,7 @@ const _init_users = (users, tenants = {}) => {
|
|
|
44
55
|
Array.isArray(user.roles) ? user.roles.push(...scopes) : (user.roles = scopes)
|
|
45
56
|
}
|
|
46
57
|
if (user.jwt.grant_type === 'client_credentials' || user.jwt.grant_type === 'client_x509') {
|
|
47
|
-
user.
|
|
58
|
+
user._is_system = true
|
|
48
59
|
}
|
|
49
60
|
if (!user.tenant && user.jwt.zid) user.tenant = user.jwt.zid
|
|
50
61
|
}
|
|
@@ -5,21 +5,19 @@ const getUserId = (user, info) => {
|
|
|
5
5
|
return user.id || (info && info.getClientId && info.getClientId())
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
const
|
|
8
|
+
const addRolesFromGrantType = (user, info, credentials) => {
|
|
9
9
|
const grantType = info && (info.grantType || (info.getGrantType && info.getGrantType()))
|
|
10
10
|
if (grantType) {
|
|
11
11
|
// > not "weak"
|
|
12
|
-
roles
|
|
12
|
+
user.roles['authenticated-user'] = true
|
|
13
13
|
if (grantType in CLIENT) {
|
|
14
|
-
|
|
15
|
-
if (info.getClientId() === credentials.clientid)
|
|
14
|
+
user._is_system = true
|
|
15
|
+
if (info.getClientId() === credentials.clientid) user._is_internal = true
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
const getRoles = (roles, info
|
|
21
|
-
_addRolesFromGrantType(roles, info, credentials)
|
|
22
|
-
|
|
20
|
+
const getRoles = (roles, info) => {
|
|
23
21
|
// convert to object
|
|
24
22
|
roles = Object.assign(...roles.map(ele => ({ [ele]: true })))
|
|
25
23
|
|
|
@@ -90,5 +88,6 @@ module.exports = {
|
|
|
90
88
|
getRoles,
|
|
91
89
|
getAttrForJWT,
|
|
92
90
|
getAttrForXSSEC,
|
|
93
|
-
getTenant
|
|
91
|
+
getTenant,
|
|
92
|
+
addRolesFromGrantType
|
|
94
93
|
}
|
|
@@ -25,6 +25,7 @@ class XSUAAStrategy extends JS {
|
|
|
25
25
|
roles: xssecUtils.getRoles(['any', 'identified-user'], info, credentials),
|
|
26
26
|
attr: xssecUtils.getAttrForXSSEC(info)
|
|
27
27
|
})
|
|
28
|
+
xssecUtils.addRolesFromGrantType(user, info, credentials)
|
|
28
29
|
const tenant = xssecUtils.getTenant(info)
|
|
29
30
|
if (tenant) user.tenant = tenant
|
|
30
31
|
// call "super.success"
|
|
@@ -60,9 +60,8 @@ const action = service => {
|
|
|
60
60
|
await tx.commit(result)
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
if (isReturnMinimal(req) || result === null) odataRes.setStatusCode(204)
|
|
63
|
+
if (isReturnMinimal(req) || result === null) odataRes.setStatusCode(204, { overwrite: true })
|
|
64
64
|
else if (req.event === 'draftActivate' || req.event === 'EDIT') {
|
|
65
|
-
odataRes.setStatusCode(201)
|
|
66
65
|
const keys = Object.keys(req.target.keys).filter(k => {
|
|
67
66
|
return k !== 'IsActiveEntity' && !req.target.keys[k]._isAssociationStrict
|
|
68
67
|
})
|
|
@@ -22,6 +22,9 @@ class ApplicationService extends cds.Service {
|
|
|
22
22
|
require('../../common/generic/temporal').call(this, this)
|
|
23
23
|
require('../../common/generic/paging').call(this, this) // > paging must be executed before sorting
|
|
24
24
|
require('../../common/generic/sorting').call(this, this)
|
|
25
|
+
|
|
26
|
+
if (cds.env.requires.extensibility?.code) require('../../common/code-ext/handlers').call(this, this)
|
|
27
|
+
|
|
25
28
|
this.registerFioriHandlers(this)
|
|
26
29
|
this.registerPersonalDataHandlers(this)
|
|
27
30
|
this.registerCrudHandlers(this) // default .on handlers, have to go last
|
|
@@ -122,44 +122,43 @@ const _getSearchableColumns = entity => {
|
|
|
122
122
|
* @returns {import('../../../types/api').ColumnRefs}
|
|
123
123
|
*/
|
|
124
124
|
const computeColumnsToBeSearched = (cqn, entity = { __searchableColumns: [] }, alias) => {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
if (cqn.
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
cqn.SELECT.columns.length === 1 &&
|
|
143
|
-
column.func === 'count' &&
|
|
144
|
-
(column.as === '_counted_' || column.as === '$count')
|
|
145
|
-
)
|
|
125
|
+
let toBeSearched = []
|
|
126
|
+
|
|
127
|
+
// aggregations case
|
|
128
|
+
// in the new parser groupBy is moved to sub select.
|
|
129
|
+
if (cqn._aggregated || /* new parser */ cqn.SELECT.groupBy || cqn.SELECT?.from?.SELECT?.groupBy) {
|
|
130
|
+
cqn.SELECT.columns &&
|
|
131
|
+
cqn.SELECT.columns.forEach(column => {
|
|
132
|
+
if (column.func) {
|
|
133
|
+
// exclude $count by SELECT of number of Items in a Collection
|
|
134
|
+
if (
|
|
135
|
+
cqn.SELECT.columns.length === 1 &&
|
|
136
|
+
column.func === 'count' &&
|
|
137
|
+
(column.as === '_counted_' || column.as === '$count')
|
|
138
|
+
)
|
|
139
|
+
return
|
|
140
|
+
|
|
141
|
+
toBeSearched.push(column)
|
|
146
142
|
return
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const columnRef = column.ref
|
|
146
|
+
if (columnRef) {
|
|
147
|
+
column = { ref: [...column.ref] }
|
|
148
|
+
if (alias) column.ref.unshift(alias)
|
|
149
|
+
toBeSearched.push(column)
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
} else {
|
|
153
|
+
toBeSearched = entity.own('__searchableColumns') || entity.set('__searchableColumns', _getSearchableColumns(entity))
|
|
154
|
+
|
|
155
|
+
if (cqn.SELECT.groupBy) toBeSearched = toBeSearched.filter(tbs => cqn.SELECT.groupBy.some(gb => gb.ref[0] === tbs))
|
|
156
|
+
toBeSearched = toBeSearched.map(c => {
|
|
157
|
+
const col = { ref: [c] }
|
|
158
|
+
if (alias) col.ref.unshift(alias)
|
|
159
|
+
return col
|
|
161
160
|
})
|
|
162
|
-
|
|
161
|
+
}
|
|
163
162
|
return toBeSearched
|
|
164
163
|
}
|
|
165
164
|
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
const { parentPort } = require('worker_threads')
|
|
2
|
+
const { Responses, Errors } = require('../../../../lib/req/response')
|
|
3
|
+
|
|
4
|
+
class WorkerReq {
|
|
5
|
+
constructor(reqData) {
|
|
6
|
+
Object.assign(this, reqData)
|
|
7
|
+
this.postMessages = []
|
|
8
|
+
this.messages = this.messages ?? []
|
|
9
|
+
this.errors = this.errors ?? new Errors()
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
#push(args) {
|
|
13
|
+
this.postMessages.push({
|
|
14
|
+
kind: 'run',
|
|
15
|
+
target: 'req',
|
|
16
|
+
...args
|
|
17
|
+
})
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
notify(...args) {
|
|
21
|
+
this.#push({
|
|
22
|
+
prop: 'notify',
|
|
23
|
+
args
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
const notify = Responses.get(1, ...args)
|
|
27
|
+
this.messages.push(notify)
|
|
28
|
+
return notify
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
info(...args) {
|
|
32
|
+
this.#push({
|
|
33
|
+
prop: 'info',
|
|
34
|
+
args
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
const info = Responses.get(2, ...args)
|
|
38
|
+
this.messages.push(info)
|
|
39
|
+
return info
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
warn(...args) {
|
|
43
|
+
this.#push({
|
|
44
|
+
prop: 'warn',
|
|
45
|
+
args
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
const warn = Responses.get(3, ...args)
|
|
49
|
+
this.messages.push(warn)
|
|
50
|
+
return warn
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
error(...args) {
|
|
54
|
+
this.#push({
|
|
55
|
+
prop: 'error',
|
|
56
|
+
args
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
let error = Responses.get(4, ...args)
|
|
60
|
+
if (!error.stack) Error.captureStackTrace((error = Object.assign(new Error(), error)), this.error)
|
|
61
|
+
this.errors.push(error)
|
|
62
|
+
return error
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
reject(...args) {
|
|
66
|
+
parentPort.postMessage({
|
|
67
|
+
kind: 'run',
|
|
68
|
+
target: 'req',
|
|
69
|
+
prop: 'reject',
|
|
70
|
+
args
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
let error = Responses.get(4, ...args)
|
|
74
|
+
if (!error.stack) Error.captureStackTrace((error = Object.assign(new Error(), error)), this.reject)
|
|
75
|
+
throw error
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
module.exports = WorkerReq
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const os = require('os')
|
|
2
|
+
const totalMemory = os.totalmem() // total amount of system memory in bytes
|
|
3
|
+
const maxOldGenerationSizeMb = Math.floor(totalMemory / 1024 ** 2 / 8) // max size of the main heap in MB
|
|
4
|
+
const maxYoungGenerationSizeMb = Math.floor(totalMemory / 1024 ** 2 / 8) // max size of a heap space for recently created objects
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
timeout: 10000,
|
|
8
|
+
resourceLimits: {
|
|
9
|
+
maxOldGenerationSizeMb,
|
|
10
|
+
maxYoungGenerationSizeMb,
|
|
11
|
+
stackSizeMb: 4 // default
|
|
12
|
+
}
|
|
13
|
+
}
|