@sap/cds 6.8.4 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +58 -5
- package/README.md +0 -1
- package/bin/cds-serve.js +50 -3
- package/bin/deploy/to-hana.js +1 -0
- package/bin/serve.js +16 -20
- package/lib/auth/basic-auth.js +6 -4
- package/lib/auth/index.js +4 -3
- package/lib/auth/jwt-auth.js +2 -5
- package/lib/compile/cds-compile.js +34 -89
- package/lib/compile/cdsc.js +11 -0
- package/lib/compile/etc/properties.js +2 -2
- package/lib/compile/for/lean_drafts.js +36 -69
- package/lib/compile/for/nodejs.js +2 -1
- package/lib/compile/load.js +1 -1
- package/lib/compile/minify.js +2 -0
- package/lib/compile/to/csn.js +74 -0
- package/{bin/build/provider/hana/2tabledata.js → lib/compile/to/hdbtabledata.js} +4 -6
- package/lib/compile/to/json.js +1 -1
- package/lib/compile/to/sql.js +8 -6
- package/lib/dbs/cds-deploy.js +174 -114
- package/lib/env/cds-env.js +64 -79
- package/lib/env/cds-requires.js +11 -28
- package/lib/env/defaults.js +13 -3
- package/lib/env/plugins.js +1 -12
- package/lib/env/presets.js +25 -21
- package/lib/index.js +121 -147
- package/lib/{core/reflect.js → linked/models.js} +2 -2
- package/lib/{core/infer.js → linked/queries.js} +2 -0
- package/lib/{core/index.js → linked/types.js} +2 -1
- package/lib/log/cds-error.js +13 -7
- package/lib/log/format/cf.js +1 -1
- package/lib/plugins.js +49 -0
- package/lib/ql/Query.js +0 -9
- package/lib/ql/STREAM.js +0 -1
- package/lib/req/context.js +2 -7
- package/lib/req/request.js +6 -2
- package/lib/req/response.js +23 -10
- package/lib/srv/middlewares/ctx-model.js +1 -1
- package/lib/srv/middlewares/errors.js +1 -1
- package/lib/srv/protocols/_legacy.js +1 -0
- package/lib/srv/protocols/graphql.js +7 -16
- package/lib/srv/protocols/index.js +59 -45
- package/lib/srv/protocols/odata-v2-proxy.js +2 -70
- package/lib/srv/srv-api.js +9 -3
- package/lib/srv/srv-dispatch.js +12 -9
- package/lib/srv/srv-models.js +4 -21
- package/lib/srv/srv-tx.js +15 -12
- package/lib/utils/cds-test.js +14 -9
- package/lib/utils/cds-utils.js +2 -12
- package/lib/utils/check-version.js +17 -0
- package/{bin/build → lib/utils}/csv-reader.js +23 -24
- package/libx/_runtime/auth/index.js +27 -23
- package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +15 -72
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +0 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +33 -63
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +14 -18
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +15 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +5 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +37 -40
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/updateToCQN.js +7 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +101 -38
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/errors/AbstractError.js +5 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/ValueConverter.js +2 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/ResourceJsonDeserializer.js +9 -8
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +15 -11
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +4 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +5 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +1 -123
- package/libx/_runtime/cds-services/services/Service.js +79 -107
- package/libx/_runtime/cds-services/services/utils/columns.js +23 -19
- package/libx/_runtime/cds-services/services/utils/compareJson.js +11 -1
- package/libx/_runtime/cds-services/services/utils/differ.js +7 -2
- package/libx/_runtime/cds-services/util/assert.js +65 -2
- package/libx/_runtime/common/composition/data.js +1 -0
- package/libx/_runtime/common/generic/auth/expand.js +1 -1
- package/libx/_runtime/common/generic/auth/restrict.js +5 -10
- package/libx/_runtime/common/generic/auth/restrictions.js +40 -0
- package/libx/_runtime/common/generic/auth/utils.js +1 -2
- package/libx/_runtime/common/generic/crud.js +32 -16
- package/libx/_runtime/common/generic/etag.js +133 -104
- package/libx/_runtime/common/generic/input.js +6 -21
- package/libx/_runtime/common/generic/put.js +1 -1
- package/libx/_runtime/common/generic/stream.js +52 -0
- package/libx/_runtime/common/generic/temporal.js +25 -8
- package/libx/_runtime/common/i18n/messages.properties +0 -2
- package/libx/_runtime/common/utils/cqn.js +1 -1
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +5 -2
- package/libx/_runtime/common/utils/csn.js +0 -51
- package/libx/_runtime/common/utils/etag.js +30 -0
- package/libx/_runtime/common/utils/keys.js +1 -1
- package/libx/_runtime/common/utils/normalizeTimestamp.js +25 -0
- package/libx/_runtime/common/utils/path.js +1 -1
- package/libx/_runtime/common/utils/resolveView.js +2 -1
- package/libx/_runtime/common/utils/rewriteAsterisks.js +6 -4
- package/libx/_runtime/common/utils/search2cqn4sql.js +12 -16
- package/libx/_runtime/common/utils/stream.js +140 -0
- package/libx/_runtime/common/utils/streamProp.js +29 -12
- package/libx/_runtime/common/utils/templateProcessorPathSerializer.js +0 -2
- package/libx/_runtime/db/generic/index.js +0 -2
- package/libx/_runtime/db/query/delete.js +2 -2
- package/libx/_runtime/db/query/insert.js +2 -2
- package/libx/_runtime/db/query/read.js +2 -2
- package/libx/_runtime/db/query/run.js +2 -2
- package/libx/_runtime/db/query/update.js +2 -2
- package/libx/_runtime/db/sql-builder/BaseBuilder.js +0 -6
- package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +23 -12
- package/libx/_runtime/db/sql-builder/FunctionBuilder.js +18 -6
- package/libx/_runtime/db/sql-builder/InsertBuilder.js +1 -0
- package/libx/_runtime/db/sql-builder/SelectBuilder.js +3 -7
- package/libx/_runtime/db/sql-builder/UpsertBuilder.js +1 -0
- package/libx/_runtime/db/utils/normalizeTimeData.js +7 -3
- package/libx/_runtime/fiori/draft.js +2 -0
- package/libx/_runtime/fiori/generic/activate.js +8 -9
- package/libx/_runtime/fiori/generic/before.js +30 -20
- package/libx/_runtime/fiori/generic/cancel.js +5 -3
- package/libx/_runtime/fiori/generic/delete.js +5 -3
- package/libx/_runtime/fiori/generic/edit.js +7 -7
- package/libx/_runtime/fiori/generic/index.js +10 -16
- package/libx/_runtime/fiori/generic/new.js +5 -3
- package/libx/_runtime/fiori/generic/patch.js +11 -8
- package/libx/_runtime/fiori/generic/prepare.js +13 -6
- package/libx/_runtime/fiori/generic/read.js +12 -6
- package/libx/_runtime/fiori/lean-draft.js +207 -152
- package/libx/_runtime/fiori/utils/delete.js +10 -5
- package/libx/_runtime/fiori/utils/req.js +17 -5
- package/libx/_runtime/fiori/utils/stream.js +36 -0
- package/libx/_runtime/hana/Service.js +12 -9
- package/libx/_runtime/hana/conversion.js +10 -15
- package/libx/_runtime/hana/driver.js +2 -0
- package/libx/_runtime/hana/execute.js +28 -6
- package/libx/_runtime/hana/pool.js +36 -122
- package/libx/_runtime/hana/search2cqn4sql.js +34 -36
- package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +2 -6
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +3 -1
- package/libx/_runtime/messaging/enterprise-messaging.js +10 -58
- package/libx/_runtime/messaging/outbox/utils.js +1 -1
- package/libx/_runtime/remote/Service.js +20 -1
- package/libx/_runtime/remote/utils/client.js +3 -5
- package/libx/_runtime/sqlite/Service.js +4 -6
- package/libx/_runtime/sqlite/conversion.js +3 -13
- package/libx/_runtime/sqlite/customBuilder/CustomFunctionBuilder.js +9 -6
- package/libx/_runtime/sqlite/customBuilder/CustomUpsertBuilder.js +6 -1
- package/libx/_runtime/sqlite/execute.js +5 -16
- package/libx/odata/afterburner.js +22 -6
- package/libx/odata/grammar.pegjs +6 -1
- package/libx/odata/parser.js +1 -1
- package/libx/rest/RestAdapter.js +16 -9
- package/libx/rest/RestRequest.js +1 -1
- package/libx/rest/middleware/input.js +2 -1
- package/libx/rest/middleware/operation.js +1 -0
- package/libx/rest/middleware/parse.js +3 -2
- package/libx/rest/middleware/payload.js +9 -8
- package/libx/rest/middleware/read.js +1 -0
- package/package.json +9 -16
- package/app/fiori/preview.js +0 -270
- package/app/fiori/routes.js +0 -59
- package/bin/build/buildTaskEngine.js +0 -360
- package/bin/build/buildTaskFactory.js +0 -283
- package/bin/build/buildTaskHandler.js +0 -241
- package/bin/build/buildTaskProvider.js +0 -22
- package/bin/build/buildTaskProviderFactory.js +0 -175
- package/bin/build/cds.js +0 -5
- package/bin/build/constants.js +0 -66
- package/bin/build/index.js +0 -58
- package/bin/build/provider/buildTaskHandlerEdmx.js +0 -82
- package/bin/build/provider/buildTaskHandlerFeatureToggles.js +0 -131
- package/bin/build/provider/buildTaskHandlerInternal.js +0 -254
- package/bin/build/provider/buildTaskProviderInternal.js +0 -383
- package/bin/build/provider/fiori/index.js +0 -171
- package/bin/build/provider/hana/2migration.js +0 -179
- package/bin/build/provider/hana/index.js +0 -505
- package/bin/build/provider/hana/migrationtable.js +0 -472
- package/bin/build/provider/hana/template/.hdiconfig-haas +0 -163
- package/bin/build/provider/hana/template/.hdiconfig-hanacloud +0 -137
- package/bin/build/provider/hana/template/.hdinamespace +0 -4
- package/bin/build/provider/hana/template/package.json +0 -12
- package/bin/build/provider/hana/template/undeploy.json +0 -5
- package/bin/build/provider/java/index.js +0 -111
- package/bin/build/provider/java-cf/index.js +0 -1
- package/bin/build/provider/mtx/index.js +0 -268
- package/bin/build/provider/mtx/resourcesTarBuilder.js +0 -95
- package/bin/build/provider/mtx-extension/index.js +0 -131
- package/bin/build/provider/mtx-sidecar/index.js +0 -137
- package/bin/build/provider/node-cf/index.js +0 -1
- package/bin/build/provider/nodejs/index.js +0 -192
- package/bin/build/util.js +0 -299
- package/bin/cds.js +0 -125
- package/bin/deploy/to-hana/cfUtil.js +0 -355
- package/bin/deploy/to-hana/gitUtil.js +0 -57
- package/bin/deploy/to-hana/hana.js +0 -306
- package/bin/deploy/to-hana/hdiDeployUtil.js +0 -153
- package/bin/deploy/to-hana/index.js +0 -16
- package/bin/deploy/to-hana/mtaUtil.js +0 -170
- package/bin/mtx/in-cds.js +0 -17
- package/bin/plugins.js +0 -32
- package/bin/run.js +0 -24
- package/bin/utils/log.js +0 -24
- package/bin/version.js +0 -178
- package/libx/_runtime/audit/Service.js +0 -222
- package/libx/_runtime/audit/generic/personal/access.js +0 -61
- package/libx/_runtime/audit/generic/personal/index.js +0 -56
- package/libx/_runtime/audit/generic/personal/modification.js +0 -132
- package/libx/_runtime/audit/generic/personal/utils.js +0 -186
- package/libx/_runtime/audit/utils/log.js +0 -23
- package/libx/_runtime/audit/utils/v2.js +0 -176
- package/libx/_runtime/db/data-conversion/timestamp.js +0 -9
- package/libx/_runtime/db/generic/integrity.js +0 -455
- package/srv/audit-log.cds +0 -87
- package/srv/mtx.cds +0 -2
- package/srv/mtx.js +0 -8
- /package/lib/{core → linked}/classes.js +0 -0
- /package/lib/{core → linked}/entities.js +0 -0
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
const cds = require('../../../cds')
|
|
2
|
-
|
|
3
|
-
const getTemplate = require('../../../common/utils/template')
|
|
4
|
-
const templateProcessor = require('../../../common/utils/templateProcessor')
|
|
5
|
-
|
|
6
|
-
const {
|
|
7
|
-
getMapKeyForCurrentRequest,
|
|
8
|
-
getRootEntity,
|
|
9
|
-
getPick,
|
|
10
|
-
createLogEntry,
|
|
11
|
-
addObjectID,
|
|
12
|
-
addDataSubject,
|
|
13
|
-
addDataSubjectForDetailsEntity,
|
|
14
|
-
resolveDataSubjectPromises
|
|
15
|
-
} = require('./utils')
|
|
16
|
-
|
|
17
|
-
let auditLogService
|
|
18
|
-
|
|
19
|
-
const attachDiffToContextHandler = async function (req) {
|
|
20
|
-
// store diff in audit data structure at context
|
|
21
|
-
const _audit = req.context._audit || (req.context._audit = {})
|
|
22
|
-
if (!_audit.diffs) _audit.diffs = new Map()
|
|
23
|
-
_audit.diffs.set(req._.query, await req.diff())
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const _getOldAndNew = (action, row, key) => {
|
|
27
|
-
let oldValue = action === 'Create' ? null : row._old && row._old[key]
|
|
28
|
-
if (oldValue === undefined) oldValue = null
|
|
29
|
-
let newValue = action === 'Delete' ? null : row[key]
|
|
30
|
-
if (newValue === undefined) newValue = null
|
|
31
|
-
return { oldValue, newValue }
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const _addAttribute = (log, action, row, key) => {
|
|
35
|
-
if (!log.attributes.find(ele => ele.name === key)) {
|
|
36
|
-
const { oldValue, newValue } = _getOldAndNew(action, row, key)
|
|
37
|
-
if (oldValue !== newValue)
|
|
38
|
-
log.attributes.push({ name: key, oldValue: String(oldValue), newValue: String(newValue) })
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const _processorFnModification = (modificationLogs, model, req, beforeWrite) => elementInfo => {
|
|
43
|
-
if (!elementInfo.row._op) return
|
|
44
|
-
|
|
45
|
-
const { row, key, element, plain } = elementInfo
|
|
46
|
-
|
|
47
|
-
// delete in before phase, create and update in after phase
|
|
48
|
-
if ((row._op === 'delete') !== !!beforeWrite) return
|
|
49
|
-
|
|
50
|
-
const entity = getRootEntity(element)
|
|
51
|
-
const action = row._op[0].toUpperCase() + row._op.slice(1)
|
|
52
|
-
|
|
53
|
-
// create or augment log entry
|
|
54
|
-
const modificationLog = createLogEntry(modificationLogs, entity, row)
|
|
55
|
-
|
|
56
|
-
// process categories
|
|
57
|
-
for (const category of plain.categories) {
|
|
58
|
-
if (category === 'ObjectID') {
|
|
59
|
-
addObjectID(modificationLog, row, key)
|
|
60
|
-
} else if (category === 'DataSubjectID') {
|
|
61
|
-
addDataSubject(modificationLog, row, key, entity)
|
|
62
|
-
} else if (category === 'IsPotentiallyPersonal' || category === 'IsPotentiallySensitive') {
|
|
63
|
-
_addAttribute(modificationLog, action, row, key)
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// add promise to determine data subject if a DataSubjectDetails entity
|
|
68
|
-
if (
|
|
69
|
-
(entity['@PersonalData.EntitySemantics'] === 'DataSubjectDetails' ||
|
|
70
|
-
entity['@PersonalData.EntitySemantics'] === 'Other') &&
|
|
71
|
-
modificationLog.dataSubject.id.length === 0 // > id still an array -> promise not yet set
|
|
72
|
-
) {
|
|
73
|
-
addDataSubjectForDetailsEntity(row, modificationLog, req, entity, model)
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const _getDataModificationLogs = (req, tx, diff, beforeWrite) => {
|
|
78
|
-
const template = getTemplate(
|
|
79
|
-
`personal_${req.event}`.toLowerCase(),
|
|
80
|
-
Object.assign({ name: req.target._service.name, model: tx.model }),
|
|
81
|
-
req.target,
|
|
82
|
-
{ pick: getPick(req.event) }
|
|
83
|
-
)
|
|
84
|
-
|
|
85
|
-
const modificationLogs = {}
|
|
86
|
-
const processFn = _processorFnModification(modificationLogs, tx.model, req, beforeWrite)
|
|
87
|
-
templateProcessor({ processFn, row: diff, template })
|
|
88
|
-
|
|
89
|
-
return modificationLogs
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const _calcModificationLogsHandler = async function (req, beforeWrite, that) {
|
|
93
|
-
const mapKey = getMapKeyForCurrentRequest(req)
|
|
94
|
-
|
|
95
|
-
const _audit = req.context._audit || (req.context._audit = {})
|
|
96
|
-
const modificationLogs = _getDataModificationLogs(req, that, _audit.diffs.get(mapKey), beforeWrite)
|
|
97
|
-
|
|
98
|
-
// store modificationLogs in audit data structure at context
|
|
99
|
-
if (!_audit.modificationLogs) _audit.modificationLogs = new Map()
|
|
100
|
-
const existingLogs = _audit.modificationLogs.get(mapKey) || {}
|
|
101
|
-
_audit.modificationLogs.set(mapKey, Object.assign(existingLogs, modificationLogs))
|
|
102
|
-
|
|
103
|
-
// execute the data subject promises before going along to on phase
|
|
104
|
-
// guarantees that the reads are executed before the data is modified
|
|
105
|
-
await resolveDataSubjectPromises(modificationLogs)
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const calcModificationLogsHandler4Before = function (req) {
|
|
109
|
-
return _calcModificationLogsHandler(req, true, this)
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const calcModificationLogsHandler4After = function (_, req) {
|
|
113
|
-
return _calcModificationLogsHandler(req, false, this)
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const emitModificationHandler = async function (_, req) {
|
|
117
|
-
auditLogService = auditLogService || (await cds.connect.to('audit-log'))
|
|
118
|
-
|
|
119
|
-
const modificationLogs = req.context._audit.modificationLogs.get(req.query)
|
|
120
|
-
const modifications = Object.keys(modificationLogs)
|
|
121
|
-
.map(k => modificationLogs[k])
|
|
122
|
-
.filter(log => log.attributes.length)
|
|
123
|
-
|
|
124
|
-
await auditLogService.emit('dataModificationLog', { modifications })
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
module.exports = {
|
|
128
|
-
attachDiffToContextHandler,
|
|
129
|
-
calcModificationLogsHandler4Before,
|
|
130
|
-
calcModificationLogsHandler4After,
|
|
131
|
-
emitModificationHandler
|
|
132
|
-
}
|
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
const cds = require('../../../cds')
|
|
2
|
-
|
|
3
|
-
const { getDataSubject } = require('../../../common/utils/csn')
|
|
4
|
-
|
|
5
|
-
const WRITE = { CREATE: 1, UPDATE: 1, DELETE: 1 }
|
|
6
|
-
|
|
7
|
-
const getMapKeyForCurrentRequest = req => {
|
|
8
|
-
// running in srv or db layer? -> srv's req.query used as key of diff and logs maps at req.context
|
|
9
|
-
// REVISIT: req._tx should not be used like that!
|
|
10
|
-
return req.tx.isDatabaseService ? req._.query : req.query
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const getRootEntity = element => {
|
|
14
|
-
let entity = element.parent
|
|
15
|
-
while (entity.kind !== 'entity') entity = entity.parent
|
|
16
|
-
return entity
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const _hasPersonalData = e => {
|
|
20
|
-
if (!e['@PersonalData.DataSubjectRole']) return
|
|
21
|
-
if (!e['@PersonalData.EntitySemantics']) return
|
|
22
|
-
return !!Object.values(e.elements).some(
|
|
23
|
-
e => e['@PersonalData.IsPotentiallyPersonal'] || e['@PersonalData.IsPotentiallySensitive']
|
|
24
|
-
)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const auditAnnotations = {
|
|
28
|
-
CREATE: '@AuditLog.Operation.Insert',
|
|
29
|
-
UPDATE: '@AuditLog.Operation.Update',
|
|
30
|
-
DELETE: '@AuditLog.Operation.Delete',
|
|
31
|
-
READ: '@AuditLog.Operation.Read'
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const getPick = event => {
|
|
35
|
-
return (element, target) => {
|
|
36
|
-
if (!_hasPersonalData(target)) return
|
|
37
|
-
if (!auditAnnotations[event] || !target[auditAnnotations[event]]) return
|
|
38
|
-
|
|
39
|
-
const categories = []
|
|
40
|
-
if (!element.isAssociation && element.key) categories.push('ObjectID')
|
|
41
|
-
if (
|
|
42
|
-
!element.isAssociation &&
|
|
43
|
-
element['@PersonalData.FieldSemantics'] === 'DataSubjectID' &&
|
|
44
|
-
target['@PersonalData.EntitySemantics'] === 'DataSubject'
|
|
45
|
-
)
|
|
46
|
-
categories.push('DataSubjectID')
|
|
47
|
-
if (event in WRITE && element['@PersonalData.IsPotentiallyPersonal']) categories.push('IsPotentiallyPersonal')
|
|
48
|
-
if (element['@PersonalData.IsPotentiallySensitive']) categories.push('IsPotentiallySensitive')
|
|
49
|
-
if (categories.length) return { categories }
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const _getHash = (entity, row) => {
|
|
54
|
-
return `${entity.name}(${Object.keys(entity.keys)
|
|
55
|
-
.map(k => `${k}=${row[k]}`)
|
|
56
|
-
.join(',')})`
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const createLogEntry = (logs, entity, row) => {
|
|
60
|
-
const hash = _getHash(entity, row)
|
|
61
|
-
let log = logs[hash]
|
|
62
|
-
if (!log) {
|
|
63
|
-
logs[hash] = {
|
|
64
|
-
dataObject: { type: entity.name, id: [] },
|
|
65
|
-
dataSubject: { id: [], role: entity['@PersonalData.DataSubjectRole'] },
|
|
66
|
-
attributes: [],
|
|
67
|
-
attachments: []
|
|
68
|
-
}
|
|
69
|
-
log = logs[hash]
|
|
70
|
-
}
|
|
71
|
-
return log
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const addObjectID = (log, row, key) => {
|
|
75
|
-
if (!log.dataObject.id.find(ele => ele.keyName === key) && key !== 'IsActiveEntity')
|
|
76
|
-
log.dataObject.id.push({ keyName: key, value: String(row[key]) })
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const addDataSubject = (log, row, key, entity) => {
|
|
80
|
-
if (!log.dataSubject.type) log.dataSubject.type = entity.name
|
|
81
|
-
if (!log.dataSubject.id.find(ele => ele.key === key)) {
|
|
82
|
-
const value = row[key] || (row._old && row._old[key])
|
|
83
|
-
log.dataSubject.id.push({ keyName: key, value: String(value) })
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const _addKeysToWhere = (keys, row, alias) =>
|
|
88
|
-
keys
|
|
89
|
-
.filter(key => !key.isAssociation && key.name !== 'IsActiveEntity')
|
|
90
|
-
.reduce((keys, key) => {
|
|
91
|
-
if (keys.length) keys.push('and')
|
|
92
|
-
keys.push({ ref: [alias, key.name] }, '=', { val: row[key.name] })
|
|
93
|
-
return keys
|
|
94
|
-
}, [])
|
|
95
|
-
|
|
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
|
-
|
|
101
|
-
const _buildSubSelect = (model, { entity, relative, element, next }, row, previousCqn) => {
|
|
102
|
-
// relative is a parent or an entity itself
|
|
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
|
-
|
|
116
|
-
if (previousCqn) {
|
|
117
|
-
childCqn.where('exists', previousCqn)
|
|
118
|
-
} else {
|
|
119
|
-
childCqn.where(_addKeysToWhere(keys, row, as))
|
|
120
|
-
}
|
|
121
|
-
if (next) return _buildSubSelect(model, next, {}, childCqn)
|
|
122
|
-
return childCqn
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const _getDataSubjectIdPromise = ({ dataSubjectEntity, subs }, row, req, model) => {
|
|
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))
|
|
131
|
-
.where(['exists', _buildSubSelect(model, subs[0], row)])
|
|
132
|
-
// entity reused in different branches => must check all
|
|
133
|
-
for (let i = 1; i < subs.length; i++) {
|
|
134
|
-
cqn.or(['exists', _buildSubSelect(model, subs[i], row)])
|
|
135
|
-
}
|
|
136
|
-
return cds
|
|
137
|
-
.tx(req)
|
|
138
|
-
.run(cqn)
|
|
139
|
-
.then(res => {
|
|
140
|
-
const id = []
|
|
141
|
-
for (const k in res[0]) id.push({ keyName: k, value: String(res[0][k]) })
|
|
142
|
-
return id
|
|
143
|
-
})
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const addDataSubjectForDetailsEntity = (row, log, req, entity, model) => {
|
|
147
|
-
const role = entity['@PersonalData.DataSubjectRole']
|
|
148
|
-
|
|
149
|
-
const dataSubjectInfo = getDataSubject(entity, model, role)
|
|
150
|
-
|
|
151
|
-
log.dataSubject.type = dataSubjectInfo.dataSubjectEntity.name
|
|
152
|
-
|
|
153
|
-
/*
|
|
154
|
-
* for each req (cf. $batch with atomicity) and data subject role (e.g., customer vs supplier),
|
|
155
|
-
* store (in audit data structure at context) and reuse a single promise to look up the respective data subject
|
|
156
|
-
*/
|
|
157
|
-
const mapKey = getMapKeyForCurrentRequest(req)
|
|
158
|
-
const _audit = req.context._audit || (req.context._audit = {})
|
|
159
|
-
if (!_audit.dataSubjects) _audit.dataSubjects = new Map()
|
|
160
|
-
if (!_audit.dataSubjects.has(mapKey)) _audit.dataSubjects.set(mapKey, new Map())
|
|
161
|
-
const map = _audit.dataSubjects.get(mapKey)
|
|
162
|
-
if (map.has(role)) log.dataSubject.id = map.get(role)
|
|
163
|
-
// REVISIT by downward lookups row might already contain ID - some potential to optimize
|
|
164
|
-
else map.set(role, _getDataSubjectIdPromise(dataSubjectInfo, row, req, model))
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const resolveDataSubjectPromises = log => {
|
|
168
|
-
const logs = Object.values(log)
|
|
169
|
-
return Promise.all(logs.map(log => log.dataSubject.id)).then(IDs =>
|
|
170
|
-
logs.map((log, i) => {
|
|
171
|
-
log.dataSubject.id = IDs[i]
|
|
172
|
-
return log
|
|
173
|
-
})
|
|
174
|
-
)
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
module.exports = {
|
|
178
|
-
getMapKeyForCurrentRequest,
|
|
179
|
-
getRootEntity,
|
|
180
|
-
getPick,
|
|
181
|
-
createLogEntry,
|
|
182
|
-
addObjectID,
|
|
183
|
-
addDataSubject,
|
|
184
|
-
addDataSubjectForDetailsEntity,
|
|
185
|
-
resolveDataSubjectPromises
|
|
186
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
function getObjectAndDataSubject(entry) {
|
|
2
|
-
const { dataObject, dataSubject } = entry
|
|
3
|
-
dataObject.id = dataObject.id.reduce((acc, cur) => {
|
|
4
|
-
acc[cur.keyName] = cur.value
|
|
5
|
-
return acc
|
|
6
|
-
}, {})
|
|
7
|
-
if (dataSubject) {
|
|
8
|
-
dataSubject.id = dataSubject.id.reduce((acc, cur) => {
|
|
9
|
-
acc[cur.keyName] = cur.value
|
|
10
|
-
return acc
|
|
11
|
-
}, {})
|
|
12
|
-
}
|
|
13
|
-
return { dataObject, dataSubject }
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function getAttributeToLog(ele) {
|
|
17
|
-
return { name: ele.name, old: ele.oldValue || 'null', new: ele.newValue || 'null' }
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
module.exports = {
|
|
21
|
-
getObjectAndDataSubject,
|
|
22
|
-
getAttributeToLog
|
|
23
|
-
}
|
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
const cds = require('../../cds')
|
|
2
|
-
const LOG = cds.log('audit-log')
|
|
3
|
-
const { getObjectAndDataSubject, getAttributeToLog } = require('./log')
|
|
4
|
-
|
|
5
|
-
async function connect(credentials, securityContext) {
|
|
6
|
-
let auditLogging
|
|
7
|
-
|
|
8
|
-
try {
|
|
9
|
-
// eslint-disable-next-line cds/no-missing-dependencies -- needs to be added by app dev
|
|
10
|
-
auditLogging = require('@sap/audit-logging')
|
|
11
|
-
} catch (error) {
|
|
12
|
-
// not able to require lib -> no audit logging ootb
|
|
13
|
-
return Promise.resolve()
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
try {
|
|
17
|
-
return await auditLogging.v2(credentials, securityContext)
|
|
18
|
-
} catch (error) {
|
|
19
|
-
LOG._warn && LOG.warn('Unable to initialize audit-logging client with error:', error)
|
|
20
|
-
return Promise.resolve()
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function sendDataAccessLog(entry) {
|
|
25
|
-
return new Promise((resolve, reject) => {
|
|
26
|
-
entry.log(function (err) {
|
|
27
|
-
if (err && LOG._warn) {
|
|
28
|
-
err.message = `Writing data access log failed with error: ${err.message}`
|
|
29
|
-
return reject(err)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
resolve()
|
|
33
|
-
})
|
|
34
|
-
})
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function sendDataModificationLog(entry) {
|
|
38
|
-
return new Promise((resolve, reject) => {
|
|
39
|
-
entry.logPrepare(function (err) {
|
|
40
|
-
if (err) {
|
|
41
|
-
err.message = `Preparing data modification log failed with error: ${err.message}`
|
|
42
|
-
return reject(err)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
entry.logSuccess(function (err) {
|
|
46
|
-
if (err) {
|
|
47
|
-
err.message = `Writing data modification log failed with error: ${err.message}`
|
|
48
|
-
return reject(err)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
resolve()
|
|
52
|
-
})
|
|
53
|
-
})
|
|
54
|
-
})
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function sendSecurityLog(entry) {
|
|
58
|
-
return new Promise((resolve, reject) => {
|
|
59
|
-
entry.log(function (err) {
|
|
60
|
-
if (err) {
|
|
61
|
-
err.message = `Writing security log failed with error: ${err.message}`
|
|
62
|
-
return reject(err)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
resolve()
|
|
66
|
-
})
|
|
67
|
-
})
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function sendConfigChangeLog(entry) {
|
|
71
|
-
return new Promise((resolve, reject) => {
|
|
72
|
-
entry.logPrepare(function (err) {
|
|
73
|
-
if (err) {
|
|
74
|
-
err.message = `Preparing configuration change log failed with error: ${err.message}`
|
|
75
|
-
return reject(err)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
entry.logSuccess(function (err) {
|
|
79
|
-
if (err) {
|
|
80
|
-
err.message = `Writing configuration change log failed with error: ${err.message}`
|
|
81
|
-
return reject(err)
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
resolve()
|
|
85
|
-
})
|
|
86
|
-
})
|
|
87
|
-
})
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function buildDataAccessLogs(auditLogClient, accesses, tenant, user) {
|
|
91
|
-
const entries = []
|
|
92
|
-
const errors = []
|
|
93
|
-
|
|
94
|
-
for (const access of accesses) {
|
|
95
|
-
try {
|
|
96
|
-
const { dataObject, dataSubject } = getObjectAndDataSubject(access)
|
|
97
|
-
const entry = auditLogClient.read(dataObject).dataSubject(dataSubject).by(user)
|
|
98
|
-
if (tenant) entry.tenant(tenant)
|
|
99
|
-
for (const each of access.attributes) entry.attribute(each)
|
|
100
|
-
if (access.attachments) for (const each of access.attachments) entry.attachment(each)
|
|
101
|
-
entries.push(entry)
|
|
102
|
-
} catch (err) {
|
|
103
|
-
err.message = `Building data access log failed with error: ${err.message}`
|
|
104
|
-
errors.push(err)
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return { entries, errors }
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function buildDataModificationLogs(auditLogClient, modifications, tenant, user) {
|
|
112
|
-
const entries = []
|
|
113
|
-
const errors = []
|
|
114
|
-
|
|
115
|
-
for (const modification of modifications) {
|
|
116
|
-
try {
|
|
117
|
-
const { dataObject, dataSubject } = getObjectAndDataSubject(modification)
|
|
118
|
-
const entry = auditLogClient.update(dataObject).dataSubject(dataSubject).by(user)
|
|
119
|
-
if (tenant) entry.tenant(tenant)
|
|
120
|
-
for (const each of modification.attributes) entry.attribute(getAttributeToLog(each))
|
|
121
|
-
entries.push(entry)
|
|
122
|
-
} catch (err) {
|
|
123
|
-
err.message = `Building data modification log failed with error: ${err.message}`
|
|
124
|
-
errors.push(err)
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
return { entries, errors }
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function buildSecurityLog(auditLogClient, action, data, tenant, user) {
|
|
132
|
-
let entry
|
|
133
|
-
|
|
134
|
-
try {
|
|
135
|
-
entry = auditLogClient.securityMessage('action: %s, data: %s', action, data)
|
|
136
|
-
if (tenant) entry.tenant(tenant)
|
|
137
|
-
if (user) entry.by(user)
|
|
138
|
-
} catch (err) {
|
|
139
|
-
err.message = `Building security log failed with error: ${err.message}`
|
|
140
|
-
throw err
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
return entry
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function buildConfigChangeLogs(auditLogClient, configurations, tenant, user) {
|
|
147
|
-
const entries = []
|
|
148
|
-
const errors = []
|
|
149
|
-
|
|
150
|
-
for (const configuration of configurations) {
|
|
151
|
-
try {
|
|
152
|
-
const { dataObject } = getObjectAndDataSubject(configuration)
|
|
153
|
-
const entry = auditLogClient.configurationChange(dataObject).by(user)
|
|
154
|
-
if (tenant) entry.tenant(tenant)
|
|
155
|
-
for (const each of configuration.attributes) entry.attribute(getAttributeToLog(each))
|
|
156
|
-
entries.push(entry)
|
|
157
|
-
} catch (err) {
|
|
158
|
-
err.message = `Building configuration change log failed with error: ${err.message}`
|
|
159
|
-
errors.push(err)
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
return { entries, errors }
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
module.exports = {
|
|
167
|
-
connect,
|
|
168
|
-
buildDataAccessLogs,
|
|
169
|
-
buildDataModificationLogs,
|
|
170
|
-
buildSecurityLog,
|
|
171
|
-
buildConfigChangeLogs,
|
|
172
|
-
sendDataAccessLog,
|
|
173
|
-
sendDataModificationLog,
|
|
174
|
-
sendSecurityLog,
|
|
175
|
-
sendConfigChangeLog
|
|
176
|
-
}
|