@sap/cds 6.0.4 → 6.1.2
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 +180 -18
- package/apis/cds.d.ts +11 -7
- package/apis/log.d.ts +124 -0
- package/apis/ql.d.ts +72 -15
- package/apis/services.d.ts +13 -2
- package/bin/build/buildTaskHandler.js +5 -2
- package/bin/build/constants.js +4 -1
- package/bin/build/provider/buildTaskHandlerEdmx.js +11 -39
- package/bin/build/provider/buildTaskHandlerFeatureToggles.js +14 -34
- package/bin/build/provider/buildTaskHandlerInternal.js +56 -4
- package/bin/build/provider/buildTaskProviderInternal.js +22 -14
- package/bin/build/provider/hana/index.js +12 -9
- package/bin/build/provider/java/index.js +18 -8
- package/bin/build/provider/mtx/index.js +7 -4
- package/bin/build/provider/mtx/resourcesTarBuilder.js +60 -35
- package/bin/build/provider/mtx-extension/index.js +57 -0
- package/bin/build/provider/mtx-sidecar/index.js +46 -18
- package/bin/build/provider/nodejs/index.js +34 -13
- package/bin/deploy/to-hana/cfUtil.js +7 -2
- package/bin/deploy/to-hana/hana.js +20 -25
- package/bin/deploy/to-hana/hdiDeployUtil.js +13 -2
- package/bin/serve.js +7 -4
- package/lib/compile/{index.js → cds-compile.js} +0 -0
- package/lib/compile/extend.js +15 -5
- package/lib/compile/minify.js +1 -15
- package/lib/compile/parse.js +1 -1
- package/lib/compile/resolve.js +2 -2
- package/lib/compile/to/srvinfo.js +6 -4
- package/lib/{deploy.js → dbs/cds-deploy.js} +9 -8
- package/lib/env/{index.js → cds-env.js} +1 -17
- package/lib/env/{requires.js → cds-requires.js} +24 -3
- package/lib/env/defaults.js +7 -1
- package/lib/env/schemas/cds-package.json +11 -0
- package/lib/env/schemas/cds-rc.json +614 -0
- package/lib/index.js +19 -16
- package/lib/log/{errors.js → cds-error.js} +1 -1
- package/lib/log/{index.js → cds-log.js} +0 -0
- package/lib/log/format/kibana.js +19 -1
- package/lib/ql/Query.js +9 -3
- package/lib/ql/SELECT.js +2 -2
- package/lib/ql/UPDATE.js +2 -2
- package/lib/ql/{index.js → cds-ql.js} +4 -10
- package/lib/req/context.js +49 -17
- package/lib/req/locale.js +5 -1
- package/lib/{serve → srv}/adapters.js +23 -19
- package/lib/{connect → srv}/bindings.js +0 -0
- package/lib/{connect/index.js → srv/cds-connect.js} +1 -1
- package/lib/{serve/index.js → srv/cds-serve.js} +0 -0
- package/lib/{serve → srv}/factory.js +1 -1
- package/lib/{serve/Service-api.js → srv/srv-api.js} +22 -6
- package/lib/{serve/Service-dispatch.js → srv/srv-dispatch.js} +13 -8
- package/lib/{serve/Service-handlers.js → srv/srv-handlers.js} +10 -0
- package/lib/{serve/Service-methods.js → srv/srv-methods.js} +10 -8
- package/lib/srv/srv-models.js +207 -0
- package/lib/{serve/Transaction.js → srv/srv-tx.js} +57 -40
- package/lib/utils/{tests.js → cds-test.js} +2 -2
- package/lib/utils/cds-utils.js +146 -0
- package/lib/utils/index.js +2 -145
- package/lib/utils/jest.js +43 -0
- package/lib/utils/resources/index.js +15 -25
- package/lib/utils/resources/tar.js +18 -41
- package/libx/_runtime/auth/index.js +14 -11
- package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +7 -19
- package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +1 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +1 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +1 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +6 -19
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +3 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +1 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/PrimitiveValueDecoder.js +0 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/DeserializerFactory.js +3 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +38 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +1 -3
- package/libx/_runtime/cds-services/services/utils/differ.js +4 -0
- package/libx/_runtime/cds-services/util/errors.js +1 -29
- package/libx/_runtime/common/i18n/messages.properties +2 -1
- package/libx/_runtime/common/perf/index.js +10 -15
- package/libx/_runtime/common/utils/binary.js +3 -4
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +0 -1
- package/libx/_runtime/common/utils/entityFromCqn.js +8 -5
- package/libx/_runtime/common/utils/keys.js +14 -6
- package/libx/_runtime/common/utils/resolveView.js +1 -1
- package/libx/_runtime/common/utils/template.js +1 -1
- package/libx/_runtime/db/Service.js +2 -14
- package/libx/_runtime/db/expand/expandCQNToJoin.js +28 -25
- package/libx/_runtime/db/expand/rawToExpanded.js +7 -6
- package/libx/_runtime/db/generic/input.js +8 -1
- package/libx/_runtime/db/sql-builder/InsertBuilder.js +1 -1
- package/libx/_runtime/db/sql-builder/SelectBuilder.js +37 -18
- package/libx/_runtime/extensibility/activate.js +47 -47
- package/libx/_runtime/extensibility/add.js +22 -13
- package/libx/_runtime/extensibility/addExtension.js +17 -13
- package/libx/_runtime/extensibility/defaults.js +25 -30
- package/libx/_runtime/extensibility/handler/transformREAD.js +20 -18
- package/libx/_runtime/extensibility/linter/allowlist_checker.js +373 -0
- package/libx/_runtime/extensibility/linter/annotations_checker.js +113 -0
- package/libx/_runtime/extensibility/linter/checker_base.js +20 -0
- package/libx/_runtime/extensibility/linter/namespace_checker.js +180 -0
- package/libx/_runtime/extensibility/linter.js +32 -0
- package/libx/_runtime/extensibility/push.js +77 -20
- package/libx/_runtime/extensibility/service.js +29 -12
- package/libx/_runtime/extensibility/token.js +57 -0
- package/libx/_runtime/extensibility/utils.js +8 -6
- package/libx/_runtime/extensibility/validation.js +6 -9
- package/libx/_runtime/fiori/generic/new.js +0 -11
- package/libx/_runtime/fiori/utils/where.js +1 -1
- package/libx/_runtime/hana/Service.js +0 -1
- package/libx/_runtime/hana/conversion.js +12 -1
- package/libx/_runtime/hana/customBuilder/CustomFunctionBuilder.js +4 -3
- package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +5 -0
- package/libx/_runtime/hana/pool.js +6 -10
- package/libx/_runtime/hana/search2Contains.js +0 -5
- package/libx/_runtime/hana/search2cqn4sql.js +1 -0
- package/libx/_runtime/messaging/common-utils/authorizedRequest.js +1 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/EMManagement.js +1 -2
- package/libx/_runtime/messaging/outbox/utils.js +1 -1
- package/libx/_runtime/messaging/service.js +11 -6
- package/libx/_runtime/remote/utils/data.js +5 -0
- package/libx/_runtime/sqlite/Service.js +7 -6
- package/libx/_runtime/sqlite/execute.js +41 -28
- package/libx/odata/afterburner.js +79 -2
- package/libx/odata/cqn2odata.js +15 -9
- package/libx/odata/grammar.pegjs +157 -76
- package/libx/odata/index.js +9 -3
- package/libx/odata/parser.js +1 -1
- package/libx/odata/utils.js +39 -5
- package/libx/rest/RestAdapter.js +3 -7
- package/libx/rest/middleware/delete.js +4 -5
- package/libx/rest/middleware/parse.js +3 -2
- package/package.json +3 -3
- package/server.js +1 -1
- package/srv/extensibility-service.cds +6 -3
- package/srv/model-provider.cds +3 -1
- package/srv/model-provider.js +86 -106
- package/srv/mtx.js +7 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +0 -240
|
@@ -18,9 +18,9 @@ const _addAnnotation = extension => {
|
|
|
18
18
|
})
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
const _addViews = csn => {
|
|
21
|
+
const _addViews = (csn, appCsn) => {
|
|
22
22
|
csn.extensions.forEach(extension => {
|
|
23
|
-
const target =
|
|
23
|
+
const target = appCsn.definitions[extension.extend]
|
|
24
24
|
const views_ = []
|
|
25
25
|
const view = resolveViews(target, views_)
|
|
26
26
|
extension.extend = view && view.name
|
|
@@ -41,28 +41,32 @@ const _addViews = csn => {
|
|
|
41
41
|
})
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
const _addExtension = async function (csn, req) {
|
|
45
|
-
|
|
46
|
-
await
|
|
44
|
+
const _addExtension = async function (csn, appCsn, req) {
|
|
45
|
+
if (req.tenant) cds.context = { tenant: req.tenant }
|
|
46
|
+
await cds.db.run(
|
|
47
47
|
INSERT.into('cds.xt.Extensions').entries([
|
|
48
48
|
{ ID: cds.utils.uuid(), tag: 'uiflex', csn: JSON.stringify(csn), activated: 'propertyBag' }
|
|
49
49
|
])
|
|
50
50
|
)
|
|
51
51
|
|
|
52
|
-
// defaults
|
|
53
52
|
for (const ext of req.data.extensions) {
|
|
54
53
|
const extension = JSON.parse(ext)
|
|
55
|
-
await handleDefaults(extension,
|
|
54
|
+
await handleDefaults(extension, appCsn, false)
|
|
56
55
|
}
|
|
57
56
|
}
|
|
58
57
|
|
|
59
58
|
const addExtension = async function (req) {
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
59
|
+
const { 'cds.xt.ModelProviderService': mps } = cds.services
|
|
60
|
+
const csn = await mps.getCsn(req.tenant, ['*'])
|
|
61
|
+
const extCsn = _getCsn(req)
|
|
62
|
+
const njCsn = cds.compile.for['nodejs'](csn)
|
|
63
|
+
// REVISIT: Optimize the validations
|
|
64
|
+
// REVISIT: Why do we need njCsn?
|
|
65
|
+
validateCsn(extCsn, njCsn, req)
|
|
66
|
+
validateExtensionFields(extCsn, njCsn, req)
|
|
67
|
+
_addViews(extCsn, njCsn)
|
|
68
|
+
validateExtension(extCsn, csn, req)
|
|
69
|
+
await _addExtension(extCsn, njCsn, req)
|
|
66
70
|
}
|
|
67
71
|
|
|
68
72
|
module.exports = addExtension
|
|
@@ -1,38 +1,33 @@
|
|
|
1
1
|
const cds = require('../cds')
|
|
2
|
-
|
|
3
|
-
const { ensureDraftsSuffix } = require('../common/utils/draft')
|
|
4
2
|
const resolveViews = require('./views')
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const draft = _getDraftTable(extension.extend)
|
|
20
|
-
const ext = Object.keys(extension.elements)
|
|
21
|
-
.filter(key => extension.elements[key].default)
|
|
3
|
+
const builtin = cds.builtin.types
|
|
4
|
+
const needsQuotes = Symbol()
|
|
5
|
+
builtin.string[needsQuotes] = true
|
|
6
|
+
builtin.date[needsQuotes] = true
|
|
7
|
+
|
|
8
|
+
const handleDefaults = async (extension, { definitions }, checkDb = true) => {
|
|
9
|
+
const target = definitions[extension.extend]
|
|
10
|
+
const entity = resolveViews(target)
|
|
11
|
+
if (checkDb && target !== entity) return // only db entities
|
|
12
|
+
|
|
13
|
+
const elements = extension.elements
|
|
14
|
+
const defaults = Object.keys(elements)
|
|
15
|
+
.filter(key => elements[key].default)
|
|
22
16
|
.map(key => {
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
const t =
|
|
26
|
-
|
|
27
|
-
return `"${key}":${value}`
|
|
17
|
+
const e = elements[key],
|
|
18
|
+
{ val } = e.default
|
|
19
|
+
const t = definitions[e.type] || builtin[e.type]
|
|
20
|
+
return `"${key}":${t && t[needsQuotes] ? `"${val}"` : val}`
|
|
28
21
|
})
|
|
29
22
|
|
|
30
|
-
if (
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
23
|
+
if (defaults.length) {
|
|
24
|
+
const newDefaults = defaults.join(',')
|
|
25
|
+
const newBagpack = `extensions__ = CASE
|
|
26
|
+
WHEN extensions__ is null THEN '{${newDefaults}}'
|
|
27
|
+
ELSE '{${newDefaults},' || substr(extensions__, 2, length(extensions__)-1)
|
|
28
|
+
END`
|
|
29
|
+
await Promise.all([UPDATE(entity).with(newBagpack), entity.drafts && UPDATE(entity.drafts).with(newBagpack)])
|
|
30
|
+
// NOTE: We don't need the model definitions for `entity` in cds.db.model to run these queries
|
|
36
31
|
}
|
|
37
32
|
}
|
|
38
33
|
|
|
@@ -42,23 +42,25 @@ const _removeExtendedFields = (columns, extFields, alias) => {
|
|
|
42
42
|
|
|
43
43
|
const _transformUnion = (req, model) => {
|
|
44
44
|
// second element is active entity
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
45
|
+
if (req.target) {
|
|
46
|
+
const name = req.target.name.SET ? req.target.name.SET.args[1]._target.name : req.target.name
|
|
47
|
+
const extFields = getExtendedFields(name, model)
|
|
48
|
+
_addBackPack(req.query.SELECT.columns, extFields)
|
|
49
|
+
_removeExtendedFields(req.query.SELECT.columns, extFields)
|
|
50
|
+
|
|
51
|
+
_addBackPack(
|
|
52
|
+
req.query.SELECT.from.SET.args[0].SELECT.columns,
|
|
53
|
+
extFields,
|
|
54
|
+
req.query.SELECT.from.SET.args[0].SELECT.from.args[0].as
|
|
55
|
+
)
|
|
56
|
+
_addBackPack(req.query.SELECT.from.SET.args[1].SELECT.columns, extFields)
|
|
57
|
+
_removeExtendedFields(
|
|
58
|
+
req.query.SELECT.from.SET.args[0].SELECT.columns,
|
|
59
|
+
extFields,
|
|
60
|
+
req.query.SELECT.from.SET.args[0].SELECT.from.args[0].as
|
|
61
|
+
)
|
|
62
|
+
_removeExtendedFields(req.query.SELECT.from.SET.args[1].SELECT.columns, extFields)
|
|
63
|
+
}
|
|
62
64
|
}
|
|
63
65
|
|
|
64
66
|
const _getAliasedEntitiesForJoin = (args, model) => {
|
|
@@ -111,7 +113,7 @@ function transformExtendedFieldsREAD(req) {
|
|
|
111
113
|
_transformColumns(req.query.SELECT.columns, target.name, this.model)
|
|
112
114
|
|
|
113
115
|
if (req.query.SELECT.from.SET) return _transformUnion(req, this.model) // union
|
|
114
|
-
if (req.query.SELECT.from.join) return _transformJoin(req, this.model) // join
|
|
116
|
+
if (req.query.SELECT.from.join && req.query.SELECT.from.args) return _transformJoin(req, this.model) // join
|
|
115
117
|
}
|
|
116
118
|
|
|
117
119
|
module.exports = {
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
const Checker = require('./checker_base')
|
|
2
|
+
|
|
3
|
+
const LABELS = {
|
|
4
|
+
service: 'Service',
|
|
5
|
+
entity: 'Entity',
|
|
6
|
+
aspect: 'Aspect',
|
|
7
|
+
type: 'Type'
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const LEGACY_ENTITY_WHITELIST = 'entity-whitelist'
|
|
11
|
+
const LEGACY_SERVICE_WHITELIST = 'service-whitelist'
|
|
12
|
+
const EXTENSION_ALLOWLIST = 'extension-allowlist'
|
|
13
|
+
const NEW_FIELDS = 'new-fields'
|
|
14
|
+
const NEW_ENTITIES = 'new-entities'
|
|
15
|
+
|
|
16
|
+
class Allowlist {
|
|
17
|
+
constructor(mtxConfig, fullCsn) {
|
|
18
|
+
this.allowlist = Allowlist._setupPermissionList(mtxConfig, fullCsn)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
get all() {
|
|
22
|
+
return this.allowlist.all
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
get entity() {
|
|
26
|
+
return this.allowlist.entity
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
get service() {
|
|
30
|
+
return this.allowlist.service
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
getList(kind) {
|
|
34
|
+
return this.allowlist[kind]
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
getPermission(kind, name) {
|
|
38
|
+
function findInList(list, name) {
|
|
39
|
+
if (list) {
|
|
40
|
+
const splitName = name.split('.')
|
|
41
|
+
while (splitName.length > 0) {
|
|
42
|
+
const nameOrPrefix = splitName.join('.')
|
|
43
|
+
if (list[nameOrPrefix]) {
|
|
44
|
+
return list[nameOrPrefix]
|
|
45
|
+
}
|
|
46
|
+
splitName.pop()
|
|
47
|
+
}
|
|
48
|
+
return list['*'] ? list['*'] : null
|
|
49
|
+
}
|
|
50
|
+
return null
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return findInList(this.allowlist[kind], name) || findInList(this.allowlist['all'], name)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
isAllowed(kind, name) {
|
|
57
|
+
return !!this.getPermission(kind, name)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
static _setupPermissionList(mtxConfig, fullCsn) {
|
|
61
|
+
// internal structure:
|
|
62
|
+
// result[name] = { kind, new-fields | new-entities }
|
|
63
|
+
|
|
64
|
+
const result = {}
|
|
65
|
+
|
|
66
|
+
// create from legacy lists
|
|
67
|
+
let { entityWhitelist, serviceWhitelist } = Allowlist._getLegacyLists(mtxConfig)
|
|
68
|
+
|
|
69
|
+
// create from new lists
|
|
70
|
+
const allowlistNewFormat = mtxConfig[EXTENSION_ALLOWLIST]
|
|
71
|
+
|
|
72
|
+
Allowlist._addLegacyLists(result, serviceWhitelist, entityWhitelist)
|
|
73
|
+
|
|
74
|
+
if (allowlistNewFormat) {
|
|
75
|
+
// seperate into single entities /services for better processing
|
|
76
|
+
for (const permission of allowlistNewFormat) {
|
|
77
|
+
if (permission.for) {
|
|
78
|
+
for (const name of permission.for) {
|
|
79
|
+
if (permission.kind) {
|
|
80
|
+
// kind is specfied
|
|
81
|
+
result[permission.kind] = result[permission.kind] || {}
|
|
82
|
+
result[permission.kind][name] = permission
|
|
83
|
+
} else {
|
|
84
|
+
// check kind
|
|
85
|
+
if (fullCsn.definitions[name]) {
|
|
86
|
+
result[fullCsn.definitions[name].kind] = result[fullCsn.definitions[name].kind] || {}
|
|
87
|
+
result[fullCsn.definitions[name].kind][name] = permission
|
|
88
|
+
} else {
|
|
89
|
+
// allow all
|
|
90
|
+
result.all = result.all || {}
|
|
91
|
+
result.all[name] = permission
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return result
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
static _addLegacyLists(result, serviceWhitelist, entityWhitelist) {
|
|
102
|
+
if (serviceWhitelist) {
|
|
103
|
+
result.service = result.service || {}
|
|
104
|
+
for (const service of serviceWhitelist) {
|
|
105
|
+
result.service[service] = {}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (entityWhitelist) {
|
|
110
|
+
result.entity = result.entity || {}
|
|
111
|
+
for (const entity of entityWhitelist) {
|
|
112
|
+
result.entity[entity] = {}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return result
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
static _getLegacyLists(mtxConfig) {
|
|
119
|
+
let entityWhitelist = mtxConfig[LEGACY_ENTITY_WHITELIST]
|
|
120
|
+
let serviceWhitelist = mtxConfig[LEGACY_SERVICE_WHITELIST]
|
|
121
|
+
|
|
122
|
+
if (entityWhitelist && !Array.isArray(entityWhitelist)) {
|
|
123
|
+
entityWhitelist = [entityWhitelist]
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (serviceWhitelist && !Array.isArray(serviceWhitelist)) {
|
|
127
|
+
serviceWhitelist = [serviceWhitelist]
|
|
128
|
+
}
|
|
129
|
+
return { entityWhitelist, serviceWhitelist }
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
class AllowlistChecker extends Checker {
|
|
134
|
+
static _setupPermissionList(mtxConfig, fullCsn) {
|
|
135
|
+
return new Allowlist(mtxConfig, fullCsn)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
static async check(reflectedExtensionCsn, fullCsn, extensionFiles, compileDir, mtxConfig) {
|
|
139
|
+
const allowList = this._setupPermissionList(mtxConfig, fullCsn)
|
|
140
|
+
|
|
141
|
+
const warnings = []
|
|
142
|
+
|
|
143
|
+
// check entities
|
|
144
|
+
if (reflectedExtensionCsn.extensions && allowList) {
|
|
145
|
+
for (const extension of reflectedExtensionCsn.extensions) {
|
|
146
|
+
this._checkEntity(extension, reflectedExtensionCsn, fullCsn, compileDir, allowList, warnings)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// check services
|
|
151
|
+
const foundServiceExt = {}
|
|
152
|
+
reflectedExtensionCsn.forall(
|
|
153
|
+
element => {
|
|
154
|
+
return ['entity', 'element', 'function', 'action'].includes(element.kind)
|
|
155
|
+
},
|
|
156
|
+
(element, name, parent) => {
|
|
157
|
+
if (allowList) {
|
|
158
|
+
this._checkService(
|
|
159
|
+
reflectedExtensionCsn,
|
|
160
|
+
fullCsn,
|
|
161
|
+
element,
|
|
162
|
+
parent,
|
|
163
|
+
{ extensionFiles, compileDir },
|
|
164
|
+
allowList,
|
|
165
|
+
warnings,
|
|
166
|
+
foundServiceExt
|
|
167
|
+
) // NOSONAR
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
)
|
|
171
|
+
this._addServiceLimitWarnings(foundServiceExt, allowList, compileDir, warnings)
|
|
172
|
+
|
|
173
|
+
return warnings
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
static _checkEntity(extension, extCsn, fullCsn, compileDir, allowlist, warnings) {
|
|
177
|
+
const extendedEntity = extension.extend
|
|
178
|
+
if (extendedEntity) {
|
|
179
|
+
if (
|
|
180
|
+
fullCsn &&
|
|
181
|
+
fullCsn.definitions &&
|
|
182
|
+
(!extCsn.definitions[extendedEntity] || extCsn.definitions[extendedEntity].kind !== 'entity')
|
|
183
|
+
) {
|
|
184
|
+
const kind = this._getExtendedKind(fullCsn, extendedEntity)
|
|
185
|
+
|
|
186
|
+
if (!allowlist.isAllowed(kind, extendedEntity)) {
|
|
187
|
+
// not allowed at all
|
|
188
|
+
this._addColumnWarnings(extension, warnings, compileDir, allowlist.getList(kind), kind)
|
|
189
|
+
this._addElementWarnings(extension, warnings, compileDir, allowlist.getList(kind), kind)
|
|
190
|
+
} else {
|
|
191
|
+
// might have limits
|
|
192
|
+
this._addEntityLimitWarnings(extension, warnings, compileDir, allowlist, kind)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
static _getExtendedKind(fullCsn, extendedEntity) {
|
|
199
|
+
const elementFromBase = fullCsn.definitions[extendedEntity]
|
|
200
|
+
return elementFromBase ? elementFromBase.kind : null
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
static _addElementWarnings(extension, warnings, compileDir, allowlist, kind) {
|
|
204
|
+
if (extension.elements) {
|
|
205
|
+
for (const element in extension.elements) {
|
|
206
|
+
warnings.push(
|
|
207
|
+
this._createAllowlistWarning(
|
|
208
|
+
extension.extend,
|
|
209
|
+
extension.elements[element],
|
|
210
|
+
compileDir,
|
|
211
|
+
allowlist,
|
|
212
|
+
LABELS[kind]
|
|
213
|
+
)
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
static _addColumnWarnings(extension, warnings, compileDir, allowlist, kind) {
|
|
220
|
+
// loop columns + elements
|
|
221
|
+
if (extension.columns) {
|
|
222
|
+
for (const column of extension.columns) {
|
|
223
|
+
warnings.push(this._createAllowlistWarning(extension.extend, column, compileDir, allowlist, LABELS[kind]))
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
static _addEntityLimitWarnings(extension, warnings, compileDir, allowlist, kind) {
|
|
229
|
+
const limit = allowlist.getPermission(kind, extension.extend)[NEW_FIELDS]
|
|
230
|
+
|
|
231
|
+
if (!limit) {
|
|
232
|
+
return
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (
|
|
236
|
+
(extension.columns ? extension.columns.length : 0) +
|
|
237
|
+
(extension.elements ? Object.keys(extension.elements).length : 0) <=
|
|
238
|
+
limit
|
|
239
|
+
) {
|
|
240
|
+
return
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// loop columns + elements
|
|
244
|
+
if (extension.columns) {
|
|
245
|
+
for (const column of extension.columns) {
|
|
246
|
+
warnings.push(this._createLimitWarning(extension.extend, column, compileDir, limit, LABELS[kind]))
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (extension.elements) {
|
|
251
|
+
for (const element in extension.elements) {
|
|
252
|
+
warnings.push(
|
|
253
|
+
this._createLimitWarning(extension.extend, extension.elements[element], compileDir, limit, LABELS[kind])
|
|
254
|
+
)
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
static _addServiceLimitWarnings(foundServiceExt, allowlist, compileDir, warnings) {
|
|
260
|
+
if (!allowlist) {
|
|
261
|
+
return
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
for (const service in foundServiceExt) {
|
|
265
|
+
let extLimit = allowlist.getPermission('service', service)[NEW_ENTITIES]
|
|
266
|
+
if (extLimit && extLimit <= foundServiceExt[service].length) {
|
|
267
|
+
// loop all extension for one service
|
|
268
|
+
for (const element of foundServiceExt[service]) {
|
|
269
|
+
warnings.push(this._createLimitWarning(service, element, compileDir, extLimit, LABELS['service']))
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
static _getParentName(element) {
|
|
276
|
+
if (element.name) {
|
|
277
|
+
const splitEntityName = element.name.split('.')
|
|
278
|
+
if (splitEntityName.length > 1) {
|
|
279
|
+
splitEntityName.pop()
|
|
280
|
+
return splitEntityName.join('.')
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return null
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
static _isDefinedInExtension(reflectedCsn, name) {
|
|
287
|
+
return reflectedCsn.definitions ? !!reflectedCsn.definitions[name] : false
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
static _isDefinedInBasemodel(fullCsn, name) {
|
|
291
|
+
return !!this._getFromBasemodel(fullCsn, name)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
static _getFromBasemodel(fullCsn, name) {
|
|
295
|
+
return fullCsn.definitions[name]
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
static _checkService(reflectedExtensionCsn, fullCsn, element, parent, extension, allowlist, warnings, foundExt) {
|
|
299
|
+
if (parent && parent.kind && parent.kind !== 'service') {
|
|
300
|
+
return
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
let parentName
|
|
304
|
+
if (!parent) {
|
|
305
|
+
parentName = this._getParentName(element)
|
|
306
|
+
} else {
|
|
307
|
+
parentName = this._getEntityName(parent)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// definition of element in extension itself
|
|
311
|
+
if (!parentName) {
|
|
312
|
+
return
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// check if parent is defined in extension itself
|
|
316
|
+
if (this._isDefinedInExtension(reflectedExtensionCsn, parentName)) {
|
|
317
|
+
return
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// check if parent is defined in basemodel
|
|
321
|
+
if (!this._isDefinedInBasemodel(fullCsn, parentName)) {
|
|
322
|
+
return
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (allowlist.isAllowed('service', parentName)) {
|
|
326
|
+
foundExt[parentName] = foundExt[parentName] || []
|
|
327
|
+
foundExt[parentName].push(element)
|
|
328
|
+
return
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
warnings.push(
|
|
332
|
+
this._createAllowlistWarning(parentName, element, extension.compileDir, allowlist.service, LABELS['service'])
|
|
333
|
+
)
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
static _createAllowlistWarning(entityName, element, compileDir, allowlist = {}, label) {
|
|
337
|
+
const originFile = this._localizeFile(element.$location.file, compileDir)
|
|
338
|
+
|
|
339
|
+
return (
|
|
340
|
+
label +
|
|
341
|
+
' ' +
|
|
342
|
+
entityName +
|
|
343
|
+
' must not be extended. See ' +
|
|
344
|
+
'(line:' +
|
|
345
|
+
element.$location.line +
|
|
346
|
+
', col:' +
|
|
347
|
+
element.$location.col +
|
|
348
|
+
')' +
|
|
349
|
+
' in ' +
|
|
350
|
+
originFile +
|
|
351
|
+
'. Check ' +
|
|
352
|
+
label +
|
|
353
|
+
' allowlist: ' +
|
|
354
|
+
(Object.keys(allowlist).length > 0 ? Object.keys(allowlist) : '<empty list>')
|
|
355
|
+
)
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
static _createLimitWarning(entityName, element, compileDir, limit, label) {
|
|
359
|
+
const originFile = this._localizeFile(element.$location.file, compileDir)
|
|
360
|
+
|
|
361
|
+
return (
|
|
362
|
+
`Extension limit of ${limit} for ${label} ${entityName} has been exceeded` +
|
|
363
|
+
`(line: ${element.$location.line}, col: ${element.$location.col})` +
|
|
364
|
+
` in ${originFile}`
|
|
365
|
+
)
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
static _getEntityName(entity) {
|
|
369
|
+
return entity.extend ? entity.extend : entity.name
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
module.exports = AllowlistChecker
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
const Checker = require('./checker_base')
|
|
2
|
+
|
|
3
|
+
const AT_REQUIRES = '@requires'
|
|
4
|
+
const AT_RESTRICT = '@restrict'
|
|
5
|
+
const AT_CDS_PERSISTENCE_JOURNAL = '@cds.persistence.journal'
|
|
6
|
+
const AT_SQL_APPEND = '@sql.append'
|
|
7
|
+
const AT_SQL_PREPEND = '@sql.prepend'
|
|
8
|
+
|
|
9
|
+
const checkedAnnotations = new Map([
|
|
10
|
+
[AT_REQUIRES, _createSecurityAnnotationWarning],
|
|
11
|
+
[AT_RESTRICT, _createSecurityAnnotationWarning],
|
|
12
|
+
[AT_CDS_PERSISTENCE_JOURNAL, _createJournalAnnotationWarning],
|
|
13
|
+
[AT_SQL_APPEND, _createSqlAnnotationWarning],
|
|
14
|
+
[AT_SQL_PREPEND, _createSqlAnnotationWarning]
|
|
15
|
+
])
|
|
16
|
+
|
|
17
|
+
function _createSqlAnnotationWarning(annotationName, originFile, annotation) {
|
|
18
|
+
return (
|
|
19
|
+
`Annotation ${annotationName} is not supported in extensions: ${originFile}` +
|
|
20
|
+
`(line: ${annotation.$location.line}, col: ${annotation.$location.col})`
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function _createSecurityAnnotationWarning(annotationName, originFile, annotation) {
|
|
25
|
+
return (
|
|
26
|
+
`Security relevant annotation ${annotationName} cannot be overwritten: ${originFile}` +
|
|
27
|
+
`(line: ${annotation.$location.line}, col: ${annotation.$location.col})`
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function _createJournalAnnotationWarning(journalAnnotationName, originFile, annotation) {
|
|
32
|
+
return (
|
|
33
|
+
`Enabling schema evolution in extensions using ${journalAnnotationName} not yet supported: ${originFile}` +
|
|
34
|
+
` (line: ${annotation.$location.line}, col: ${annotation.$location.col})`
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
class AnnotationsChecker extends Checker {
|
|
39
|
+
static async check(reflectedCsn, extensionFiles, compileDir) {
|
|
40
|
+
if (!reflectedCsn.extensions) {
|
|
41
|
+
return []
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// annotations via annotate - applies for all
|
|
45
|
+
const annotationExtensions = Object.values(reflectedCsn.extensions).filter(
|
|
46
|
+
value => value.annotate && Object.getOwnPropertyNames(value).filter(property => checkedAnnotations.get(property))
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
// check annotations for extensions including fields
|
|
50
|
+
reflectedCsn.forall(
|
|
51
|
+
() => true,
|
|
52
|
+
element => {
|
|
53
|
+
if (element[AT_SQL_PREPEND] || element[AT_SQL_APPEND]) {
|
|
54
|
+
if (!element.annotate) {
|
|
55
|
+
// do not add annotation extensions again
|
|
56
|
+
annotationExtensions.push(element)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
reflectedCsn.extensions
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
// check entities and fields from definitions
|
|
64
|
+
const annotatedDefinitions = []
|
|
65
|
+
reflectedCsn.forall(
|
|
66
|
+
() => true,
|
|
67
|
+
element => {
|
|
68
|
+
if (element[AT_SQL_PREPEND] || element[AT_SQL_APPEND] || element[AT_CDS_PERSISTENCE_JOURNAL]) {
|
|
69
|
+
annotatedDefinitions.push(element)
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
reflectedCsn.definitions
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
const warnings = []
|
|
76
|
+
|
|
77
|
+
for (const annotationExtension of annotationExtensions) {
|
|
78
|
+
const warning = this._checkAnnotation(annotationExtension, reflectedCsn.definitions, compileDir)
|
|
79
|
+
if (warning) {
|
|
80
|
+
warnings.push(warning)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
for (const annotatedDefinition of annotatedDefinitions) {
|
|
85
|
+
const warning = this._checkAnnotation(annotatedDefinition, reflectedCsn.definitions, compileDir)
|
|
86
|
+
if (warning) {
|
|
87
|
+
warnings.push(warning)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return warnings
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
static _checkAnnotation(annotation, definitions, compileDir) {
|
|
95
|
+
if (!definitions[annotation.annotate]) {
|
|
96
|
+
return this._createAnnotationsWarning(annotation, compileDir)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return null
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
static _createAnnotationsWarning(annotation, compileDir) {
|
|
103
|
+
const annotationName = Object.getOwnPropertyNames(annotation).filter(property => checkedAnnotations.get(property))
|
|
104
|
+
|
|
105
|
+
const originFile = this._localizeFile(annotation.$location.file, compileDir)
|
|
106
|
+
|
|
107
|
+
if (annotationName.length) {
|
|
108
|
+
return checkedAnnotations.get(annotationName[0])(annotationName, originFile, annotation)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
module.exports = AnnotationsChecker
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const path = require('path')
|
|
2
|
+
|
|
3
|
+
class Checker {
|
|
4
|
+
static _isFromExtension(element, extensionFiles, compileDir) {
|
|
5
|
+
if (!element.$location) {
|
|
6
|
+
return false
|
|
7
|
+
}
|
|
8
|
+
const originFile = this._localizeFile(element.$location.file, compileDir)
|
|
9
|
+
return extensionFiles.includes(originFile)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
static _localizeFile(filename, compileDir) {
|
|
13
|
+
if (path.isAbsolute(filename) || filename.startsWith('..')) {
|
|
14
|
+
filename = path.relative(compileDir, filename)
|
|
15
|
+
}
|
|
16
|
+
return filename.replace(/\\/g, '/')
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
module.exports = Checker
|