@cap-js/audit-logging 1.0.0 → 1.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cap-js/audit-logging",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
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)",
@@ -9,11 +9,10 @@
9
9
  "main": "cds-plugin.js",
10
10
  "files": [
11
11
  "lib",
12
- "srv",
13
- "CHANGELOG.md"
12
+ "srv"
14
13
  ],
15
14
  "scripts": {
16
- "lint": "npx eslint .",
15
+ "lint": "npx eslint . --max-warnings=0",
17
16
  "test": "npx jest --silent"
18
17
  },
19
18
  "peerDependencies": {
@@ -26,13 +25,12 @@
26
25
  "axios": "^1",
27
26
  "eslint": "^9",
28
27
  "express": "^4",
29
- "jest": "^29"
28
+ "jest": "^29",
29
+ "pretty-quick": "^4.2.2",
30
+ "simple-git-hooks": "^2.13.1"
30
31
  },
31
32
  "cds": {
32
33
  "requires": {
33
- "[test]": {
34
- "outbox": "persistent-outbox"
35
- },
36
34
  "audit-log": {
37
35
  "handle": [
38
36
  "READ",
package/srv/log2alsng.js CHANGED
@@ -1,5 +1,4 @@
1
1
  const cds = require('@sap/cds')
2
-
3
2
  const LOG = cds.log('audit-log')
4
3
 
5
4
  const https = require('https')
@@ -12,7 +11,7 @@ module.exports = class AuditLog2ALSNG extends AuditLogService {
12
11
  this._vcap = JSON.parse(process.env.VCAP_SERVICES || '{}')
13
12
  this._userProvided = this._vcap['user-provided']?.find(obj => obj.tags.includes('auditlog-ng')) || {}
14
13
  if (!this._userProvided.credentials) throw new Error('No credentials found for SAP Audit Log Service NG')
15
- this._vcapApplication = this._vcap['VCAP_APPLICATION'] || {}
14
+ this._vcapApplication = JSON.parse(process.env.VCAP_APPLICATION || '{}')
16
15
  }
17
16
 
18
17
  async init() {
@@ -24,12 +23,22 @@ module.exports = class AuditLog2ALSNG extends AuditLogService {
24
23
  }
25
24
 
26
25
  eventMapper(event, data) {
27
- return {
26
+ const known = {
28
27
  PersonalDataModified: () => this.logEvent('dppDataModification', data),
29
28
  SensitiveDataRead: () => this.logEvent('dppDataAccess', data),
30
29
  ConfigurationModified: () => this.logEvent('configurationChange', data),
31
30
  SecurityEvent: () => this.logEvent('legacySecurityWrapper', data)
32
- }[event]()
31
+ }
32
+ const dfault = () => this.logEvent(event, data)
33
+ return (known[event] ?? dfault)()
34
+ }
35
+
36
+ flattenAndSortIdObject(id) {
37
+ if (!id || !Object.keys(id).length) return 'not provided'
38
+
39
+ let s = ''
40
+ for (const k of Object.keys(id).sort()) s += `${k}:${id[k]} `
41
+ return s.trim()
33
42
  }
34
43
 
35
44
  eventDataPayload(event, data) {
@@ -37,11 +46,12 @@ module.exports = class AuditLog2ALSNG extends AuditLogService {
37
46
  const channel = data['channel'] || { type: 'not specified', id: 'not specified' }
38
47
  const subject = data['data_subject'] || { type: 'not provided', id: { ID: 'not provided' } }
39
48
  const attributes = data['attributes'] || [{ name: 'not provided', old: 'not provided', new: 'not provided' }]
40
- const objectId = object['id']?.['ID'] || 'not provided'
49
+ const objectId = this.flattenAndSortIdObject(object['id'])
41
50
  const oldValue = attributes[0]['old'] ?? ''
42
51
  const newValue = attributes[0]['new'] ?? ''
43
- const dataSubjectId = subject['id']?.['ID'] || 'not provided'
44
- return {
52
+ const dataSubjectId = this.flattenAndSortIdObject(subject['id'])
53
+
54
+ const known = {
45
55
  dppDataModification: {
46
56
  objectType: object['type'],
47
57
  objectId: objectId,
@@ -76,7 +86,26 @@ module.exports = class AuditLog2ALSNG extends AuditLogService {
76
86
  : data.data
77
87
  })
78
88
  }
79
- }[event]
89
+ }
90
+ if (event in known) return known[event]
91
+
92
+ // For unknown events, remove common audit log entry fields from the event payload
93
+ if (typeof data === 'object' && data !== null) {
94
+ const rest = this.removeCommonAuditLogFields(data)
95
+ return rest
96
+ }
97
+
98
+ return data
99
+ }
100
+
101
+ removeCommonAuditLogFields(obj) {
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
80
109
  }
81
110
 
82
111
  eventPayload(event, data) {
@@ -113,23 +142,30 @@ module.exports = class AuditLog2ALSNG extends AuditLogService {
113
142
  return eventData
114
143
  }
115
144
 
116
- logEvent(event, data) {
117
- const passphrase = this._userProvided.credentials?.keyPassphrase
118
- const url = new URL(`${this._userProvided.credentials?.url}/ingestion/v1/events`)
119
- let eventData = []
120
-
145
+ formatEventData(event, data) {
121
146
  if (event === 'legacySecurityWrapper') {
122
- eventData = JSON.stringify([this.eventPayload(event, data)])
123
- } else {
124
- eventData = data['attributes'].map(attr => {
147
+ return JSON.stringify([this.eventPayload(event, data)])
148
+ }
149
+
150
+ if (event in { dppDataModification: 1, dppDataAccess: 1, configurationChange: 1 }) {
151
+ const eventData = data['attributes']?.map(attr => {
125
152
  return this.eventPayload(event, {
126
153
  ...data,
127
154
  attributes: [attr]
128
155
  })
129
156
  })
130
- eventData = JSON.stringify(eventData)
157
+ return JSON.stringify(eventData || [])
131
158
  }
132
159
 
160
+ // Always wrap event in an envelope for custom events
161
+ return JSON.stringify([this.eventPayload(event, data)])
162
+ }
163
+
164
+ logEvent(event, data) {
165
+ const passphrase = this._userProvided.credentials?.keyPassphrase
166
+ const url = new URL(`${this._userProvided.credentials?.url}/ingestion/v1/events`)
167
+ const eventData = this.formatEventData(event, data)
168
+
133
169
  const options = {
134
170
  method: 'POST',
135
171
  headers: {
package/CHANGELOG.md DELETED
@@ -1,164 +0,0 @@
1
- # Change Log
2
-
3
- All notable changes to this project will be documented in this file.
4
- This project adheres to [Semantic Versioning](http://semver.org/).
5
- The format is based on [Keep a Changelog](http://keepachangelog.com/).
6
-
7
- ## Version 1.0.0 - 2025-07-11
8
-
9
- ### Added
10
-
11
- - Beta support for next generation SAP Audit Log Service
12
- - Use explicit kind `audit-log-to-alsng` or alpha auto-detect kind `audit-log-to-als`
13
-
14
- ## Version 0.9.0 - 2025-06-05
15
-
16
- ### Added
17
-
18
- - Support for `@sap/cds^9`
19
-
20
- ## Version 0.8.3 - 2025-04-09
21
-
22
- ### Fixed
23
-
24
- - Preperation for `@sap/cds^9`
25
-
26
- ## Version 0.8.2 - 2024-11-27
27
-
28
- ### Fixed
29
-
30
- - Erroneous modification log for non-updated key properties
31
- - Error during non-modifying queries on database level
32
- - Specify charset UTF-8 for requests to SAP Audit Log Service
33
-
34
- ## Version 0.8.1 - 2024-09-13
35
-
36
- ### Fixed
37
-
38
- - Support for `@sap/cds^8.2`
39
- - Reduce clutter in error raised for outbound requests
40
-
41
- ## Version 0.8.0 - 2024-05-24
42
-
43
- ### Added
44
-
45
- - Allow to specify undefined tenant in order to log to provider account in multi-tenant scenarios
46
-
47
- ### Changed
48
-
49
- - Use kind `audit-log-to-restv2` in profile `hybrid`
50
-
51
- ## Version 0.7.0 - 2024-05-15
52
-
53
- ### Added
54
-
55
- - Automatically promote entities that are associated with data subjects
56
-
57
- ## Version 0.6.0 - 2024-02-05
58
-
59
- ### Added
60
-
61
- - Support for `@sap/cds^7.5`
62
-
63
- ### Fixed
64
-
65
- - Automatic personal data modification logging for data subject details with renamed keys
66
- - Data subject resolution in circular models
67
-
68
- ## Version 0.5.2 - 2023-12-08
69
-
70
- ### Fixed
71
-
72
- - Automatic personal data modification logging for deep data structures with renamings
73
-
74
- ## Version 0.5.1 - 2023-11-30
75
-
76
- ### Fixed
77
-
78
- - Falsy early exit during bootstrapping in case a service does not contain personal data
79
-
80
- ## Version 0.5.0 - 2023-11-22
81
-
82
- ### Added
83
-
84
- - Common log entry fields `uuid`, `tenant`, `user` and `time` can be provided manually
85
-
86
- ## Version 0.4.0 - 2023-10-24
87
-
88
- ### Added
89
-
90
- - Support for Premium plan of SAP Audit Log Service
91
- - Support for XSUAA credential type `x509`
92
- - Support for generic outbox
93
-
94
- ### Changed
95
-
96
- - Always use outbox (as configured in project)
97
-
98
- ### Fixed
99
-
100
- - Avoid dangling `SELECT`s to resolve data subject IDs, which resulted in "Transaction already closed" errors
101
-
102
- ## Version 0.3.2 - 2023-10-11
103
-
104
- ### Fixed
105
-
106
- - If the request has no tenant (e.g., Unauthorized), the audit log shall be sent to the provider account
107
-
108
- ## Version 0.3.1 - 2023-09-25
109
-
110
- ### Fixed
111
-
112
- - Defaulting of `@PersonalData.DataSubjectRole` to entity name
113
- - Overriding service configuration
114
-
115
- ## Version 0.3.0 - 2023-09-05
116
-
117
- ### Changed
118
-
119
- - Default value for `cds.requires['audit-log'].handle` changed to `['READ', 'WRITE']`, i.e., accessing sensitive data is now logged by default.
120
-
121
- ## Version 0.2.0 - 2023-09-01
122
-
123
- ### Added
124
-
125
- - Export class `AuditLogService` for extending in custom implementations as follows:
126
- ```js
127
- const { AuditLogService } = require('@cap-js/audit-logging')
128
- class MyAuditLogService extends AuditLogService {
129
- async init() {
130
- [...]
131
- // call AuditLogService's init
132
- await super.init()
133
- }
134
- }
135
- module.exports = MyAuditLogService
136
- ```
137
-
138
- ## Version 0.1.0 - 2023-08-18
139
-
140
- ### Added
141
-
142
- - New API:
143
- - `await audit.log('<event>', <data>)` for asynchronous logs (cf. `emit`)
144
- - `await audit.logSync('<event>', <data>)` for synchronous logs (cf. `send`)
145
- - New REST API-based schema with auto-filled `LogEntry` aspect
146
- - New events `SensitiveDataRead`, `PersonalDataModified`, `ConfigurationModified`, and `SecurityEvent`
147
- - Full support for OAuth2 plan of SAP Audit Log Service
148
-
149
- ### Changed
150
-
151
- - Whether reading sensitive data and modifying personal data is logged is determined by `cds.requires['audit-log'].handle: [...]`.
152
- Possible values in the array are `READ` and/ or `WRITE`, with `WRITE` as the sole default entry.
153
- Hence, accessing sensitive data is not logged by default.
154
- - Integration with SAP Audit Log Service via REST API instead of client library (`@sap/audit-logging`)
155
-
156
- ### Fixed
157
-
158
- - Various glitches in log calculation
159
-
160
- ### Removed
161
-
162
- - Old events `dataAccessLog`, `dataModificationLog`, `configChangeLog`, and `securityLog`
163
- - `@AuditLog.Operation` annotations are ignored. Having the plugin as dependency signals the intent to audit log.
164
- - `cds.features.audit_personal_data: true` is no longer necessary. Instead, simply add the plugin as a dependency.