@sap/cds 5.6.2 → 5.7.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 +133 -0
- package/_i18n/i18n_fr.properties +4 -4
- package/apis/cds.d.ts +7 -10
- package/apis/connect.d.ts +3 -3
- package/apis/core.d.ts +2 -4
- package/apis/models.d.ts +2 -3
- package/apis/ql.d.ts +0 -1
- package/apis/services.d.ts +7 -3
- package/bin/build/buildTaskFactory.js +16 -10
- package/bin/build/buildTaskProviderFactory.js +3 -3
- package/bin/build/constants.js +2 -1
- package/bin/build/provider/buildTaskProviderInternal.js +14 -14
- package/bin/build/provider/hana/2migration.js +2 -3
- package/bin/build/provider/hana/index.js +34 -0
- package/bin/build/provider/hana/migrationtable.js +90 -22
- package/bin/build/provider/hana/template/undeploy.json +5 -0
- package/bin/build/provider/node-cf/index.js +9 -2
- package/bin/serve.js +16 -18
- package/lib/compile/cdsc.js +15 -5
- package/lib/compile/etc/_localized.js +4 -4
- package/lib/compile/extend.js +8 -0
- package/lib/compile/index.js +3 -1
- package/lib/compile/minify.js +61 -0
- package/lib/compile/resolve.js +4 -1
- package/lib/compile/to/gql.js +9 -0
- package/lib/compile/to/sql.js +26 -30
- package/lib/connect/index.js +1 -1
- package/lib/core/entities.js +0 -3
- package/lib/core/infer.js +1 -0
- package/lib/core/reflect.js +0 -34
- package/lib/deploy.js +25 -17
- package/lib/env/defaults.js +3 -1
- package/lib/env/index.js +13 -4
- package/lib/env/presets.js +38 -0
- package/lib/env/requires.js +16 -11
- package/lib/index.js +13 -11
- package/lib/log/format/kibana.js +4 -2
- package/lib/log/index.js +2 -2
- package/lib/ql/Whereable.js +1 -0
- package/lib/req/cds-context.js +79 -0
- package/lib/req/context.js +5 -77
- package/lib/req/request.js +1 -1
- package/lib/serve/Service-api.js +8 -4
- package/lib/serve/Service-dispatch.js +0 -7
- package/lib/serve/Service-methods.js +6 -8
- package/lib/serve/Transaction.js +35 -30
- package/lib/serve/adapters.js +1 -4
- package/lib/utils/axios.js +1 -1
- package/libx/_runtime/audit/Service.js +44 -20
- package/libx/_runtime/audit/generic/personal/access.js +16 -11
- package/libx/_runtime/audit/generic/personal/modification.js +5 -5
- package/libx/_runtime/audit/generic/personal/utils.js +46 -37
- package/libx/_runtime/{common/auth → auth}/index.js +21 -7
- package/libx/_runtime/{common/auth → auth}/strategies/JWT.js +2 -2
- package/libx/_runtime/{common/auth → auth}/strategies/basic.js +2 -2
- package/libx/_runtime/{common/auth → auth}/strategies/dummy.js +1 -1
- package/libx/_runtime/{common/auth → auth}/strategies/mock.js +2 -2
- package/libx/_runtime/{common/auth → auth}/strategies/utils/uaa.js +1 -1
- package/libx/_runtime/{common/auth → auth}/strategies/utils/xssec.js +0 -0
- package/libx/_runtime/{common/auth → auth}/strategies/xsuaa.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +7 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +0 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +0 -8
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +3 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +6 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +3 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +2 -11
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +16 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +26 -65
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +0 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +3 -66
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +26 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +5 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +13 -10
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/ConditionalRequestControlCommand.js +0 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/ConditionalRequestValidator.js +0 -8
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +54 -76
- package/libx/_runtime/cds-services/adapter/rest/RestRequest.js +0 -7
- package/libx/_runtime/cds-services/adapter/rest/handlers/create.js +3 -6
- package/libx/_runtime/cds-services/adapter/rest/handlers/delete.js +3 -6
- package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +3 -6
- package/libx/_runtime/cds-services/adapter/rest/handlers/read.js +3 -6
- package/libx/_runtime/cds-services/adapter/rest/handlers/update.js +3 -6
- package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/index.js +2 -2
- package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +8 -4
- package/libx/_runtime/cds-services/services/Service.js +0 -6
- package/libx/_runtime/cds-services/services/utils/columns.js +10 -3
- package/libx/_runtime/cds-services/services/utils/compareJson.js +4 -7
- package/libx/_runtime/cds-services/services/utils/differ.js +4 -1
- package/libx/_runtime/cds-services/services/utils/handlerUtils.js +1 -41
- package/libx/_runtime/cds-services/util/assert.js +1 -262
- package/libx/_runtime/cds.js +6 -9
- package/libx/_runtime/common/aspects/entity.js +1 -1
- package/libx/_runtime/common/composition/delete.js +4 -2
- package/libx/_runtime/common/composition/update.js +22 -35
- package/libx/_runtime/common/composition/utils.js +3 -7
- package/libx/_runtime/common/error/standardError.js +11 -0
- package/libx/_runtime/common/generic/auth.js +63 -33
- package/libx/_runtime/common/generic/crud.js +11 -23
- package/libx/_runtime/common/generic/input.js +20 -0
- package/libx/_runtime/common/generic/paging.js +2 -2
- package/libx/_runtime/common/generic/put.js +4 -10
- package/libx/_runtime/common/generic/sorting.js +12 -30
- package/libx/_runtime/common/perf/index.js +24 -0
- package/libx/_runtime/common/utils/cqn.js +58 -1
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +297 -121
- package/libx/_runtime/common/utils/csn.js +38 -56
- package/libx/_runtime/common/utils/entityFromCqn.js +6 -6
- package/libx/_runtime/common/utils/resolveView.js +4 -5
- package/libx/_runtime/common/utils/rewriteAsterisks.js +46 -5
- package/libx/_runtime/common/utils/search2cqn4sql.js +21 -9
- package/libx/_runtime/common/utils/structured.js +35 -25
- package/libx/_runtime/db/Service.js +0 -6
- package/libx/_runtime/db/expand/expand-v2.js +130 -0
- package/libx/_runtime/db/expand/expandCQNToJoin.js +38 -52
- package/libx/_runtime/db/expand/index.js +3 -1
- package/libx/_runtime/db/generic/arrayed.js +3 -1
- package/libx/_runtime/db/generic/input.js +52 -10
- package/libx/_runtime/db/generic/integrity.js +367 -26
- package/libx/_runtime/db/generic/virtual.js +51 -13
- package/libx/_runtime/db/query/update.js +9 -3
- package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +8 -9
- package/libx/_runtime/{common → db}/utils/propagateForeignKeys.js +11 -14
- package/libx/_runtime/fiori/generic/activate.js +1 -0
- package/libx/_runtime/fiori/generic/before.js +2 -1
- package/libx/_runtime/fiori/generic/edit.js +1 -0
- package/libx/_runtime/fiori/generic/patch.js +1 -1
- package/libx/_runtime/fiori/generic/read.js +155 -57
- package/libx/_runtime/fiori/uiflex/handler/transformRESULT.js +0 -4
- package/libx/_runtime/fiori/uiflex/index.js +1 -1
- package/libx/_runtime/fiori/uiflex/{extensibility/index.js → service.js} +6 -4
- package/libx/_runtime/fiori/utils/delete.js +7 -1
- package/libx/_runtime/hana/Service.js +1 -8
- package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +5 -14
- package/libx/_runtime/hana/execute.js +10 -4
- package/libx/_runtime/hana/pool.js +55 -45
- package/libx/_runtime/hana/search.js +7 -6
- package/libx/_runtime/hana/search2cqn4sql.js +8 -5
- package/libx/_runtime/hana/searchToContains.js +3 -1
- package/libx/_runtime/index.js +5 -5
- package/libx/_runtime/messaging/AMQPWebhookMessaging.js +3 -3
- package/libx/_runtime/messaging/Outbox.js +53 -0
- package/libx/_runtime/messaging/common-utils/AMQPClient.js +17 -10
- package/libx/_runtime/messaging/common-utils/connections.js +14 -9
- package/libx/_runtime/messaging/common-utils/waitingTime.js +2 -0
- package/libx/_runtime/messaging/enterprise-messaging-shared.js +2 -3
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -2
- package/libx/_runtime/messaging/enterprise-messaging.js +21 -15
- package/libx/_runtime/messaging/file-based.js +5 -5
- package/libx/_runtime/messaging/message-queuing.js +2 -3
- package/libx/_runtime/messaging/outbox/OutboxRunner.js +75 -0
- package/libx/_runtime/messaging/outbox/utils.js +192 -0
- package/libx/_runtime/messaging/service.js +16 -30
- package/libx/_runtime/remote/Service.js +15 -0
- package/libx/_runtime/remote/utils/client.js +15 -3
- package/libx/_runtime/remote/utils/{dataConversion.js → data.js} +12 -2
- package/libx/_runtime/sqlite/Service.js +7 -10
- package/libx/_runtime/sqlite/customBuilder/CustomExpressionBuilder.js +19 -0
- package/libx/_runtime/sqlite/execute.js +18 -12
- package/libx/_runtime/types/api.js +2 -1
- package/libx/gql/resolvers/parse/ast2cqn/columns.js +1 -1
- package/libx/odata/{odata2cqn/afterburner.js → afterburner.js} +28 -16
- package/libx/odata/{cqn2odata/index.js → cqn2odata.js} +1 -1
- package/libx/odata/{odata2cqn/grammar.pegjs → grammar.pegjs} +182 -118
- package/libx/odata/index.js +18 -15
- package/libx/odata/parser.js +1 -0
- package/libx/odata/utils.js +57 -0
- package/libx/rest/RestAdapter.js +2 -6
- package/libx/rest/utils/data.js +1 -6
- package/package.json +4 -3
- package/server.js +4 -5
- package/srv/audit-log.cds +87 -0
- package/{libx/_runtime/fiori/uiflex/extensibility/index.cds → srv/flex.cds} +0 -0
- package/srv/flex.js +1 -0
- package/srv/outbox.cds +11 -0
- package/srv/outbox.js +0 -0
- package/libx/_runtime/cds-services/adapter/perf/performance.js +0 -104
- package/libx/_runtime/cds-services/adapter/perf/performanceMeasurement.js +0 -33
- package/libx/odata/odata2cqn/index.js +0 -3
- package/libx/odata/odata2cqn/parser.js +0 -1
- package/libx/odata/readme.md +0 -1
- package/libx/odata/utils/index.js +0 -64
package/lib/serve/Transaction.js
CHANGED
|
@@ -1,39 +1,38 @@
|
|
|
1
|
-
const cds = require('../index'), {
|
|
2
|
-
const _context = Symbol()
|
|
1
|
+
const cds = require('../index'), { EventContext } = cds, { cds_tx_protection } = cds.env.features
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* This is the implementation of the `srv.tx(req)` method. It constructs
|
|
6
5
|
* a new Transaction as a derivate of the `srv` (i.e. {__proto__:srv})
|
|
7
6
|
* @returns { Transaction & import('./Service-api') }
|
|
7
|
+
* @param { EventContext } ctx
|
|
8
8
|
*/
|
|
9
|
-
module.exports = function tx (
|
|
10
|
-
|
|
11
|
-
if (
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
if (typeof req === 'function') [ req, fn ] = [ Context.new(), req ]
|
|
9
|
+
module.exports = function tx (ctx,fn) { const srv = this
|
|
10
|
+
|
|
11
|
+
if (srv.context) return srv // srv.tx().tx() -> idempotent
|
|
12
|
+
|
|
13
|
+
// Last arg may be a function -> srv.tx (tx => { ... })
|
|
14
|
+
if (typeof ctx === 'function') [ ctx, fn ] = [ undefined, ctx ]
|
|
16
15
|
if (typeof fn === 'function') {
|
|
17
|
-
|
|
18
|
-
const tx = srv.tx(req)
|
|
16
|
+
const tx = srv.tx(ctx)
|
|
19
17
|
return Promise.resolve(tx).then(fn) .then (tx.commit,tx.rollback)
|
|
20
18
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if (
|
|
26
|
-
|
|
27
|
-
else return RootTransaction.for (srv, req)
|
|
19
|
+
|
|
20
|
+
// This is for compatibility with cds.tx(req)
|
|
21
|
+
if (ctx instanceof EventContext) {
|
|
22
|
+
if (ctx.context !== ctx) return NestedTransaction.for (srv, ctx.context)
|
|
23
|
+
if (ctx._tx) return NestedTransaction.for (srv, ctx)
|
|
24
|
+
else return RootTransaction.for (srv, ctx)
|
|
28
25
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
26
|
+
|
|
27
|
+
// `ctx` is a plain context object or undefined
|
|
28
|
+
if (ctx) { // REVISIT: This is for compatibility with AFC only
|
|
29
|
+
if (ctx._txed_before) return NestedTransaction.for (srv, ctx._txed_before)
|
|
30
|
+
else {
|
|
31
|
+
Object.defineProperty(ctx, '_txed_before', { value: EventContext.for(ctx) }) // > must be non-enumerable
|
|
32
|
+
return RootTransaction.for (srv, ctx._txed_before)
|
|
33
|
+
}
|
|
36
34
|
}
|
|
35
|
+
return RootTransaction.for (srv, EventContext.for(ctx))
|
|
37
36
|
}
|
|
38
37
|
|
|
39
38
|
|
|
@@ -59,8 +58,8 @@ class Transaction {
|
|
|
59
58
|
}
|
|
60
59
|
|
|
61
60
|
/**
|
|
62
|
-
* In addition to srv.commit,
|
|
63
|
-
* in order to
|
|
61
|
+
* In addition to srv.commit, sets the transaction to committed state,
|
|
62
|
+
* in order to prevent continuous use without explicit reopen (i.e., begin).
|
|
64
63
|
*/
|
|
65
64
|
async commit (res) {
|
|
66
65
|
if (this.ready) { //> nothing to do if no transaction started at all
|
|
@@ -71,10 +70,13 @@ class Transaction {
|
|
|
71
70
|
}
|
|
72
71
|
|
|
73
72
|
/**
|
|
74
|
-
* In addition to srv.rollback,
|
|
75
|
-
* in order to
|
|
73
|
+
* In addition to srv.rollback, sets the transaction to rolled back state,
|
|
74
|
+
* in order to prevent continuous use without explicit reopen (i.e., begin).
|
|
76
75
|
*/
|
|
77
76
|
async rollback (err) {
|
|
77
|
+
// nothing to do if transaction already rolled back or committed (committed occurs if error thrown in on succeeded handler)
|
|
78
|
+
if (this.ready === 'rolled back' || this.ready === 'committed') return
|
|
79
|
+
|
|
78
80
|
/*
|
|
79
81
|
* srv.on('error', function (err, req) { ... })
|
|
80
82
|
* synchroneous modification of passed error only
|
|
@@ -103,7 +105,7 @@ class RootTransaction extends Transaction {
|
|
|
103
105
|
|
|
104
106
|
/**
|
|
105
107
|
* In addition to srv.commit, ensures all nested transactions
|
|
106
|
-
* are informed by emitting '
|
|
108
|
+
* are informed by emitting 'succeeded' event to them all.
|
|
107
109
|
*/
|
|
108
110
|
async commit (res) {
|
|
109
111
|
if (cds_tx_protection) this.context._done = 'committed'
|
|
@@ -122,6 +124,9 @@ class RootTransaction extends Transaction {
|
|
|
122
124
|
* are informed by emitting 'failed' event to them all.
|
|
123
125
|
*/
|
|
124
126
|
async rollback (err) {
|
|
127
|
+
// nothing to do if transaction already rolled back (we need to check here as well to not emit failed twice)
|
|
128
|
+
if (this.ready === 'rolled back') return
|
|
129
|
+
|
|
125
130
|
if (cds_tx_protection) this.context._done = 'rolled back'
|
|
126
131
|
try {
|
|
127
132
|
await this.context.emit ('failed',err)
|
package/lib/serve/adapters.js
CHANGED
|
@@ -32,10 +32,7 @@ class ProtocolAdapter {
|
|
|
32
32
|
*/
|
|
33
33
|
in (app) {
|
|
34
34
|
const srv = this.service
|
|
35
|
-
|
|
36
|
-
lib.performanceMeasurement (app)
|
|
37
|
-
app._perf_measured = true
|
|
38
|
-
}
|
|
35
|
+
lib.perf (app)
|
|
39
36
|
lib.auth (srv, app, srv.options)
|
|
40
37
|
app.use (srv.path+'/webapp/', (_,res)=> res.sendStatus(404))
|
|
41
38
|
app.use (srv.path, this)
|
package/lib/utils/axios.js
CHANGED
|
@@ -45,7 +45,7 @@ const _error = (e) => {
|
|
|
45
45
|
}
|
|
46
46
|
if (!e.response) throw e
|
|
47
47
|
if (!e.response.data) throw e
|
|
48
|
-
if (!e.response.data.error) throw new Error(e.message + '\n\n' + e.response.data)
|
|
48
|
+
if (!e.response.data.error) throw new Error(e.message + '\n\n' + JSON.stringify(e.response.data, null, 2))
|
|
49
49
|
const { code, message } = e.response.data.error
|
|
50
50
|
throw new Error (code && code !== 'null' ? `${code} - ${message}` : message)
|
|
51
51
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const cds = require('../cds')
|
|
2
|
-
const
|
|
2
|
+
const OutboxService = require('../messaging/Outbox')
|
|
3
3
|
|
|
4
4
|
const v2utils = require('./utils/v2')
|
|
5
5
|
|
|
@@ -10,10 +10,17 @@ const _getTenantAndUser = () => ({
|
|
|
10
10
|
tenant: (cds.context && cds.context.tenant) || ANONYMOUS
|
|
11
11
|
})
|
|
12
12
|
|
|
13
|
-
module.exports = class AuditLogService extends
|
|
13
|
+
module.exports = class AuditLogService extends OutboxService {
|
|
14
14
|
async init() {
|
|
15
|
-
// call
|
|
15
|
+
// call OutboxService's init, which handles outboxing
|
|
16
16
|
await super.init()
|
|
17
|
+
|
|
18
|
+
// register handlers
|
|
19
|
+
this.on('dataAccessLog', this.dataAccessLog)
|
|
20
|
+
this.on('dataModificationLog', this.dataModificationLog)
|
|
21
|
+
this.on('securityLog', this.securityLog)
|
|
22
|
+
this.on('configChangeLog', this.configChangeLog)
|
|
23
|
+
|
|
17
24
|
// connect to audit log service
|
|
18
25
|
// REVISIT for GA: throw error on connect issue instead of warn and this.ready?
|
|
19
26
|
this.alc = await v2utils.connect(this.options.credentials)
|
|
@@ -25,20 +32,25 @@ module.exports = class AuditLogService extends cds.MessagingService {
|
|
|
25
32
|
if (!this.options.outbox) return this.send(event, data)
|
|
26
33
|
|
|
27
34
|
if (this.ready && this[event]) {
|
|
28
|
-
// best effort until persistent outbox -> only log the failures
|
|
29
35
|
try {
|
|
30
|
-
|
|
36
|
+
// this will open a new tx -> preserve user
|
|
37
|
+
await super.send(new cds.Request({ method: event, data, user: cds.context.user }))
|
|
31
38
|
} catch (e) {
|
|
32
|
-
|
|
39
|
+
if (e.code === 'ERR_ASSERTION') {
|
|
40
|
+
e.unrecoverable = true
|
|
41
|
+
}
|
|
42
|
+
throw e
|
|
33
43
|
}
|
|
34
44
|
}
|
|
35
45
|
}
|
|
36
46
|
|
|
37
47
|
async send(event, data) {
|
|
38
|
-
if (this.ready && this[event]) return
|
|
48
|
+
if (this.ready && this[event]) return super.send(event, data)
|
|
39
49
|
}
|
|
40
50
|
|
|
41
|
-
async dataAccessLog(
|
|
51
|
+
async dataAccessLog(arg) {
|
|
52
|
+
const accesses = arg.accesses || arg.data.accesses
|
|
53
|
+
|
|
42
54
|
if (!this.ready) throw new Error('AuditLogService not connected')
|
|
43
55
|
|
|
44
56
|
const { tenant, user } = _getTenantAndUser()
|
|
@@ -61,7 +73,9 @@ module.exports = class AuditLogService extends cds.MessagingService {
|
|
|
61
73
|
}
|
|
62
74
|
|
|
63
75
|
// REVISIT: modification.action not used in auditlog v2
|
|
64
|
-
async dataModificationLog(
|
|
76
|
+
async dataModificationLog(arg) {
|
|
77
|
+
const modifications = arg.modifications || arg.data.modifications
|
|
78
|
+
|
|
65
79
|
if (!this.ready) throw new Error('AuditLogService not connected')
|
|
66
80
|
|
|
67
81
|
const { tenant, user } = _getTenantAndUser()
|
|
@@ -83,16 +97,19 @@ module.exports = class AuditLogService extends cds.MessagingService {
|
|
|
83
97
|
}
|
|
84
98
|
}
|
|
85
99
|
|
|
86
|
-
async securityLog(
|
|
100
|
+
async securityLog(arg) {
|
|
101
|
+
let { action, data } = arg
|
|
102
|
+
if (arg.data && arg.data.action) {
|
|
103
|
+
action = arg.data.action
|
|
104
|
+
data = arg.data.data
|
|
105
|
+
}
|
|
106
|
+
|
|
87
107
|
if (!this.ready) throw new Error('AuditLogService not connected')
|
|
88
108
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
tenant = tenantAndUser.tenant
|
|
94
|
-
user = tenantAndUser.user
|
|
95
|
-
} else {
|
|
109
|
+
let { tenant, user } = _getTenantAndUser()
|
|
110
|
+
|
|
111
|
+
// cds.context may not be proper on auth-related errors -> try to extract from data
|
|
112
|
+
if (tenant === ANONYMOUS && user === ANONYMOUS) {
|
|
96
113
|
try {
|
|
97
114
|
const parsed = JSON.parse(data)
|
|
98
115
|
if (parsed.tenant) {
|
|
@@ -106,8 +123,6 @@ module.exports = class AuditLogService extends cds.MessagingService {
|
|
|
106
123
|
data = JSON.stringify(parsed)
|
|
107
124
|
} catch (e) {}
|
|
108
125
|
}
|
|
109
|
-
if (!tenant) tenant = ANONYMOUS
|
|
110
|
-
if (!user) user = ANONYMOUS
|
|
111
126
|
|
|
112
127
|
// build the log
|
|
113
128
|
const entry = v2utils.buildSecurityLog(this.alc, action, data, tenant, user)
|
|
@@ -117,7 +132,16 @@ module.exports = class AuditLogService extends cds.MessagingService {
|
|
|
117
132
|
}
|
|
118
133
|
|
|
119
134
|
// REVISIT: action and success not used in auditlog v2
|
|
120
|
-
async configChangeLog(
|
|
135
|
+
async configChangeLog(arg) {
|
|
136
|
+
let { action, success, configurations } = arg
|
|
137
|
+
if (arg.data) {
|
|
138
|
+
// eslint-disable-next-line no-unused-vars
|
|
139
|
+
action = arg.data.action
|
|
140
|
+
// eslint-disable-next-line no-unused-vars
|
|
141
|
+
success = arg.data.success
|
|
142
|
+
configurations = arg.data.configurations
|
|
143
|
+
}
|
|
144
|
+
|
|
121
145
|
if (!this.ready) throw new Error('AuditLogService not connected')
|
|
122
146
|
|
|
123
147
|
const { tenant, user } = _getTenantAndUser()
|
|
@@ -17,6 +17,8 @@ let als
|
|
|
17
17
|
|
|
18
18
|
const _processorFnAccess = (accessLogs, model, req) => {
|
|
19
19
|
return ({ row, key, element, plain }) => {
|
|
20
|
+
if (row.IsActiveEntity === false) return
|
|
21
|
+
|
|
20
22
|
const entity = getRootEntity(element)
|
|
21
23
|
|
|
22
24
|
// create or augment log entry
|
|
@@ -28,7 +30,7 @@ const _processorFnAccess = (accessLogs, model, req) => {
|
|
|
28
30
|
addObjectID(accessLog, row, key)
|
|
29
31
|
} else if (category === 'DataSubjectID') {
|
|
30
32
|
addDataSubject(accessLog, row, key, entity)
|
|
31
|
-
} else if (category === 'IsPotentiallySensitive') {
|
|
33
|
+
} else if (category === 'IsPotentiallySensitive' && key in row) {
|
|
32
34
|
// add attribute
|
|
33
35
|
if (!accessLog.attributes.find(ele => ele.name === key)) accessLog.attributes.push({ name: key })
|
|
34
36
|
// REVISIT: attribute vs. attachment?
|
|
@@ -37,10 +39,11 @@ const _processorFnAccess = (accessLogs, model, req) => {
|
|
|
37
39
|
|
|
38
40
|
// add promise to determine data subject if a DataSubjectDetails entity
|
|
39
41
|
if (
|
|
40
|
-
|
|
42
|
+
(entity['@PersonalData.EntitySemantics'] === 'DataSubjectDetails' ||
|
|
43
|
+
entity['@PersonalData.EntitySemantics'] === 'Other') &&
|
|
41
44
|
accessLog.dataSubject.id.length === 0 // > id still an array -> promise not yet set
|
|
42
45
|
) {
|
|
43
|
-
addDataSubjectForDetailsEntity(row, accessLog, req, entity, model
|
|
46
|
+
addDataSubjectForDetailsEntity(row, accessLog, req, entity, model)
|
|
44
47
|
}
|
|
45
48
|
}
|
|
46
49
|
}
|
|
@@ -55,11 +58,13 @@ const _getDataAccessLogs = (data, req, tx) => {
|
|
|
55
58
|
|
|
56
59
|
const accessLogs = {}
|
|
57
60
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
if (typeof data === 'object' && data !== null) {
|
|
62
|
+
const processFn = _processorFnAccess(accessLogs, tx.model, req)
|
|
63
|
+
const data_ = Array.isArray(data) ? data : [data]
|
|
64
|
+
data_.forEach(row => {
|
|
65
|
+
templateProcessor({ processFn, row, template })
|
|
66
|
+
})
|
|
67
|
+
}
|
|
63
68
|
|
|
64
69
|
return accessLogs
|
|
65
70
|
}
|
|
@@ -69,9 +74,9 @@ const auditAccessHandler = async function (data, req) {
|
|
|
69
74
|
if (!als.ready) return
|
|
70
75
|
|
|
71
76
|
const accessLogs = _getDataAccessLogs(data, req, this)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
77
|
+
// REVISIT: a function called resolveDataSubjectPromises should not also convert an object to an array
|
|
78
|
+
let accesses = await resolveDataSubjectPromises(accessLogs)
|
|
79
|
+
accesses = accesses.filter(ele => ele.attributes.length)
|
|
75
80
|
|
|
76
81
|
if (accesses.length) await als.emit('dataAccessLog', { accesses })
|
|
77
82
|
}
|
|
@@ -73,10 +73,11 @@ const _processorFnModification = (modificationLogs, model, req, beforeWrite) =>
|
|
|
73
73
|
|
|
74
74
|
// add promise to determine data subject if a DataSubjectDetails entity
|
|
75
75
|
if (
|
|
76
|
-
|
|
76
|
+
(entity['@PersonalData.EntitySemantics'] === 'DataSubjectDetails' ||
|
|
77
|
+
entity['@PersonalData.EntitySemantics'] === 'Other') &&
|
|
77
78
|
modificationLog.dataSubject.id.length === 0 // > id still an array -> promise not yet set
|
|
78
79
|
) {
|
|
79
|
-
addDataSubjectForDetailsEntity(row, modificationLog, req, entity, model
|
|
80
|
+
addDataSubjectForDetailsEntity(row, modificationLog, req, entity, model)
|
|
80
81
|
}
|
|
81
82
|
}
|
|
82
83
|
|
|
@@ -107,7 +108,7 @@ const _calcModificationLogsHandler = async function (req, beforeWrite, that) {
|
|
|
107
108
|
|
|
108
109
|
// execute the data subject promises before going along to on phase
|
|
109
110
|
// guarantees that the reads are executed before the data is modified
|
|
110
|
-
await
|
|
111
|
+
await resolveDataSubjectPromises(modificationLogs)
|
|
111
112
|
}
|
|
112
113
|
|
|
113
114
|
const calcModificationLogsHandler4Before = function (req) {
|
|
@@ -125,9 +126,8 @@ const emitModificationHandler = async function (_, req) {
|
|
|
125
126
|
const modificationLogs = req.context._audit.modificationLogs.get(req.query)
|
|
126
127
|
const modifications = Object.keys(modificationLogs)
|
|
127
128
|
.map(k => modificationLogs[k])
|
|
128
|
-
.filter(
|
|
129
|
+
.filter(log => log.attributes.length)
|
|
129
130
|
|
|
130
|
-
await resolveDataSubjectPromises(modifications)
|
|
131
131
|
await als.emit('dataModificationLog', { modifications })
|
|
132
132
|
}
|
|
133
133
|
|
|
@@ -7,7 +7,7 @@ const ASPECTS = { CREATE: '_auditCreate', READ: '_auditRead', UPDATE: '_auditUpd
|
|
|
7
7
|
|
|
8
8
|
const getMapKeyForCurrentRequest = req => {
|
|
9
9
|
// running in srv or db layer? -> srv's req.query used as key of diff and logs maps at req.context
|
|
10
|
-
return req._tx.
|
|
10
|
+
return req._tx instanceof cds.DatabaseService ? req._.query : req.query
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
const getRootEntity = element => {
|
|
@@ -22,11 +22,9 @@ const getPick = event => {
|
|
|
22
22
|
const categories = []
|
|
23
23
|
if (!element.isAssociation && element.key) categories.push('ObjectID')
|
|
24
24
|
if (
|
|
25
|
+
!element.isAssociation &&
|
|
25
26
|
element['@PersonalData.FieldSemantics'] === 'DataSubjectID' &&
|
|
26
|
-
|
|
27
|
-
// at the moment annotation on item level is not supported
|
|
28
|
-
element.parent &&
|
|
29
|
-
element.parent['@PersonalData.EntitySemantics'] === 'DataSubject'
|
|
27
|
+
target['@PersonalData.EntitySemantics'] === 'DataSubject'
|
|
30
28
|
)
|
|
31
29
|
categories.push('DataSubjectID')
|
|
32
30
|
if (event in WRITE && element['@PersonalData.IsPotentiallyPersonal']) categories.push('IsPotentiallyPersonal')
|
|
@@ -57,7 +55,7 @@ const createLogEntry = (logs, entity, row) => {
|
|
|
57
55
|
}
|
|
58
56
|
|
|
59
57
|
const addObjectID = (log, row, key) => {
|
|
60
|
-
if (!log.dataObject.id.find(ele => ele.keyName === key))
|
|
58
|
+
if (!log.dataObject.id.find(ele => ele.keyName === key) && key !== 'IsActiveEntity')
|
|
61
59
|
log.dataObject.id.push({ keyName: key, value: String(row[key]) })
|
|
62
60
|
}
|
|
63
61
|
|
|
@@ -69,36 +67,42 @@ const addDataSubject = (log, row, key, entity) => {
|
|
|
69
67
|
}
|
|
70
68
|
}
|
|
71
69
|
|
|
72
|
-
const _addKeysToWhere = (
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
val: row[
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
70
|
+
const _addKeysToWhere = (entity, row) =>
|
|
71
|
+
Object.values(entity.keys)
|
|
72
|
+
.filter(key => !key.isAssociation && key.name !== 'IsActiveEntity')
|
|
73
|
+
.reduce((keys, key) => {
|
|
74
|
+
if (keys.length) keys.push('and')
|
|
75
|
+
keys.push({ ref: [entity.name, key.name] }, '=', { val: row[key.name] })
|
|
76
|
+
return keys
|
|
77
|
+
}, [])
|
|
78
|
+
|
|
79
|
+
const _keyColumns = entity =>
|
|
80
|
+
Object.values(entity.keys)
|
|
81
|
+
.filter(key => !key.isAssociation && key.name !== 'IsActiveEntity')
|
|
82
|
+
.map(key => key.name)
|
|
83
|
+
|
|
84
|
+
const _buildSubSelect = (model, { entity, relative, element, next }, row, previousCqn) => {
|
|
85
|
+
// relative is a parent or an entity itself
|
|
86
|
+
const childCqn = SELECT.from(entity.name)
|
|
87
|
+
.columns(_keyColumns(entity))
|
|
88
|
+
.where(relative._relations[element.name].join(element._target.name, relative.name))
|
|
88
89
|
if (previousCqn) {
|
|
89
90
|
childCqn.where('exists', previousCqn)
|
|
90
91
|
} else {
|
|
91
|
-
childCqn.where(_addKeysToWhere(
|
|
92
|
+
childCqn.where(_addKeysToWhere(entity, row))
|
|
92
93
|
}
|
|
93
|
-
if (
|
|
94
|
+
if (next) return _buildSubSelect(model, next, {}, childCqn)
|
|
94
95
|
return childCqn
|
|
95
96
|
}
|
|
96
97
|
|
|
97
|
-
const _getDataSubjectIdPromise = (
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
.
|
|
101
|
-
|
|
98
|
+
const _getDataSubjectIdPromise = ({ dataSubjectEntity, subs }, row, req, model) => {
|
|
99
|
+
const cqn = SELECT.from(dataSubjectEntity.name)
|
|
100
|
+
.columns(_keyColumns(dataSubjectEntity))
|
|
101
|
+
.where(['exists', _buildSubSelect(model, subs[0], row)])
|
|
102
|
+
// entity reused in different branches => must check all
|
|
103
|
+
for (let i = 1; i < subs.length; i++) {
|
|
104
|
+
cqn.or(['exists', _buildSubSelect(model, subs[i], row)])
|
|
105
|
+
}
|
|
102
106
|
return cds
|
|
103
107
|
.tx(req)
|
|
104
108
|
.run(cqn)
|
|
@@ -109,12 +113,12 @@ const _getDataSubjectIdPromise = (child, dataSubjectInfo, row, req, model) => {
|
|
|
109
113
|
})
|
|
110
114
|
}
|
|
111
115
|
|
|
112
|
-
const addDataSubjectForDetailsEntity = (row, log, req, entity, model
|
|
116
|
+
const addDataSubjectForDetailsEntity = (row, log, req, entity, model) => {
|
|
113
117
|
const role = entity['@PersonalData.DataSubjectRole']
|
|
114
118
|
|
|
115
|
-
const dataSubjectInfo = getDataSubject(entity, model, role
|
|
119
|
+
const dataSubjectInfo = getDataSubject(entity, model, role)
|
|
116
120
|
|
|
117
|
-
log.dataSubject.type = dataSubjectInfo.
|
|
121
|
+
log.dataSubject.type = dataSubjectInfo.dataSubjectEntity.name
|
|
118
122
|
|
|
119
123
|
/*
|
|
120
124
|
* for each req (cf. $batch with atomicity) and data subject role (e.g., customer vs supplier),
|
|
@@ -125,13 +129,18 @@ const addDataSubjectForDetailsEntity = (row, log, req, entity, model, element) =
|
|
|
125
129
|
if (!req.context._audit.dataSubjects.has(mapKey)) req.context._audit.dataSubjects.set(mapKey, new Map())
|
|
126
130
|
const map = req.context._audit.dataSubjects.get(mapKey)
|
|
127
131
|
if (map.has(role)) log.dataSubject.id = map.get(role)
|
|
128
|
-
|
|
132
|
+
// REVISIT by downward lookups row might already contain ID - some potential to optimize
|
|
133
|
+
else map.set(role, _getDataSubjectIdPromise(dataSubjectInfo, row, req, model))
|
|
129
134
|
}
|
|
130
135
|
|
|
131
|
-
const resolveDataSubjectPromises =
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
136
|
+
const resolveDataSubjectPromises = log => {
|
|
137
|
+
const logs = Object.values(log)
|
|
138
|
+
return Promise.all(logs.map(log => log.dataSubject.id)).then(IDs =>
|
|
139
|
+
logs.map((log, i) => {
|
|
140
|
+
log.dataSubject.id = IDs[i]
|
|
141
|
+
return log
|
|
142
|
+
})
|
|
143
|
+
)
|
|
135
144
|
}
|
|
136
145
|
|
|
137
146
|
module.exports = {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
const cds = require('
|
|
1
|
+
const cds = require('../cds')
|
|
2
2
|
const LOG = cds.log('app')
|
|
3
3
|
|
|
4
|
-
const _require = require('../utils/require')
|
|
5
|
-
const { UNAUTHORIZED } = require('../utils/auth')
|
|
4
|
+
const _require = require('../common/utils/require')
|
|
5
|
+
const { UNAUTHORIZED } = require('../common/utils/auth')
|
|
6
6
|
|
|
7
7
|
let passport
|
|
8
8
|
|
|
@@ -105,7 +105,24 @@ module.exports = (srv, app, options) => {
|
|
|
105
105
|
if (config.impl) {
|
|
106
106
|
// > custom middleware
|
|
107
107
|
app.use(srv.path, _require(cds.resolve(config.impl)))
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
config.strategy = Array.isArray(config.strategy) ? config.strategy : [config.strategy]
|
|
108
112
|
|
|
113
|
+
if (config.strategy.length === 1 && config.strategy[0] in { dummy: 1, mock: 1 }) {
|
|
114
|
+
// > without passport
|
|
115
|
+
const impl =
|
|
116
|
+
config.strategy[0] === 'dummy'
|
|
117
|
+
? new (require('./strategies/dummy'))()
|
|
118
|
+
: new (require('./strategies/mock'))(config.users, `mock_${srv.name}`)
|
|
119
|
+
app.use(srv.path, (req, res, next) => {
|
|
120
|
+
let user, challenge
|
|
121
|
+
impl.success = arg => (user = arg)
|
|
122
|
+
impl.fail = arg => (challenge = arg)
|
|
123
|
+
impl.authenticate(req)
|
|
124
|
+
_callback(req, res, next, undefined, user, [challenge])
|
|
125
|
+
})
|
|
109
126
|
return
|
|
110
127
|
}
|
|
111
128
|
|
|
@@ -113,10 +130,7 @@ module.exports = (srv, app, options) => {
|
|
|
113
130
|
passport = passport || _require('passport')
|
|
114
131
|
|
|
115
132
|
// initialize strategies
|
|
116
|
-
|
|
117
|
-
for (const strategy of config.strategy) {
|
|
118
|
-
_initializeStrategy(strategy, config, srv)
|
|
119
|
-
}
|
|
133
|
+
for (const strategy of config.strategy) _initializeStrategy(strategy, config, srv)
|
|
120
134
|
|
|
121
135
|
// authenticate
|
|
122
136
|
app.use(srv.path, passport.initialize())
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
const cds = require('
|
|
1
|
+
const cds = require('../../cds')
|
|
2
2
|
|
|
3
|
-
const _require = require('../../utils/require')
|
|
3
|
+
const _require = require('../../common/utils/require')
|
|
4
4
|
const uaaUtils = require('./utils/uaa')
|
|
5
5
|
const xssecUtils = require('./utils/xssec')
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
const cds = require('
|
|
1
|
+
const cds = require('../../cds')
|
|
2
2
|
|
|
3
|
-
const _require = require('../../utils/require')
|
|
3
|
+
const _require = require('../../common/utils/require')
|
|
4
4
|
|
|
5
5
|
// use _require for a better error message
|
|
6
6
|
const { BasicStrategy: BS } = _require('passport-http')
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const cds = require('
|
|
1
|
+
const cds = require('../../cds')
|
|
2
2
|
|
|
3
3
|
const CHALLENGE = 'Basic realm="Users"'
|
|
4
4
|
|
|
@@ -56,7 +56,7 @@ class MockStrategy {
|
|
|
56
56
|
|
|
57
57
|
const [scheme, base64] = authorization.split(' ')
|
|
58
58
|
if (!scheme || scheme.toLowerCase() !== 'basic') return this.fail(CHALLENGE)
|
|
59
|
-
if (!base64) return this.fail(
|
|
59
|
+
if (!base64) return this.fail(CHALLENGE)
|
|
60
60
|
|
|
61
61
|
const [id, password] = Buffer.from(base64, 'base64').toString().split(':')
|
|
62
62
|
|
|
File without changes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
const cds = require('
|
|
1
|
+
const cds = require('../../cds')
|
|
2
2
|
|
|
3
|
-
const _require = require('../../utils/require')
|
|
3
|
+
const _require = require('../../common/utils/require')
|
|
4
4
|
const uaaUtils = require('./utils/uaa')
|
|
5
5
|
const xssecUtils = require('./utils/xssec')
|
|
6
6
|
|
|
@@ -5,6 +5,8 @@ const OData = require('./OData')
|
|
|
5
5
|
|
|
6
6
|
const { alias2ref } = require('../../../common/utils/csn')
|
|
7
7
|
|
|
8
|
+
const { normalizeError } = require('../../../common/error/frontend')
|
|
9
|
+
|
|
8
10
|
function _createNewService(name, csn, defaultOptions) {
|
|
9
11
|
const reflectedModel = cds.linked(cds.compile.for.odata(csn))
|
|
10
12
|
const options = Object.assign({}, defaultOptions, { reflectedModel })
|
|
@@ -117,8 +119,11 @@ class Dispatcher {
|
|
|
117
119
|
e.message = 'Unable to get service from service map due to error: ' + e.message
|
|
118
120
|
LOG.error(e)
|
|
119
121
|
}
|
|
120
|
-
//
|
|
121
|
-
|
|
122
|
+
// clear map entry
|
|
123
|
+
this._extMap.delete(hash)
|
|
124
|
+
// return 503 to client
|
|
125
|
+
const { error } = normalizeError(Object.assign(e, { statusCode: 503 }), req)
|
|
126
|
+
return res.status(503).send({ error })
|
|
122
127
|
}
|
|
123
128
|
|
|
124
129
|
// invoke extended service, if exists
|
|
@@ -229,13 +229,6 @@ class OData {
|
|
|
229
229
|
this._odataService.use(ACTION_EXECUTE_HANDLER, _action(cdsService))
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
-
// _startPerfMeasurementOData (req) {
|
|
233
|
-
// if (req.performanceMeasurement) {
|
|
234
|
-
// const uuid = req.performanceMeasurement.uuid
|
|
235
|
-
// req.performanceMeasurement.performance.mark(`${uuid} ODataIn Start`)
|
|
236
|
-
// }
|
|
237
|
-
// }
|
|
238
|
-
|
|
239
232
|
/**
|
|
240
233
|
* Process request.
|
|
241
234
|
*
|