@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 +9 -12
- package/cds-plugin.js +9 -13
- package/lib/_relation.js +103 -100
- package/lib/access.js +38 -37
- package/lib/modification.js +117 -108
- package/lib/utils.js +185 -160
- package/package.json +7 -8
- package/srv/log2alsng.js +101 -98
- package/srv/log2console.js +7 -7
- package/srv/log2restv2.js +20 -42
- package/srv/service.js +15 -15
package/srv/log2alsng.js
CHANGED
|
@@ -1,114 +1,116 @@
|
|
|
1
|
-
const cds = require(
|
|
2
|
-
const LOG = cds.log(
|
|
1
|
+
const cds = require("@sap/cds");
|
|
2
|
+
const LOG = cds.log("audit-log");
|
|
3
3
|
const { appMetadata } = require("../lib/utils");
|
|
4
|
-
const https = require(
|
|
4
|
+
const https = require("https");
|
|
5
5
|
|
|
6
|
-
const AuditLogService = require(
|
|
6
|
+
const AuditLogService = require("./service");
|
|
7
7
|
|
|
8
8
|
module.exports = class AuditLog2ALSNG extends AuditLogService {
|
|
9
9
|
constructor() {
|
|
10
|
-
super()
|
|
11
|
-
if (!cds.env.requires[
|
|
12
|
-
throw new Error(
|
|
10
|
+
super();
|
|
11
|
+
if (!cds.env.requires["audit-log"]?.credentials)
|
|
12
|
+
throw new Error("No credentials found for SAP Audit Log Service NG");
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
async init() {
|
|
16
|
-
this.on(
|
|
17
|
-
const { event, data } = req
|
|
18
|
-
return this.eventMapper(event, data)
|
|
19
|
-
})
|
|
20
|
-
await super.init()
|
|
16
|
+
this.on("*", function (req) {
|
|
17
|
+
const { event, data } = req;
|
|
18
|
+
return this.eventMapper(event, data);
|
|
19
|
+
});
|
|
20
|
+
await super.init();
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
eventMapper(event, data) {
|
|
24
24
|
const known = {
|
|
25
|
-
PersonalDataModified: () => this.logEvent(
|
|
26
|
-
SensitiveDataRead: () => this.logEvent(
|
|
27
|
-
ConfigurationModified: () => this.logEvent(
|
|
28
|
-
SecurityEvent: () => this.logEvent(
|
|
29
|
-
}
|
|
30
|
-
const dfault = () => this.logEvent(event, data)
|
|
31
|
-
return (known[event] ?? dfault)()
|
|
25
|
+
PersonalDataModified: () => this.logEvent("dppDataModification", data),
|
|
26
|
+
SensitiveDataRead: () => this.logEvent("dppDataAccess", data),
|
|
27
|
+
ConfigurationModified: () => this.logEvent("configurationChange", data),
|
|
28
|
+
SecurityEvent: () => this.logEvent("legacySecurityWrapper", data)
|
|
29
|
+
};
|
|
30
|
+
const dfault = () => this.logEvent(event, data);
|
|
31
|
+
return (known[event] ?? dfault)();
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
flattenAndSortIdObject(id) {
|
|
35
|
-
if (!id || !Object.keys(id).length) return
|
|
35
|
+
if (!id || !Object.keys(id).length) return "not provided";
|
|
36
36
|
|
|
37
|
-
let s =
|
|
38
|
-
for (const k of Object.keys(id).sort()) s += `${k}:${id[k]}
|
|
39
|
-
return s.trim()
|
|
37
|
+
let s = "";
|
|
38
|
+
for (const k of Object.keys(id).sort()) s += `${k}:${id[k]} `;
|
|
39
|
+
return s.trim();
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
eventDataPayload(event, data) {
|
|
43
|
-
const object = data[
|
|
44
|
-
const channel = data[
|
|
45
|
-
const subject = data[
|
|
46
|
-
const attributes = data[
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
const
|
|
43
|
+
const object = data["object"] || { type: "not provided", id: { ID: "not provided" } };
|
|
44
|
+
const channel = data["channel"] || { type: "not specified", id: "not specified" };
|
|
45
|
+
const subject = data["data_subject"] || { type: "not provided", id: { ID: "not provided" } };
|
|
46
|
+
const attributes = data["attributes"] || [
|
|
47
|
+
{ name: "not provided", old: "not provided", new: "not provided" }
|
|
48
|
+
];
|
|
49
|
+
const objectId = this.flattenAndSortIdObject(object["id"]);
|
|
50
|
+
const oldValue = attributes[0]["old"] ?? "";
|
|
51
|
+
const newValue = attributes[0]["new"] ?? "";
|
|
52
|
+
const dataSubjectId = this.flattenAndSortIdObject(subject["id"]);
|
|
51
53
|
|
|
52
54
|
const known = {
|
|
53
55
|
dppDataModification: {
|
|
54
|
-
objectType: object[
|
|
56
|
+
objectType: object["type"],
|
|
55
57
|
objectId: objectId,
|
|
56
|
-
attribute: attributes[0][
|
|
58
|
+
attribute: attributes[0]["name"],
|
|
57
59
|
oldValue: oldValue,
|
|
58
60
|
newValue: newValue,
|
|
59
|
-
dataSubjectType: subject[
|
|
61
|
+
dataSubjectType: subject["type"],
|
|
60
62
|
dataSubjectId: dataSubjectId
|
|
61
63
|
},
|
|
62
64
|
dppDataAccess: {
|
|
63
|
-
channelType: channel[
|
|
64
|
-
channelId: channel[
|
|
65
|
-
dataSubjectType: subject[
|
|
65
|
+
channelType: channel["type"],
|
|
66
|
+
channelId: channel["id"],
|
|
67
|
+
dataSubjectType: subject["type"],
|
|
66
68
|
dataSubjectId: dataSubjectId,
|
|
67
|
-
objectType: object[
|
|
69
|
+
objectType: object["type"],
|
|
68
70
|
objectId: objectId,
|
|
69
|
-
attribute: attributes[0][
|
|
71
|
+
attribute: attributes[0]["name"]
|
|
70
72
|
},
|
|
71
73
|
configurationChange: {
|
|
72
|
-
propertyName: attributes[0][
|
|
74
|
+
propertyName: attributes[0]["name"],
|
|
73
75
|
oldValue: oldValue,
|
|
74
76
|
newValue: newValue,
|
|
75
|
-
objectType: object[
|
|
77
|
+
objectType: object["type"],
|
|
76
78
|
objectId: objectId
|
|
77
79
|
},
|
|
78
80
|
legacySecurityWrapper: {
|
|
79
81
|
origEvent: JSON.stringify({
|
|
80
82
|
...data,
|
|
81
83
|
data:
|
|
82
|
-
typeof data.data ===
|
|
84
|
+
typeof data.data === "object" && data.data !== null && !Array.isArray(data.data)
|
|
83
85
|
? JSON.stringify(data.data)
|
|
84
86
|
: data.data
|
|
85
87
|
})
|
|
86
88
|
}
|
|
87
|
-
}
|
|
88
|
-
if (event in known) return known[event]
|
|
89
|
+
};
|
|
90
|
+
if (event in known) return known[event];
|
|
89
91
|
|
|
90
92
|
// For unknown events, remove common audit log entry fields from the event payload
|
|
91
|
-
if (typeof data ===
|
|
92
|
-
const rest = this.removeCommonAuditLogFields(data)
|
|
93
|
-
return rest
|
|
93
|
+
if (typeof data === "object" && data !== null) {
|
|
94
|
+
const rest = this.removeCommonAuditLogFields(data);
|
|
95
|
+
return rest;
|
|
94
96
|
}
|
|
95
97
|
|
|
96
|
-
return data
|
|
98
|
+
return data;
|
|
97
99
|
}
|
|
98
100
|
|
|
99
101
|
removeCommonAuditLogFields(obj) {
|
|
100
|
-
if (typeof obj !==
|
|
101
|
-
const { ...rest } = obj
|
|
102
|
-
delete rest.uuid
|
|
103
|
-
delete rest.user
|
|
104
|
-
delete rest.time
|
|
105
|
-
delete rest.tenant
|
|
106
|
-
return rest
|
|
102
|
+
if (typeof obj !== "object" || obj === null) return obj;
|
|
103
|
+
const { ...rest } = obj;
|
|
104
|
+
delete rest.uuid;
|
|
105
|
+
delete rest.user;
|
|
106
|
+
delete rest.time;
|
|
107
|
+
delete rest.tenant;
|
|
108
|
+
return rest;
|
|
107
109
|
}
|
|
108
110
|
|
|
109
111
|
eventPayload(event, data) {
|
|
110
|
-
const tenant = cds.context?.tenant || null
|
|
111
|
-
const timestamp = new Date().toISOString()
|
|
112
|
+
const tenant = cds.context?.tenant || null;
|
|
113
|
+
const timestamp = new Date().toISOString();
|
|
112
114
|
|
|
113
115
|
const eventData = {
|
|
114
116
|
id: cds.utils.uuid(),
|
|
@@ -122,12 +124,12 @@ module.exports = class AuditLog2ALSNG extends AuditLogService {
|
|
|
122
124
|
appId: appMetadata.appID || "default app",
|
|
123
125
|
infrastructure: {
|
|
124
126
|
other: {
|
|
125
|
-
runtimeType:
|
|
127
|
+
runtimeType: "Node.js"
|
|
126
128
|
}
|
|
127
129
|
},
|
|
128
130
|
platform: {
|
|
129
131
|
other: {
|
|
130
|
-
platformName:
|
|
132
|
+
platformName: "CAP"
|
|
131
133
|
}
|
|
132
134
|
}
|
|
133
135
|
},
|
|
@@ -135,28 +137,28 @@ module.exports = class AuditLog2ALSNG extends AuditLogService {
|
|
|
135
137
|
[event]: this.eventDataPayload(event, data)
|
|
136
138
|
}
|
|
137
139
|
}
|
|
138
|
-
}
|
|
140
|
+
};
|
|
139
141
|
|
|
140
|
-
return eventData
|
|
142
|
+
return eventData;
|
|
141
143
|
}
|
|
142
144
|
|
|
143
145
|
formatEventData(event, data) {
|
|
144
|
-
if (event ===
|
|
145
|
-
return JSON.stringify([this.eventPayload(event, data)])
|
|
146
|
+
if (event === "legacySecurityWrapper") {
|
|
147
|
+
return JSON.stringify([this.eventPayload(event, data)]);
|
|
146
148
|
}
|
|
147
149
|
|
|
148
150
|
if (event in { dppDataModification: 1, dppDataAccess: 1, configurationChange: 1 }) {
|
|
149
|
-
const eventData = data[
|
|
151
|
+
const eventData = data["attributes"]?.map((attr) => {
|
|
150
152
|
return this.eventPayload(event, {
|
|
151
153
|
...data,
|
|
152
154
|
attributes: [attr]
|
|
153
|
-
})
|
|
154
|
-
})
|
|
155
|
-
return JSON.stringify(eventData || [])
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
return JSON.stringify(eventData || []);
|
|
156
158
|
}
|
|
157
159
|
|
|
158
160
|
// Always wrap event in an envelope for custom events
|
|
159
|
-
return JSON.stringify([this.eventPayload(event, data)])
|
|
161
|
+
return JSON.stringify([this.eventPayload(event, data)]);
|
|
160
162
|
}
|
|
161
163
|
|
|
162
164
|
logEvent(event, data) {
|
|
@@ -165,53 +167,54 @@ module.exports = class AuditLog2ALSNG extends AuditLogService {
|
|
|
165
167
|
throw new Error("No credentials found for SAP Audit Log Service NG");
|
|
166
168
|
}
|
|
167
169
|
|
|
168
|
-
const passphrase = credentials.keyPassphrase
|
|
169
|
-
const url = new URL(`${credentials.url}/ingestion/v1/events`)
|
|
170
|
-
const eventData = this.formatEventData(event, data)
|
|
170
|
+
const passphrase = credentials.keyPassphrase;
|
|
171
|
+
const url = new URL(`${credentials.url}/ingestion/v1/events`);
|
|
172
|
+
const eventData = this.formatEventData(event, data);
|
|
171
173
|
|
|
172
174
|
const options = {
|
|
173
|
-
method:
|
|
175
|
+
method: "POST",
|
|
174
176
|
headers: {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
+
"Content-Type": "application/json",
|
|
178
|
+
"Content-Length": Buffer.byteLength(eventData)
|
|
177
179
|
},
|
|
178
180
|
key: credentials.key,
|
|
179
181
|
cert: credentials.cert,
|
|
180
182
|
...(passphrase !== undefined && { passphrase })
|
|
181
|
-
}
|
|
183
|
+
};
|
|
182
184
|
|
|
183
185
|
return new Promise((resolve, reject) => {
|
|
184
|
-
const req = https.request(url, options, res => {
|
|
185
|
-
LOG.trace(
|
|
186
|
+
const req = https.request(url, options, (res) => {
|
|
187
|
+
LOG.trace("🛰️ Status Code:", res.statusCode);
|
|
186
188
|
|
|
187
|
-
const chunks = []
|
|
188
|
-
res.on(
|
|
189
|
+
const chunks = [];
|
|
190
|
+
res.on("data", (chunk) => chunks.push(chunk));
|
|
189
191
|
|
|
190
|
-
res.on(
|
|
191
|
-
const { statusCode, statusMessage } = res
|
|
192
|
-
let body = Buffer.concat(chunks).toString()
|
|
193
|
-
if (res.headers[
|
|
192
|
+
res.on("end", () => {
|
|
193
|
+
const { statusCode, statusMessage } = res;
|
|
194
|
+
let body = Buffer.concat(chunks).toString();
|
|
195
|
+
if (res.headers["content-type"]?.match(/json/)) body = JSON.parse(body);
|
|
194
196
|
if (res.statusCode >= 400) {
|
|
195
197
|
// prettier-ignore
|
|
196
198
|
const err = new Error(`Request failed with${statusMessage ? `: ${statusCode} - ${statusMessage}` : ` status ${statusCode}`}`)
|
|
197
|
-
err.request = { method: options.method, url, headers: options.headers, body: data }
|
|
199
|
+
err.request = { method: options.method, url, headers: options.headers, body: data };
|
|
198
200
|
if (err.request.headers.authorization)
|
|
199
|
-
err.request.headers.authorization =
|
|
200
|
-
|
|
201
|
-
|
|
201
|
+
err.request.headers.authorization =
|
|
202
|
+
err.request.headers.authorization.split(" ")[0] + " ***";
|
|
203
|
+
err.response = { statusCode, statusMessage, headers: res.headers, body };
|
|
204
|
+
reject(err);
|
|
202
205
|
} else {
|
|
203
|
-
resolve(body)
|
|
206
|
+
resolve(body);
|
|
204
207
|
}
|
|
205
|
-
})
|
|
206
|
-
})
|
|
208
|
+
});
|
|
209
|
+
});
|
|
207
210
|
|
|
208
|
-
req.on(
|
|
209
|
-
reject(e.message)
|
|
210
|
-
LOG.trace(`Problem with request: ${e.message}`)
|
|
211
|
-
})
|
|
211
|
+
req.on("error", (e) => {
|
|
212
|
+
reject(e.message);
|
|
213
|
+
LOG.trace(`Problem with request: ${e.message}`);
|
|
214
|
+
});
|
|
212
215
|
|
|
213
|
-
req.write(eventData)
|
|
214
|
-
req.end()
|
|
215
|
-
})
|
|
216
|
+
req.write(eventData);
|
|
217
|
+
req.end();
|
|
218
|
+
});
|
|
216
219
|
}
|
|
217
|
-
}
|
|
220
|
+
};
|
package/srv/log2console.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
const AuditLogService = require(
|
|
1
|
+
const AuditLogService = require("./service");
|
|
2
2
|
|
|
3
3
|
module.exports = class AuditLog2Console extends AuditLogService {
|
|
4
4
|
async init() {
|
|
5
|
-
this.on(
|
|
6
|
-
const { event, data } = req
|
|
5
|
+
this.on("*", function (req) {
|
|
6
|
+
const { event, data } = req;
|
|
7
7
|
|
|
8
8
|
// eslint-disable-next-line no-console
|
|
9
|
-
console.log(`[audit-log] - ${event}:`, data)
|
|
10
|
-
})
|
|
9
|
+
console.log(`[audit-log] - ${event}:`, data);
|
|
10
|
+
});
|
|
11
11
|
|
|
12
12
|
// call AuditLogService's init
|
|
13
|
-
await super.init()
|
|
13
|
+
await super.init();
|
|
14
14
|
}
|
|
15
|
-
}
|
|
15
|
+
};
|
package/srv/log2restv2.js
CHANGED
|
@@ -9,17 +9,13 @@ module.exports = class AuditLog2RESTv2 extends AuditLogService {
|
|
|
9
9
|
async init() {
|
|
10
10
|
// credentials stuff
|
|
11
11
|
const { credentials } = this.options;
|
|
12
|
-
if (!credentials)
|
|
13
|
-
throw new Error('No or malformed credentials for "audit-log"');
|
|
12
|
+
if (!credentials) throw new Error('No or malformed credentials for "audit-log"');
|
|
14
13
|
if (!credentials.uaa) {
|
|
15
14
|
this._plan = "standard";
|
|
16
15
|
this._auth =
|
|
17
|
-
"Basic " +
|
|
18
|
-
Buffer.from(credentials.user + ":" + credentials.password).toString(
|
|
19
|
-
"base64",
|
|
20
|
-
);
|
|
16
|
+
"Basic " + Buffer.from(credentials.user + ":" + credentials.password).toString("base64");
|
|
21
17
|
} else {
|
|
22
|
-
this._plan = credentials.url.match(/6081/) ? "premium" : "oauth2";
|
|
18
|
+
this._plan = credentials.url.match(/6081|\/premium/) ? "premium" : "oauth2";
|
|
23
19
|
this._tokens = new Map();
|
|
24
20
|
this._provider = credentials.uaa.tenantid;
|
|
25
21
|
}
|
|
@@ -31,10 +27,7 @@ module.exports = class AuditLog2RESTv2 extends AuditLogService {
|
|
|
31
27
|
if (event === "SensitiveDataRead" || event.match(/^dataAccess/i)) {
|
|
32
28
|
return this._handle(data, "DATA_ACCESS");
|
|
33
29
|
}
|
|
34
|
-
if (
|
|
35
|
-
event === "PersonalDataModified" ||
|
|
36
|
-
event.match(/^dataModification/i)
|
|
37
|
-
) {
|
|
30
|
+
if (event === "PersonalDataModified" || event.match(/^dataModification/i)) {
|
|
38
31
|
data.success = true;
|
|
39
32
|
return this._handle(data, "DATA_MODIFICATION");
|
|
40
33
|
}
|
|
@@ -43,8 +36,7 @@ module.exports = class AuditLog2RESTv2 extends AuditLogService {
|
|
|
43
36
|
return this._handle(data, "CONFIGURATION_CHANGE");
|
|
44
37
|
}
|
|
45
38
|
if (event === "SecurityEvent" || event.match(/^security/i)) {
|
|
46
|
-
if (typeof data.data === "object")
|
|
47
|
-
data.data = JSON.stringify(data.data);
|
|
39
|
+
if (typeof data.data === "object") data.data = JSON.stringify(data.data);
|
|
48
40
|
return this._handle(data, "SECURITY_EVENT");
|
|
49
41
|
}
|
|
50
42
|
|
|
@@ -64,10 +56,10 @@ module.exports = class AuditLog2RESTv2 extends AuditLogService {
|
|
|
64
56
|
const data = {
|
|
65
57
|
grant_type: "client_credentials",
|
|
66
58
|
response_type: "token",
|
|
67
|
-
client_id: uaa.clientid
|
|
59
|
+
client_id: uaa.clientid
|
|
68
60
|
};
|
|
69
61
|
const options = {
|
|
70
|
-
headers: { "content-type": "application/x-www-form-urlencoded" }
|
|
62
|
+
headers: { "content-type": "application/x-www-form-urlencoded" }
|
|
71
63
|
};
|
|
72
64
|
if (tenant !== this._provider) options.headers["x-zid"] = tenant;
|
|
73
65
|
|
|
@@ -77,16 +69,9 @@ module.exports = class AuditLog2RESTv2 extends AuditLogService {
|
|
|
77
69
|
} else {
|
|
78
70
|
data.client_secret = uaa.clientsecret;
|
|
79
71
|
}
|
|
80
|
-
const
|
|
81
|
-
acc += (acc ? "&" : "") + cur + "=" + data[cur];
|
|
82
|
-
return acc;
|
|
83
|
-
}, "");
|
|
72
|
+
const encodedParams = new URLSearchParams(data).toString();
|
|
84
73
|
try {
|
|
85
|
-
const { access_token, expires_in } = await _post(
|
|
86
|
-
url,
|
|
87
|
-
urlencoded,
|
|
88
|
-
options,
|
|
89
|
-
);
|
|
74
|
+
const { access_token, expires_in } = await _post(url, encodedParams, options);
|
|
90
75
|
tokens.set(tenant, access_token);
|
|
91
76
|
// remove token from cache 60 seconds before it expires
|
|
92
77
|
setTimeout(() => tokens.delete(tenant), (expires_in - 60) * 1000);
|
|
@@ -94,8 +79,7 @@ module.exports = class AuditLog2RESTv2 extends AuditLogService {
|
|
|
94
79
|
} catch (err) {
|
|
95
80
|
LOG._trace && LOG.trace("error during token fetch:", err);
|
|
96
81
|
// 401 could also mean x-zid is not valid
|
|
97
|
-
if (String(err.response?.statusCode).match(/^4\d\d$/))
|
|
98
|
-
err.unrecoverable = true;
|
|
82
|
+
if (String(err.response?.statusCode).match(/^4\d\d$/)) err.unrecoverable = true;
|
|
99
83
|
throw err;
|
|
100
84
|
}
|
|
101
85
|
}
|
|
@@ -118,16 +102,15 @@ module.exports = class AuditLog2RESTv2 extends AuditLogService {
|
|
|
118
102
|
data.tenant ??= this._provider; //> if request has no tenant, stay in provider account
|
|
119
103
|
if (data.tenant === "$PROVIDER") data.tenant = this._provider;
|
|
120
104
|
headers.authorization = "Bearer " + (await this._getToken(data.tenant));
|
|
121
|
-
data.tenant =
|
|
122
|
-
data.tenant === this._provider ? "$PROVIDER" : "$SUBSCRIBER";
|
|
105
|
+
data.tenant = data.tenant === this._provider ? "$PROVIDER" : "$SUBSCRIBER";
|
|
123
106
|
}
|
|
124
107
|
if (LOG._debug) {
|
|
125
108
|
const _headers = Object.assign({}, headers, {
|
|
126
|
-
authorization: headers.authorization.split(" ")[0] + " ***"
|
|
109
|
+
authorization: headers.authorization.split(" ")[0] + " ***"
|
|
127
110
|
});
|
|
128
111
|
LOG.debug(
|
|
129
112
|
`sending audit log to ${url} with tenant "${data.tenant}", user "${data.user}", and headers`,
|
|
130
|
-
_headers
|
|
113
|
+
_headers
|
|
131
114
|
);
|
|
132
115
|
}
|
|
133
116
|
try {
|
|
@@ -135,10 +118,7 @@ module.exports = class AuditLog2RESTv2 extends AuditLogService {
|
|
|
135
118
|
} catch (err) {
|
|
136
119
|
LOG._trace && LOG.trace("error during log send:", err);
|
|
137
120
|
// 429 (rate limit) is not unrecoverable
|
|
138
|
-
if (
|
|
139
|
-
String(err.response?.statusCode).match(/^4\d\d$/) &&
|
|
140
|
-
err.response?.statusCode !== 429
|
|
141
|
-
)
|
|
121
|
+
if (String(err.response?.statusCode).match(/^4\d\d$/) && err.response?.statusCode !== 429)
|
|
142
122
|
err.unrecoverable = true;
|
|
143
123
|
throw err;
|
|
144
124
|
}
|
|
@@ -149,9 +129,7 @@ module.exports = class AuditLog2RESTv2 extends AuditLogService {
|
|
|
149
129
|
|
|
150
130
|
// write the logs
|
|
151
131
|
const errors = [];
|
|
152
|
-
await Promise.all(
|
|
153
|
-
logs.map((log) => this._send(log, path).catch((err) => errors.push(err))),
|
|
154
|
-
);
|
|
132
|
+
await Promise.all(logs.map((log) => this._send(log, path).catch((err) => errors.push(err))));
|
|
155
133
|
if (errors.length) throw _getErrorToThrow(errors);
|
|
156
134
|
}
|
|
157
135
|
};
|
|
@@ -165,14 +143,14 @@ const PATHS = {
|
|
|
165
143
|
DATA_ACCESS: "/audit-log/v2/data-accesses",
|
|
166
144
|
DATA_MODIFICATION: "/audit-log/v2/data-modifications",
|
|
167
145
|
CONFIGURATION_CHANGE: "/audit-log/v2/configuration-changes",
|
|
168
|
-
SECURITY_EVENT: "/audit-log/v2/security-events"
|
|
146
|
+
SECURITY_EVENT: "/audit-log/v2/security-events"
|
|
169
147
|
},
|
|
170
148
|
OAUTH2: {
|
|
171
149
|
DATA_ACCESS: "/audit-log/oauth2/v2/data-accesses",
|
|
172
150
|
DATA_MODIFICATION: "/audit-log/oauth2/v2/data-modifications",
|
|
173
151
|
CONFIGURATION_CHANGE: "/audit-log/oauth2/v2/configuration-changes",
|
|
174
|
-
SECURITY_EVENT: "/audit-log/oauth2/v2/security-events"
|
|
175
|
-
}
|
|
152
|
+
SECURITY_EVENT: "/audit-log/oauth2/v2/security-events"
|
|
153
|
+
}
|
|
176
154
|
};
|
|
177
155
|
|
|
178
156
|
/*
|
|
@@ -198,13 +176,13 @@ async function _post(url, data, options) {
|
|
|
198
176
|
: "";
|
|
199
177
|
LOG._trace && LOG.trace(`Request body of failed audit-log: `, data);
|
|
200
178
|
const err = new Error(
|
|
201
|
-
`Request failed with${message ? `: ${statusCode} - ${message}` : ` status ${statusCode}`}
|
|
179
|
+
`Request failed with${message ? `: ${statusCode} - ${message}` : ` status ${statusCode}`}`
|
|
202
180
|
);
|
|
203
181
|
err.request = {
|
|
204
182
|
method: options.method,
|
|
205
183
|
url,
|
|
206
184
|
headers: options.headers,
|
|
207
|
-
body: data
|
|
185
|
+
body: data
|
|
208
186
|
};
|
|
209
187
|
if (err.request.headers.authorization)
|
|
210
188
|
err.request.headers.authorization =
|
package/srv/service.js
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
const cds = require(
|
|
1
|
+
const cds = require("@sap/cds");
|
|
2
2
|
|
|
3
3
|
module.exports = class AuditLogService extends cds.Service {
|
|
4
4
|
async init() {
|
|
5
5
|
// add common audit log entry fields
|
|
6
|
-
this.before(
|
|
7
|
-
const { tenant, user, timestamp } = cds.context
|
|
8
|
-
req.data.uuid ??= cds.utils.uuid()
|
|
6
|
+
this.before("*", (req) => {
|
|
7
|
+
const { tenant, user, timestamp } = cds.context;
|
|
8
|
+
req.data.uuid ??= cds.utils.uuid();
|
|
9
9
|
// allows to specify null as tenant in order to log to provider in multi-tenant scenarios
|
|
10
10
|
// NOTE: tenant: null is not a public API!
|
|
11
|
-
if (!(
|
|
12
|
-
req.data.user ??= user.id
|
|
13
|
-
req.data.time ??= timestamp
|
|
14
|
-
})
|
|
11
|
+
if (!("tenant" in req.data)) req.data.tenant = tenant;
|
|
12
|
+
req.data.user ??= user.id;
|
|
13
|
+
req.data.time ??= timestamp;
|
|
14
|
+
});
|
|
15
15
|
|
|
16
16
|
// call OutboxService's init
|
|
17
|
-
await super.init()
|
|
17
|
+
await super.init();
|
|
18
18
|
|
|
19
19
|
// add self-explanatory api (await audit.log/logSync(event, data))
|
|
20
|
-
this.log = this.emit
|
|
20
|
+
this.log = this.emit;
|
|
21
21
|
// NOTE: logSync is not a public API!
|
|
22
22
|
this.logSync = (...args) => {
|
|
23
|
-
if (cds.unboxed) return cds.unboxed(this).send(...args) //> cds >= 7.5
|
|
24
|
-
if (this.immediate instanceof cds.Service) return this.immediate.send(...args) //> cds ~ 7.4
|
|
25
|
-
return this.send(...args) //> cds <= 7.3
|
|
26
|
-
}
|
|
23
|
+
if (cds.unboxed) return cds.unboxed(this).send(...args); //> cds >= 7.5
|
|
24
|
+
if (this.immediate instanceof cds.Service) return this.immediate.send(...args); //> cds ~ 7.4
|
|
25
|
+
return this.send(...args); //> cds <= 7.3
|
|
26
|
+
};
|
|
27
27
|
}
|
|
28
|
-
}
|
|
28
|
+
};
|