@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.
- package/CHANGELOG.md +53 -1
- package/README.md +1 -0
- package/_i18n/i18n.properties +9 -6
- package/_i18n/i18n_ar.properties +6 -6
- package/_i18n/i18n_cs.properties +6 -6
- package/_i18n/i18n_da.properties +6 -6
- package/_i18n/i18n_de.properties +6 -6
- package/_i18n/i18n_en.properties +6 -6
- package/_i18n/i18n_es.properties +6 -6
- package/_i18n/i18n_fi.properties +6 -6
- package/_i18n/i18n_fr.properties +6 -6
- package/_i18n/i18n_hu.properties +6 -6
- package/_i18n/i18n_it.properties +6 -6
- package/_i18n/i18n_ja.properties +6 -6
- package/_i18n/i18n_ko.properties +6 -6
- package/_i18n/i18n_ms.properties +6 -6
- package/_i18n/i18n_nl.properties +6 -6
- package/_i18n/i18n_no.properties +6 -6
- package/_i18n/i18n_pl.properties +6 -6
- package/_i18n/i18n_pt.properties +6 -6
- package/_i18n/i18n_ro.properties +6 -6
- package/_i18n/i18n_ru.properties +6 -6
- package/_i18n/i18n_sv.properties +6 -6
- package/_i18n/i18n_th.properties +6 -6
- package/_i18n/i18n_tr.properties +8 -8
- package/_i18n/i18n_zh_CN.properties +3 -3
- package/_i18n/i18n_zh_TW.properties +6 -6
- package/apis/core.d.ts +30 -31
- package/apis/csn.d.ts +1 -1
- package/apis/ql.d.ts +69 -39
- package/apis/serve.d.ts +4 -3
- package/apis/services.d.ts +20 -7
- package/bin/build/buildTaskEngine.js +1 -1
- package/bin/build/index.js +1 -1
- package/bin/build/provider/buildTaskProviderInternal.js +9 -6
- package/bin/build/provider/hana/index.js +11 -4
- package/bin/build/provider/mtx-extension/index.js +13 -1
- package/bin/build/provider/mtx-sidecar/index.js +3 -3
- package/bin/build/provider/nodejs/index.js +23 -0
- package/bin/plugins.js +2 -1
- package/bin/version.js +3 -2
- package/common.cds +3 -2
- package/lib/auth/index.js +3 -0
- package/lib/auth/mocked-users.js +13 -0
- package/lib/compile/etc/_localized.js +3 -0
- package/lib/compile/for/lean_drafts.js +0 -1
- package/lib/core/entities.js +7 -3
- package/lib/dbs/cds-deploy.js +36 -12
- package/lib/env/cds-env.js +47 -14
- package/lib/env/cds-requires.js +16 -7
- package/lib/env/defaults.js +2 -2
- package/lib/env/schemas/cds-rc.json +1 -8
- package/lib/index.js +1 -1
- package/lib/ql/STREAM.js +89 -0
- package/lib/ql/cds-ql.js +2 -1
- package/lib/req/request.js +6 -2
- package/lib/req/user.js +1 -1
- package/lib/srv/middlewares/index.js +9 -7
- package/lib/srv/middlewares/trace.js +6 -5
- package/lib/srv/srv-api.js +1 -0
- package/lib/utils/cds-utils.js +1 -1
- package/lib/utils/tar.js +30 -31
- package/libx/_runtime/audit/Service.js +96 -37
- package/libx/_runtime/audit/generic/personal/utils.js +26 -13
- package/libx/_runtime/audit/utils/v2.js +21 -22
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +2 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +2 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/batch/BatchProcessor.js +2 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +2 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +10 -3
- package/libx/_runtime/cds-services/services/Service.js +2 -7
- package/libx/_runtime/cds-services/services/utils/differ.js +1 -1
- package/libx/_runtime/cds-services/util/assert.js +28 -5
- package/libx/_runtime/common/aspects/any.js +4 -1
- package/libx/_runtime/common/generic/auth/utils.js +30 -41
- package/libx/_runtime/common/generic/crud.js +1 -1
- package/libx/_runtime/common/i18n/messages.properties +1 -1
- package/libx/_runtime/common/utils/generateOnCond.js +18 -22
- package/libx/_runtime/db/expand/expandCQNToJoin.js +49 -41
- package/libx/_runtime/db/expand/rawToExpanded.js +3 -5
- package/libx/_runtime/db/generic/rewrite.js +3 -0
- package/libx/_runtime/db/utils/generateAliases.js +1 -1
- package/libx/_runtime/fiori/generic/activate.js +1 -1
- package/libx/_runtime/fiori/generic/before.js +18 -19
- package/libx/_runtime/fiori/generic/prepare.js +1 -1
- package/libx/_runtime/fiori/generic/read.js +1 -1
- package/libx/_runtime/fiori/lean-draft.js +87 -53
- package/libx/_runtime/fiori/utils/handler.js +0 -6
- package/libx/_runtime/hana/customBuilder/CustomFunctionBuilder.js +1 -1
- package/libx/_runtime/hana/customBuilder/CustomReferenceBuilder.js +2 -1
- package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +0 -5
- package/libx/_runtime/hana/execute.js +18 -11
- package/libx/_runtime/hana/pool.js +26 -18
- package/libx/_runtime/hana/search2Contains.js +1 -1
- package/libx/_runtime/hana/search2cqn4sql.js +26 -18
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +23 -16
- package/libx/_runtime/messaging/outbox/utils.js +6 -1
- package/libx/_runtime/remote/Service.js +83 -48
- package/libx/_runtime/remote/utils/client.js +17 -19
- package/libx/_runtime/sqlite/execute.js +2 -0
- package/libx/rest/middleware/read.js +2 -1
- package/libx/rest/middleware/update.js +1 -1
- 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 (
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
c.
|
|
119
|
-
c.
|
|
120
|
-
c.on('
|
|
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',
|
|
129
|
-
c.on('error',
|
|
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(
|
|
147
|
-
out.on('close',
|
|
148
|
-
c.on('error',
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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(
|
|
188
|
-
x.on('close',
|
|
189
|
-
|
|
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
|
|
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(
|
|
211
|
-
x.on('close',()
|
|
212
|
-
x.on('error',
|
|
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
|
|
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
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
17
|
-
this.
|
|
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
|
-
//
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
74
|
-
|
|
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
|
-
|
|
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 =>
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 = (
|
|
88
|
-
|
|
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: [
|
|
92
|
+
keys.push({ ref: [alias, key.name] }, '=', { val: row[key.name] })
|
|
93
93
|
return keys
|
|
94
94
|
}, [])
|
|
95
95
|
|
|
96
|
-
const _keyColumns =
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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(
|
|
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
|
|
117
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
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 =
|
|
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 =
|
|
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(
|
|
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 =
|
|
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 =
|
|
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(
|
|
131
|
+
function buildSecurityLog(auditLogClient, action, data, tenant, user) {
|
|
133
132
|
let entry
|
|
134
133
|
|
|
135
134
|
try {
|
|
136
|
-
entry =
|
|
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 =
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 (
|
|
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
|
|
@@ -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
|
|
506
|
+
if (result == null) {
|
|
508
507
|
result = { value: null }
|
|
509
508
|
} else {
|
|
510
509
|
_postProcess(odataReq, req, odataRes, service, result)
|
package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/batch/BatchProcessor.js
CHANGED
|
@@ -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
|