@sap/cds 5.8.3 → 5.9.1

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 (246) hide show
  1. package/CHANGELOG.md +193 -77
  2. package/app/fiori/preview.js +16 -11
  3. package/app/fiori/routes.js +15 -8
  4. package/app/index.js +1 -1
  5. package/bin/build/buildTaskFactory.js +3 -3
  6. package/bin/build/buildTaskProviderFactory.js +1 -1
  7. package/bin/build/constants.js +1 -1
  8. package/bin/build/provider/buildTaskHandlerEdmx.js +12 -7
  9. package/bin/build/provider/buildTaskHandlerInternal.js +1 -1
  10. package/bin/build/provider/buildTaskProviderInternal.js +8 -2
  11. package/bin/build/provider/hana/2migration.js +27 -24
  12. package/bin/build/provider/hana/index.js +17 -18
  13. package/bin/build/provider/hana/migrationtable.js +9 -10
  14. package/bin/build/provider/java-cf/index.js +4 -5
  15. package/bin/build/provider/node-cf/index.js +99 -6
  16. package/bin/cds.js +17 -18
  17. package/bin/deploy/to-hana/cfUtil.js +16 -19
  18. package/bin/deploy/to-hana/hana.js +7 -24
  19. package/bin/deploy/to-hana/hdiDeployUtil.js +8 -4
  20. package/bin/mtx/in-cds.js +2 -2
  21. package/bin/serve.js +10 -3
  22. package/bin/utils/modules.js +7 -0
  23. package/bin/version.js +56 -3
  24. package/lib/compile/cdsc.js +7 -2
  25. package/lib/compile/etc/_localized.js +36 -25
  26. package/lib/compile/etc/csv.js +8 -8
  27. package/lib/compile/for/drafts.js +9 -0
  28. package/lib/compile/for/java.js +16 -0
  29. package/lib/compile/for/nodejs.js +12 -0
  30. package/lib/compile/index.js +3 -0
  31. package/lib/compile/minify.js +16 -2
  32. package/lib/compile/parse.js +2 -2
  33. package/lib/compile/resolve.js +35 -18
  34. package/lib/compile/to/json.js +3 -1
  35. package/lib/compile/to/sql.js +2 -2
  36. package/lib/compile/to/srvinfo.js +4 -2
  37. package/lib/connect/index.js +1 -1
  38. package/lib/core/entities.js +15 -14
  39. package/lib/core/index.js +39 -36
  40. package/lib/core/reflect.js +4 -2
  41. package/lib/deploy.js +114 -127
  42. package/lib/env/defaults.js +1 -0
  43. package/lib/env/index.js +165 -165
  44. package/lib/env/presets.js +1 -0
  45. package/lib/env/requires.js +120 -49
  46. package/lib/index.js +1 -0
  47. package/lib/log/format/kibana.js +2 -2
  48. package/lib/ql/SELECT.js +10 -0
  49. package/lib/ql/parse.js +1 -0
  50. package/lib/req/cds-context.js +4 -1
  51. package/lib/req/context.js +50 -56
  52. package/lib/req/event.js +1 -6
  53. package/lib/req/locale.js +6 -5
  54. package/lib/req/request.js +2 -0
  55. package/lib/req/user.js +7 -5
  56. package/lib/serve/Service-api.js +10 -7
  57. package/lib/serve/Service-dispatch.js +9 -11
  58. package/lib/serve/Service-methods.js +30 -41
  59. package/lib/serve/Transaction.js +10 -7
  60. package/lib/serve/adapters.js +7 -5
  61. package/lib/serve/index.js +24 -12
  62. package/lib/utils/data.js +1 -1
  63. package/lib/utils/index.js +27 -30
  64. package/lib/utils/resources/index.js +101 -0
  65. package/lib/utils/resources/tar.js +71 -0
  66. package/lib/utils/resources/utils.js +11 -0
  67. package/libx/_runtime/audit/Service.js +36 -39
  68. package/libx/_runtime/audit/generic/personal/access.js +3 -4
  69. package/libx/_runtime/audit/generic/personal/modification.js +3 -4
  70. package/libx/_runtime/audit/utils/v2.js +1 -2
  71. package/libx/_runtime/auth/index.js +126 -84
  72. package/libx/_runtime/auth/strategies/JWT.js +12 -19
  73. package/libx/_runtime/auth/strategies/dummy.js +1 -5
  74. package/libx/_runtime/auth/strategies/dwc.js +11 -9
  75. package/libx/_runtime/auth/strategies/mock.js +0 -4
  76. package/libx/_runtime/auth/strategies/{utils/xssec.js → xssecUtils.js} +7 -4
  77. package/libx/_runtime/auth/strategies/xsuaa.js +12 -19
  78. package/libx/_runtime/auth/utils.js +22 -1
  79. package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +104 -98
  80. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +2 -2
  81. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +1 -1
  82. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -1
  83. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/language.js +2 -8
  84. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +4 -29
  85. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +2 -1
  86. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +3 -2
  87. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +2 -2
  88. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +4 -6
  89. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +24 -21
  90. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +8 -2
  91. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/DeserializerFactory.js +2 -0
  92. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/DispatcherCommand.js +2 -6
  93. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -12
  94. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +33 -9
  95. package/libx/_runtime/cds-services/adapter/odata-v4/utils/dispatcherUtils.js +51 -0
  96. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +2 -2
  97. package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +10 -3
  98. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +9 -11
  99. package/libx/_runtime/cds-services/adapter/rest/RestRequest.js +6 -3
  100. package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +4 -2
  101. package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/utils.js +1 -1
  102. package/libx/_runtime/cds-services/adapter/rest/utils/binary.js +1 -1
  103. package/libx/_runtime/cds-services/adapter/rest/utils/key-value-utils.js +2 -3
  104. package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +6 -4
  105. package/libx/_runtime/cds-services/adapter/rest/utils/result.js +1 -0
  106. package/libx/_runtime/cds-services/adapter/rest/utils/validation-checks.js +8 -5
  107. package/libx/_runtime/cds-services/services/Service.js +40 -0
  108. package/libx/_runtime/cds-services/services/utils/columns.js +4 -3
  109. package/libx/_runtime/cds-services/services/utils/compareJson.js +4 -4
  110. package/libx/_runtime/cds-services/services/utils/differ.js +3 -3
  111. package/libx/_runtime/cds-services/services/utils/handlerUtils.js +4 -4
  112. package/libx/_runtime/cds-services/services/utils/restrictions.js +78 -0
  113. package/libx/_runtime/cds-services/util/assert.js +20 -14
  114. package/libx/_runtime/cds.js +9 -1
  115. package/libx/_runtime/common/aspects/any.js +5 -0
  116. package/libx/_runtime/common/aspects/entity.js +25 -7
  117. package/libx/_runtime/common/aspects/utils.js +2 -2
  118. package/libx/_runtime/common/composition/data.js +6 -0
  119. package/libx/_runtime/common/composition/insert.js +3 -2
  120. package/libx/_runtime/common/composition/tree.js +4 -10
  121. package/libx/_runtime/common/composition/update.js +4 -4
  122. package/libx/_runtime/common/constants/draft.js +29 -26
  123. package/libx/_runtime/common/error/constants.js +2 -2
  124. package/libx/_runtime/common/error/frontend.js +7 -15
  125. package/libx/_runtime/common/generic/auth/capabilities.js +59 -0
  126. package/libx/_runtime/common/generic/auth/constants.js +20 -0
  127. package/libx/_runtime/common/generic/auth/expand.js +54 -0
  128. package/libx/_runtime/common/generic/auth/index.js +32 -0
  129. package/libx/_runtime/common/generic/auth/insertOnly.js +15 -0
  130. package/libx/_runtime/common/generic/auth/readOnly.js +26 -0
  131. package/libx/_runtime/common/generic/auth/requires.js +34 -0
  132. package/libx/_runtime/common/generic/auth/restrict.js +296 -0
  133. package/libx/_runtime/common/generic/auth/utils.js +213 -0
  134. package/libx/_runtime/common/generic/crud.js +14 -10
  135. package/libx/_runtime/common/generic/etag.js +1 -1
  136. package/libx/_runtime/common/generic/input.js +35 -35
  137. package/libx/_runtime/common/generic/sorting.js +2 -3
  138. package/libx/_runtime/common/generic/temporal.js +2 -2
  139. package/libx/_runtime/common/i18n/messages.properties +1 -1
  140. package/libx/_runtime/common/toggles/handler.js +21 -0
  141. package/libx/_runtime/common/utils/copy.js +10 -1
  142. package/libx/_runtime/common/utils/cqn2cqn4sql.js +100 -29
  143. package/libx/_runtime/common/utils/csn.js +63 -1
  144. package/libx/_runtime/common/utils/dollar.js +10 -1
  145. package/libx/_runtime/common/utils/draft.js +46 -7
  146. package/libx/_runtime/common/utils/entityFromCqn.js +13 -9
  147. package/libx/_runtime/common/utils/extensibilityUtils.js +18 -0
  148. package/libx/_runtime/common/utils/foreignKeyPropagations.js +88 -104
  149. package/libx/_runtime/common/utils/generateOnCond.js +5 -2
  150. package/libx/_runtime/common/utils/quotingStyles.js +2 -0
  151. package/libx/_runtime/common/utils/resolveStructured.js +25 -9
  152. package/libx/_runtime/common/utils/resolveView.js +4 -1
  153. package/libx/_runtime/common/utils/rewriteAsterisks.js +3 -16
  154. package/libx/_runtime/common/utils/structured.js +33 -37
  155. package/libx/_runtime/common/utils/template.js +17 -8
  156. package/libx/_runtime/common/utils/templateProcessor.js +28 -28
  157. package/libx/_runtime/db/data-conversion/post-processing.js +118 -412
  158. package/libx/_runtime/db/expand/expandCQNToJoin.js +45 -41
  159. package/libx/_runtime/db/expand/rawToExpanded.js +29 -8
  160. package/libx/_runtime/db/generic/index.js +1 -3
  161. package/libx/_runtime/db/generic/input.js +5 -10
  162. package/libx/_runtime/db/generic/rewrite.js +5 -2
  163. package/libx/_runtime/db/generic/structured.js +2 -2
  164. package/libx/_runtime/db/query/delete.js +2 -2
  165. package/libx/_runtime/db/query/insert.js +1 -1
  166. package/libx/_runtime/db/query/update.js +9 -14
  167. package/libx/_runtime/db/sql-builder/CreateBuilder.js +4 -3
  168. package/libx/_runtime/db/sql-builder/FunctionBuilder.js +8 -8
  169. package/libx/_runtime/db/sql-builder/InsertBuilder.js +14 -1
  170. package/libx/_runtime/db/sql-builder/SelectBuilder.js +3 -2
  171. package/libx/_runtime/db/sql-builder/dataTypes.js +3 -3
  172. package/libx/_runtime/db/utils/columns.js +3 -3
  173. package/libx/_runtime/db/utils/normalizeTimeData.js +2 -2
  174. package/libx/_runtime/db/utils/propagateForeignKeys.js +6 -2
  175. package/libx/_runtime/extensibility/mps/index.js +5 -0
  176. package/libx/_runtime/extensibility/mps/service.js +111 -0
  177. package/libx/_runtime/extensibility/mps/tar.js +42 -0
  178. package/libx/_runtime/extensibility/mps/utils.js +11 -0
  179. package/libx/_runtime/{fiori → extensibility}/uiflex/handler/transformREAD.js +0 -0
  180. package/libx/_runtime/{fiori → extensibility}/uiflex/handler/transformRESULT.js +17 -5
  181. package/libx/_runtime/{fiori → extensibility}/uiflex/handler/transformWRITE.js +1 -0
  182. package/libx/_runtime/extensibility/uiflex/index.js +54 -0
  183. package/libx/_runtime/extensibility/uiflex/service.js +276 -0
  184. package/libx/_runtime/{fiori → extensibility}/uiflex/utils.js +22 -7
  185. package/libx/_runtime/fiori/generic/activate.js +2 -2
  186. package/libx/_runtime/fiori/generic/before.js +4 -4
  187. package/libx/_runtime/fiori/generic/new.js +3 -3
  188. package/libx/_runtime/fiori/generic/patch.js +1 -1
  189. package/libx/_runtime/fiori/generic/read.js +58 -66
  190. package/libx/_runtime/fiori/generic/readOverDraft.js +71 -16
  191. package/libx/_runtime/fiori/utils/handler.js +6 -13
  192. package/libx/_runtime/fiori/utils/where.js +6 -5
  193. package/libx/_runtime/hana/Service.js +4 -10
  194. package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +1 -1
  195. package/libx/_runtime/hana/driver.js +2 -2
  196. package/libx/_runtime/hana/execute.js +27 -74
  197. package/libx/_runtime/hana/pool.js +1 -1
  198. package/libx/_runtime/hana/streaming.js +2 -1
  199. package/libx/_runtime/index.js +6 -6
  200. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +5 -21
  201. package/libx/_runtime/messaging/Outbox.js +2 -2
  202. package/libx/_runtime/messaging/common-utils/AMQPClient.js +4 -14
  203. package/libx/_runtime/messaging/common-utils/connections.js +5 -7
  204. package/libx/_runtime/messaging/common-utils/normalizeIncomingMessage.js +30 -0
  205. package/libx/_runtime/messaging/enterprise-messaging-shared.js +2 -1
  206. package/libx/_runtime/messaging/enterprise-messaging-utils/EMManagement.js +36 -30
  207. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +19 -12
  208. package/libx/_runtime/messaging/enterprise-messaging.js +8 -8
  209. package/libx/_runtime/messaging/file-based.js +5 -5
  210. package/libx/_runtime/messaging/message-queuing.js +14 -12
  211. package/libx/_runtime/messaging/outbox/utils.js +18 -19
  212. package/libx/_runtime/messaging/redis-messaging.js +91 -0
  213. package/libx/_runtime/messaging/service.js +8 -6
  214. package/libx/_runtime/remote/Service.js +44 -8
  215. package/libx/_runtime/remote/utils/client.js +24 -19
  216. package/libx/_runtime/remote/utils/data.js +11 -11
  217. package/libx/_runtime/sqlite/Service.js +6 -9
  218. package/libx/_runtime/sqlite/customBuilder/CustomFunctionBuilder.js +5 -2
  219. package/libx/_runtime/types/api.js +10 -2
  220. package/libx/common/utils/ucsn.js +109 -0
  221. package/libx/gql/resolvers/crud/update.js +5 -0
  222. package/libx/gql/resolvers/parse/ast2cqn/columns.js +3 -1
  223. package/libx/gql/schema/typeDefMap.js +2 -2
  224. package/libx/odata/afterburner.js +110 -16
  225. package/libx/odata/cqn2odata.js +24 -27
  226. package/libx/odata/grammar.pegjs +9 -1
  227. package/libx/odata/parseToCqn.js +39 -0
  228. package/libx/odata/parser.js +1 -1
  229. package/libx/rest/RestAdapter.js +9 -1
  230. package/libx/rest/middleware/input.js +54 -0
  231. package/libx/rest/middleware/operation.js +14 -1
  232. package/libx/rest/middleware/parse.js +11 -7
  233. package/package.json +2 -2
  234. package/server.js +34 -19
  235. package/srv/audit-log.cds +2 -2
  236. package/srv/flex.cds +8 -2
  237. package/srv/flex.js +1 -1
  238. package/srv/mps.cds +23 -0
  239. package/srv/mps.js +1 -0
  240. package/libx/_runtime/auth/strategies/utils/uaa.js +0 -21
  241. package/libx/_runtime/common/generic/auth.js +0 -874
  242. package/libx/_runtime/common/toggles/alpha.js +0 -43
  243. package/libx/_runtime/db/generic/arrayed.js +0 -33
  244. package/libx/_runtime/fiori/uiflex/index.js +0 -35
  245. package/libx/_runtime/fiori/uiflex/service.js +0 -150
  246. package/libx/rest/utils/data.js +0 -60
@@ -45,14 +45,16 @@ const assertError = (code, element, value, key, pathSegments = []) => {
45
45
  code = code.code
46
46
  }
47
47
  const { name, type, precision, scale } = element
48
- const path = pathSegments.join('/') || name || key
48
+ pathSegments = pathSegments.slice(0)
49
+ pathSegments.push({ key: name || key })
50
+ const path = pathSegments.map(({ key, url }) => url || key).join('/')
49
51
 
50
52
  const e = new Error()
51
53
  const error = Object.assign(e, getEntry({ code, message: code, target: path, args: args || [name || key] }))
52
54
  Object.assign(error, {
53
55
  entity: element.parent && element.parent.name,
54
56
  element: name, // > REVISIT: when is error.element needed?
55
- type: element.items ? element.items.type : type,
57
+ type: element.items ? element.items._type : type,
56
58
  value
57
59
  })
58
60
 
@@ -154,7 +156,7 @@ const checkComplexType = ([key, value], elements, ignoreNonModelledData) => {
154
156
  for (const objKey in elements) {
155
157
  if (objKey.startsWith(`${key}_`)) {
156
158
  const element = elements[objKey]
157
- const check = CDS_TYPE_CHECKS[element.type]
159
+ const check = CDS_TYPE_CHECKS[element._type]
158
160
  found = true
159
161
 
160
162
  const nestedData = value[objKey.substring(key.length + 1)]
@@ -168,7 +170,7 @@ const checkComplexType = ([key, value], elements, ignoreNonModelledData) => {
168
170
  return found || ignoreNonModelledData
169
171
  }
170
172
 
171
- const _checkStaticElementByKey = (definition, key, value, result, ignoreNonModelledData) => {
173
+ const _checkStaticElementByKey = (definition, key, value, result = [], ignoreNonModelledData = true) => {
172
174
  const elementsOrParameters = definition.elements || definition.params
173
175
  if (!elementsOrParameters) return result
174
176
  const elementOrParameter = elementsOrParameters[key]
@@ -182,16 +184,16 @@ const _checkStaticElementByKey = (definition, key, value, result, ignoreNonModel
182
184
  }
183
185
 
184
186
  let check
185
- if (elementOrParameter.type === 'cds.UUID' && definition.name === 'ProvisioningService.tenant') {
187
+ if (elementOrParameter.isUUID && definition.name === 'ProvisioningService.tenant') {
186
188
  // > old SCP accounts don't have UUID ids
187
189
  check = CDS_TYPE_CHECKS['cds.String']
188
190
  } else {
189
- check = CDS_TYPE_CHECKS[elementOrParameter.type]
191
+ check = CDS_TYPE_CHECKS[elementOrParameter._type]
190
192
  }
191
193
 
192
194
  if (check && !check(value, elementOrParameter)) {
193
195
  // code, entity, element, value
194
- const args = [typeof value === 'string' ? '"' + value + '"' : value, elementOrParameter.type]
196
+ const args = [typeof value === 'string' ? '"' + value + '"' : value, elementOrParameter._type]
195
197
  result.push(assertError({ code: ASSERT_DATA_TYPE, args }, elementOrParameter, value, key))
196
198
  }
197
199
 
@@ -274,18 +276,18 @@ const checkIfAssocDeep = (element, value, req) => {
274
276
  /**
275
277
  * @param {import('../../types/api').InputConstraints} constraints
276
278
  */
277
- const checkInputConstraints = ({ element, value, errors, key, pathSegments, event }) => {
279
+ const checkInputConstraints = ({ element, value, errors, key, path, event }) => {
278
280
  if (!element) return errors
279
281
 
280
- _checkMandatoryElement(element, value, errors, key, pathSegments)
282
+ _checkMandatoryElement(element, value, errors, key, path)
281
283
 
282
284
  if (value == null) return errors
283
285
 
284
- _checkEnumElement(element, value, errors, key, pathSegments)
286
+ _checkEnumElement(element, value, errors, key, path)
285
287
 
286
- _checkRangeElement(element, value, errors, key, pathSegments)
288
+ _checkRangeElement(element, value, errors, key, path)
287
289
 
288
- _checkFormatElement(element, value, errors, key, pathSegments)
290
+ _checkFormatElement(element, value, errors, key, path)
289
291
 
290
292
  return errors
291
293
  }
@@ -310,13 +312,15 @@ const checkKeys = (entity, data) => {
310
312
  const entityKeys = Object.keys(entity.keys)
311
313
  return data.reduce((result, row) => {
312
314
  for (const key of entityKeys) {
313
- if (row[key] === undefined && entity.elements[key].type !== 'cds.Association')
315
+ if (row[key] === undefined && !entity.elements[key].isAssociation)
314
316
  result.push(assertError(ASSERT_NOT_NULL, entity.elements[key]))
315
317
  }
316
318
  return result
317
319
  }, [])
318
320
  }
319
321
 
322
+ const assertNotNullError = element => assertError(ASSERT_NOT_NULL, element)
323
+
320
324
  module.exports = {
321
325
  CDS_TYPE_CHECKS,
322
326
  checkComplexType,
@@ -324,5 +328,7 @@ module.exports = {
324
328
  checkInputConstraints,
325
329
  checkKeys,
326
330
  assertError,
327
- checkIfAssocDeep
331
+ checkIfAssocDeep,
332
+ checkStaticElementByKey: _checkStaticElementByKey,
333
+ assertNotNullError
328
334
  }
@@ -14,7 +14,14 @@ cds.extend(entity).with(require('./common/aspects/entity'))
14
14
  * mtx?
15
15
  */
16
16
  Object.defineProperty(cds, '_mtxEnabled', {
17
- get: () => cds.mtx && typeof cds.mtx.in === 'function',
17
+ get: () => cds.mtx && typeof cds.mtx.in === 'function' && !cds.env.features.streamlined_mtx,
18
+ configurable: true
19
+ })
20
+ Object.defineProperty(cds, '_mpsEnabled', {
21
+ get: () =>
22
+ cds.requires.extensibility ||
23
+ cds.requires.toggles ||
24
+ (cds.requires.multitenancy && cds.env.features.streamlined_mtx),
18
25
  configurable: true
19
26
  })
20
27
 
@@ -23,6 +30,7 @@ Object.defineProperty(cds, '_mtxEnabled', {
23
30
  */
24
31
  // referential integrity
25
32
  // REVISIT: why is _db_foreign_key_constraints necessary?
33
+ // REVISIT: Do not access cds.env too early!
26
34
  Object.defineProperty(cds.env.features, '_db_foreign_key_constraints', {
27
35
  get: () => {
28
36
  const { assert_integrity: ai, assert_integrity_type: ait } = cds.env.features
@@ -1,4 +1,5 @@
1
1
  const { getRelations, isMandatory, isReadOnly } = require('./utils')
2
+ const { foreignKey4 } = require('../../common/utils/foreignKeyPropagations')
2
3
 
3
4
  // NOTE: Please only add things which are relevant to _any_ type,
4
5
  // use specialized types otherwise (entity, Association, ...).
@@ -19,4 +20,8 @@ module.exports = class {
19
20
  get _relations() {
20
21
  return this.own('__relations') || this.set('__relations', getRelations(this))
21
22
  }
23
+
24
+ get _foreignKey4() {
25
+ return this.own('__foreignKey4') || this.set('__foreignKey4', foreignKey4(this))
26
+ }
22
27
  }
@@ -7,13 +7,23 @@ const { getETag, hasPersonalData, hasSensitiveData } = require('./utils')
7
7
 
8
8
  let getSearchableColumns
9
9
 
10
+ const _flat2struct = (def, prefix = '') => {
11
+ const map = {}
12
+ for (const ele in def.elements) {
13
+ if (def.elements[ele].elements)
14
+ Object.assign(map, _flat2struct(def.elements[ele], prefix ? prefix + '$$$' + ele : ele))
15
+ else if (prefix) map[prefix.replace(/\$\$\$/g, '_') + '_' + ele] = [...prefix.split('$$$'), ele]
16
+ }
17
+ return map
18
+ }
19
+
10
20
  module.exports = class {
11
21
  get _isSingleton() {
12
22
  return (
13
23
  this.own('__isSingleton') ||
14
24
  this.set(
15
25
  '__isSingleton',
16
- this['@odata.singleton'] || (this['@odata.singleton.nullable'] && this['@odata.singleton'] !== false)
26
+ !!(this['@odata.singleton'] || (this['@odata.singleton.nullable'] && this['@odata.singleton'] !== false))
17
27
  )
18
28
  )
19
29
  }
@@ -23,7 +33,7 @@ module.exports = class {
23
33
  this.own('__hasPersistenceSkip') ||
24
34
  this.set(
25
35
  '__hasPersistenceSkip',
26
- this.own('@cds.persistence.skip') && this.own('@cds.persistence.skip') !== 'if-unused'
36
+ !!(this.own('@cds.persistence.skip') && this.own('@cds.persistence.skip') !== 'if-unused')
27
37
  )
28
38
  )
29
39
  }
@@ -33,9 +43,11 @@ module.exports = class {
33
43
  this.own('__isDraftEnabled') ||
34
44
  this.set(
35
45
  '__isDraftEnabled',
36
- (this.associations && this.associations.DraftAdministrativeData) ||
46
+ !!(
47
+ (this.associations && this.associations.DraftAdministrativeData) ||
37
48
  this.name.match(/\.DraftAdministrativeData$/) ||
38
49
  (this.own('@odata.draft.enabled') && this.own('@Common.DraftRoot.ActivationAction'))
50
+ )
39
51
  )
40
52
  )
41
53
  }
@@ -66,25 +78,31 @@ module.exports = class {
66
78
  get _auditCreate() {
67
79
  return (
68
80
  this.own('__auditCreate') ||
69
- this.set('__auditCreate', this._hasPersonalData && this['@AuditLog.Operation.Insert'])
81
+ this.set('__auditCreate', !!(this._hasPersonalData && this['@AuditLog.Operation.Insert']))
70
82
  )
71
83
  }
72
84
 
73
85
  get _auditRead() {
74
- return this.own('__auditRead') || this.set('__auditRead', this._hasPersonalData && this['@AuditLog.Operation.Read'])
86
+ return (
87
+ this.own('__auditRead') || this.set('__auditRead', !!(this._hasPersonalData && this['@AuditLog.Operation.Read']))
88
+ )
75
89
  }
76
90
 
77
91
  get _auditUpdate() {
78
92
  return (
79
93
  this.own('__auditUpdate') ||
80
- this.set('__auditUpdate', this._hasPersonalData && this['@AuditLog.Operation.Update'])
94
+ this.set('__auditUpdate', !!(this._hasPersonalData && this['@AuditLog.Operation.Update']))
81
95
  )
82
96
  }
83
97
 
84
98
  get _auditDelete() {
85
99
  return (
86
100
  this.own('__auditDelete') ||
87
- this.set('__auditDelete', this._hasPersonalData && this['@AuditLog.Operation.Delete'])
101
+ this.set('__auditDelete', !!(this._hasPersonalData && this['@AuditLog.Operation.Delete']))
88
102
  )
89
103
  }
104
+
105
+ get _flat2struct() {
106
+ return this.own('_flat2struct') || this.set('_flat2struct', _flat2struct(this))
107
+ }
90
108
  }
@@ -51,7 +51,7 @@ const hasPersonalData = entity => {
51
51
  }
52
52
  }
53
53
  }
54
- return val
54
+ return !!val
55
55
  }
56
56
 
57
57
  const hasSensitiveData = entity => {
@@ -64,7 +64,7 @@ const hasSensitiveData = entity => {
64
64
  }
65
65
  }
66
66
  }
67
- return val
67
+ return !!val
68
68
  }
69
69
 
70
70
  const _exposeRelation = relation => Object.defineProperty({}, '_', { get: () => relation })
@@ -236,6 +236,11 @@ const _selectDeepUpdateData = async args => {
236
236
  return _mergeResults(result, selectData || [], root, model, compositionTree, entityName)
237
237
  }
238
238
 
239
+ // if a view has an orderBy with renamed field, we need to resolve it
240
+ const _resolveOrderBy = (orderBy, transitions) => {
241
+ if (orderBy && transitions.length > 0) orderBy.map(el => (el.ref[0] = transitions[0].mapping.get(el.ref[0]).ref[0]))
242
+ }
243
+
239
244
  /*
240
245
  * exports
241
246
  */
@@ -255,6 +260,7 @@ const selectDeepUpdateData = (service, model, req, selectAllColumns = false) =>
255
260
  const entityName = ensureNoDraftsSuffix(from)
256
261
  const draft = entityName !== from
257
262
  const orderBy = req && req.target && req.target.query && req.target.query.SELECT && req.target.query.SELECT.orderBy
263
+ _resolveOrderBy(orderBy, sqlQuery.UPDATE._transitions)
258
264
  const data = Object.assign({}, sqlQuery.UPDATE.data || {}, query.UPDATE.with || {})
259
265
  const compositionTree = getCompositionTree({
260
266
  definitions: model.definitions,
@@ -55,7 +55,8 @@ const _addSubDeepInsertCQN = (model, compositionTree, data, cqns, draft) => {
55
55
 
56
56
  const _entityFromINSERT = (model, INSERT) => {
57
57
  if (INSERT && INSERT.into) {
58
- const entityName = ensureNoDraftsSuffix(INSERT.into.name || INSERT.into)
58
+ const into = (INSERT.into.ref && INSERT.into.ref[0]) || INSERT.into.name || INSERT.into
59
+ const entityName = ensureNoDraftsSuffix(into)
59
60
  return model.definitions[entityName]
60
61
  }
61
62
  }
@@ -75,7 +76,7 @@ const hasDeepInsert = (model, cqn) => {
75
76
  }
76
77
 
77
78
  const getDeepInsertCQNs = (model, cqn) => {
78
- const into = cqn.INSERT.into.name || cqn.INSERT.into
79
+ const into = (cqn.INSERT.into.ref && cqn.INSERT.into.ref[0]) || cqn.INSERT.into.name || cqn.INSERT.into
79
80
  const entityName = ensureNoDraftsSuffix(into)
80
81
  const draft = entityName !== into
81
82
  const dataEntries = cqn.INSERT.entries ? deepCopyArray(cqn.INSERT.entries) : []
@@ -5,6 +5,7 @@ const { isRootEntity } = require('../utils/csn')
5
5
  const { getTransition, getDBTable } = require('../utils/resolveView')
6
6
 
7
7
  const getError = require('../../common/error')
8
+ const { prefixForStruct } = require('../../common/utils/csn')
8
9
 
9
10
  /*
10
11
  * own utils
@@ -17,19 +18,12 @@ const _foreignKeysToLinks = (element, inverse) =>
17
18
  childElement: e.parentElement,
18
19
  parentElement: e.childElement,
19
20
  childFieldValue: e.parentFieldValue,
20
- parentFieldValue: e.childFieldValue,
21
- prefix: e.prefix
21
+ parentFieldValue: e.childFieldValue
22
22
  }
23
23
  : e
24
24
  const link = {}
25
- if (e.parentElement)
26
- link.entityKey =
27
- e.prefix && !e.parentElement.name.includes(e.prefix)
28
- ? `${e.prefix}_${e.parentElement.name}`
29
- : e.parentElement.name
30
- if (e.childElement)
31
- link.targetKey =
32
- e.prefix && !e.childElement.name.includes(e.prefix) ? `${e.prefix}_${e.childElement.name}` : e.childElement.name
25
+ if (e.parentElement) link.entityKey = prefixForStruct(e.parentElement) + e.parentElement.name
26
+ if (e.childElement) link.targetKey = prefixForStruct(e.childElement) + e.childElement.name
33
27
  if (e.parentFieldValue !== undefined) link.entityVal = e.parentFieldValue
34
28
  if (e.childFieldValue !== undefined) link.targetVal = e.childFieldValue
35
29
  return link
@@ -58,11 +58,11 @@ const _unwrapVal = obj => {
58
58
 
59
59
  function _fillLinkFromStructuredData(entity, entry) {
60
60
  for (const elementName in entity.elements) {
61
- const foreignKey4 = entity.elements[elementName]['@odata.foreignKey4']
62
- if (foreignKey4 && entry[foreignKey4]) {
61
+ const _foreignKey4 = entity.elements._foreignKey4
62
+ if (_foreignKey4 && entry[_foreignKey4]) {
63
63
  const foreignKey = entity.elements[elementName].name
64
64
  const childKey = foreignKey.split('_')[1]
65
- const val = _unwrapVal(entry[foreignKey4])[childKey]
65
+ const val = _unwrapVal(entry[_foreignKey4])[childKey]
66
66
  if (val !== undefined) entry[foreignKey] = val
67
67
  }
68
68
  }
@@ -89,7 +89,7 @@ const _diffData = (newData, oldData, entity, newEntry, oldEntry, model) => {
89
89
  }
90
90
 
91
91
  // comp2one removed?
92
- const fk = entity.elements[key] && entity.elements[key]['@odata.foreignKey4']
92
+ const fk = entity.elements[key] && entity.elements[key]._foreignKey4
93
93
  if (fk && newVal === undefined && oldVal !== undefined) {
94
94
  const nav = entity.elements[fk]
95
95
  // REVISIT: why check @cds.persistence.skip needed? bad tests?
@@ -1,30 +1,33 @@
1
+ const DRAFT_COLUMNS = ['IsActiveEntity', 'HasActiveEntity', 'HasDraftEntity', 'DraftAdministrativeData_DraftUUID']
2
+ const DRAFT_COLUMNS_UNION = [
3
+ 'IsActiveEntity',
4
+ 'HasActiveEntity',
5
+ 'HasDraftEntity',
6
+ 'DraftAdministrativeData_DraftUUID',
7
+ 'SiblingEntity',
8
+ 'DraftAdministrativeData'
9
+ ]
10
+ const DRAFT_COLUMNS_ADMIN = [
11
+ 'DraftUUID',
12
+ 'CreatedByUser',
13
+ 'InProcessByUser',
14
+ 'CreationDateTime',
15
+ 'LastChangeDateTime',
16
+ 'LastChangedByUser',
17
+ 'DraftIsProcessedByMe',
18
+ 'DraftIsCreatedByMe'
19
+ ]
20
+
21
+ const objectify = arr =>
22
+ arr.reduce((acc, cur) => {
23
+ acc[cur] = 1
24
+ return acc
25
+ }, {})
26
+
1
27
  module.exports = {
2
- DRAFT_COLUMNS: ['IsActiveEntity', 'HasActiveEntity', 'HasDraftEntity', 'DraftAdministrativeData_DraftUUID'],
3
- DRAFT_COLUMNS_MAP: {
4
- IsActiveEntity: true,
5
- HasActiveEntity: true,
6
- HasDraftEntity: true,
7
- DraftAdministrativeData_DraftUUID: true,
8
- DraftUUID: true
9
- },
10
- DRAFT_COLUMNS_UNION: [
11
- 'IsActiveEntity',
12
- 'HasActiveEntity',
13
- 'HasDraftEntity',
14
- 'DraftAdministrativeData_DraftUUID',
15
- 'SiblingEntity',
16
- 'DraftAdministrativeData'
17
- ],
18
- DRAFT_COLUMNS_ADMIN: [
19
- 'DraftUUID',
20
- 'CreatedByUser',
21
- 'InProcessByUser',
22
- 'CreationDateTime',
23
- 'LastChangeDateTime',
24
- 'LastChangedByUser',
25
- 'DraftIsProcessedByMe',
26
- 'DraftIsCreatedByMe'
27
- ],
28
+ DRAFT_COLUMNS_MAP: objectify(DRAFT_COLUMNS),
29
+ DRAFT_COLUMNS_UNION_MAP: objectify(DRAFT_COLUMNS_UNION),
30
+ DRAFT_COLUMNS_ADMIN_MAP: objectify(DRAFT_COLUMNS_ADMIN),
28
31
  DRAFT_COLUMNS_CQN: [
29
32
  { ref: ['IsActiveEntity'], cast: { type: 'cds.Boolean' } },
30
33
  { ref: ['HasActiveEntity'], cast: { type: 'cds.Boolean' } },
@@ -1,6 +1,6 @@
1
1
  module.exports = {
2
- ALLOWED_PROPERTIES: ['code', 'message', 'target', 'details', 'innererror'],
3
- ADDITIONAL_MSG_PROPERTIES: ['numericSeverity', 'longtextUrl', 'transition'],
2
+ ALLOWED_PROPERTIES_MAP: { code: 1, message: 1, target: 1, details: 1, innererror: 1 },
3
+ ADDITIONAL_MSG_PROPERTIES_MAP: { numericSeverity: 1, longtextUrl: 1, transition: 1 },
4
4
  /*
5
5
  * severities:
6
6
  * 1: success
@@ -6,8 +6,6 @@
6
6
  * Error responses MAY contain annotations in any of its JSON objects.
7
7
  */
8
8
 
9
- const cds = require('../../cds')
10
-
11
9
  let _i18n
12
10
  const i18n = (...args) => {
13
11
  if (!_i18n) _i18n = require('../i18n')
@@ -15,12 +13,13 @@ const i18n = (...args) => {
15
13
  }
16
14
 
17
15
  const {
18
- ALLOWED_PROPERTIES,
19
- ADDITIONAL_MSG_PROPERTIES,
16
+ ALLOWED_PROPERTIES_MAP,
17
+ ADDITIONAL_MSG_PROPERTIES_MAP,
20
18
  DEFAULT_SEVERITY,
21
19
  MIN_SEVERITY,
22
20
  MAX_SEVERITY
23
21
  } = require('./constants')
22
+ const ADDITIONAL_MSG_PROPERTIES = Object.keys(ADDITIONAL_MSG_PROPERTIES_MAP)
24
23
 
25
24
  const SKIP_SANITIZATION = '@cds.skip_sanitization'
26
25
 
@@ -32,7 +31,7 @@ const _getFiltered = err => {
32
31
  .forEach(k => {
33
32
  // REVISIT: do not remove innererror with cds^6
34
33
  if (k === 'innererror' && process.env.NODE_ENV === 'production' && !err[SKIP_SANITIZATION]) return
35
- if (ALLOWED_PROPERTIES.includes(k) || k.startsWith('@')) {
34
+ if (k in ALLOWED_PROPERTIES_MAP || k.startsWith('@')) {
36
35
  error[k] = err[k]
37
36
  } else if (k === 'numericSeverity') {
38
37
  error['@Common.numericSeverity'] = err[k]
@@ -84,10 +83,7 @@ const _isAllowedError = errorCode => {
84
83
  return errorCode >= 300 && errorCode < 505
85
84
  }
86
85
 
87
- const _anonymousUser = req => {
88
- // if no authentication used, we create a new user object in order to get the correct locale
89
- return Object.defineProperty(new cds.User(), '_req', { enumerable: false, value: req })
90
- }
86
+ const localeFrom = require('../../../../lib/req/locale')
91
87
 
92
88
  // - for one unique value, we use it
93
89
  // - if at least one 5xx exists, we use 500
@@ -104,9 +100,7 @@ const _statusCodeFromDetails = details => {
104
100
  }
105
101
 
106
102
  const normalizeError = (err, req) => {
107
- const user = (req && req.user) || _anonymousUser(req)
108
- const locale = user.locale
109
-
103
+ const locale = req.locale || (req.locale = localeFrom(req))
110
104
  const error = _normalize(err, locale)
111
105
 
112
106
  // derive status code from err status OR root code OR matching detail codes
@@ -160,9 +154,7 @@ const _normalizeMessage = (message, locale) => {
160
154
  }
161
155
 
162
156
  const getSapMessages = (messages, req) => {
163
- const user = (req && req.user) || _anonymousUser(req)
164
- const locale = user.locale
165
-
157
+ const locale = req.locale || (req.locale = localeFrom(req))
166
158
  const s = JSON.stringify(messages.map(message => _normalizeMessage(message, locale)))
167
159
  // convert non ascii to unicode
168
160
  return s.replace(/[\u007F-\uFFFF]/g, chr => {
@@ -0,0 +1,59 @@
1
+ const { cqnFrom } = require('./utils')
2
+ const { RESTRICTIONS } = require('./constants')
3
+
4
+ const _isRestricted = (req, capability, capabilityReadByKey) => {
5
+ if (capabilityReadByKey !== undefined && req.query.SELECT.one) {
6
+ return capabilityReadByKey === false
7
+ }
8
+ return capability === false
9
+ }
10
+
11
+ const _isNavigationRestricted = (target, path, annotation, req) => {
12
+ if (!target) return
13
+ const parts = annotation.split('.')
14
+ if (target && Array.isArray(target['@Capabilities.NavigationRestrictions.RestrictedProperties'])) {
15
+ for (const r of target['@Capabilities.NavigationRestrictions.RestrictedProperties']) {
16
+ if (r.NavigationProperty['='] === path && r[parts[0]]) {
17
+ return _isRestricted(
18
+ req,
19
+ r[parts[0]][parts[1]],
20
+ r.ReadRestrictions && r.ReadRestrictions['ReadByKeyRestrictions.Readable']
21
+ )
22
+ }
23
+ }
24
+ }
25
+ }
26
+
27
+ const _localName = entity => entity.name.replace(entity._service.name + '.', '')
28
+
29
+ function handler(req) {
30
+ // TODO: Determine auth-relevant entity
31
+ const annotation = RESTRICTIONS[req.event]
32
+ if (!req.target || !annotation) return
33
+
34
+ const action = annotation.split('.').pop().toUpperCase()
35
+ const from = cqnFrom(req)
36
+ const nav = (from && from.ref && from.ref.map(el => el.id || el)) || []
37
+
38
+ if (nav.length > 1) {
39
+ const path = nav.slice(1).join('.')
40
+ const target = this.model.definitions[nav[0]]
41
+ if (_isNavigationRestricted(target, path, annotation, req)) {
42
+ // REVISIT: rework exception with using target
43
+ const trgt = `${_localName(target)}.${path}`
44
+ req.reject(405, 'ENTITY_IS_NOT_CRUD_VIA_NAVIGATION', [_localName(req.target), action, trgt])
45
+ }
46
+ } else if (
47
+ _isRestricted(
48
+ req,
49
+ req.target['@Capabilities.' + annotation],
50
+ req.target['@Capabilities.' + RESTRICTIONS.READABLE_BY_KEY]
51
+ )
52
+ ) {
53
+ req.reject(405, 'ENTITY_IS_NOT_CRUD', [_localName(req.target), action])
54
+ }
55
+ }
56
+
57
+ handler._initial = true
58
+
59
+ module.exports = handler
@@ -0,0 +1,20 @@
1
+ const MOD_EVENTS = { UPDATE: 1, DELETE: 1, EDIT: 1 }
2
+ const WRITE_EVENTS = Object.assign({ CREATE: 1, NEW: 1, PATCH: 1, CANCEL: 1 }, MOD_EVENTS)
3
+ const CRUD_EVENTS = Object.assign({ READ: 1 }, WRITE_EVENTS)
4
+ const DRAFT_EVENTS = { PATCH: 1, CANCEL: 1, draftActivate: 1, draftPrepare: 1 }
5
+
6
+ const RESTRICTIONS = {
7
+ CREATE: 'InsertRestrictions.Insertable',
8
+ READ: 'ReadRestrictions.Readable',
9
+ READABLE_BY_KEY: 'ReadRestrictions.ReadByKeyRestrictions.Readable',
10
+ UPDATE: 'UpdateRestrictions.Updatable',
11
+ DELETE: 'DeleteRestrictions.Deletable'
12
+ }
13
+
14
+ module.exports = {
15
+ MOD_EVENTS,
16
+ WRITE_EVENTS,
17
+ CRUD_EVENTS,
18
+ DRAFT_EVENTS,
19
+ RESTRICTIONS
20
+ }
@@ -0,0 +1,54 @@
1
+ const cds = require('../../../cds')
2
+
3
+ const { rewriteExpandAsterisk } = require('../../utils/rewriteAsterisks')
4
+
5
+ const { ensureNoDraftsSuffix } = require('../../../fiori/utils/handler')
6
+
7
+ const _getTarget = (ref, target, definitions) => {
8
+ if (cds.env.effective.odata.proxies) {
9
+ const target_ = target.elements[ref[0]]
10
+ if (ref.length === 1) return definitions[ensureNoDraftsSuffix(target_.target)]
11
+ return _getTarget(ref.slice(1), target_, definitions)
12
+ }
13
+ const target_ = target.elements[ref.join('_')]
14
+ return definitions[ensureNoDraftsSuffix(target_.target)]
15
+ }
16
+
17
+ const _getRestrictedExpand = (columns, target, definitions) => {
18
+ if (!columns || !target || columns === '*') return
19
+
20
+ const annotation = target['@Capabilities.ExpandRestrictions.NonExpandableProperties']
21
+ const restrictions = annotation && annotation.map(element => element['='])
22
+
23
+ rewriteExpandAsterisk(columns, target)
24
+
25
+ for (const col of columns) {
26
+ if (col.expand) {
27
+ if (restrictions && restrictions.length !== 0) {
28
+ const ref = col.ref.join('_')
29
+ const ref_ = restrictions.find(element => element.replace(/\./g, '_') === ref)
30
+ if (ref_) return ref_
31
+ }
32
+ // expand: '**' or '*3' is only possible within custom handler, no check needed
33
+ if (typeof col.expand === 'string' && /^\*{1}[\d|*]+/.test(col.expand)) {
34
+ continue
35
+ } else {
36
+ const restricted = _getRestrictedExpand(col.expand, _getTarget(col.ref, target, definitions), definitions)
37
+ if (restricted) return restricted
38
+ }
39
+ }
40
+ }
41
+ }
42
+
43
+ function handler(req) {
44
+ const restricted = _getRestrictedExpand(
45
+ req.query.SELECT && req.query.SELECT.columns,
46
+ req.target,
47
+ this.model.definitions
48
+ )
49
+ if (restricted) req.reject(400, 'EXPAND_IS_RESTRICTED', [restricted])
50
+ }
51
+
52
+ handler._initial = true
53
+
54
+ module.exports = handler
@@ -0,0 +1,32 @@
1
+ const cds = require('../../../cds')
2
+
3
+ const requiresHandler = require('./requires')
4
+ const readOnlyHandler = require('./readOnly')
5
+ const insertOnlyHandler = require('./insertOnly')
6
+ const capabilitiesHandler = require('./capabilities')
7
+ const restrictHandler = require('./restrict')
8
+ const restrictExpandHandler = require('./expand')
9
+
10
+ module.exports = cds.service.impl(function authorization() {
11
+ /*
12
+ * @requires
13
+ */
14
+ this.before('*', requiresHandler)
15
+
16
+ /*
17
+ * access control (cheaper than @restrict -> do first)
18
+ */
19
+ this.before('*', readOnlyHandler)
20
+ this.before('*', insertOnlyHandler)
21
+ this.before('*', capabilitiesHandler)
22
+
23
+ /*
24
+ * @restrict
25
+ */
26
+ this.before('*', restrictHandler)
27
+
28
+ /*
29
+ * expand restrictions
30
+ */
31
+ this.before('READ', '*', restrictExpandHandler)
32
+ })
@@ -0,0 +1,15 @@
1
+ const { getAuthRelevantEntity } = require('./utils')
2
+
3
+ function handler(req) {
4
+ const entity = getAuthRelevantEntity(req, this.model, ['@insertonly'])
5
+ if (!entity || !entity['@insertonly']) return
6
+
7
+ const allowed = entity._isDraftEnabled ? { NEW: 1, PATCH: 1 } : { CREATE: 1 }
8
+ if (!(req.event in allowed)) {
9
+ req.reject(405, 'ENTITY_IS_INSERT_ONLY', [entity.name])
10
+ }
11
+ }
12
+
13
+ handler._initial = true
14
+
15
+ module.exports = handler