@sap/cds 5.8.2 → 5.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (252) hide show
  1. package/CHANGELOG.md +214 -78
  2. package/app/fiori/preview.js +16 -11
  3. package/app/fiori/routes.js +3 -0
  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 +20 -17
  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 +12 -5
  22. package/bin/utils/modules.js +7 -0
  23. package/bin/version.js +56 -3
  24. package/lib/compile/cdsc.js +26 -3
  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/for/odata.js +1 -1
  31. package/lib/compile/index.js +3 -0
  32. package/lib/compile/minify.js +16 -2
  33. package/lib/compile/parse.js +2 -2
  34. package/lib/compile/resolve.js +35 -18
  35. package/lib/compile/to/json.js +3 -1
  36. package/lib/compile/to/sql.js +2 -2
  37. package/lib/compile/to/srvinfo.js +4 -2
  38. package/lib/connect/index.js +1 -1
  39. package/lib/core/entities.js +15 -14
  40. package/lib/core/index.js +39 -36
  41. package/lib/core/reflect.js +4 -2
  42. package/lib/deploy.js +114 -127
  43. package/lib/env/defaults.js +1 -0
  44. package/lib/env/index.js +165 -165
  45. package/lib/env/presets.js +1 -0
  46. package/lib/env/requires.js +120 -49
  47. package/lib/index.js +1 -0
  48. package/lib/log/format/kibana.js +2 -2
  49. package/lib/ql/SELECT.js +10 -0
  50. package/lib/ql/parse.js +1 -0
  51. package/lib/req/cds-context.js +4 -1
  52. package/lib/req/context.js +50 -56
  53. package/lib/req/event.js +1 -6
  54. package/lib/req/locale.js +6 -5
  55. package/lib/req/request.js +2 -0
  56. package/lib/req/user.js +7 -5
  57. package/lib/serve/Service-api.js +10 -7
  58. package/lib/serve/Service-dispatch.js +9 -11
  59. package/lib/serve/Service-methods.js +30 -41
  60. package/lib/serve/Transaction.js +10 -7
  61. package/lib/serve/adapters.js +7 -5
  62. package/lib/serve/index.js +24 -12
  63. package/lib/utils/data.js +1 -1
  64. package/lib/utils/index.js +27 -30
  65. package/lib/utils/resources/index.js +101 -0
  66. package/lib/utils/resources/tar.js +71 -0
  67. package/lib/utils/resources/utils.js +11 -0
  68. package/libx/_runtime/audit/Service.js +36 -39
  69. package/libx/_runtime/audit/generic/personal/access.js +3 -4
  70. package/libx/_runtime/audit/generic/personal/modification.js +3 -4
  71. package/libx/_runtime/audit/utils/v2.js +1 -2
  72. package/libx/_runtime/auth/index.js +126 -84
  73. package/libx/_runtime/auth/strategies/JWT.js +12 -19
  74. package/libx/_runtime/auth/strategies/dummy.js +1 -5
  75. package/libx/_runtime/auth/strategies/dwc.js +11 -9
  76. package/libx/_runtime/auth/strategies/mock.js +0 -4
  77. package/libx/_runtime/auth/strategies/{utils/xssec.js → xssecUtils.js} +7 -4
  78. package/libx/_runtime/auth/strategies/xsuaa.js +12 -19
  79. package/libx/_runtime/auth/utils.js +22 -1
  80. package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +104 -98
  81. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +2 -2
  82. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +1 -1
  83. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +13 -0
  84. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/language.js +2 -8
  85. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +4 -29
  86. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +2 -1
  87. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +3 -2
  88. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +2 -2
  89. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +4 -6
  90. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +24 -21
  91. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +8 -2
  92. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriHelper.js +1 -1
  93. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/DeserializerFactory.js +2 -0
  94. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/ResourceJsonDeserializer.js +5 -6
  95. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/DispatcherCommand.js +2 -6
  96. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/utils/UriHelper.js +4 -1
  97. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -12
  98. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +33 -9
  99. package/libx/_runtime/cds-services/adapter/odata-v4/utils/dispatcherUtils.js +50 -0
  100. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +2 -2
  101. package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +10 -3
  102. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +9 -11
  103. package/libx/_runtime/cds-services/adapter/rest/RestRequest.js +6 -3
  104. package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +4 -2
  105. package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/utils.js +1 -1
  106. package/libx/_runtime/cds-services/adapter/rest/utils/binary.js +1 -1
  107. package/libx/_runtime/cds-services/adapter/rest/utils/key-value-utils.js +2 -3
  108. package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +6 -4
  109. package/libx/_runtime/cds-services/adapter/rest/utils/result.js +1 -0
  110. package/libx/_runtime/cds-services/adapter/rest/utils/validation-checks.js +8 -5
  111. package/libx/_runtime/cds-services/services/Service.js +40 -0
  112. package/libx/_runtime/cds-services/services/utils/columns.js +4 -3
  113. package/libx/_runtime/cds-services/services/utils/compareJson.js +4 -4
  114. package/libx/_runtime/cds-services/services/utils/differ.js +3 -3
  115. package/libx/_runtime/cds-services/services/utils/handlerUtils.js +4 -4
  116. package/libx/_runtime/cds-services/services/utils/restrictions.js +78 -0
  117. package/libx/_runtime/cds-services/util/assert.js +20 -14
  118. package/libx/_runtime/cds.js +9 -1
  119. package/libx/_runtime/common/aspects/any.js +5 -0
  120. package/libx/_runtime/common/aspects/entity.js +25 -7
  121. package/libx/_runtime/common/aspects/utils.js +2 -2
  122. package/libx/_runtime/common/composition/data.js +6 -0
  123. package/libx/_runtime/common/composition/insert.js +3 -2
  124. package/libx/_runtime/common/composition/tree.js +4 -10
  125. package/libx/_runtime/common/composition/update.js +4 -4
  126. package/libx/_runtime/common/constants/draft.js +29 -26
  127. package/libx/_runtime/common/error/constants.js +2 -2
  128. package/libx/_runtime/common/error/frontend.js +7 -15
  129. package/libx/_runtime/common/generic/auth/capabilities.js +59 -0
  130. package/libx/_runtime/common/generic/auth/constants.js +20 -0
  131. package/libx/_runtime/common/generic/auth/expand.js +54 -0
  132. package/libx/_runtime/common/generic/auth/index.js +32 -0
  133. package/libx/_runtime/common/generic/auth/insertOnly.js +15 -0
  134. package/libx/_runtime/common/generic/auth/readOnly.js +26 -0
  135. package/libx/_runtime/common/generic/auth/requires.js +34 -0
  136. package/libx/_runtime/common/generic/auth/restrict.js +296 -0
  137. package/libx/_runtime/common/generic/auth/utils.js +213 -0
  138. package/libx/_runtime/common/generic/crud.js +14 -10
  139. package/libx/_runtime/common/generic/etag.js +1 -1
  140. package/libx/_runtime/common/generic/input.js +35 -35
  141. package/libx/_runtime/common/generic/sorting.js +2 -3
  142. package/libx/_runtime/common/generic/temporal.js +2 -2
  143. package/libx/_runtime/common/i18n/index.js +2 -31
  144. package/libx/_runtime/common/i18n/messages.properties +1 -1
  145. package/libx/_runtime/common/toggles/handler.js +21 -0
  146. package/libx/_runtime/common/utils/copy.js +10 -1
  147. package/libx/_runtime/common/utils/cqn2cqn4sql.js +100 -29
  148. package/libx/_runtime/common/utils/csn.js +63 -1
  149. package/libx/_runtime/common/utils/dollar.js +10 -1
  150. package/libx/_runtime/common/utils/draft.js +46 -7
  151. package/libx/_runtime/common/utils/entityFromCqn.js +13 -9
  152. package/libx/_runtime/common/utils/extensibilityUtils.js +18 -0
  153. package/libx/_runtime/common/utils/foreignKeyPropagations.js +88 -104
  154. package/libx/_runtime/common/utils/generateOnCond.js +9 -6
  155. package/libx/_runtime/common/utils/quotingStyles.js +2 -0
  156. package/libx/_runtime/common/utils/resolveStructured.js +25 -9
  157. package/libx/_runtime/common/utils/resolveView.js +4 -1
  158. package/libx/_runtime/common/utils/rewriteAsterisks.js +3 -16
  159. package/libx/_runtime/common/utils/structured.js +33 -37
  160. package/libx/_runtime/common/utils/template.js +17 -8
  161. package/libx/_runtime/common/utils/templateProcessor.js +28 -28
  162. package/libx/_runtime/db/data-conversion/post-processing.js +118 -417
  163. package/libx/_runtime/db/expand/expandCQNToJoin.js +45 -41
  164. package/libx/_runtime/db/expand/rawToExpanded.js +29 -8
  165. package/libx/_runtime/db/generic/index.js +1 -3
  166. package/libx/_runtime/db/generic/input.js +5 -10
  167. package/libx/_runtime/db/generic/rewrite.js +5 -2
  168. package/libx/_runtime/db/generic/structured.js +2 -2
  169. package/libx/_runtime/db/query/delete.js +2 -2
  170. package/libx/_runtime/db/query/insert.js +1 -1
  171. package/libx/_runtime/db/query/update.js +9 -14
  172. package/libx/_runtime/db/sql-builder/CreateBuilder.js +4 -3
  173. package/libx/_runtime/db/sql-builder/InsertBuilder.js +14 -1
  174. package/libx/_runtime/db/sql-builder/SelectBuilder.js +3 -2
  175. package/libx/_runtime/db/sql-builder/dataTypes.js +3 -3
  176. package/libx/_runtime/db/utils/columns.js +3 -3
  177. package/libx/_runtime/db/utils/normalizeTimeData.js +2 -2
  178. package/libx/_runtime/db/utils/propagateForeignKeys.js +6 -2
  179. package/libx/_runtime/extensibility/mps/index.js +5 -0
  180. package/libx/_runtime/extensibility/mps/service.js +111 -0
  181. package/libx/_runtime/extensibility/mps/tar.js +42 -0
  182. package/libx/_runtime/extensibility/mps/utils.js +11 -0
  183. package/libx/_runtime/{fiori → extensibility}/uiflex/handler/transformREAD.js +0 -0
  184. package/libx/_runtime/{fiori → extensibility}/uiflex/handler/transformRESULT.js +17 -5
  185. package/libx/_runtime/{fiori → extensibility}/uiflex/handler/transformWRITE.js +1 -0
  186. package/libx/_runtime/extensibility/uiflex/index.js +54 -0
  187. package/libx/_runtime/extensibility/uiflex/service.js +276 -0
  188. package/libx/_runtime/{fiori → extensibility}/uiflex/utils.js +22 -7
  189. package/libx/_runtime/fiori/generic/activate.js +2 -2
  190. package/libx/_runtime/fiori/generic/before.js +4 -4
  191. package/libx/_runtime/fiori/generic/new.js +3 -3
  192. package/libx/_runtime/fiori/generic/patch.js +1 -1
  193. package/libx/_runtime/fiori/generic/read.js +58 -66
  194. package/libx/_runtime/fiori/generic/readOverDraft.js +71 -16
  195. package/libx/_runtime/fiori/utils/handler.js +6 -13
  196. package/libx/_runtime/fiori/utils/where.js +6 -5
  197. package/libx/_runtime/hana/Service.js +4 -10
  198. package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +2 -2
  199. package/libx/_runtime/hana/driver.js +2 -2
  200. package/libx/_runtime/hana/execute.js +29 -75
  201. package/libx/_runtime/hana/pool.js +1 -1
  202. package/libx/_runtime/hana/streaming.js +2 -1
  203. package/libx/_runtime/index.js +6 -6
  204. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +5 -21
  205. package/libx/_runtime/messaging/Outbox.js +2 -2
  206. package/libx/_runtime/messaging/common-utils/AMQPClient.js +4 -14
  207. package/libx/_runtime/messaging/common-utils/connections.js +5 -7
  208. package/libx/_runtime/messaging/common-utils/normalizeIncomingMessage.js +30 -0
  209. package/libx/_runtime/messaging/enterprise-messaging-shared.js +2 -1
  210. package/libx/_runtime/messaging/enterprise-messaging-utils/EMManagement.js +36 -30
  211. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +19 -12
  212. package/libx/_runtime/messaging/enterprise-messaging.js +8 -8
  213. package/libx/_runtime/messaging/file-based.js +5 -5
  214. package/libx/_runtime/messaging/message-queuing.js +14 -12
  215. package/libx/_runtime/messaging/outbox/utils.js +18 -19
  216. package/libx/_runtime/messaging/redis-messaging.js +91 -0
  217. package/libx/_runtime/messaging/service.js +8 -6
  218. package/libx/_runtime/remote/Service.js +44 -8
  219. package/libx/_runtime/remote/utils/client.js +25 -13
  220. package/libx/_runtime/remote/utils/data.js +11 -11
  221. package/libx/_runtime/sqlite/Service.js +6 -9
  222. package/libx/_runtime/sqlite/customBuilder/CustomFunctionBuilder.js +5 -2
  223. package/libx/_runtime/types/api.js +10 -2
  224. package/libx/common/utils/ucsn.js +109 -0
  225. package/libx/gql/resolvers/crud/create.js +6 -1
  226. package/libx/gql/resolvers/crud/delete.js +6 -1
  227. package/libx/gql/resolvers/crud/read.js +6 -1
  228. package/libx/gql/resolvers/crud/update.js +9 -1
  229. package/libx/gql/resolvers/parse/ast2cqn/columns.js +3 -1
  230. package/libx/gql/schema/typeDefMap.js +2 -2
  231. package/libx/odata/afterburner.js +110 -16
  232. package/libx/odata/grammar.pegjs +9 -1
  233. package/libx/odata/parseToCqn.js +39 -0
  234. package/libx/odata/parser.js +1 -1
  235. package/libx/rest/RestAdapter.js +9 -1
  236. package/libx/rest/middleware/input.js +54 -0
  237. package/libx/rest/middleware/operation.js +14 -1
  238. package/libx/rest/middleware/parse.js +11 -7
  239. package/package.json +1 -1
  240. package/server.js +34 -19
  241. package/srv/audit-log.cds +2 -2
  242. package/srv/flex.cds +8 -2
  243. package/srv/flex.js +1 -1
  244. package/srv/mps.cds +23 -0
  245. package/srv/mps.js +1 -0
  246. package/libx/_runtime/auth/strategies/utils/uaa.js +0 -21
  247. package/libx/_runtime/common/generic/auth.js +0 -874
  248. package/libx/_runtime/common/toggles/alpha.js +0 -43
  249. package/libx/_runtime/db/generic/arrayed.js +0 -33
  250. package/libx/_runtime/fiori/uiflex/index.js +0 -35
  251. package/libx/_runtime/fiori/uiflex/service.js +0 -150
  252. package/libx/rest/utils/data.js +0 -60
@@ -5,12 +5,12 @@ module.exports = {
5
5
  return this._odatav4 || (this._odatav4 = require('./cds-services/adapter/odata-v4/to'))
6
6
  },
7
7
 
8
- get rest() {
9
- if (!this._rest) {
10
- if (global.cds.env.features.rest_new_adapter) this._rest = require('../rest')
11
- else this._rest = require('./cds-services/adapter/rest/to')
12
- }
13
- return this._rest
8
+ get old_rest() {
9
+ return this._old_rest || (this._old_rest = require('./cds-services/adapter/rest/to'))
10
+ },
11
+
12
+ get new_rest() {
13
+ return this._new_rest || (this._new_rest = require('../rest'))
14
14
  }
15
15
  },
16
16
 
@@ -1,8 +1,8 @@
1
1
  const cds = require('../cds')
2
- const LOG = cds.log('messaging')
3
2
  const MessagingService = require('./service.js')
4
3
  const { queueName } = require('./common-utils/naming-conventions')
5
4
  const optionsApp = require('../common/utils/vcap.js')
5
+ const normalizeIncomingMessage = require('./common-utils/normalizeIncomingMessage')
6
6
 
7
7
  class AMQPWebhookMessaging extends MessagingService {
8
8
  async init() {
@@ -33,25 +33,9 @@ class AMQPWebhookMessaging extends MessagingService {
33
33
  const management = this.getManagement()
34
34
  if (!opt.doNotDeploy) this.queued(management.createQueueAndSubscriptions.bind(management))()
35
35
  this.queued(this.listenToClient.bind(this))(async (_topic, _payload, _other, { done, failed }) => {
36
- const event = _topic
37
- // Some messaging systems don't adhere to the standard that the payload has a `data` property.
38
- // For these cases, we interpret the whole payload as `data`.
39
- let data, headers
40
- if (typeof _payload === 'object' && 'data' in _payload) {
41
- data = _payload.data
42
- headers = { ..._payload }
43
- delete headers.data
44
- } else {
45
- data = _payload
46
- headers = {}
47
- }
48
- const msg = {
49
- event,
50
- data,
51
- headers,
52
- inbound: true,
53
- ...(_other || {})
54
- }
36
+ const msg = Object.assign(normalizeIncomingMessage(_payload), _other || {})
37
+ msg.event = _topic
38
+
55
39
  if (!msg._) msg._ = {}
56
40
  msg._.topic = _topic
57
41
  try {
@@ -61,7 +45,7 @@ class AMQPWebhookMessaging extends MessagingService {
61
45
  // In case of AMQP and Solace, the `failed` callback must be called
62
46
  // with an error, otherwise there are problems with the redelivery count.
63
47
  failed(new Error('processing failed'))
64
- LOG.error('ERROR occured in asynchronous event processing:', e)
48
+ this.LOG.error('ERROR occured in asynchronous event processing:', e)
65
49
  }
66
50
  })
67
51
  }
@@ -19,7 +19,7 @@ class OutboxService extends cds.Service {
19
19
  this.emit = async function (...args) {
20
20
  const msg = typeof args[0] === 'object' ? args[0] : { event: args[0], data: args[1], headers: args[2] }
21
21
  const context = this.context || cds.context
22
- if (this.options.outbox && context && typeof context.on === 'function') {
22
+ if (this.options.outbox && context) {
23
23
  const outboxOpts = Object.assign(
24
24
  {},
25
25
  (typeof cds.requires.outbox === 'object' && cds.requires.outbox) || {},
@@ -38,7 +38,7 @@ class OutboxService extends cds.Service {
38
38
  try {
39
39
  await this._emitImmediate(msg)
40
40
  } catch (e) {
41
- LOG._error && LOG.error('Emit failed', { event: msg.event, cause: e })
41
+ LOG.error('Emit failed', { event: msg.event, cause: e })
42
42
  // opts.crashOnError is not official!!!
43
43
  if (isUnrecoverable(this, e) && outboxOpts.crashOnError !== false) process.exit(1)
44
44
  }
@@ -1,17 +1,8 @@
1
1
  const cds = require('../../cds.js')
2
- const LOG = cds.log('messaging')
3
2
  const ClientAmqp = require('@sap/xb-msg-amqp-v100').Client
4
3
  const { connect, disconnect } = require('./connections')
5
4
  const { hasPersistentOutbox } = require('../outbox/utils')
6
5
 
7
- const _JSONorString = string => {
8
- try {
9
- return JSON.parse(string)
10
- } catch (e) {
11
- return string
12
- }
13
- }
14
-
15
6
  const addDataListener = (client, queue, prefix, cb) =>
16
7
  new Promise((resolve, reject) => {
17
8
  const source = `${prefix}${queue}`
@@ -19,8 +10,7 @@ const addDataListener = (client, queue, prefix, cb) =>
19
10
  .receiver(queue)
20
11
  .attach(source)
21
12
  .on('data', async raw => {
22
- const buffer = raw.payload && Buffer.concat(raw.payload.chunks)
23
- const payload = buffer && _JSONorString(buffer.toString())
13
+ const payload = raw.payload && Buffer.concat(raw.payload.chunks).toString()
24
14
  const topic =
25
15
  raw.source &&
26
16
  raw.source.properties &&
@@ -36,7 +26,7 @@ const addDataListener = (client, queue, prefix, cb) =>
36
26
 
37
27
  const sender = (client, optionsApp) => client.sender(`${optionsApp.appName}-${optionsApp.appID}`)
38
28
 
39
- const emit = ({ data, event: topic, headers = {} }, stream, prefix) =>
29
+ const emit = ({ data, event: topic, headers = {} }, stream, prefix, LOG) =>
40
30
  new Promise((resolve, reject) => {
41
31
  LOG._info && LOG.info('Emit', { topic })
42
32
  const message = { ...headers, data }
@@ -69,7 +59,7 @@ class AMQPClient {
69
59
  this.client = new ClientAmqp(this.optionsAMQP)
70
60
  this.sender = sender(this.client, this.service.optionsApp)
71
61
  this.stream = this.sender.attach('')
72
- return connect(this.client, this.keepAlive)
62
+ return connect(this.client, this.service.LOG, this.keepAlive)
73
63
  }
74
64
 
75
65
  async disconnect() {
@@ -84,7 +74,7 @@ class AMQPClient {
84
74
  // REVISIT: Is this a robust way to find out if the connection is working?
85
75
  if (hasPersistentOutbox(this.service, cds.context && cds.context.tenant) && !this.sender.opened())
86
76
  throw new Error('AMQP: Sender is not open')
87
- await emit(msg, this.stream, this.prefix.topic)
77
+ await emit(msg, this.stream, this.prefix.topic, this.service.LOG)
88
78
  if (!this.keepAlive) return this.disconnect()
89
79
  }
90
80
 
@@ -1,11 +1,9 @@
1
- const cds = require('../../cds')
2
- const LOG = cds.log('messaging')
3
1
  const waitingTime = require('./waitingTime')
4
2
 
5
- const _connectUntilConnected = (client, x) => {
3
+ const _connectUntilConnected = (client, LOG, x) => {
6
4
  const _waitingTime = waitingTime(x)
7
5
  setTimeout(() => {
8
- connect(client, true)
6
+ connect(client, LOG, true)
9
7
  .then(() => {
10
8
  LOG._warn && LOG.warn('Reconnected to Enterprise Messaging Client')
11
9
  })
@@ -14,12 +12,12 @@ const _connectUntilConnected = (client, x) => {
14
12
  LOG.warn(
15
13
  `Connection to Enterprise Messaging Client lost: Reconnecting in ${Math.round(_waitingTime / 1000)} s`
16
14
  )
17
- _connectUntilConnected(client, x + 1)
15
+ _connectUntilConnected(client, LOG, x + 1)
18
16
  })
19
17
  }, _waitingTime)
20
18
  }
21
19
 
22
- const connect = (client, keepAlive) => {
20
+ const connect = (client, LOG, keepAlive) => {
23
21
  return new Promise((resolve, reject) => {
24
22
  client
25
23
  .once('connected', function () {
@@ -37,7 +35,7 @@ const connect = (client, keepAlive) => {
37
35
  client.once('disconnected', () => {
38
36
  client.removeAllListeners('error')
39
37
  client.removeAllListeners('connected')
40
- _connectUntilConnected(client, 0)
38
+ _connectUntilConnected(client, LOG, 0)
41
39
  })
42
40
  }
43
41
 
@@ -0,0 +1,30 @@
1
+ const _JSONorString = string => {
2
+ try {
3
+ return JSON.parse(string)
4
+ } catch (e) {
5
+ return string
6
+ }
7
+ }
8
+
9
+ // Some messaging systems don't adhere to the standard that the payload has a `data` property.
10
+ // For these cases, we interpret the whole payload as `data`.
11
+ const normalizeIncomingMessage = message => {
12
+ const _payload = typeof message === 'object' ? message : _JSONorString(message)
13
+ let data, headers
14
+ if (typeof _payload === 'object' && 'data' in _payload) {
15
+ data = _payload.data
16
+ headers = { ..._payload }
17
+ delete headers.data
18
+ } else {
19
+ data = _payload
20
+ headers = {}
21
+ }
22
+
23
+ return {
24
+ data,
25
+ headers,
26
+ inbound: true
27
+ }
28
+ }
29
+
30
+ module.exports = normalizeIncomingMessage
@@ -34,7 +34,8 @@ class EnterpriseMessagingShared extends AMQPWebhookMessaging {
34
34
  queueName,
35
35
  subscribedTopics: this.subscribedTopics,
36
36
  alternativeTopics: this.alternativeTopics,
37
- namespace: this.options.credentials && this.options.credentials.namespace
37
+ namespace: this.options.credentials && this.options.credentials.namespace,
38
+ LOG: this.LOG
38
39
  })
39
40
  return this.management
40
41
  }
@@ -1,6 +1,4 @@
1
1
  const authorizedRequest = require('../common-utils/authorizedRequest')
2
- const cds = require('../../cds.js')
3
- const LOG = cds.log('messaging')
4
2
  const sleep = require('util').promisify(setTimeout)
5
3
 
6
4
  const _getWebhookName = queueName => queueName
@@ -19,7 +17,8 @@ class EMManagement {
19
17
  subscribedTopics,
20
18
  maxRetries,
21
19
  subdomain,
22
- namespace
20
+ namespace,
21
+ LOG
23
22
  }) {
24
23
  this.subdomain = subdomain
25
24
  this.options = optionsManagement
@@ -33,6 +32,7 @@ class EMManagement {
33
32
  this.maxRetries = maxRetries === undefined ? 10 : maxRetries
34
33
  this.subdomainInfo = this.subdomain ? `(subdomain: ${this.subdomain})` : ''
35
34
  this.namespace = namespace
35
+ this.LOG = LOG
36
36
  }
37
37
 
38
38
  async getQueue(queueName = this.queueName) {
@@ -42,8 +42,11 @@ class EMManagement {
42
42
  path: `/hub/rest/api/v1/management/messaging/queues/${encodeURIComponent(queueName)}`,
43
43
  oa2: this.options.oa2,
44
44
  attemptInfo: () =>
45
- LOG._info &&
46
- LOG.info('Get queue', this.subdomain ? { queue: queueName, subdomain: this.subdomain } : { queue: queueName }),
45
+ this.LOG._info &&
46
+ this.LOG.info(
47
+ 'Get queue',
48
+ this.subdomain ? { queue: queueName, subdomain: this.subdomain } : { queue: queueName }
49
+ ),
47
50
  errMsg: `Queue "${queueName}" could not be retrieved ${this.subdomainInfo}`,
48
51
  target: { kind: 'QUEUE', queue: queueName },
49
52
  tokenStore: this
@@ -57,7 +60,8 @@ class EMManagement {
57
60
  uri: this.options.uri,
58
61
  path: `/hub/rest/api/v1/management/messaging/queues`,
59
62
  oa2: this.options.oa2,
60
- attemptInfo: () => LOG._info && LOG.info('Get queues', this.subdomain ? { subdomain: this.subdomain } : {}),
63
+ attemptInfo: () =>
64
+ this.LOG._info && this.LOG.info('Get queues', this.subdomain ? { subdomain: this.subdomain } : {}),
61
65
  errMsg: `Queues could not be retrieved ${this.subdomainInfo}`,
62
66
  target: { kind: 'QUEUE' },
63
67
  tokenStore: this
@@ -73,8 +77,8 @@ class EMManagement {
73
77
  oa2: this.options.oa2,
74
78
  dataObj: this.queueConfig,
75
79
  attemptInfo: () =>
76
- LOG._info &&
77
- LOG.info(
80
+ this.LOG._info &&
81
+ this.LOG.info(
78
82
  'Create queue',
79
83
  this.subdomain ? { queue: queueName, subdomain: this.subdomain } : { queue: queueName }
80
84
  ),
@@ -91,8 +95,8 @@ class EMManagement {
91
95
  path: `/hub/rest/api/v1/management/messaging/queues/${encodeURIComponent(queueName)}`,
92
96
  oa2: this.options.oa2,
93
97
  attemptInfo: () =>
94
- LOG._info &&
95
- LOG.info(
98
+ this.LOG._info &&
99
+ this.LOG.info(
96
100
  'Delete queue',
97
101
  this.subdomain ? { queue: queueName, subdomain: this.subdomain } : { queue: queueName }
98
102
  ),
@@ -109,8 +113,8 @@ class EMManagement {
109
113
  path: `/hub/rest/api/v1/management/messaging/queues/${encodeURIComponent(queueName)}/subscriptions`,
110
114
  oa2: this.options.oa2,
111
115
  attemptInfo: () =>
112
- LOG._info &&
113
- LOG.info(
116
+ this.LOG._info &&
117
+ this.LOG.info(
114
118
  'Get subscriptions',
115
119
  this.subdomain ? { queue: queueName, subdomain: this.subdomain } : { queue: queueName }
116
120
  ),
@@ -130,8 +134,8 @@ class EMManagement {
130
134
  )}/subscriptions/${encodeURIComponent(topicPattern)}`,
131
135
  oa2: this.options.oa2,
132
136
  attemptInfo: () =>
133
- LOG._info &&
134
- LOG.info(
137
+ this.LOG._info &&
138
+ this.LOG.info(
135
139
  'Create subscription',
136
140
  this.subdomain
137
141
  ? { topic: topicPattern, queue: queueName, subdomain: this.subdomain }
@@ -152,8 +156,8 @@ class EMManagement {
152
156
  )}/subscriptions/${encodeURIComponent(topicPattern)}`,
153
157
  oa2: this.options.oa2,
154
158
  attemptInfo: () =>
155
- LOG._info &&
156
- LOG.info(
159
+ this.LOG._info &&
160
+ this.LOG.info(
157
161
  'Delete subscription',
158
162
  this.subdomain
159
163
  ? { topic: topicPattern, queue: queueName, subdomain: this.subdomain }
@@ -173,8 +177,8 @@ class EMManagement {
173
177
  path: `/messagingrest/v1/subscriptions/${encodeURIComponent(webhookName)}`,
174
178
  oa2: this.optionsMessagingREST.oa2,
175
179
  attemptInfo: () =>
176
- LOG._info &&
177
- LOG.info(
180
+ this.LOG._info &&
181
+ this.LOG.info(
178
182
  'Get webhook',
179
183
  this.subdomain
180
184
  ? { webhook: webhookName, queue: queueName, subdomain: this.subdomain }
@@ -195,8 +199,8 @@ class EMManagement {
195
199
  path: `/messagingrest/v1/subscriptions/${encodeURIComponent(webhookName)}`,
196
200
  oa2: this.optionsMessagingREST.oa2,
197
201
  attemptInfo: () =>
198
- LOG._info &&
199
- LOG.info(
202
+ this.LOG._info &&
203
+ this.LOG.info(
200
204
  'Delete webhook',
201
205
  this.subdomain
202
206
  ? { webhook: webhookName, queue: queueName, subdomain: this.subdomain }
@@ -245,8 +249,8 @@ class EMManagement {
245
249
  oa2: this.optionsMessagingREST.oa2,
246
250
  dataObj,
247
251
  attemptInfo: () =>
248
- LOG._info &&
249
- LOG.info(
252
+ this.LOG._info &&
253
+ this.LOG.info(
250
254
  'Create webhook',
251
255
  this.subdomain
252
256
  ? { webhook: webhookName, queue: queueName, subdomain: this.subdomain }
@@ -266,8 +270,8 @@ class EMManagement {
266
270
  path: `/messagingrest/v1/subscriptions/${encodeURIComponent(webhookName)}`,
267
271
  oa2: this.optionsMessagingREST.oa2,
268
272
  attemptInfo: () =>
269
- LOG._info &&
270
- LOG.info(
273
+ this.LOG._info &&
274
+ this.LOG.info(
271
275
  'Delete webhook',
272
276
  this.subdomain
273
277
  ? { webhook: webhookName, queue: queueName, subdomain: this.subdomain }
@@ -280,7 +284,7 @@ class EMManagement {
280
284
  }
281
285
 
282
286
  async createQueueAndSubscriptions() {
283
- LOG._info && LOG.info(`Create messaging artifacts ${this.subdomainInfo}`)
287
+ this.LOG._info && this.LOG.info(`Create messaging artifacts ${this.subdomainInfo}`)
284
288
 
285
289
  const created = await this.createQueue()
286
290
  if (created && created.statusCode === 200) {
@@ -297,7 +301,7 @@ class EMManagement {
297
301
  for (const [s, _] of this.subscribedTopics) {
298
302
  if (existingSubscriptions.some(e => s === e)) unchangedSubs.push(s)
299
303
  }
300
- LOG._info && LOG.info('Unchanged subscriptions', unchangedSubs, ' ', this.subdomainInfo)
304
+ this.LOG._info && this.LOG.info('Unchanged subscriptions', unchangedSubs, ' ', this.subdomainInfo)
301
305
  await Promise.all([
302
306
  ...obsoleteSubs.map(s => this.deleteSubscription(s)),
303
307
  ...additionalSubs.map(async s => this._createSubscription(s))
@@ -331,7 +335,7 @@ class EMManagement {
331
335
  }
332
336
 
333
337
  async undeploy() {
334
- LOG._info && LOG.info(`Delete messaging artifacts ${this.subdomainInfo}`)
338
+ this.LOG._info && this.LOG.info(`Delete messaging artifacts ${this.subdomainInfo}`)
335
339
  await this.deleteQueue()
336
340
  if (this.optionsMessagingREST) await this.deleteWebhook()
337
341
  }
@@ -342,7 +346,7 @@ class EMManagement {
342
346
  uri: this.options.uri,
343
347
  path: `/hub/rest/api/v1/management/messaging/readinessCheck`,
344
348
  oa2: this.options.oa2,
345
- attemptInfo: () => LOG._info && LOG.info(`Readiness Check ${this.subdomainInfo}`),
349
+ attemptInfo: () => this.LOG._info && this.LOG.info(`Readiness Check ${this.subdomainInfo}`),
346
350
  errMsg: `Readiness Check failed ${this.subdomainInfo}`,
347
351
  target: { kind: 'READINESSCHECK' },
348
352
  tokenStore: this
@@ -365,8 +369,10 @@ class EMManagement {
365
369
  }
366
370
  const retryAfter = e.response && e.response.headers && e.response.headers['retry-after']
367
371
  const _waitingPeriod = waitingPeriod || (retryAfter && Number(retryAfter) * 1000) || 120 * 1000
368
- LOG._info &&
369
- LOG.info(`Readiness Check failed ${this.subdomainInfo}, retrying in ${_waitingPeriod / 1000} seconds...`)
372
+ this.LOG._info &&
373
+ this.LOG.info(
374
+ `Readiness Check failed ${this.subdomainInfo}, retrying in ${_waitingPeriod / 1000} seconds...`
375
+ )
370
376
  await sleep(_waitingPeriod)
371
377
  await check()
372
378
  } else {
@@ -1,16 +1,13 @@
1
1
  const cds = require('../../cds.js')
2
- const LOG = cds.log('messaging')
3
2
  const express = require('express')
4
3
  const getTenantInfo = require('./getTenantInfo.js')
5
- const isSecured = () => cds.requires.uaa && cds.requires.uaa.credentials
6
- const { getTenant } = require('../../auth/strategies/utils/xssec.js')
4
+ const isSecured = () => cds.requires.auth && cds.requires.auth.credentials
5
+ const { getTenant } = require('../../auth/strategies/xssecUtils')
7
6
 
8
7
  const _isAll = a => a && a.includes('all')
9
- const _hasScope = (scope, req) =>
10
- req && req.authInfo && req.authInfo.checkLocalScope && req.authInfo.checkLocalScope(scope)
11
8
 
12
9
  class EndpointRegistry {
13
- constructor(basePath) {
10
+ constructor(basePath, LOG) {
14
11
  const deployPath = basePath + '/deploy'
15
12
  const paths = [basePath, deployPath]
16
13
  this.webhookCallbacks = new Map()
@@ -18,11 +15,20 @@ class EndpointRegistry {
18
15
  if (isSecured()) {
19
16
  const JWTStrategy = require('../../auth/strategies/JWT.js')
20
17
  const passport = require('passport')
21
- passport.use(new JWTStrategy(cds.requires.uaa))
18
+ // REVISIT: It's unclear if the credentials from cds.requires.auth need to be used here.
19
+ // In principle, user-facing endpoints might differ from messaging ones.
20
+ passport.use(new JWTStrategy(cds.requires.auth.credentials))
22
21
  paths.forEach(path => {
23
22
  cds.app.use(path, passport.initialize())
24
23
  cds.app.use(path, passport.authenticate('JWT', { session: false }))
24
+ cds.app.use(path, (req, res, next) => {
25
+ // unsuccessful auth doesn't automatically reject!
26
+ if (!req.user) return res.status(401).json({ message: 'Unauthorized' })
27
+ next()
28
+ })
25
29
  })
30
+ } else if (process.env.NODE_ENV === 'production') {
31
+ LOG.warn('Messaging endpoints not secured')
26
32
  }
27
33
  paths.forEach(path => {
28
34
  cds.app.use(path, express.json({ type: 'application/*+json' }))
@@ -43,7 +49,7 @@ class EndpointRegistry {
43
49
 
44
50
  cds.app.options(basePath, (req, res) => {
45
51
  try {
46
- if (isSecured() && !_hasScope('emcallback', req)) return res.sendStatus(403)
52
+ if (isSecured() && !req.user.is('emcallback')) return res.sendStatus(403)
47
53
  res.set('WebHook-Allowed-Origin', req.headers['webhook-request-origin'])
48
54
  res.sendStatus(200)
49
55
  } catch (error) {
@@ -53,7 +59,7 @@ class EndpointRegistry {
53
59
  LOG._debug && LOG.debug('Register inbound endpoint', { basePath, method: 'POST' })
54
60
  cds.app.post(basePath, (req, res) => {
55
61
  try {
56
- if (isSecured() && !_hasScope('emcallback', req)) return res.sendStatus(403)
62
+ if (isSecured() && !req.user.is('emcallback')) return res.sendStatus(403)
57
63
  const queueName = req.query.q
58
64
  const authInfo = req.authInfo
59
65
  const xAddress = req.headers['x-address']
@@ -83,7 +89,7 @@ class EndpointRegistry {
83
89
  })
84
90
  cds.app.post(deployPath, async (req, res) => {
85
91
  try {
86
- if (isSecured() && !_hasScope('emmanagement', req)) return res.sendStatus(403)
92
+ if (isSecured() && !req.user.is('emmanagement')) return res.sendStatus(403)
87
93
  const tenants = req.body && !_isAll(req.body.tenants) && req.body.tenants
88
94
  const queues = req.body && !_isAll(req.body.queues) && req.body.queues
89
95
  const options = { wipeData: req.body && req.body.wipeData }
@@ -120,9 +126,10 @@ class EndpointRegistry {
120
126
  const registries = new Map()
121
127
 
122
128
  // REVISIT: Use cds mechanism instead of express? -> Need option method and handler for specifica
123
- const registerWebhookEndpoints = (basePath, queueName, cb) => {
129
+ const registerWebhookEndpoints = (basePath, queueName, LOG, cb) => {
124
130
  const registry =
125
- registries.get(basePath) || (registries.set(basePath, new EndpointRegistry(basePath)) && registries.get(basePath))
131
+ registries.get(basePath) ||
132
+ (registries.set(basePath, new EndpointRegistry(basePath, LOG)) && registries.get(basePath))
126
133
  registry.registerWebhookCallback(queueName, cb)
127
134
  }
128
135
 
@@ -10,7 +10,6 @@ const {
10
10
  registerDeployEndpoints,
11
11
  registerWebhookEndpoints
12
12
  } = require('./enterprise-messaging-utils/registerEndpoints.js')
13
- const LOG = cds.log('messaging')
14
13
  const cloudEvents = require('./enterprise-messaging-utils/cloudEvents.js')
15
14
 
16
15
  const BASE_PATH = '/messaging/enterprise-messaging'
@@ -61,12 +60,12 @@ class EnterpriseMessaging extends AMQPWebhookMessaging {
61
60
  const management = await this.getManagement(subdomain).waitUntilReady()
62
61
  await management.undeploy()
63
62
  } catch (error) {
64
- LOG._error && LOG.error('Failed to delete messaging artifacts for subdomain', subdomain, '(', error, ')')
63
+ this.LOG.error('Failed to delete messaging artifacts for subdomain', subdomain, '(', error, ')')
65
64
  }
66
65
  return next()
67
66
  })
68
67
  provisioning.on('dependencies', async (req, next) => {
69
- LOG._info && LOG.info('Include Enterprise-Messaging as SaaS dependency')
68
+ this.LOG._info && this.LOG.info('Include Enterprise-Messaging as SaaS dependency')
70
69
  const res = await next()
71
70
  const xsappname = this.options.credentials && this.options.credentials.xsappname
72
71
  if (xsappname) {
@@ -80,7 +79,7 @@ class EnterpriseMessaging extends AMQPWebhookMessaging {
80
79
 
81
80
  startListening() {
82
81
  const doNotDeploy = cds._mtxEnabled && !this.options.deployForProvider
83
- if (doNotDeploy) LOG._info && LOG.info('Skipping deployment of messaging artifacts for provider account')
82
+ if (doNotDeploy) this.LOG._info && this.LOG.info('Skipping deployment of messaging artifacts for provider account')
84
83
  super.startListening({ doNotDeploy })
85
84
  if (!doNotDeploy && this.subscribedTopics.size) {
86
85
  const management = this.getManagement()
@@ -97,7 +96,7 @@ class EnterpriseMessaging extends AMQPWebhookMessaging {
97
96
 
98
97
  async listenToClient(cb) {
99
98
  _checkAppURL(this.optionsApp.appURL)
100
- registerWebhookEndpoints(BASE_PATH, this.queueName, cb)
99
+ registerWebhookEndpoints(BASE_PATH, this.queueName, this.LOG, cb)
101
100
  if (cds._mtxEnabled) {
102
101
  await this.addMTXHandlers()
103
102
  registerDeployEndpoints(BASE_PATH, this.queueName, async (tenantInfo, options) => {
@@ -110,7 +109,7 @@ class EnterpriseMessaging extends AMQPWebhookMessaging {
110
109
  await management.deploy()
111
110
  result.succeeded.push(info.tenant)
112
111
  } catch (error) {
113
- LOG._error && LOG.error('Failed to create messaging artifacts for subdomain', info.subdomain, ':', error)
112
+ this.LOG.error('Failed to create messaging artifacts for subdomain', info.subdomain, ':', error)
114
113
  result.failed.push({ error: error.message, tenant: info.tenant })
115
114
  }
116
115
  })
@@ -147,7 +146,8 @@ class EnterpriseMessaging extends AMQPWebhookMessaging {
147
146
  subscribedTopics: this.subscribedTopics,
148
147
  alternativeTopics: this.alternativeTopics,
149
148
  subdomain: _subdomain,
150
- namespace: this.options.credentials && this.options.credentials.namespace
149
+ namespace: this.options.credentials && this.options.credentials.namespace,
150
+ LOG: this.LOG
151
151
  })
152
152
  }
153
153
 
@@ -174,7 +174,7 @@ class EnterpriseMessaging extends AMQPWebhookMessaging {
174
174
  headers: {
175
175
  'x-qos': 1
176
176
  },
177
- attemptInfo: () => LOG._info && LOG.info('Emit', { topic }),
177
+ attemptInfo: () => this.LOG._info && this.LOG.info('Emit', { topic }),
178
178
  errMsg,
179
179
  target: { kind: 'MESSAGE', topic },
180
180
  tokenStore: {}
@@ -1,5 +1,4 @@
1
1
  const cds = require('../cds')
2
- const LOG = cds.log('messaging')
3
2
 
4
3
  const path = require('path')
5
4
  const fs = require('fs').promises
@@ -25,11 +24,11 @@ class FileBasedMessaging extends MessagingService {
25
24
  const e = _msg.event
26
25
  delete _msg.event
27
26
  await this.queued(lock)(this.file)
28
- LOG._debug && LOG.debug('Emit', { topic: e, file: this.file })
27
+ this.LOG._debug && this.LOG.debug('Emit', { topic: e, file: this.file })
29
28
  try {
30
29
  await fs.appendFile(this.file, `\n${e} ${JSON.stringify(_msg)}`)
31
30
  } catch (e) {
32
- LOG._debug && LOG.debug('Error', e)
31
+ this.LOG._debug && this.LOG.debug('Error', e)
33
32
  } finally {
34
33
  unlock(this.file)
35
34
  }
@@ -53,9 +52,10 @@ class FileBasedMessaging extends MessagingService {
53
52
  if (this.subscribedTopics.has(topic)) {
54
53
  const event = this.subscribedTopics.get(topic)
55
54
  if (!event) return
55
+ // REVISIT: should we use this.dispatch instead of super.emit for inbound messages?
56
56
  super
57
57
  .emit({ event, ...json, inbound: true })
58
- .catch(e => LOG.error('ERROR occured in asynchronous event processing:', e))
58
+ .catch(e => this.LOG.error('ERROR occured in asynchronous event processing:', e))
59
59
  } else other.push(each + '\n')
60
60
  }
61
61
  } catch (e) {
@@ -65,7 +65,7 @@ class FileBasedMessaging extends MessagingService {
65
65
  if (other.length < lines.length) await fs.writeFile(this.file, other.join(''))
66
66
  this.recent = await touched(this.file)
67
67
  } catch (e) {
68
- LOG._debug && LOG.debug(e)
68
+ this.LOG._debug && this.LOG.debug(e)
69
69
  } finally {
70
70
  unlock(this.file)
71
71
  }