@cap-js/audit-logging 1.2.0 → 1.2.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/README.md CHANGED
@@ -4,11 +4,10 @@
4
4
 
5
5
  `@cap-js/audit-logging` is a 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.
6
6
 
7
- Documentation can be found at [cap.cloud.sap](https://cap.cloud.sap/docs/guides/data-privacy).
7
+ Documentation can be found at [cap.cloud.sap](https://cap.cloud.sap/docs/guides/data-privacy).
8
8
 
9
9
  > [!IMPORTANT]
10
- > The information in this file is by no means complete but enables you to get started quickly. Make sure to read the provided documentation at [cap.cloud.sap](https://cap.cloud.sap/docs/guides/data-privacy) to get the full picture.
11
-
10
+ > The information in this file is by no means complete but enables you to get started quickly. Make sure to read the provided documentation at [cap.cloud.sap](https://cap.cloud.sap/docs/guides/data-privacy) to get the full picture.
12
11
 
13
12
  ## Preliminaries
14
13
 
@@ -20,7 +19,6 @@ cd incidents-app
20
19
  npm i
21
20
  ```
22
21
 
23
-
24
22
  ## Setup
25
23
 
26
24
  To enable audit logging, simply add this self-configuring plugin package to your project:
@@ -29,7 +27,6 @@ To enable audit logging, simply add this self-configuring plugin package to your
29
27
  npm add @cap-js/audit-logging
30
28
  ```
31
29
 
32
-
33
30
  ## Annotate Personal Data
34
31
 
35
32
  Identify entities and elements (potentially) holding personal data using `@PersonalData` annotations. Create a `db/data-privacy.cds` file and add the following:
@@ -65,24 +62,27 @@ annotate my.Incidents with @PersonalData : {
65
62
  };
66
63
 
67
64
  ```
65
+
68
66
  Learn more about the annotations in capire:
67
+
69
68
  - [@PersonalData.EntitySemantics](https://cap.cloud.sap/docs/guides/data-privacy/annotations#entitysemantics)
70
69
  - [@PersonalData.EntitySemantics: 'DataSubject'](https://cap.cloud.sap/docs/guides/data-privacy/annotations#datasubjectrole)
71
70
  - [@PersonalData.FieldSemantics: 'DataSubjectID'](https://cap.cloud.sap/docs/guides/data-privacy/annotations#fieldsemantics-datasubjectid)
72
71
  - [@PersonalData.IsPotentiallyPersonal](https://cap.cloud.sap/docs/guides/data-privacy/annotations#ispotentiallypersonal)
73
72
  - [@PersonalData.IsPotentiallySensitive](https://cap.cloud.sap/docs/guides/data-privacy/annotations#ispotentiallysensitive)
74
73
 
75
-
76
74
  ## Test-Drive Locally
77
75
 
78
76
  You've prepared everything to log personal data-related events. Let's see that in action.
79
77
 
80
78
  Start the server as usual:
79
+
81
80
  ```sh
82
81
  cds watch
83
82
  ```
84
83
 
85
84
  Send an update request that changes personal data:
85
+
86
86
  ```http
87
87
  PATCH http://localhost:4004/odata/v4/admin/Customers('1004155')
88
88
  Authorization: Basic alice:in-wonderland
@@ -95,6 +95,7 @@ Content-Type: application/json
95
95
  ```
96
96
 
97
97
  See the audit logs in the server's console output:
98
+
98
99
  ```sh
99
100
  [audit-log] - PersonalDataModified: {
100
101
  data_subject: {
@@ -117,26 +118,22 @@ See the audit logs in the server's console output:
117
118
  }
118
119
  ```
119
120
 
120
-
121
121
  ## In Production
122
122
 
123
123
  The end-to-end out-of-the-box functionality provided by this plugin requires a paid-for instance of the [SAP Audit Log service for customers](https://help.sap.com/docs/btp/sap-business-technology-platform/audit-log-write-api-for-customers?locale=en-US). However, it is possible to provide an own implementation that writes the audit logs to a custom store.
124
124
 
125
- [_Learn more about using the SAP Audit Log service._](https://cap.cloud.sap/docs/guides/data-privacy/audit-logging#use-sap-audit-log-service)
126
-
127
- [_Learn more about custom audit logging._](https://cap.cloud.sap/docs/guides/data-privacy/audit-logging#custom-audit-logging)
125
+ [_Learn more about using the SAP Audit Log service._](https://cap.cloud.sap/docs/guides/security/dpp-audit-logging#use-sap-audit-log-service)
128
126
 
127
+ [_Learn more about custom audit logging._](https://cap.cloud.sap/docs/guides/security/dpp-audit-logging#custom-audit-logging)
129
128
 
130
129
  ## Support, Feedback, Contributing
131
130
 
132
131
  This project is open to feature requests/suggestions, bug reports etc. via [GitHub issues](https://github.com/cap-js/audit-logging/issues). Contribution and feedback are encouraged and always welcome. For more information about how to contribute, the project structure, as well as additional contribution information, see our [Contribution Guidelines](CONTRIBUTING.md).
133
132
 
134
-
135
133
  ## Code of Conduct
136
134
 
137
135
  We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone. By participating in this project, you agree to abide by its [Code of Conduct](CODE_OF_CONDUCT.md) at all times.
138
136
 
139
-
140
137
  ## Licensing
141
138
 
142
139
  Copyright 2023 SAP SE or an SAP affiliate company and contributors. Please see our [LICENSE](LICENSE) for copyright and license information. Detailed information including third-party components and their licensing/copyright information is available [via the REUSE tool](https://api.reuse.software/info/github.com/cap-js/audit-logging).
package/cds-plugin.js CHANGED
@@ -5,7 +5,7 @@ const {
5
5
  addDiffToCtx,
6
6
  calcModLogs4Before,
7
7
  calcModLogs4After,
8
- emitModLogs,
8
+ emitModLogs
9
9
  } = require("./lib/modification");
10
10
  const { hasPersonalData } = require("./lib/utils");
11
11
 
@@ -30,13 +30,9 @@ cds.on("served", (services) => {
30
30
  if (entity["@PersonalData.EntitySemantics"] !== "DataSubject") continue;
31
31
  for (const e of service.entities) {
32
32
  for (const k in e.associations) {
33
- if (
34
- e.associations[k].target === entity.name &&
35
- k !== "SiblingEntity"
36
- ) {
33
+ if (e.associations[k].target === entity.name && k !== "SiblingEntity") {
37
34
  e["@PersonalData.EntitySemantics"] ??= "Other";
38
- e.associations[k]["@PersonalData.FieldSemantics"] ??=
39
- "DataSubjectID";
35
+ e.associations[k]["@PersonalData.FieldSemantics"] ??= "DataSubjectID";
40
36
  if (!relevantEntities.includes(e)) relevantEntities.push(e);
41
37
  }
42
38
  }
@@ -47,18 +43,18 @@ cds.on("served", (services) => {
47
43
  if (!(service instanceof cds.ApplicationService)) continue;
48
44
  service.after("READ", async (res, req) => {
49
45
  // Checking for req.target._service to make sure only entities within services are considered
50
- if (!req.target._service || !hasPersonalData(req.target)) return;
46
+ if (!req.target?._service || !hasPersonalData(req.target)) return;
51
47
  await auditAccess.call(service, res, req);
52
48
  });
53
49
 
54
50
  service.after(WRITE, async (res, req) => {
55
- if (!req.target._service || !hasPersonalData(req.target)) return;
51
+ if (!req.target?._service || !hasPersonalData(req.target)) return;
56
52
  await emitModLogs.call(service, res, req);
57
53
  });
58
54
  }
59
55
 
60
56
  db.before("CREATE", async (req) => {
61
- if (!req.target._service || !hasPersonalData(req.target)) return;
57
+ if (!req.target?._service || !hasPersonalData(req.target)) return;
62
58
  await addDiffToCtx.call(db, req);
63
59
  });
64
60
  /*
@@ -67,11 +63,11 @@ cds.on("served", (services) => {
67
63
  * deep updates can contain new, modified and deleted data -> both phases
68
64
  */
69
65
  db.after(["CREATE", "UPDATE"], async (res, req) => {
70
- if (!req.target._service || !hasPersonalData(req.target)) return;
66
+ if (!req.target?._service || !hasPersonalData(req.target)) return;
71
67
  await calcModLogs4After.call(db, res, req);
72
68
  });
73
69
  db.before(["DELETE", "UPDATE"], async (req) => {
74
- if (!req.target._service || !hasPersonalData(req.target)) return;
70
+ if (!req.target?._service || !hasPersonalData(req.target)) return;
75
71
  await addDiffToCtx.call(db, req);
76
72
  await calcModLogs4Before.call(db, req);
77
73
  });
@@ -81,5 +77,5 @@ cds.on("served", (services) => {
81
77
  * Export base class for extending in custom implementations
82
78
  */
83
79
  module.exports = {
84
- AuditLogService: require("./srv/service"),
80
+ AuditLogService: require("./srv/service")
85
81
  };
package/lib/_relation.js CHANGED
@@ -1,208 +1,211 @@
1
- let initializing = false
1
+ let initializing = false;
2
2
 
3
3
  class Relation {
4
4
  constructor(csn, path = []) {
5
- if (!initializing) throw new Error(`Do not new a relation, use 'Relation.to()' instead`)
6
- Object.defineProperty(this, 'csn', { get: () => csn })
7
- Object.defineProperty(this, 'path', {
5
+ if (!initializing) throw new Error(`Do not new a relation, use 'Relation.to()' instead`);
6
+ Object.defineProperty(this, "csn", { get: () => csn });
7
+ Object.defineProperty(this, "path", {
8
8
  get: () => path,
9
- set: _ => {
10
- path = _
9
+ set: (_) => {
10
+ path = _;
11
11
  }
12
- })
13
- if (csn.target) Object.defineProperty(this, 'target', { get: () => csn.target })
14
- initializing = false
12
+ });
13
+ if (csn.target) Object.defineProperty(this, "target", { get: () => csn.target });
14
+ initializing = false;
15
15
  }
16
16
 
17
17
  static to(from, name) {
18
- initializing = true
19
- if (!name) return new Relation(from)
20
- return from._elements[name] && new Relation(from._elements[name], [...from.path, name])
18
+ initializing = true;
19
+ if (!name) return new Relation(from);
20
+ return from._elements[name] && new Relation(from._elements[name], [...from.path, name]);
21
21
  }
22
22
 
23
23
  _has(prop) {
24
- return Reflect.has(this, prop)
24
+ return Reflect.has(this, prop);
25
25
  }
26
26
 
27
27
  get _elements() {
28
- if (this.csn.elements) return this.csn.elements
29
- if (this.csn._target && this.csn._target.elements) return this.csn._target.elements
28
+ if (this.csn.elements) return this.csn.elements;
29
+ if (this.csn._target && this.csn._target.elements) return this.csn._target.elements;
30
30
  // if (csn.targetAspect) relation.elements = model.definitions[csn.targetAspect].elements
31
31
  // if (csn.kind = 'type') relation.elements = model.definitions[csn.type].element
32
- return {}
32
+ return {};
33
33
  }
34
34
 
35
- join(fromAlias = '', toAlias = '') {
36
- return _getOnCond(this.csn, this.path, { select: fromAlias, join: toAlias })
35
+ join(fromAlias = "", toAlias = "") {
36
+ return _getOnCond(this.csn, this.path, { select: fromAlias, join: toAlias });
37
37
  }
38
38
  }
39
39
 
40
- const exposeRelation = relation => Object.defineProperty({}, '_', { get: () => relation })
40
+ const exposeRelation = (relation) => Object.defineProperty({}, "_", { get: () => relation });
41
41
 
42
- const relationHandler = relation => ({
42
+ const relationHandler = (relation) => ({
43
43
  get: (target, name) => {
44
- const path = name.split(',')
45
- const prop = path.join('_')
44
+ const path = name.split(",");
45
+ const prop = path.join("_");
46
46
  if (!target[prop]) {
47
47
  if (path.length === 1) {
48
48
  // REVISIT: property 'join' must not be used in CSN to make this working
49
- if (relation._has(prop)) return relation[prop]
50
- const newRelation = Relation.to(relation, prop)
49
+ if (relation._has(prop)) return relation[prop];
50
+ const newRelation = Relation.to(relation, prop);
51
51
  if (newRelation) {
52
- target[prop] = new Proxy(exposeRelation(newRelation), relationHandler(newRelation))
52
+ target[prop] = new Proxy(exposeRelation(newRelation), relationHandler(newRelation));
53
53
  }
54
54
 
55
- return target[prop]
55
+ return target[prop];
56
56
  }
57
57
 
58
- target[prop] = path.reduce((relation, value) => relation[value] || relation.csn._relations[value], relation)
59
- target[prop].path = path
58
+ target[prop] = path.reduce(
59
+ (relation, value) => relation[value] || relation.csn._relations[value],
60
+ relation
61
+ );
62
+ target[prop].path = path;
60
63
  }
61
64
 
62
- return target[prop]
65
+ return target[prop];
63
66
  }
64
- })
67
+ });
65
68
 
66
69
  module.exports = {
67
70
  Relation,
68
71
  exposeRelation,
69
72
  relationHandler
70
- }
73
+ };
71
74
 
72
75
  //
73
76
  // ----- utils
74
77
  //
75
78
 
76
- const _prefixForStruct = element => {
77
- const prefixes = []
78
- let parent = element.parent
79
- while (parent && parent.kind !== 'entity') {
80
- prefixes.push(parent.name)
81
- parent = parent.parent
79
+ const _prefixForStruct = (element) => {
80
+ const prefixes = [];
81
+ let parent = element.parent;
82
+ while (parent && parent.kind !== "entity") {
83
+ prefixes.push(parent.name);
84
+ parent = parent.parent;
82
85
  }
83
- return prefixes.length ? prefixes.reverse().join('_') + '_' : ''
84
- }
86
+ return prefixes.length ? prefixes.reverse().join("_") + "_" : "";
87
+ };
85
88
 
86
89
  const _toRef = (alias, column) => {
87
- if (Array.isArray(column)) column = column.join('_')
88
- return { ref: alias ? [alias, column] : [column] }
89
- }
90
+ if (Array.isArray(column)) column = column.join("_");
91
+ return { ref: alias ? [alias, column] : [column] };
92
+ };
90
93
 
91
94
  const _adaptRefs = (onCond, path, { select, join }) => {
92
- const _adaptEl = el => {
93
- const ref = el.ref
95
+ const _adaptEl = (el) => {
96
+ const ref = el.ref;
94
97
 
95
98
  if (ref) {
96
- if (ref[0] === path.join('_') && ref[1]) {
97
- return _toRef(select, ref.slice(1))
99
+ if (ref[0] === path.join("_") && ref[1]) {
100
+ return _toRef(select, ref.slice(1));
98
101
  }
99
102
 
100
103
  // no alias for special $user of canonical localized association
101
- if (ref[0] === '$user' && path[0] === 'localized') {
102
- return _toRef(undefined, ref.slice(0))
104
+ if (ref[0] === "$user" && path[0] === "localized") {
105
+ return _toRef(undefined, ref.slice(0));
103
106
  }
104
107
 
105
- return _toRef(join, ref.slice(0))
108
+ return _toRef(join, ref.slice(0));
106
109
  }
107
110
 
108
- if (el.xpr) return { xpr: el.xpr.map(_adaptEl) }
109
- return el
110
- }
111
+ if (el.xpr) return { xpr: el.xpr.map(_adaptEl) };
112
+ return el;
113
+ };
111
114
 
112
- return onCond.map(_adaptEl)
113
- }
115
+ return onCond.map(_adaptEl);
116
+ };
114
117
 
115
118
  const _replace$selfAndAliasOnCond = (xpr, csnElement, aliases, path) => {
116
- const selfIndex = xpr.findIndex(({ ref }) => ref?.[0] === '$self')
119
+ const selfIndex = xpr.findIndex(({ ref }) => ref?.[0] === "$self");
117
120
  if (selfIndex != -1) {
118
- let backLinkIndex
119
- if (xpr[selfIndex + 1] && xpr[selfIndex + 1] === '=') backLinkIndex = selfIndex + 2
120
- if (xpr[selfIndex - 1] && xpr[selfIndex - 1] === '=') backLinkIndex = selfIndex - 2
121
+ let backLinkIndex;
122
+ if (xpr[selfIndex + 1] && xpr[selfIndex + 1] === "=") backLinkIndex = selfIndex + 2;
123
+ if (xpr[selfIndex - 1] && xpr[selfIndex - 1] === "=") backLinkIndex = selfIndex - 2;
121
124
  if (backLinkIndex != null) {
122
- const ref = xpr[backLinkIndex].ref
123
- const backlinkName = ref[ref.length - 1]
125
+ const ref = xpr[backLinkIndex].ref;
126
+ const backlinkName = ref[ref.length - 1];
124
127
  const mutOnCond = _newOnConditions(csnElement._backlink, [backlinkName], {
125
128
  select: aliases.join,
126
129
  join: aliases.select
127
- })
130
+ });
128
131
 
129
- xpr.splice(Math.min(backLinkIndex, selfIndex), 3, ...mutOnCond)
132
+ xpr.splice(Math.min(backLinkIndex, selfIndex), 3, ...mutOnCond);
130
133
  }
131
134
  }
132
135
 
133
136
  for (let i = 0; i < xpr.length; i++) {
134
- const element = xpr[i]
137
+ const element = xpr[i];
135
138
  if (element.xpr) {
136
- _replace$selfAndAliasOnCond(element.xpr, csnElement, aliases, path)
137
- continue
139
+ _replace$selfAndAliasOnCond(element.xpr, csnElement, aliases, path);
140
+ continue;
138
141
  }
139
142
 
140
143
  if (element.ref) {
141
- if (element.ref[0] === path.join('_') && element.ref[1]) {
142
- element.ref = _toRef(aliases.select, element.ref.slice(1)).ref
143
- continue
144
+ if (element.ref[0] === path.join("_") && element.ref[1]) {
145
+ element.ref = _toRef(aliases.select, element.ref.slice(1)).ref;
146
+ continue;
144
147
  }
145
148
 
146
149
  // no alias for special $user of canonical localized association
147
- if (element.ref[0] === '$user' && path[0] === 'localized') {
148
- element.ref = _toRef(undefined, element.ref.slice(0)).ref
149
- continue
150
+ if (element.ref[0] === "$user" && path[0] === "localized") {
151
+ element.ref = _toRef(undefined, element.ref.slice(0)).ref;
152
+ continue;
150
153
  }
151
154
  //no alias for special $now variable
152
- if (element.ref[0] === '$now') {
153
- continue
155
+ if (element.ref[0] === "$now") {
156
+ continue;
154
157
  }
155
158
 
156
159
  if (element.ref[0] === aliases.join || element.ref[0] === aliases.select) {
157
160
  // nothing todo here, as already right alias
158
- continue
161
+ continue;
159
162
  }
160
163
 
161
- element.ref = _toRef(aliases.join, element.ref.slice(0)).ref
164
+ element.ref = _toRef(aliases.join, element.ref.slice(0)).ref;
162
165
  }
163
166
  }
164
- }
167
+ };
165
168
 
166
169
  const _args = (csnElement, path, aliases) => {
167
- const onCond = csnElement.on
168
- if (!onCond || onCond.length === 0) return []
169
- if (onCond.length < 3 && !onCond[0]?.xpr) return onCond
170
- if (!csnElement._isSelfManaged) return _adaptRefs(onCond, path, aliases)
170
+ const onCond = csnElement.on;
171
+ if (!onCond || onCond.length === 0) return [];
172
+ if (onCond.length < 3 && !onCond[0]?.xpr) return onCond;
173
+ if (!csnElement._isSelfManaged) return _adaptRefs(onCond, path, aliases);
171
174
 
172
- const onCondCopy = JSON.parse(JSON.stringify(onCond))
173
- _replace$selfAndAliasOnCond(onCondCopy, csnElement, aliases, path)
175
+ const onCondCopy = JSON.parse(JSON.stringify(onCond));
176
+ _replace$selfAndAliasOnCond(onCondCopy, csnElement, aliases, path);
174
177
 
175
- return onCondCopy
176
- }
178
+ return onCondCopy;
179
+ };
177
180
 
178
181
  // this is only for 2one managed w/o on-conditions, i.e. no static values are possible
179
182
  const _foreignToOn = (csnElement, path, { select, join }) => {
180
- const on = []
183
+ const on = [];
181
184
 
182
185
  for (const key of csnElement._foreignKeys) {
183
186
  if (on.length !== 0) {
184
- on.push('and')
187
+ on.push("and");
185
188
  }
186
189
 
187
- const prefixChild = _prefixForStruct(key.childElement)
188
- const ref1 = _toRef(select, prefixChild + key.childElement.name)
189
- const structPrefix = path.length > 1 ? path.slice(0, -1) : []
190
- const ref2 = _toRef(join, [...structPrefix, key.parentElement.name])
191
- on.push(ref1, '=', ref2)
190
+ const prefixChild = _prefixForStruct(key.childElement);
191
+ const ref1 = _toRef(select, prefixChild + key.childElement.name);
192
+ const structPrefix = path.length > 1 ? path.slice(0, -1) : [];
193
+ const ref2 = _toRef(join, [...structPrefix, key.parentElement.name]);
194
+ on.push(ref1, "=", ref2);
192
195
  }
193
196
 
194
- return on
195
- }
197
+ return on;
198
+ };
196
199
 
197
200
  const _newOnConditions = (csnElement, path, aliases) => {
198
201
  if (csnElement.keys) {
199
- return _foreignToOn(csnElement, path, aliases)
202
+ return _foreignToOn(csnElement, path, aliases);
200
203
  }
201
204
 
202
- return _args(csnElement, path, aliases)
203
- }
205
+ return _args(csnElement, path, aliases);
206
+ };
204
207
 
205
- const _getOnCond = (csnElement, path = [], aliases = { select: '', join: '' }) => {
206
- const onCond = _newOnConditions(csnElement, path, aliases)
207
- return [{ xpr: onCond }]
208
- }
208
+ const _getOnCond = (csnElement, path = [], aliases = { select: "", join: "" }) => {
209
+ const onCond = _newOnConditions(csnElement, path, aliases);
210
+ return [{ xpr: onCond }];
211
+ };
package/lib/access.js CHANGED
@@ -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
  getRootEntity,
@@ -11,73 +11,74 @@ const {
11
11
  addDataSubject,
12
12
  addDataSubjectForDetailsEntity,
13
13
  resolveDataSubjects
14
- } = require('./utils')
14
+ } = require("./utils");
15
15
 
16
- let audit
16
+ let audit;
17
17
 
18
18
  const _processorFnAccess = (accessLogs, model, req) => {
19
19
  return ({ row, key, element, plain }) => {
20
- if (row.IsActiveEntity === false) return
20
+ if (row.IsActiveEntity === false) return;
21
21
 
22
- const entity = getRootEntity(element)
22
+ const entity = getRootEntity(element);
23
23
 
24
24
  // create or augment log entry
25
- const entry = createLogEntry(accessLogs, entity, row)
25
+ const entry = createLogEntry(accessLogs, entity, row);
26
26
 
27
27
  // process categories
28
28
  for (const category of plain.categories) {
29
- if (category === 'ObjectID') addObjectID(entry, row, key)
30
- else if (category === 'DataSubjectID') addDataSubject(entry, row, key, entity)
31
- else if (category === 'IsPotentiallySensitive' && key in row) {
32
- if (!entry.attributes.some(e => e.name === key)) entry.attributes.push({ name: key })
29
+ if (category === "ObjectID") addObjectID(entry, row, key);
30
+ else if (category === "DataSubjectID") addDataSubject(entry, row, key, entity);
31
+ else if (category === "IsPotentiallySensitive" && key in row) {
32
+ if (!entry.attributes.some((e) => e.name === key)) entry.attributes.push({ name: key });
33
33
  // REVISIT: attribute vs. attachment?
34
34
  }
35
35
  }
36
36
 
37
37
  // add promise to determine data subject if a DataSubjectDetails entity
38
- const semantics = entity['@PersonalData.EntitySemantics']
38
+ const semantics = entity["@PersonalData.EntitySemantics"];
39
39
  if (
40
- (semantics === 'DataSubjectDetails' || semantics === 'Other') &&
40
+ (semantics === "DataSubjectDetails" || semantics === "Other") &&
41
41
  Object.keys(entry.data_subject.id).length === 0 // > id still an empty object -> promise not yet set
42
42
  ) {
43
- addDataSubjectForDetailsEntity(row, entry, req, entity, model)
43
+ addDataSubjectForDetailsEntity(row, entry, req, entity, model);
44
44
  }
45
- }
46
- }
45
+ };
46
+ };
47
47
 
48
48
  const auditAccess = async function (data, req) {
49
- if (!cds.env.requires['audit-log'].handle?.includes('READ')) return
49
+ if (!cds.env.requires["audit-log"].handle?.includes("READ")) return;
50
50
 
51
- if (typeof data !== 'object' || data == null) return
51
+ if (typeof data !== "object" || data == null) return;
52
52
 
53
- const mock = Object.assign({ name: req.target._service.name, model: this.model })
54
- const template = getTemplate('personal_read', mock, req.target, { pick: getPick('READ') })
55
- if (!template.elements.size) return
53
+ const mock = Object.assign({ name: req.target._service.name, model: this.model });
54
+ const template = getTemplate("personal_read", mock, req.target, { pick: getPick("READ") });
55
+ if (!template.elements.size) return;
56
56
 
57
- const accessLogs = {}
58
- const processFn = _processorFnAccess(accessLogs, this.model, req)
57
+ const accessLogs = {};
58
+ const processFn = _processorFnAccess(accessLogs, this.model, req);
59
59
  // cds internal templating mechanism api changed in 8.2.0 -> polyfill
60
60
  if (!template.process) {
61
- module.exports._templateProcessor ??= require('@sap/cds/libx/_runtime/common/utils/templateProcessor')
61
+ module.exports._templateProcessor ??= require("@sap/cds/libx/_runtime/common/utils/templateProcessor");
62
62
  template.process = (data, processFn) => {
63
- const _data = Array.isArray(data) ? data : [data]
64
- _data.forEach(row => module.exports._templateProcessor({ processFn, row, template }))
65
- }
63
+ const _data = Array.isArray(data) ? data : [data];
64
+ _data.forEach((row) => module.exports._templateProcessor({ processFn, row, template }));
65
+ };
66
66
  }
67
- template.process(data, processFn)
67
+ template.process(data, processFn);
68
68
 
69
- for (const each of Object.keys(accessLogs)) if (!accessLogs[each].attributes.length) delete accessLogs[each]
70
- if (!Object.keys(accessLogs).length) return
69
+ for (const each of Object.keys(accessLogs))
70
+ if (!accessLogs[each].attributes.length) delete accessLogs[each];
71
+ if (!Object.keys(accessLogs).length) return;
71
72
 
72
- await resolveDataSubjects(accessLogs, req)
73
- const accesses = Object.values(accessLogs).filter(ele => ele.attributes.length)
74
- if (!accesses.length) return
73
+ await resolveDataSubjects(accessLogs, req);
74
+ const accesses = Object.values(accessLogs).filter((ele) => ele.attributes.length);
75
+ if (!accesses.length) return;
75
76
 
76
- audit = audit || (await cds.connect.to('audit-log'))
77
+ audit = audit || (await cds.connect.to("audit-log"));
77
78
 
78
- await Promise.all(accesses.map(access => audit.log('SensitiveDataRead', access)))
79
- }
79
+ await Promise.all(accesses.map((access) => audit.log("SensitiveDataRead", access)));
80
+ };
80
81
 
81
82
  module.exports = {
82
83
  auditAccess
83
- }
84
+ };