@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.
Files changed (139) hide show
  1. package/CHANGELOG.md +180 -18
  2. package/apis/cds.d.ts +11 -7
  3. package/apis/log.d.ts +124 -0
  4. package/apis/ql.d.ts +72 -15
  5. package/apis/services.d.ts +13 -2
  6. package/bin/build/buildTaskHandler.js +5 -2
  7. package/bin/build/constants.js +4 -1
  8. package/bin/build/provider/buildTaskHandlerEdmx.js +11 -39
  9. package/bin/build/provider/buildTaskHandlerFeatureToggles.js +14 -34
  10. package/bin/build/provider/buildTaskHandlerInternal.js +56 -4
  11. package/bin/build/provider/buildTaskProviderInternal.js +22 -14
  12. package/bin/build/provider/hana/index.js +12 -9
  13. package/bin/build/provider/java/index.js +18 -8
  14. package/bin/build/provider/mtx/index.js +7 -4
  15. package/bin/build/provider/mtx/resourcesTarBuilder.js +60 -35
  16. package/bin/build/provider/mtx-extension/index.js +57 -0
  17. package/bin/build/provider/mtx-sidecar/index.js +46 -18
  18. package/bin/build/provider/nodejs/index.js +34 -13
  19. package/bin/deploy/to-hana/cfUtil.js +7 -2
  20. package/bin/deploy/to-hana/hana.js +20 -25
  21. package/bin/deploy/to-hana/hdiDeployUtil.js +13 -2
  22. package/bin/serve.js +7 -4
  23. package/lib/compile/{index.js → cds-compile.js} +0 -0
  24. package/lib/compile/extend.js +15 -5
  25. package/lib/compile/minify.js +1 -15
  26. package/lib/compile/parse.js +1 -1
  27. package/lib/compile/resolve.js +2 -2
  28. package/lib/compile/to/srvinfo.js +6 -4
  29. package/lib/{deploy.js → dbs/cds-deploy.js} +9 -8
  30. package/lib/env/{index.js → cds-env.js} +1 -17
  31. package/lib/env/{requires.js → cds-requires.js} +24 -3
  32. package/lib/env/defaults.js +7 -1
  33. package/lib/env/schemas/cds-package.json +11 -0
  34. package/lib/env/schemas/cds-rc.json +614 -0
  35. package/lib/index.js +19 -16
  36. package/lib/log/{errors.js → cds-error.js} +1 -1
  37. package/lib/log/{index.js → cds-log.js} +0 -0
  38. package/lib/log/format/kibana.js +19 -1
  39. package/lib/ql/Query.js +9 -3
  40. package/lib/ql/SELECT.js +2 -2
  41. package/lib/ql/UPDATE.js +2 -2
  42. package/lib/ql/{index.js → cds-ql.js} +4 -10
  43. package/lib/req/context.js +49 -17
  44. package/lib/req/locale.js +5 -1
  45. package/lib/{serve → srv}/adapters.js +23 -19
  46. package/lib/{connect → srv}/bindings.js +0 -0
  47. package/lib/{connect/index.js → srv/cds-connect.js} +1 -1
  48. package/lib/{serve/index.js → srv/cds-serve.js} +0 -0
  49. package/lib/{serve → srv}/factory.js +1 -1
  50. package/lib/{serve/Service-api.js → srv/srv-api.js} +22 -6
  51. package/lib/{serve/Service-dispatch.js → srv/srv-dispatch.js} +13 -8
  52. package/lib/{serve/Service-handlers.js → srv/srv-handlers.js} +10 -0
  53. package/lib/{serve/Service-methods.js → srv/srv-methods.js} +10 -8
  54. package/lib/srv/srv-models.js +207 -0
  55. package/lib/{serve/Transaction.js → srv/srv-tx.js} +57 -40
  56. package/lib/utils/{tests.js → cds-test.js} +2 -2
  57. package/lib/utils/cds-utils.js +146 -0
  58. package/lib/utils/index.js +2 -145
  59. package/lib/utils/jest.js +43 -0
  60. package/lib/utils/resources/index.js +15 -25
  61. package/lib/utils/resources/tar.js +18 -41
  62. package/libx/_runtime/auth/index.js +14 -11
  63. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +7 -19
  64. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +1 -4
  65. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -4
  66. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +1 -4
  67. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +1 -4
  68. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +2 -2
  69. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +6 -19
  70. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +3 -5
  71. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +1 -1
  72. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +1 -4
  73. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/PrimitiveValueDecoder.js +0 -2
  74. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/DeserializerFactory.js +3 -1
  75. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +38 -4
  76. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +1 -3
  77. package/libx/_runtime/cds-services/services/utils/differ.js +4 -0
  78. package/libx/_runtime/cds-services/util/errors.js +1 -29
  79. package/libx/_runtime/common/i18n/messages.properties +2 -1
  80. package/libx/_runtime/common/perf/index.js +10 -15
  81. package/libx/_runtime/common/utils/binary.js +3 -4
  82. package/libx/_runtime/common/utils/cqn2cqn4sql.js +0 -1
  83. package/libx/_runtime/common/utils/entityFromCqn.js +8 -5
  84. package/libx/_runtime/common/utils/keys.js +14 -6
  85. package/libx/_runtime/common/utils/resolveView.js +1 -1
  86. package/libx/_runtime/common/utils/template.js +1 -1
  87. package/libx/_runtime/db/Service.js +2 -14
  88. package/libx/_runtime/db/expand/expandCQNToJoin.js +28 -25
  89. package/libx/_runtime/db/expand/rawToExpanded.js +7 -6
  90. package/libx/_runtime/db/generic/input.js +8 -1
  91. package/libx/_runtime/db/sql-builder/InsertBuilder.js +1 -1
  92. package/libx/_runtime/db/sql-builder/SelectBuilder.js +37 -18
  93. package/libx/_runtime/extensibility/activate.js +47 -47
  94. package/libx/_runtime/extensibility/add.js +22 -13
  95. package/libx/_runtime/extensibility/addExtension.js +17 -13
  96. package/libx/_runtime/extensibility/defaults.js +25 -30
  97. package/libx/_runtime/extensibility/handler/transformREAD.js +20 -18
  98. package/libx/_runtime/extensibility/linter/allowlist_checker.js +373 -0
  99. package/libx/_runtime/extensibility/linter/annotations_checker.js +113 -0
  100. package/libx/_runtime/extensibility/linter/checker_base.js +20 -0
  101. package/libx/_runtime/extensibility/linter/namespace_checker.js +180 -0
  102. package/libx/_runtime/extensibility/linter.js +32 -0
  103. package/libx/_runtime/extensibility/push.js +77 -20
  104. package/libx/_runtime/extensibility/service.js +29 -12
  105. package/libx/_runtime/extensibility/token.js +57 -0
  106. package/libx/_runtime/extensibility/utils.js +8 -6
  107. package/libx/_runtime/extensibility/validation.js +6 -9
  108. package/libx/_runtime/fiori/generic/new.js +0 -11
  109. package/libx/_runtime/fiori/utils/where.js +1 -1
  110. package/libx/_runtime/hana/Service.js +0 -1
  111. package/libx/_runtime/hana/conversion.js +12 -1
  112. package/libx/_runtime/hana/customBuilder/CustomFunctionBuilder.js +4 -3
  113. package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +5 -0
  114. package/libx/_runtime/hana/pool.js +6 -10
  115. package/libx/_runtime/hana/search2Contains.js +0 -5
  116. package/libx/_runtime/hana/search2cqn4sql.js +1 -0
  117. package/libx/_runtime/messaging/common-utils/authorizedRequest.js +1 -1
  118. package/libx/_runtime/messaging/enterprise-messaging-utils/EMManagement.js +1 -2
  119. package/libx/_runtime/messaging/outbox/utils.js +1 -1
  120. package/libx/_runtime/messaging/service.js +11 -6
  121. package/libx/_runtime/remote/utils/data.js +5 -0
  122. package/libx/_runtime/sqlite/Service.js +7 -6
  123. package/libx/_runtime/sqlite/execute.js +41 -28
  124. package/libx/odata/afterburner.js +79 -2
  125. package/libx/odata/cqn2odata.js +15 -9
  126. package/libx/odata/grammar.pegjs +157 -76
  127. package/libx/odata/index.js +9 -3
  128. package/libx/odata/parser.js +1 -1
  129. package/libx/odata/utils.js +39 -5
  130. package/libx/rest/RestAdapter.js +3 -7
  131. package/libx/rest/middleware/delete.js +4 -5
  132. package/libx/rest/middleware/parse.js +3 -2
  133. package/package.json +3 -3
  134. package/server.js +1 -1
  135. package/srv/extensibility-service.cds +6 -3
  136. package/srv/model-provider.cds +3 -1
  137. package/srv/model-provider.js +86 -106
  138. package/srv/mtx.js +7 -1
  139. 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 = cds.model.definitions[extension.extend]
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
- const tx = cds.tx(req)
46
- await tx.run(
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, tx, false)
54
+ await handleDefaults(extension, appCsn, false)
56
55
  }
57
56
  }
58
57
 
59
58
  const addExtension = async function (req) {
60
- const csn = _getCsn(req)
61
- validateCsn(csn, req)
62
- validateExtensionFields(csn, req)
63
- _addViews(csn, cds)
64
- await validateExtension(csn, req.tenant, req)
65
- await _addExtension(csn, req)
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 { EXT_BACK_PACK } = require('./utils')
6
-
7
- const _needsQuotations = t => t instanceof cds.builtin.classes.string || t instanceof cds.builtin.classes.date
8
-
9
- const _getDraftTable = view => {
10
- return cds.model.definitions[view]._isDraftEnabled ? ensureDraftsSuffix(view) : undefined
11
- }
12
-
13
- const handleDefaults = async (extension, tx, checkDb = true) => {
14
- const target = cds.model.definitions[extension.extend]
15
- const dbEntity = resolveViews(target).name
16
-
17
- if (checkDb && target.name !== dbEntity) return // only db entities
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 element = extension.elements[key]
24
- // .type as ui flex extensions are not linked
25
- const t = cds.model.definitions[element.type] || cds.builtin.types[element.type]
26
- const value = t && _needsQuotations(t) ? `"${element.default.val}"` : element.default.val
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 (ext.length !== 0) {
31
- const extStr = ext.join(',')
32
- const changed = `'{${extStr},' || substr(${EXT_BACK_PACK}, 2, length(${EXT_BACK_PACK})-1)`
33
- const assign = `${EXT_BACK_PACK} = CASE WHEN ${EXT_BACK_PACK} IS NULL THEN '{${extStr}}' ELSE ${changed} END`
34
- await tx.run(UPDATE(dbEntity).with(assign))
35
- if (draft) await tx.run(UPDATE(draft).with(assign))
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
- const name = req.target.name.SET ? req.target.name.SET.args[1]._target.name : req.target.name
46
- const extFields = getExtendedFields(name, model)
47
- _addBackPack(req.query.SELECT.columns, extFields)
48
- _removeExtendedFields(req.query.SELECT.columns, extFields)
49
-
50
- _addBackPack(
51
- req.query.SELECT.from.SET.args[0].SELECT.columns,
52
- extFields,
53
- req.query.SELECT.from.SET.args[0].SELECT.from.args[0].as
54
- )
55
- _addBackPack(req.query.SELECT.from.SET.args[1].SELECT.columns, extFields)
56
- _removeExtendedFields(
57
- req.query.SELECT.from.SET.args[0].SELECT.columns,
58
- extFields,
59
- req.query.SELECT.from.SET.args[0].SELECT.from.args[0].as
60
- )
61
- _removeExtendedFields(req.query.SELECT.from.SET.args[1].SELECT.columns, extFields)
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