@cap-js/audit-logging 0.8.1 → 0.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 +8 -0
- package/lib/modification.js +6 -6
- package/lib/utils.js +48 -25
- package/package.json +1 -1
- package/srv/log2restv2.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
This project adheres to [Semantic Versioning](http://semver.org/).
|
|
5
5
|
The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
6
6
|
|
|
7
|
+
## Version 0.8.2 - 2024-11-27
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Erroneous modification log for non-updated key properties
|
|
12
|
+
- Error during non-modifying queries on database level
|
|
13
|
+
- Specify charset UTF-8 for requests to SAP Audit Log Service
|
|
14
|
+
|
|
7
15
|
## Version 0.8.1 - 2024-09-13
|
|
8
16
|
|
|
9
17
|
### Fixed
|
package/lib/modification.js
CHANGED
|
@@ -76,7 +76,7 @@ const addDiffToCtx = async function (req) {
|
|
|
76
76
|
if (!_audit.diffs) _audit.diffs = new Map()
|
|
77
77
|
|
|
78
78
|
// get diff
|
|
79
|
-
let diff = await req.diff()
|
|
79
|
+
let diff = (await req.diff()) || {}
|
|
80
80
|
diff = _getDataWithAppliedTransitions(diff, req)
|
|
81
81
|
|
|
82
82
|
// add keys, if necessary
|
|
@@ -88,9 +88,9 @@ const addDiffToCtx = async function (req) {
|
|
|
88
88
|
}
|
|
89
89
|
addDiffToCtx._initial = true
|
|
90
90
|
|
|
91
|
-
const _getOldAndNew = (action, row, key) => {
|
|
91
|
+
const _getOldAndNew = (action, row, key, entity) => {
|
|
92
92
|
let oldValue = action === 'Create' ? null : row._old && row._old[key]
|
|
93
|
-
if (oldValue === undefined) oldValue = null
|
|
93
|
+
if (oldValue === undefined) oldValue = action === 'Update' && key in entity.keys ? row[key] : null
|
|
94
94
|
else if (Array.isArray(oldValue)) oldValue = JSON.stringify(oldValue)
|
|
95
95
|
let newValue = action === 'Delete' ? null : row[key]
|
|
96
96
|
if (newValue === undefined) newValue = null
|
|
@@ -98,9 +98,9 @@ const _getOldAndNew = (action, row, key) => {
|
|
|
98
98
|
return { oldValue, newValue }
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
const _addAttribute = (log, action, row, key) => {
|
|
101
|
+
const _addAttribute = (log, action, row, key, entity) => {
|
|
102
102
|
if (!log.attributes.find(ele => ele.name === key)) {
|
|
103
|
-
const { oldValue, newValue } = _getOldAndNew(action, row, key)
|
|
103
|
+
const { oldValue, newValue } = _getOldAndNew(action, row, key, entity)
|
|
104
104
|
if (oldValue !== newValue) {
|
|
105
105
|
const attr = { name: key }
|
|
106
106
|
if (action !== 'Create') attr.old = oldValue
|
|
@@ -139,7 +139,7 @@ const _processorFnModification = (modificationLogs, model, req, beforeWrite) =>
|
|
|
139
139
|
} else if (category === 'DataSubjectID') {
|
|
140
140
|
addDataSubject(modificationLog, row, key, entity)
|
|
141
141
|
} else if (category === 'IsPotentiallyPersonal' || category === 'IsPotentiallySensitive') {
|
|
142
|
-
_addAttribute(modificationLog, action, row, key)
|
|
142
|
+
_addAttribute(modificationLog, action, row, key, entity)
|
|
143
143
|
// do not log the value of a sensitive attribute
|
|
144
144
|
if (element['@PersonalData.IsPotentiallySensitive']) _maskAttribute(modificationLog.attributes, key)
|
|
145
145
|
}
|
package/lib/utils.js
CHANGED
|
@@ -9,7 +9,7 @@ const $visitedUp = Symbol('@cap-js/audit-logging:visitedUp')
|
|
|
9
9
|
const $visitedDown = Symbol('@cap-js/audit-logging:visitedDown')
|
|
10
10
|
|
|
11
11
|
const hasPersonalData = entity => {
|
|
12
|
-
if (
|
|
12
|
+
if (entity.own($hasPersonalData) == null) {
|
|
13
13
|
if (!entity['@PersonalData.EntitySemantics']) entity.set($hasPersonalData, false)
|
|
14
14
|
else {
|
|
15
15
|
// default role to entity name
|
|
@@ -121,7 +121,17 @@ const _buildSubSelect = (model, { entity, relative, element, next }, row, previo
|
|
|
121
121
|
const targetAlias = _alias(element._target)
|
|
122
122
|
const relativeAlias = _alias(relative)
|
|
123
123
|
|
|
124
|
-
|
|
124
|
+
let w = relative._relations[element.name].join(targetAlias, relativeAlias)
|
|
125
|
+
|
|
126
|
+
// REVISIT: rewrite to path expression, if alias for relative is already used in subselect to avoid sql error
|
|
127
|
+
if (previousCqn?._aliases.has(relativeAlias)) {
|
|
128
|
+
let t
|
|
129
|
+
for (const a in entity.associations) if (entity.associations[a].target === relative.name) t = entity.associations[a]
|
|
130
|
+
if (t && w[0]?.xpr) for (const ele of w[0].xpr) if (ele.ref?.[0] === relativeAlias) ele.ref.splice(0, 1, as, t.name)
|
|
131
|
+
}
|
|
132
|
+
childCqn._aliases = new Set(previousCqn ? [...previousCqn._aliases.values(), as] : [as])
|
|
133
|
+
|
|
134
|
+
childCqn.where(w)
|
|
125
135
|
|
|
126
136
|
if (previousCqn) childCqn.where('exists', previousCqn)
|
|
127
137
|
else childCqn.where(_addKeysToWhere(keys, row, as))
|
|
@@ -147,22 +157,24 @@ const _getDataSubjectIdQuery = ({ dataSubjectEntity, subs }, row, model) => {
|
|
|
147
157
|
}
|
|
148
158
|
|
|
149
159
|
const _getUps = (entity, model) => {
|
|
150
|
-
if (entity.own($parents))
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
160
|
+
if (entity.own($parents) == null) {
|
|
161
|
+
const ups = []
|
|
162
|
+
for (const def of Object.values(model.definitions)) {
|
|
163
|
+
if (def.kind !== 'entity' || !def.associations) continue
|
|
164
|
+
for (const element of Object.values(def.associations)) {
|
|
165
|
+
if (element.target !== entity.name || element._isBacklink || element.name === 'SiblingEntity') continue
|
|
166
|
+
ups.push(element)
|
|
167
|
+
}
|
|
157
168
|
}
|
|
169
|
+
entity.set($parents, ups)
|
|
158
170
|
}
|
|
159
|
-
return entity.
|
|
171
|
+
return entity.own($parents)
|
|
160
172
|
}
|
|
161
173
|
|
|
162
174
|
const _getDataSubjectUp = (root, model, entity, prev, next, result) => {
|
|
163
175
|
for (const element of _getUps(entity, model)) {
|
|
164
176
|
// cycle detection
|
|
165
|
-
if (
|
|
177
|
+
if (element.own($visitedUp) == null) element.set($visitedUp, new Set())
|
|
166
178
|
if (element.own($visitedUp).has(root)) continue
|
|
167
179
|
element.own($visitedUp).add(root)
|
|
168
180
|
|
|
@@ -192,7 +204,7 @@ const _getDataSubjectDown = (root, entity, prev, next) => {
|
|
|
192
204
|
}
|
|
193
205
|
for (const element of associations) {
|
|
194
206
|
// cycle detection
|
|
195
|
-
if (
|
|
207
|
+
if (element.own($visitedDown) == null) element.set($visitedDown, new Set())
|
|
196
208
|
if (element.own($visitedDown).has(root)) continue
|
|
197
209
|
element.own($visitedDown).add(root)
|
|
198
210
|
|
|
@@ -204,12 +216,14 @@ const _getDataSubjectDown = (root, entity, prev, next) => {
|
|
|
204
216
|
}
|
|
205
217
|
|
|
206
218
|
const getDataSubject = (entity, model) => {
|
|
207
|
-
if (entity.own($dataSubject))
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
219
|
+
if (entity.own($dataSubject) == null) {
|
|
220
|
+
// entities with EntitySemantics 'DataSubjectDetails' or 'Other' must not necessarily
|
|
221
|
+
// be always below or always above 'DataSubject' entity in CSN tree
|
|
222
|
+
let dataSubjectInfo = _getDataSubjectUp(entity.name, model, entity)
|
|
223
|
+
if (!dataSubjectInfo) dataSubjectInfo = _getDataSubjectDown(entity.name, entity)
|
|
224
|
+
entity.set($dataSubject, dataSubjectInfo)
|
|
225
|
+
}
|
|
226
|
+
return entity.own($dataSubject)
|
|
213
227
|
}
|
|
214
228
|
|
|
215
229
|
const _getDataSubjectsMap = req => {
|
|
@@ -235,20 +249,29 @@ const addDataSubjectForDetailsEntity = (row, log, req, entity, model) => {
|
|
|
235
249
|
else map.set(role, _getDataSubjectIdQuery(dataSubjectInfo, row, model))
|
|
236
250
|
}
|
|
237
251
|
|
|
238
|
-
const resolveDataSubjects =
|
|
252
|
+
const resolveDataSubjects = (logs, req) => {
|
|
253
|
+
const ps = []
|
|
254
|
+
|
|
239
255
|
const map = _getDataSubjectsMap(req)
|
|
256
|
+
|
|
240
257
|
for (const each of Object.values(logs)) {
|
|
241
258
|
if (each.data_subject.id instanceof cds.ql.Query) {
|
|
242
259
|
const q = each.data_subject.id
|
|
243
|
-
if (map.has(q)) {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
map.set(q, res)
|
|
248
|
-
each.data_subject.id = res
|
|
260
|
+
if (!map.has(q)) {
|
|
261
|
+
const p = cds.run(q).then(res => map.set(q, res))
|
|
262
|
+
map.set(q, p)
|
|
263
|
+
ps.push(p)
|
|
249
264
|
}
|
|
250
265
|
}
|
|
251
266
|
}
|
|
267
|
+
|
|
268
|
+
return Promise.all(ps).then(() => {
|
|
269
|
+
for (const each of Object.values(logs)) {
|
|
270
|
+
if (each.data_subject.id instanceof cds.ql.Query) {
|
|
271
|
+
each.data_subject.id = map.get(each.data_subject.id)
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
})
|
|
252
275
|
}
|
|
253
276
|
|
|
254
277
|
module.exports = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cap-js/audit-logging",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.2",
|
|
4
4
|
"description": "CDS plugin providing integration to the SAP Audit Log service as well as out-of-the-box personal data-related audit logging based on annotations.",
|
|
5
5
|
"repository": "cap-js/audit-logging",
|
|
6
6
|
"author": "SAP SE (https://www.sap.com)",
|
package/srv/log2restv2.js
CHANGED
|
@@ -80,7 +80,7 @@ module.exports = class AuditLog2RESTv2 extends AuditLogService {
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
async _send(data, path) {
|
|
83
|
-
const headers = { 'content-type': 'application/json' }
|
|
83
|
+
const headers = { 'content-type': 'application/json;charset=utf-8' }
|
|
84
84
|
if (this._vcap) {
|
|
85
85
|
headers.XS_AUDIT_ORG = this._vcap.organization_name
|
|
86
86
|
headers.XS_AUDIT_SPACE = this._vcap.space_name
|