@cap-js/audit-logging 1.1.1 → 1.2.1

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.
@@ -1,7 +1,7 @@
1
- const cds = require('@sap/cds')
1
+ const cds = require("@sap/cds");
2
2
 
3
3
  // REVISIT: don't require internal stuff
4
- const getTemplate = require('@sap/cds/libx/_runtime/common/utils/template')
4
+ const getTemplate = require("@sap/cds/libx/_runtime/common/utils/template");
5
5
 
6
6
  const {
7
7
  getMapKeyForCurrentRequest,
@@ -12,58 +12,58 @@ const {
12
12
  addDataSubject,
13
13
  addDataSubjectForDetailsEntity,
14
14
  resolveDataSubjects
15
- } = require('./utils')
15
+ } = require("./utils");
16
16
 
17
- let audit
17
+ let audit;
18
18
 
19
19
  const _applyTransitionRecursively = (transition, data, d, old = {}) => {
20
20
  for (const [k, v] of transition.mapping) {
21
21
  if (v.transition) {
22
- const ok = v.ref?.[0] || k
23
- if (!data[ok]) continue
22
+ const ok = v.ref?.[0] || k;
23
+ if (!data[ok]) continue;
24
24
  if (Array.isArray(data[ok])) {
25
- d[k] = []
25
+ d[k] = [];
26
26
  for (let i = 0; i < data[ok].length; i++) {
27
- const _op = data[ok][i]._op || d._op
28
- d[k].push(_op ? { _op } : {})
29
- const _old = old[ok]?.[i] || data[ok][i]._old
30
- if (_old) d[k][i]._old = _old
31
- _applyTransitionRecursively(v.transition, data[ok][i], d[k][i], _old)
27
+ const _op = data[ok][i]._op || d._op;
28
+ d[k].push(_op ? { _op } : {});
29
+ const _old = old[ok]?.[i] || data[ok][i]._old;
30
+ if (_old) d[k][i]._old = _old;
31
+ _applyTransitionRecursively(v.transition, data[ok][i], d[k][i], _old);
32
32
  }
33
33
  if (old[ok] && data[ok].length !== old[ok].length) {
34
- for (const each of Object.values(old[ok]).filter(ele => !ele.__visited)) {
35
- const i = d[k].push({ _op: 'delete' })
36
- d[k][i - 1]._old = each
37
- _applyTransitionRecursively(v.transition, each, d[k][i - 1], each)
34
+ for (const each of Object.values(old[ok]).filter((ele) => !ele.__visited)) {
35
+ const i = d[k].push({ _op: "delete" });
36
+ d[k][i - 1]._old = each;
37
+ _applyTransitionRecursively(v.transition, each, d[k][i - 1], each);
38
38
  }
39
39
  }
40
40
  } else {
41
- d[k] = { _op: data[ok]._op || d._op }
42
- const _old = old[ok] || data[ok]._old
43
- if (_old) d[k]._old = _old
44
- _applyTransitionRecursively(v.transition, data[ok], d[k], _old)
41
+ d[k] = { _op: data[ok]._op || d._op };
42
+ const _old = old[ok] || data[ok]._old;
43
+ if (_old) d[k]._old = _old;
44
+ _applyTransitionRecursively(v.transition, data[ok], d[k], _old);
45
45
  }
46
46
  } else if (v.ref) {
47
- if (v.ref[0] in data) d[k] = data[v.ref[0]]
48
- if (v.ref[0] in old) d._old[k] = old[v.ref[0]]
47
+ if (v.ref[0] in data) d[k] = data[v.ref[0]];
48
+ if (v.ref[0] in old) d._old[k] = old[v.ref[0]];
49
49
  }
50
50
  }
51
- if (Object.keys(old).length) old.__visited = true
52
- }
51
+ if (Object.keys(old).length) old.__visited = true;
52
+ };
53
53
 
54
54
  // REVISIT: remove once old database impl is removed
55
55
  const _getDataWithAppliedTransitions = (data, req) => {
56
- let d
57
- const query = req.query.INSERT || req.query.UPDATE || req.query.DELETE
56
+ let d;
57
+ const query = req.query.INSERT || req.query.UPDATE || req.query.DELETE;
58
58
  // NOTE: there will only be transitions if old database impl is used
59
- const transition = query._transitions?.find(t => t.queryTarget.name === req.target.name)
59
+ const transition = query._transitions?.find((t) => t.queryTarget.name === req.target.name);
60
60
  if (transition) {
61
- d = data._op ? { _op: data._op } : {}
62
- if (data._old) d._old = {}
63
- _applyTransitionRecursively(transition, data, d, data._old)
61
+ d = data._op ? { _op: data._op } : {};
62
+ if (data._old) d._old = {};
63
+ _applyTransitionRecursively(transition, data, d, data._old);
64
64
  }
65
- return d || data
66
- }
65
+ return d || data;
66
+ };
67
67
 
68
68
  /*
69
69
  * REVISIT: diff() doesn't work in srv after phase but foreign key propagation has not yet taken place in srv before phase
@@ -72,88 +72,90 @@ const _getDataWithAppliedTransitions = (data, req) => {
72
72
  */
73
73
  const addDiffToCtx = async function (req) {
74
74
  // store diff in audit data structure at context
75
- const _audit = (req.context._audit ??= {})
76
- if (!_audit.diffs) _audit.diffs = new Map()
75
+ const _audit = (req.context._audit ??= {});
76
+ if (!_audit.diffs) _audit.diffs = new Map();
77
77
 
78
78
  // get diff
79
- let diff = (await req.diff()) || {}
80
- diff = _getDataWithAppliedTransitions(diff, req)
79
+ let diff = (await req.diff()) || {};
80
+ diff = _getDataWithAppliedTransitions(diff, req);
81
81
 
82
82
  // add keys, if necessary
83
- let keys = _getDataWithAppliedTransitions(Object.assign({}, req.data), req)
84
- for (const key in keys) if (!(key in req.target.keys)) delete keys[key]
85
- Object.assign(diff, keys)
83
+ let keys = _getDataWithAppliedTransitions(Object.assign({}, req.data), req);
84
+ for (const key in keys) if (!(key in req.target.keys)) delete keys[key];
85
+ Object.assign(diff, keys);
86
86
 
87
- _audit.diffs.set(req._.query, diff)
88
- }
89
- addDiffToCtx._initial = true
87
+ _audit.diffs.set(req._.query, diff);
88
+ };
89
+ addDiffToCtx._initial = true;
90
90
 
91
91
  const _getOldAndNew = (action, row, key, entity) => {
92
- let oldValue = action === 'Create' ? null : row._old && row._old[key]
93
- if (oldValue === undefined) oldValue = action === 'Update' && key in entity.keys ? row[key] : null
94
- else if (Array.isArray(oldValue)) oldValue = JSON.stringify(oldValue)
95
- let newValue = action === 'Delete' ? null : row[key]
96
- if (newValue === undefined) newValue = null
97
- else if (Array.isArray(newValue)) newValue = JSON.stringify(newValue)
98
- return { oldValue, newValue }
99
- }
92
+ let oldValue = action === "Create" ? null : row._old && row._old[key];
93
+ if (oldValue === undefined)
94
+ oldValue = action === "Update" && key in entity.keys ? row[key] : null;
95
+ else if (Array.isArray(oldValue)) oldValue = JSON.stringify(oldValue);
96
+ let newValue = action === "Delete" ? null : row[key];
97
+ if (newValue === undefined) newValue = null;
98
+ else if (Array.isArray(newValue)) newValue = JSON.stringify(newValue);
99
+ return { oldValue, newValue };
100
+ };
100
101
 
101
102
  const _addAttribute = (log, action, row, key, entity) => {
102
- if (!log.attributes.find(ele => ele.name === key)) {
103
- const { oldValue, newValue } = _getOldAndNew(action, row, key, entity)
103
+ if (!log.attributes.find((ele) => ele.name === key)) {
104
+ const { oldValue, newValue } = _getOldAndNew(action, row, key, entity);
104
105
  if (oldValue !== newValue) {
105
- const attr = { name: key }
106
- if (action !== 'Create') attr.old = oldValue
107
- if (action !== 'Delete') attr.new = newValue
108
- log.attributes.push(attr)
106
+ const attr = { name: key };
107
+ if (action !== "Create") attr.old = oldValue;
108
+ if (action !== "Delete") attr.new = newValue;
109
+ log.attributes.push(attr);
109
110
  }
110
111
  }
111
- }
112
+ };
112
113
 
113
114
  const _maskAttribute = (attributes, key) => {
114
- const attribute = attributes?.find(ele => ele.name === key)
115
+ const attribute = attributes?.find((ele) => ele.name === key);
115
116
  if (attribute) {
116
- if ('old' in attribute) attribute.old = '***'
117
- if ('new' in attribute) attribute.new = '***'
117
+ if ("old" in attribute) attribute.old = "***";
118
+ if ("new" in attribute) attribute.new = "***";
118
119
  }
119
- }
120
+ };
120
121
 
121
- const _processorFnModification = (modificationLogs, model, req, beforeWrite) => elementInfo => {
122
- if (!elementInfo.row?._op) return
122
+ const _processorFnModification = (modificationLogs, model, req, beforeWrite) => (elementInfo) => {
123
+ if (!elementInfo.row?._op) return;
123
124
 
124
- let { row, key, element, plain } = elementInfo
125
+ let { row, key, element, plain } = elementInfo;
125
126
 
126
127
  // delete in before phase, create and update in after phase
127
- if ((row._op === 'delete') !== !!beforeWrite) return
128
+ if ((row._op === "delete") !== !!beforeWrite) return;
128
129
 
129
- const entity = getRootEntity(element)
130
- const action = row._op[0].toUpperCase() + row._op.slice(1)
130
+ const entity = getRootEntity(element);
131
+ const action = row._op[0].toUpperCase() + row._op.slice(1);
131
132
 
132
133
  // create or augment log entry
133
- const modificationLog = createLogEntry(modificationLogs, entity, row)
134
+ const modificationLog = createLogEntry(modificationLogs, entity, row);
134
135
 
135
136
  // process categories
136
137
  for (const category of plain.categories) {
137
- if (category === 'ObjectID') {
138
- addObjectID(modificationLog, row, key)
139
- } else if (category === 'DataSubjectID') {
140
- addDataSubject(modificationLog, row, key, entity)
141
- } else if (category === 'IsPotentiallyPersonal' || category === 'IsPotentiallySensitive') {
142
- _addAttribute(modificationLog, action, row, key, entity)
138
+ if (category === "ObjectID") {
139
+ addObjectID(modificationLog, row, key);
140
+ } else if (category === "DataSubjectID") {
141
+ addDataSubject(modificationLog, row, key, entity);
142
+ } else if (category === "IsPotentiallyPersonal" || category === "IsPotentiallySensitive") {
143
+ _addAttribute(modificationLog, action, row, key, entity);
143
144
  // do not log the value of a sensitive attribute
144
- if (element['@PersonalData.IsPotentiallySensitive']) _maskAttribute(modificationLog.attributes, key)
145
+ if (element["@PersonalData.IsPotentiallySensitive"])
146
+ _maskAttribute(modificationLog.attributes, key);
145
147
  }
146
148
  }
147
149
 
148
150
  // add promise to determine data subject if a DataSubjectDetails entity
149
151
  if (
150
- (entity['@PersonalData.EntitySemantics'] === 'DataSubjectDetails' ||
151
- entity['@PersonalData.EntitySemantics'] === 'Other') &&
152
+ (entity["@PersonalData.EntitySemantics"] === "DataSubjectDetails" ||
153
+ entity["@PersonalData.EntitySemantics"] === "Other") &&
152
154
  Object.keys(modificationLog.data_subject.id).length === 0 // > id still an empty object -> promise not yet set
153
155
  ) {
154
- addDataSubjectForDetailsEntity(row, modificationLog, req, entity, model)
156
+ addDataSubjectForDetailsEntity(row, modificationLog, req, entity, model);
155
157
  }
156
- }
158
+ };
157
159
 
158
160
  const _getDataModificationLogs = (req, tx, diff, beforeWrite) => {
159
161
  const template = getTemplate(
@@ -161,62 +163,69 @@ const _getDataModificationLogs = (req, tx, diff, beforeWrite) => {
161
163
  Object.assign({ name: req.target._service.name, model: tx.model }),
162
164
  req.target,
163
165
  { pick: getPick(req.event) }
164
- )
166
+ );
165
167
 
166
- const modificationLogs = {}
167
- const processFn = _processorFnModification(modificationLogs, tx.model, req, beforeWrite)
168
+ const modificationLogs = {};
169
+ const processFn = _processorFnModification(modificationLogs, tx.model, req, beforeWrite);
168
170
  // cds internal templating mechanism api changed in 8.2.0 -> polyfill
169
171
  if (!template.process) {
170
- module.exports._templateProcessor ??= require('@sap/cds/libx/_runtime/common/utils/templateProcessor')
172
+ module.exports._templateProcessor ??= require("@sap/cds/libx/_runtime/common/utils/templateProcessor");
171
173
  template.process = (data, processFn) => {
172
- module.exports._templateProcessor({ processFn, row: data, template })
173
- }
174
+ module.exports._templateProcessor({ processFn, row: data, template });
175
+ };
174
176
  }
175
- template.process(diff, processFn)
177
+ template.process(diff, processFn);
176
178
 
177
- return modificationLogs
178
- }
179
+ return modificationLogs;
180
+ };
179
181
 
180
182
  const _calcModificationLogsHandler = async function (req, beforeWrite, that) {
181
- const mapKey = getMapKeyForCurrentRequest(req)
183
+ const mapKey = getMapKeyForCurrentRequest(req);
182
184
 
183
- const _audit = (req.context._audit ??= {})
184
- const modificationLogs = _getDataModificationLogs(req, that, _audit.diffs.get(mapKey), beforeWrite)
185
+ const _audit = (req.context._audit ??= {});
186
+ const modificationLogs = _getDataModificationLogs(
187
+ req,
188
+ that,
189
+ _audit.diffs.get(mapKey),
190
+ beforeWrite
191
+ );
185
192
 
186
193
  // store modificationLogs in audit data structure at context
187
- if (!_audit.modificationLogs) _audit.modificationLogs = new Map()
188
- const existingLogs = _audit.modificationLogs.get(mapKey) || {}
189
- _audit.modificationLogs.set(mapKey, Object.assign(existingLogs, modificationLogs))
194
+ if (!_audit.modificationLogs) _audit.modificationLogs = new Map();
195
+ const existingLogs = _audit.modificationLogs.get(mapKey) || {};
196
+ _audit.modificationLogs.set(mapKey, Object.assign(existingLogs, modificationLogs));
190
197
 
191
198
  // execute the data subject promises before going along to on phase
192
199
  // guarantees that the reads are executed before the data is modified
193
- await resolveDataSubjects(modificationLogs, req)
194
- }
200
+ await resolveDataSubjects(modificationLogs, req);
201
+ };
195
202
 
196
203
  const calcModLogs4Before = function (req) {
197
- return _calcModificationLogsHandler(req, true, this)
198
- }
204
+ return _calcModificationLogsHandler(req, true, this);
205
+ };
199
206
 
200
207
  const calcModLogs4After = function (_, req) {
201
- return _calcModificationLogsHandler(req, false, this)
202
- }
208
+ return _calcModificationLogsHandler(req, false, this);
209
+ };
203
210
 
204
211
  const emitModLogs = async function (_, req) {
205
- const modificationLogs = req.context?._audit?.modificationLogs?.get(req.query)
206
- if (!modificationLogs) return
212
+ const modificationLogs = req.context?._audit?.modificationLogs?.get(req.query);
213
+ if (!modificationLogs) return;
207
214
 
208
- audit = audit || (await cds.connect.to('audit-log'))
215
+ audit = audit || (await cds.connect.to("audit-log"));
209
216
 
210
217
  const modifications = Object.keys(modificationLogs)
211
- .map(k => modificationLogs[k])
212
- .filter(log => log.attributes.length)
218
+ .map((k) => modificationLogs[k])
219
+ .filter((log) => log.attributes.length);
213
220
 
214
- await Promise.all(modifications.map(modification => audit.log('PersonalDataModified', modification)))
215
- }
221
+ await Promise.all(
222
+ modifications.map((modification) => audit.log("PersonalDataModified", modification))
223
+ );
224
+ };
216
225
 
217
226
  module.exports = {
218
227
  addDiffToCtx,
219
228
  calcModLogs4Before,
220
229
  calcModLogs4After,
221
230
  emitModLogs
222
- }
231
+ };