@sap/cds 6.7.2 → 6.8.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 (106) hide show
  1. package/CHANGELOG.md +53 -1
  2. package/README.md +1 -0
  3. package/_i18n/i18n.properties +9 -6
  4. package/_i18n/i18n_ar.properties +6 -6
  5. package/_i18n/i18n_cs.properties +6 -6
  6. package/_i18n/i18n_da.properties +6 -6
  7. package/_i18n/i18n_de.properties +6 -6
  8. package/_i18n/i18n_en.properties +6 -6
  9. package/_i18n/i18n_es.properties +6 -6
  10. package/_i18n/i18n_fi.properties +6 -6
  11. package/_i18n/i18n_fr.properties +6 -6
  12. package/_i18n/i18n_hu.properties +6 -6
  13. package/_i18n/i18n_it.properties +6 -6
  14. package/_i18n/i18n_ja.properties +6 -6
  15. package/_i18n/i18n_ko.properties +6 -6
  16. package/_i18n/i18n_ms.properties +6 -6
  17. package/_i18n/i18n_nl.properties +6 -6
  18. package/_i18n/i18n_no.properties +6 -6
  19. package/_i18n/i18n_pl.properties +6 -6
  20. package/_i18n/i18n_pt.properties +6 -6
  21. package/_i18n/i18n_ro.properties +6 -6
  22. package/_i18n/i18n_ru.properties +6 -6
  23. package/_i18n/i18n_sv.properties +6 -6
  24. package/_i18n/i18n_th.properties +6 -6
  25. package/_i18n/i18n_tr.properties +8 -8
  26. package/_i18n/i18n_zh_CN.properties +3 -3
  27. package/_i18n/i18n_zh_TW.properties +6 -6
  28. package/apis/core.d.ts +30 -31
  29. package/apis/csn.d.ts +1 -1
  30. package/apis/ql.d.ts +69 -39
  31. package/apis/serve.d.ts +4 -3
  32. package/apis/services.d.ts +20 -7
  33. package/bin/build/buildTaskEngine.js +1 -1
  34. package/bin/build/index.js +1 -1
  35. package/bin/build/provider/buildTaskProviderInternal.js +9 -6
  36. package/bin/build/provider/hana/index.js +11 -4
  37. package/bin/build/provider/mtx-extension/index.js +13 -1
  38. package/bin/build/provider/mtx-sidecar/index.js +3 -3
  39. package/bin/build/provider/nodejs/index.js +23 -0
  40. package/bin/plugins.js +2 -1
  41. package/bin/version.js +3 -2
  42. package/common.cds +3 -2
  43. package/lib/auth/index.js +3 -0
  44. package/lib/auth/mocked-users.js +13 -0
  45. package/lib/compile/etc/_localized.js +3 -0
  46. package/lib/compile/for/lean_drafts.js +0 -1
  47. package/lib/core/entities.js +7 -3
  48. package/lib/dbs/cds-deploy.js +36 -12
  49. package/lib/env/cds-env.js +47 -14
  50. package/lib/env/cds-requires.js +16 -7
  51. package/lib/env/defaults.js +2 -2
  52. package/lib/env/schemas/cds-rc.json +1 -8
  53. package/lib/index.js +1 -1
  54. package/lib/ql/STREAM.js +89 -0
  55. package/lib/ql/cds-ql.js +2 -1
  56. package/lib/req/request.js +6 -2
  57. package/lib/req/user.js +1 -1
  58. package/lib/srv/middlewares/index.js +9 -7
  59. package/lib/srv/middlewares/trace.js +6 -5
  60. package/lib/srv/srv-api.js +1 -0
  61. package/lib/utils/cds-utils.js +1 -1
  62. package/lib/utils/tar.js +30 -31
  63. package/libx/_runtime/audit/Service.js +96 -37
  64. package/libx/_runtime/audit/generic/personal/utils.js +26 -13
  65. package/libx/_runtime/audit/utils/v2.js +21 -22
  66. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -1
  67. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +1 -1
  68. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +2 -0
  69. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +2 -3
  70. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +1 -1
  71. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/batch/BatchProcessor.js +2 -0
  72. package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +2 -1
  73. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +10 -3
  74. package/libx/_runtime/cds-services/services/Service.js +2 -7
  75. package/libx/_runtime/cds-services/services/utils/differ.js +1 -1
  76. package/libx/_runtime/cds-services/util/assert.js +28 -5
  77. package/libx/_runtime/common/aspects/any.js +4 -1
  78. package/libx/_runtime/common/generic/auth/utils.js +30 -41
  79. package/libx/_runtime/common/generic/crud.js +1 -1
  80. package/libx/_runtime/common/i18n/messages.properties +1 -1
  81. package/libx/_runtime/common/utils/generateOnCond.js +18 -22
  82. package/libx/_runtime/db/expand/expandCQNToJoin.js +49 -41
  83. package/libx/_runtime/db/expand/rawToExpanded.js +3 -5
  84. package/libx/_runtime/db/generic/rewrite.js +3 -0
  85. package/libx/_runtime/db/utils/generateAliases.js +1 -1
  86. package/libx/_runtime/fiori/generic/activate.js +1 -1
  87. package/libx/_runtime/fiori/generic/before.js +18 -19
  88. package/libx/_runtime/fiori/generic/prepare.js +1 -1
  89. package/libx/_runtime/fiori/generic/read.js +1 -1
  90. package/libx/_runtime/fiori/lean-draft.js +87 -53
  91. package/libx/_runtime/fiori/utils/handler.js +0 -6
  92. package/libx/_runtime/hana/customBuilder/CustomFunctionBuilder.js +1 -1
  93. package/libx/_runtime/hana/customBuilder/CustomReferenceBuilder.js +2 -1
  94. package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +0 -5
  95. package/libx/_runtime/hana/execute.js +18 -11
  96. package/libx/_runtime/hana/pool.js +26 -18
  97. package/libx/_runtime/hana/search2Contains.js +1 -1
  98. package/libx/_runtime/hana/search2cqn4sql.js +26 -18
  99. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +23 -16
  100. package/libx/_runtime/messaging/outbox/utils.js +6 -1
  101. package/libx/_runtime/remote/Service.js +83 -48
  102. package/libx/_runtime/remote/utils/client.js +17 -19
  103. package/libx/_runtime/sqlite/execute.js +2 -0
  104. package/libx/rest/middleware/read.js +2 -1
  105. package/libx/rest/middleware/update.js +1 -1
  106. package/package.json +1 -1
package/lib/utils/tar.js CHANGED
@@ -83,7 +83,7 @@ exports.create = async (dir='.', ...args) => {
83
83
  let c, temp
84
84
  args = args.filter(el => el)
85
85
  if (process.platform === 'win32') {
86
- const spawnDir = (dir, args) => {
86
+ const spawnDir = (dir, args) => {
87
87
  if (args.some(arg => arg === '-f')) return spawn ('tar', ['c', '-C', win(dir), ...win(args)])
88
88
  else return spawn ('tar', ['cf', '-', '-C', win(dir), ...win(args)])
89
89
  }
@@ -101,7 +101,7 @@ exports.create = async (dir='.', ...args) => {
101
101
  } else {
102
102
  args.push('.')
103
103
  }
104
-
104
+
105
105
  c = spawn ('tar', ['c', '-C', dir, ...args])
106
106
  }
107
107
 
@@ -112,21 +112,15 @@ exports.create = async (dir='.', ...args) => {
112
112
  * in-memory Buffer holding the tar output, hence enabling this usage:
113
113
  * @example const buffer = await tar.c('src/dir')
114
114
  */
115
- then (r,e) {
116
- const bb=[]
117
- const eb=[]
118
- c.stdout.on('data', b => bb.push(b))
119
- c.stderr.on('data', b => eb.push(b))
120
- c.on('close', (code) => {
121
- if (code === 0) {
122
- return r(Buffer.concat(bb))
123
- }
124
- e(new Error('tar: ' + Buffer.concat(eb)))
125
- })
126
- c.on('error', e)
115
+ then (resolve, reject) {
116
+ let data=[], stderr=''
117
+ c.stdout.on('data', d => data.push(d))
118
+ c.stderr.on('data', d => stderr += d)
119
+ c.on('close', code => code ? reject(new Error(stderr)) : resolve(Buffer.concat(data)))
120
+ c.on('error', reject)
127
121
  if (process.platform === 'win32') {
128
- c.on('close', async () => temp && exists(temp) && await rimraf(temp))
129
- c.on('error', async () => temp && exists(temp) && await rimraf(temp))
122
+ c.on('close', () => temp && exists(temp) && rimraf(temp))
123
+ c.on('error', () => temp && exists(temp) && rimraf(temp))
130
124
  }
131
125
  },
132
126
 
@@ -143,9 +137,9 @@ exports.create = async (dir='.', ...args) => {
143
137
  }
144
138
  // Returning a thenable ChildProcess.stdout
145
139
  return {__proto__: c.stdout.pipe (out),
146
- then(r,e) {
147
- out.on('close',r)
148
- c.on('error',e)
140
+ then (resolve, reject) {
141
+ out.on('close', code => code ? reject(code) : resolve())
142
+ c.on('error', reject)
149
143
  }
150
144
  }
151
145
  }
@@ -175,18 +169,21 @@ exports.extract = (archive, ...args) => ({
175
169
  to (...dest) {
176
170
  if (typeof dest === 'string') dest = _resolve(...dest)
177
171
  const input = typeof archive !== 'string' || archive == '-' ? '-' : _resolve(archive)
178
- const x = spawn('tar', ['xf', win(input), '-C', win(dest), ...args])
172
+ const x = spawn('tar', ['xf', win(input), '-C', win(dest), ...args], { env: { COPYFILE_DISABLE: 1 }})
179
173
  if (archive === '-') return x.stdin
180
174
  if (Buffer.isBuffer(archive)) archive = require('stream').Readable.from (archive)
181
175
  if (typeof archive !== 'string') archive.pipe (x.stdin)
182
- if (args.includes('-v')) {
183
- var list = '';
184
- ;(process.platform === 'linux' ? x.stdout : x.stderr) .on ('data', d => list+=d)
185
- }
176
+ let stdout='', stderr=''
177
+ x.stdout.on ('data', d => stdout += d)
178
+ x.stderr.on ('data', d => stderr += d)
186
179
  return {__proto__:x,
187
- then(r,e) {
188
- x.on('close',()=>r(list ? list.split('\n').slice(0,-1).map(x => x.replace(/^x |\r/g,'')) : undefined))
189
- x.on('error',e)
180
+ then (resolve, reject) {
181
+ x.on('close', code => {
182
+ if (code) return reject (new Error(stderr))
183
+ if (process.platform === 'linux') stdout = stderr
184
+ resolve (stdout ? stdout.split('\n').slice(0,-1).map(x => x.replace(/^x |\r/g,'')): undefined)
185
+ })
186
+ x.on('error', reject)
190
187
  }
191
188
  }
192
189
  },
@@ -205,11 +202,13 @@ exports.extract = (archive, ...args) => ({
205
202
  exports.list = (archive, ...more) => {
206
203
  const input = typeof archive !== 'string' ? '-' : archive === '-' ? archive : _resolve(archive)
207
204
  const x = spawn(`tar tf`, [ input, ...more ], { shell:true })
208
- let list = ''; x.stdout.on ('data', d => list+=d)
205
+ let stdout='', stderr=''
206
+ x.stdout.on ('data', d => stdout += d)
207
+ x.stderr.on ('data', d => stderr += d)
209
208
  return {__proto__:x,
210
- then(r,e) {
211
- x.on('close',()=>r(list.split('\n').slice(0,-1)))
212
- x.on('error',e)
209
+ then (resolve, reject) {
210
+ x.on('close', code => code ? reject(new Error(stderr)) : resolve(stdout.split('\n').slice(0,-1)))
211
+ x.on('error', reject)
213
212
  }
214
213
  }
215
214
  }
@@ -1,28 +1,73 @@
1
1
  const cds = require('../cds')
2
2
  const OutboxService = require('../messaging/Outbox')
3
- const v2utils = require('./utils/v2')
3
+ const {
4
+ connect,
5
+ buildDataAccessLogs,
6
+ sendDataAccessLog,
7
+ buildDataModificationLogs,
8
+ sendDataModificationLog,
9
+ buildSecurityLog,
10
+ sendSecurityLog,
11
+ buildConfigChangeLogs,
12
+ sendConfigChangeLog
13
+ } = require('./utils/v2')
4
14
  const ANONYMOUS = 'anonymous'
15
+ const PLAN = {
16
+ standard: 'standard',
17
+ OAuth2: 'OAuth2'
18
+ }
19
+
20
+ const _getTenantAndUser = options => {
21
+ // REVISIT: the standard plan is deprecated/insecure
22
+ if (options.plan === PLAN.standard) {
23
+ return {
24
+ user: cds.context?.user?.id ?? ANONYMOUS,
25
+ tenant: cds.context?.tenant
26
+ }
27
+ }
28
+
29
+ return {
30
+ user: '$USER',
31
+ tenant: '$PROVIDER'
32
+ }
33
+ }
5
34
 
6
- const _getTenantAndUser = () => ({
7
- user: cds.context?.user?.id ?? ANONYMOUS,
8
- tenant: cds.context?.tenant
9
- })
35
+ const _getSecurityContext = () => {
36
+ const securityContext = cds?.context?.http?.req?.authInfo
37
+
38
+ if (!securityContext) {
39
+ const errorMsg =
40
+ '[Audit Logging]: The Programmatic API of the Audit Logging service for the OAuth2 plan (API - V2) can only be ' +
41
+ 'called within the context of a request event handler. The authInfo property of the request interface (req.authInfo) ' +
42
+ "is used to get the security context required for the implementation's proper functioning."
43
+ throw new Error(errorMsg)
44
+ }
45
+
46
+ return securityContext
47
+ }
10
48
 
11
49
  module.exports = class AuditLogService extends OutboxService {
12
50
  async init() {
13
51
  // call OutboxService's init, which handles outboxing
14
52
  await super.init()
15
53
 
16
- // connect to audit log service
17
- this.alc = await v2utils.connect(this.options.credentials)
54
+ const credentials = this.options.credentials
55
+ this.options.plan = !credentials || !credentials.uaa ? PLAN.standard : PLAN.OAuth2
56
+
57
+ // REVISIT: the standard plan is deprecated/insecure
58
+ if (this.options.plan === PLAN.standard) {
59
+ const auditLogClient = await connect(credentials)
60
+ if (auditLogClient) this.#registerOnHandlers(auditLogClient)
61
+ return
62
+ }
18
63
 
19
- // register handlers, if connected
20
- if (this.alc) {
21
- this.on('dataAccessLog', this._dataAccessLog)
22
- this.on('dataModificationLog', this._dataModificationLog)
23
- this.on('securityLog', this._securityLog)
24
- this.on('configChangeLog', this._configChangeLog)
64
+ // OAuth2 plan
65
+ const outbox = cds.requires['audit-log'].outbox
66
+ if (outbox?.kind === 'persistent-outbox' || (outbox && cds.requires.outbox?.kind === 'persistent-outbox')) {
67
+ throw new Error('The OAuth2 plan is not currently supported with persistent outbox')
25
68
  }
69
+
70
+ if (credentials?.uaa) this.#registerOnHandlers()
26
71
  }
27
72
 
28
73
  async emit(first, second) {
@@ -70,17 +115,36 @@ module.exports = class AuditLogService extends OutboxService {
70
115
  * impl
71
116
  */
72
117
 
73
- async _dataAccessLog({ data: { accesses } }) {
74
- const { tenant, user } = _getTenantAndUser()
118
+ #registerOnHandlers(auditLogClient) {
119
+ this.on('dataAccessLog', function (req) {
120
+ return this._dataAccessLog(req, auditLogClient)
121
+ })
122
+
123
+ this.on('dataModificationLog', function (req) {
124
+ return this._dataModificationLog(req, auditLogClient)
125
+ })
126
+
127
+ this.on('securityLog', function (req) {
128
+ return this._securityLog(req, auditLogClient)
129
+ })
130
+
131
+ this.on('configChangeLog', function (req) {
132
+ return this._configChangeLog(req, auditLogClient)
133
+ })
134
+ }
135
+
136
+ async _dataAccessLog({ data: { accesses } }, auditLogClient) {
137
+ const { tenant, user } = _getTenantAndUser(this.options)
75
138
 
76
139
  // build the logs
77
- const { entries, errors } = v2utils.buildDataAccessLogs(this.alc, accesses, tenant, user)
140
+ auditLogClient = auditLogClient ?? (await connect(this.options.credentials, _getSecurityContext()))
141
+ const { entries, errors } = buildDataAccessLogs(auditLogClient, accesses, tenant, user)
78
142
  if (errors.length) {
79
143
  throw errors.length === 1 ? errors[0] : Object.assign(new Error('MULTIPLE_ERRORS'), { details: errors })
80
144
  }
81
145
 
82
146
  // write the logs
83
- await Promise.all(entries.map(entry => v2utils.sendDataAccessLog(entry).catch(err => errors.push(err))))
147
+ await Promise.all(entries.map(entry => sendDataAccessLog(entry).catch(err => errors.push(err))))
84
148
 
85
149
  if (errors.length) {
86
150
  throw errors.length === 1 ? errors[0] : Object.assign(new Error('MULTIPLE_ERRORS'), { details: errors })
@@ -88,29 +152,26 @@ module.exports = class AuditLogService extends OutboxService {
88
152
  }
89
153
 
90
154
  // REVISIT: modification.action not used in auditlog v2
91
- async _dataModificationLog({ data: { modifications } }) {
92
- const { tenant, user } = _getTenantAndUser()
155
+ async _dataModificationLog({ data: { modifications } }, auditLogClient) {
156
+ const { tenant, user } = _getTenantAndUser(this.options)
93
157
 
94
158
  // build the logs
95
- const { entries, errors } = v2utils.buildDataModificationLogs(this.alc, modifications, tenant, user)
159
+ auditLogClient = auditLogClient ?? (await connect(this.options.credentials, _getSecurityContext()))
160
+ const { entries, errors } = buildDataModificationLogs(auditLogClient, modifications, tenant, user)
96
161
  if (errors.length) {
97
162
  throw errors.length === 1 ? errors[0] : Object.assign(new Error('MULTIPLE_ERRORS'), { details: errors })
98
163
  }
99
164
 
100
165
  // write the logs
101
- await Promise.all(
102
- entries.map(entry => {
103
- v2utils.sendDataModificationLog(entry).catch(err => errors.push(err))
104
- })
105
- )
166
+ await Promise.all(entries.map(entry => sendDataModificationLog(entry).catch(err => errors.push(err))))
106
167
 
107
168
  if (errors.length) {
108
169
  throw errors.length === 1 ? errors[0] : Object.assign(new Error('MULTIPLE_ERRORS'), { details: errors })
109
170
  }
110
171
  }
111
172
 
112
- async _securityLog({ data: { action, data } }) {
113
- let { tenant, user } = _getTenantAndUser()
173
+ async _securityLog({ data: { action, data } }, auditLogClient) {
174
+ let { tenant, user } = _getTenantAndUser(this.options)
114
175
 
115
176
  // cds.context may not be proper on auth-related errors -> try to extract from data
116
177
  if (!tenant && user === ANONYMOUS) {
@@ -133,28 +194,26 @@ module.exports = class AuditLogService extends OutboxService {
133
194
  }
134
195
 
135
196
  // build the log
136
- const entry = v2utils.buildSecurityLog(this.alc, action, data, tenant, user)
197
+ auditLogClient = auditLogClient ?? (await connect(this.options.credentials, _getSecurityContext()))
198
+ const entry = buildSecurityLog(auditLogClient, action, data, tenant, user)
137
199
 
138
200
  // write the log
139
- await v2utils.sendSecurityLog(entry)
201
+ await sendSecurityLog(entry)
140
202
  }
141
203
 
142
204
  // REVISIT: action and success not used in auditlog v2
143
- async _configChangeLog({ data: { configurations } }) {
144
- const { tenant, user } = _getTenantAndUser()
205
+ async _configChangeLog({ data: { configurations } }, auditLogClient) {
206
+ const { tenant, user } = _getTenantAndUser(this.options)
145
207
 
146
208
  // build the logs
147
- const { entries, errors } = v2utils.buildConfigChangeLogs(this.alc, configurations, tenant, user)
209
+ auditLogClient = auditLogClient ?? (await connect(this.options.credentials, _getSecurityContext()))
210
+ const { entries, errors } = buildConfigChangeLogs(auditLogClient, configurations, tenant, user)
148
211
  if (errors.length) {
149
212
  throw errors.length === 1 ? errors[0] : Object.assign(new Error('MULTIPLE_ERRORS'), { details: errors })
150
213
  }
151
214
 
152
215
  // write the logs
153
- await Promise.all(
154
- entries.map(entry => {
155
- v2utils.sendConfigChangeLog(entry).catch(err => errors.push(err))
156
- })
157
- )
216
+ await Promise.all(entries.map(entry => sendConfigChangeLog(entry).catch(err => errors.push(err))))
158
217
 
159
218
  if (errors.length) {
160
219
  throw errors.length === 1 ? errors[0] : Object.assign(new Error('MULTIPLE_ERRORS'), { details: errors })
@@ -84,37 +84,50 @@ const addDataSubject = (log, row, key, entity) => {
84
84
  }
85
85
  }
86
86
 
87
- const _addKeysToWhere = (entity, row) =>
88
- Object.values(entity.keys)
87
+ const _addKeysToWhere = (keys, row, alias) =>
88
+ keys
89
89
  .filter(key => !key.isAssociation && key.name !== 'IsActiveEntity')
90
90
  .reduce((keys, key) => {
91
91
  if (keys.length) keys.push('and')
92
- keys.push({ ref: [entity.name, key.name] }, '=', { val: row[key.name] })
92
+ keys.push({ ref: [alias, key.name] }, '=', { val: row[key.name] })
93
93
  return keys
94
94
  }, [])
95
95
 
96
- const _keyColumns = entity =>
97
- Object.values(entity.keys)
98
- .filter(key => !key.isAssociation && key.name !== 'IsActiveEntity')
99
- .map(key => key.name)
96
+ const _keyColumns = (keys, alias) =>
97
+ keys.filter(key => !key.isAssociation && key.name !== 'IsActiveEntity').map(key => ({ ref: [alias, key.name] }))
98
+
99
+ const _alias = entity => entity.name.replace(`${entity._service.name}.`, '').replace('.', '_')
100
100
 
101
101
  const _buildSubSelect = (model, { entity, relative, element, next }, row, previousCqn) => {
102
102
  // relative is a parent or an entity itself
103
- const childCqn = SELECT.from(entity.name)
104
- .columns(_keyColumns(entity))
105
- .where(relative._relations[element.name].join(element._target.name, relative.name))
103
+
104
+ const keys = Object.values(entity.keys)
105
+
106
+ const entityName = entity.name
107
+ const as = _alias(entity)
108
+
109
+ const childCqn = SELECT.from({ ref: [entityName], as }).columns(_keyColumns(keys, as))
110
+
111
+ const targetAlias = _alias(element._target)
112
+ const relativeAlias = _alias(relative)
113
+
114
+ childCqn.where(relative._relations[element.name].join(targetAlias, relativeAlias))
115
+
106
116
  if (previousCqn) {
107
117
  childCqn.where('exists', previousCqn)
108
118
  } else {
109
- childCqn.where(_addKeysToWhere(entity, row))
119
+ childCqn.where(_addKeysToWhere(keys, row, as))
110
120
  }
111
121
  if (next) return _buildSubSelect(model, next, {}, childCqn)
112
122
  return childCqn
113
123
  }
114
124
 
115
125
  const _getDataSubjectIdPromise = ({ dataSubjectEntity, subs }, row, req, model) => {
116
- const cqn = SELECT.from(dataSubjectEntity.name)
117
- .columns(_keyColumns(dataSubjectEntity))
126
+ const keys = Object.values(dataSubjectEntity.keys)
127
+ const as = _alias(dataSubjectEntity)
128
+
129
+ const cqn = SELECT.from({ ref: [dataSubjectEntity.name], as })
130
+ .columns(_keyColumns(keys, as))
118
131
  .where(['exists', _buildSubSelect(model, subs[0], row)])
119
132
  // entity reused in different branches => must check all
120
133
  for (let i = 1; i < subs.length; i++) {
@@ -1,9 +1,8 @@
1
1
  const cds = require('../../cds')
2
2
  const LOG = cds.log('audit-log')
3
-
4
3
  const { getObjectAndDataSubject, getAttributeToLog } = require('./log')
5
4
 
6
- async function connect(credentials) {
5
+ async function connect(credentials, securityContext) {
7
6
  let auditLogging
8
7
 
9
8
  try {
@@ -15,7 +14,7 @@ async function connect(credentials) {
15
14
  }
16
15
 
17
16
  try {
18
- return await auditLogging.v2(credentials)
17
+ return await auditLogging.v2(credentials, securityContext)
19
18
  } catch (error) {
20
19
  LOG._warn && LOG.warn('Unable to initialize audit-logging client with error:', error)
21
20
  return Promise.resolve()
@@ -26,7 +25,7 @@ function sendDataAccessLog(entry) {
26
25
  return new Promise((resolve, reject) => {
27
26
  entry.log(function (err) {
28
27
  if (err && LOG._warn) {
29
- err.message = 'Writing data access log failed with error: ' + err.message
28
+ err.message = `Writing data access log failed with error: ${err.message}`
30
29
  return reject(err)
31
30
  }
32
31
 
@@ -39,13 +38,13 @@ function sendDataModificationLog(entry) {
39
38
  return new Promise((resolve, reject) => {
40
39
  entry.logPrepare(function (err) {
41
40
  if (err) {
42
- err.message = 'Preparing data modification log failed with error: ' + err.message
41
+ err.message = `Preparing data modification log failed with error: ${err.message}`
43
42
  return reject(err)
44
43
  }
45
44
 
46
45
  entry.logSuccess(function (err) {
47
46
  if (err) {
48
- err.message = 'Writing data modification log failed with error: ' + err.message
47
+ err.message = `Writing data modification log failed with error: ${err.message}`
49
48
  return reject(err)
50
49
  }
51
50
 
@@ -59,7 +58,7 @@ function sendSecurityLog(entry) {
59
58
  return new Promise((resolve, reject) => {
60
59
  entry.log(function (err) {
61
60
  if (err) {
62
- err.message = 'Writing security log failed with error: ' + err.message
61
+ err.message = `Writing security log failed with error: ${err.message}`
63
62
  return reject(err)
64
63
  }
65
64
 
@@ -72,13 +71,13 @@ function sendConfigChangeLog(entry) {
72
71
  return new Promise((resolve, reject) => {
73
72
  entry.logPrepare(function (err) {
74
73
  if (err) {
75
- err.message = 'Preparing configuration change log failed with error: ' + err.message
74
+ err.message = `Preparing configuration change log failed with error: ${err.message}`
76
75
  return reject(err)
77
76
  }
78
77
 
79
78
  entry.logSuccess(function (err) {
80
79
  if (err) {
81
- err.message = 'Writing configuration change log failed with error: ' + err.message
80
+ err.message = `Writing configuration change log failed with error: ${err.message}`
82
81
  return reject(err)
83
82
  }
84
83
 
@@ -88,20 +87,20 @@ function sendConfigChangeLog(entry) {
88
87
  })
89
88
  }
90
89
 
91
- function buildDataAccessLogs(alc, accesses, tenant, user) {
90
+ function buildDataAccessLogs(auditLogClient, accesses, tenant, user) {
92
91
  const entries = []
93
92
  const errors = []
94
93
 
95
94
  for (const access of accesses) {
96
95
  try {
97
96
  const { dataObject, dataSubject } = getObjectAndDataSubject(access)
98
- const entry = alc.read(dataObject).dataSubject(dataSubject).by(user)
97
+ const entry = auditLogClient.read(dataObject).dataSubject(dataSubject).by(user)
99
98
  if (tenant) entry.tenant(tenant)
100
99
  for (const each of access.attributes) entry.attribute(each)
101
- for (const each of access.attachments) entry.attachment(each)
100
+ if (access.attachments) for (const each of access.attachments) entry.attachment(each)
102
101
  entries.push(entry)
103
102
  } catch (err) {
104
- err.message = 'Building data access log failed with error: ' + err.message
103
+ err.message = `Building data access log failed with error: ${err.message}`
105
104
  errors.push(err)
106
105
  }
107
106
  }
@@ -109,19 +108,19 @@ function buildDataAccessLogs(alc, accesses, tenant, user) {
109
108
  return { entries, errors }
110
109
  }
111
110
 
112
- function buildDataModificationLogs(alc, modifications, tenant, user) {
111
+ function buildDataModificationLogs(auditLogClient, modifications, tenant, user) {
113
112
  const entries = []
114
113
  const errors = []
115
114
 
116
115
  for (const modification of modifications) {
117
116
  try {
118
117
  const { dataObject, dataSubject } = getObjectAndDataSubject(modification)
119
- const entry = alc.update(dataObject).dataSubject(dataSubject).by(user)
118
+ const entry = auditLogClient.update(dataObject).dataSubject(dataSubject).by(user)
120
119
  if (tenant) entry.tenant(tenant)
121
120
  for (const each of modification.attributes) entry.attribute(getAttributeToLog(each))
122
121
  entries.push(entry)
123
122
  } catch (err) {
124
- err.message = 'Building data modification log failed with error: ' + err.message
123
+ err.message = `Building data modification log failed with error: ${err.message}`
125
124
  errors.push(err)
126
125
  }
127
126
  }
@@ -129,34 +128,34 @@ function buildDataModificationLogs(alc, modifications, tenant, user) {
129
128
  return { entries, errors }
130
129
  }
131
130
 
132
- function buildSecurityLog(alc, action, data, tenant, user) {
131
+ function buildSecurityLog(auditLogClient, action, data, tenant, user) {
133
132
  let entry
134
133
 
135
134
  try {
136
- entry = alc.securityMessage('action: %s, data: %s', action, data)
135
+ entry = auditLogClient.securityMessage('action: %s, data: %s', action, data)
137
136
  if (tenant) entry.tenant(tenant)
138
137
  if (user) entry.by(user)
139
138
  } catch (err) {
140
- err.message = 'Building security log failed with error: ' + err.message
139
+ err.message = `Building security log failed with error: ${err.message}`
141
140
  throw err
142
141
  }
143
142
 
144
143
  return entry
145
144
  }
146
145
 
147
- function buildConfigChangeLogs(alc, configurations, tenant, user) {
146
+ function buildConfigChangeLogs(auditLogClient, configurations, tenant, user) {
148
147
  const entries = []
149
148
  const errors = []
150
149
 
151
150
  for (const configuration of configurations) {
152
151
  try {
153
152
  const { dataObject } = getObjectAndDataSubject(configuration)
154
- const entry = alc.configurationChange(dataObject).by(user)
153
+ const entry = auditLogClient.configurationChange(dataObject).by(user)
155
154
  if (tenant) entry.tenant(tenant)
156
155
  for (const each of configuration.attributes) entry.attribute(getAttributeToLog(each))
157
156
  entries.push(entry)
158
157
  } catch (err) {
159
- err.message = 'Building configuration change log failed with error: ' + err.message
158
+ err.message = `Building configuration change log failed with error: ${err.message}`
160
159
  errors.push(err)
161
160
  }
162
161
  }
@@ -60,7 +60,7 @@ const action = service => {
60
60
  await tx.commit(result)
61
61
  }
62
62
 
63
- if (isReturnMinimal(req) || result === null) odataRes.setStatusCode(204, { overwrite: true })
63
+ if (result == null || isReturnMinimal(req)) odataRes.setStatusCode(204, { overwrite: true })
64
64
  else if (req.event === 'draftActivate' || req.event === 'EDIT') {
65
65
  const keys = Object.keys(req.target.keys).filter(k => {
66
66
  return k !== 'IsActiveEntity' && !req.target.keys[k]._isAssociationStrict
@@ -57,7 +57,7 @@ const create = service => {
57
57
  await tx.commit(result)
58
58
  }
59
59
 
60
- if (isReturnMinimal(req) || result === null) {
60
+ if (result == null || isReturnMinimal(req)) {
61
61
  odataRes.setStatusCode(204)
62
62
  }
63
63
  } catch (e) {
@@ -22,6 +22,8 @@ const metadata = service => {
22
22
  const { 'cds.xt.ModelProviderService': mps } = cds.services
23
23
  let edmx = mps
24
24
  ? await mps.getEdmx({ tenant, model: service.model, service: service.definition.name, locale })
25
+ : cds.mtx && cds.mtx.isExtended(tenant)
26
+ ? await cds.mtx.getEdmx(tenant, service.definition.name, locale)
25
27
  : cds.localize(
26
28
  service.model,
27
29
  locale,
@@ -390,8 +390,7 @@ const _readStream = async (tx, req) => {
390
390
 
391
391
  const _readSingleton = async (tx, req, lastSegment) => {
392
392
  let result = await tx.dispatch(req)
393
-
394
- if (result === null && !req.target['@odata.singleton.nullable']) throw getError(404)
393
+ if (result == null && !req.target['@odata.singleton.nullable']) throw getError(404)
395
394
 
396
395
  if (lastSegment.getKind() === PRIMITIVE_PROPERTY) {
397
396
  result = result[lastSegment.getProperty().getName()]
@@ -504,7 +503,7 @@ const read = service => {
504
503
  // REVISIT: refactor _readAndTransform
505
504
  result = await _readAndTransform(tx, req, odataReq)
506
505
 
507
- if (result === null) {
506
+ if (result == null) {
508
507
  result = { value: null }
509
508
  } else {
510
509
  _postProcess(odataReq, req, odataRes, service, result)
@@ -168,7 +168,7 @@ const update = service => {
168
168
  await tx.commit(result)
169
169
  }
170
170
 
171
- if (isReturnMinimal(req) || result === null) {
171
+ if (result == null || isReturnMinimal(req)) {
172
172
  odataRes.setStatusCode(204)
173
173
  }
174
174
  } catch (e) {
@@ -9,6 +9,7 @@ const PlainHttpResponse = require('../core/PlainHttpResponse')
9
9
  const BatchExitHandler = require('./BatchExitHandler')
10
10
  const BatchedRequestExecutor = require('./BatchedRequestExecutor')
11
11
  const BatchContext = require('./BatchContext')
12
+ const cds = require('../../../../../../cds')
12
13
 
13
14
  /**
14
15
  * The BatchProcessor executes the batched requests and handles possible errors.
@@ -35,6 +36,7 @@ class BatchProcessor {
35
36
  * @returns {Promise} the overall result
36
37
  */
37
38
  process () {
39
+ if(cds.odata.batch_limit < this._batchContext.getRequestList().length) return Promise.reject({statusCode: 429, message: 'Batch request contains too many requests'})
38
40
  const request = this._batchContext.getRequest()
39
41
  const componentManager = request.getService().getComponentManager()
40
42
 
@@ -11,12 +11,13 @@ const { DRAFT_COLUMNS_MAP } = require('../../../../common/constants/draft')
11
11
  const { SELECT } = cds.ql
12
12
 
13
13
  const _keysOf = (row, target) => {
14
- const keyElements = Object.values(target.keys || {})
14
+ const keyElements = Object.values(target.keys || {}).filter(v => !v.virtual)
15
15
  // > singleton
16
16
  if (!keyElements.length) return
17
17
  const keys = {}
18
18
  for (const key of keyElements) {
19
19
  if (key._isAssociationStrict) continue
20
+ if (row[key.name] === undefined) continue // key is not in data, so ignore it
20
21
  keys[key.name] = key.elements ? { val: JSON.stringify(row[key.name]) } : row[key.name]
21
22
  }
22
23
  return keys