diff --git a/bookshop/app/index.cds b/bookshop/app/index.cds index ce737b46..1ae1bc46 100644 --- a/bookshop/app/index.cds +++ b/bookshop/app/index.cds @@ -1,2 +1,2 @@ // Incorporate pre-build extensions from... -using from '@capire/common'; +using from '../../common'; diff --git a/bookshop/db/data/sap.capire.bookshop-Authors.csv b/bookshop/db/data/_sap.capire.bookshop-Authors.csv similarity index 100% rename from bookshop/db/data/sap.capire.bookshop-Authors.csv rename to bookshop/db/data/_sap.capire.bookshop-Authors.csv diff --git a/bookshop/db/data/sap.capire.bookshop-Books.csv b/bookshop/db/data/_sap.capire.bookshop-Books.csv similarity index 100% rename from bookshop/db/data/sap.capire.bookshop-Books.csv rename to bookshop/db/data/_sap.capire.bookshop-Books.csv diff --git a/bookshop/db/data/sap.capire.bookshop-Books_texts.csv b/bookshop/db/data/_sap.capire.bookshop-Books_texts.csv similarity index 100% rename from bookshop/db/data/sap.capire.bookshop-Books_texts.csv rename to bookshop/db/data/_sap.capire.bookshop-Books_texts.csv diff --git a/bookshop/db/data/sap.capire.bookshop-Genres.csv b/bookshop/db/data/_sap.capire.bookshop-Genres.csv similarity index 100% rename from bookshop/db/data/sap.capire.bookshop-Genres.csv rename to bookshop/db/data/_sap.capire.bookshop-Genres.csv diff --git a/bookshop/srv/cat-service.cds b/bookshop/srv/cat-service.cds index b596cbea..07031ea6 100644 --- a/bookshop/srv/cat-service.cds +++ b/bookshop/srv/cat-service.cds @@ -6,5 +6,5 @@ service CatalogService @(path:'/browse') { } excluding { createdBy, modifiedBy }; @requires_: 'authenticated-user' - action submitOrder (book : Books.ID, amount: Integer); + action submitOrder (book : Integer, amount: Integer); } diff --git a/gdpr/db/AuditLogStore.cds b/gdpr/db/AuditLogStore.cds new file mode 100644 index 00000000..cb76a917 --- /dev/null +++ b/gdpr/db/AuditLogStore.cds @@ -0,0 +1,23 @@ +using { managed, cuid, sap.common.CodeList } from '@sap/cds/common'; + +namespace sap.capire.auditLog; + +entity AuditLogStore : cuid { + + Action : String enum{DataAccess; DataModification}; + + User : String; + Timestamp : Timestamp; + Tenant : String; + Channel : String; + + DataSubjectType : String; // Bussiness Partner + DataSubjectRole : String; // Customer // Employee // ... + DataSubjectID : LargeString; // key value pair as JSON + ObjectType : String; // like SalesOrder + ObjectKey : LargeString; // key value pair as JSON + + Blob : LargeString; // Payload: DataModification or Data Access as BLOB + +} + diff --git a/gdpr/index.cds b/gdpr/index.cds index 3f57f998..ab5068be 100644 --- a/gdpr/index.cds +++ b/gdpr/index.cds @@ -1,3 +1,4 @@ namespace sap.capire.gdpr; //> important for reflection using from './db/schema'; -using from './srv/pdm-service'; \ No newline at end of file +using from './srv/pdm-service'; +using from './srv/log-service'; \ No newline at end of file diff --git a/gdpr/package.json b/gdpr/package.json index 2408036e..d6f0d5d1 100644 --- a/gdpr/package.json +++ b/gdpr/package.json @@ -5,7 +5,6 @@ "@capire/bookshop": "../bookshop", "@capire/common": "../common", "@capire/orders": "../orders", - "@sap/audit-logging": "^5.0.0", "@sap/cds": "^5", "@sap/hana-client": "^2.4.177", "@sap/xsenv": "^3.1.0", @@ -24,7 +23,11 @@ }, "uaa": { "kind": "xsuaa" + }, + "audit-log": { + "impl": "srv/customAuditLog.js" } - } + }, + "features": {"audit_personal_data": true} } } diff --git a/gdpr/srv/customAuditLog.js b/gdpr/srv/customAuditLog.js new file mode 100644 index 00000000..e4c81073 --- /dev/null +++ b/gdpr/srv/customAuditLog.js @@ -0,0 +1,143 @@ +const cds = require('@sap/cds') + +module.exports = class MyAuditLogService extends cds.AuditLogService { + async init() { + + // console.log('My Audit Log'); + // call AuditLogService's init + await super.init() + + const db = await cds.connect.to('db') + const { AuditLogStore } = db.entities('sap.capire.auditLog') + + // register custom handlers + this.on('dataAccessLog', async req => { + + const logs = []; + + const action = 'DataAccess'; + const user = req.user.id; + const timestamp = req.timestamp; + const tenant = req.tenant; + const channel = req.channel; + + req.data.accesses.forEach( dataAccess => { + logs.push({ + Action: action, + User: user, + Timestamp: timestamp, + Tenant: tenant, + Channel: channel, + DataSubjectType: dataAccess.dataSubject.type, + DataSubjectRole: dataAccess.dataSubject.role, + DataSubjectID: JSON.stringify(dataAccess.dataSubject.id), + ObjectType: dataAccess.dataObject.type, + ObjectKey: JSON.stringify(dataAccess.dataObject.id), + Blob: JSON.stringify(dataAccess) + }) } + ) + + + await INSERT.into(AuditLogStore).entries(logs) + } + ) + + this.on('dataModificationLog', async req => { + + const mods = []; + + const action = 'DataModification'; + const user = req.user.id; + const timestamp = req.timestamp; + const tenant = req.tenant; + const channel = req.channel; + + req.data.modifications.forEach( dataModification => { + mods.push({ + Action: action, + User: user, + Timestamp: timestamp, + Tenant: tenant, + Channel: channel, + DataSubjectType: dataModification.dataSubject.type, + DataSubjectRole: dataModification.dataSubject.role, + DataSubjectID: JSON.stringify(dataModification.dataSubject.id), + ObjectType: dataModification.dataObject.type, + ObjectKey: JSON.stringify(dataModification.dataObject.id), + Blob: JSON.stringify(dataModification) + }) } + ) + + + + await INSERT.into(AuditLogStore).entries(mods) + } + ) + } + + + + + + + + } + +/* +service AuditLogService { + + // SEC-254: Log read access to sensitive personal data + event dataAccessLog { + accesses : array of Access; + }; + + // SEC-265: Log changes to personal data + event dataModificationLog : { + c : array of DataModification; + }; +} +*/ + + +/* +define type KeyValuePair { + keyName : String; + value : String; +}; + +define type DataObject { + type : String; + id : array of KeyValuePair; +}; + +define type DataSubject { + type : String; + id : array of KeyValuePair; + role : String; +}; + +define type Attribute { + name : String; +}; + + +define type Access { + dataObject : DataObject; + dataSubject : DataSubject; + attributes : array of Attribute; + attachments : array of Attachment; +}; + +define type ChangedAttribute { + name : String; + oldValue : String; + newValue : String; +}; + +define type DataModification { + dataObject : DataObject; + dataSubject : DataSubject; + action : String @assert.range enum { Create; Update; Delete; }; + attributes : array of ChangedAttribute; +} +*/ \ No newline at end of file diff --git a/gdpr/srv/log-service.cds b/gdpr/srv/log-service.cds new file mode 100644 index 00000000..ec31ad49 --- /dev/null +++ b/gdpr/srv/log-service.cds @@ -0,0 +1,13 @@ +using {sap.capire.bookshop as db} from '../db/data-privacy'; +using {sap.capire.auditLog as log} from '../db/AuditLogStore.cds'; + +//@requires: 'PersonalDataManagerUser' // security check +service LogService { + + entity Customers as projection on db.Customers; + entity CustomerPostalAddress as projection on db.CustomerPostalAddress; + entity Orders as projection on db.Orders; + + entity AuditLogStore as projection on log.AuditLogStore; + +}; diff --git a/gdpr/srv/pdm-service.cds b/gdpr/srv/pdm-service.cds index e120aa05..c8a93f0e 100644 --- a/gdpr/srv/pdm-service.cds +++ b/gdpr/srv/pdm-service.cds @@ -3,7 +3,7 @@ using {sap.capire.bookshop.Books} from '../db/data-privacy'; using {sap.capire.bookshop.Orders} from '../db/data-privacy'; using {sap.capire.bookshop.OrderItems} from '../db/data-privacy'; -@requires: 'PersonalDataManagerUser' // security check +//@requires: 'PersonalDataManagerUser' // security check service PDMService { entity Customers as projection on db.Customers; diff --git a/gdpr/test.http b/gdpr/test.http new file mode 100644 index 00000000..6633d67c --- /dev/null +++ b/gdpr/test.http @@ -0,0 +1,25 @@ +### + +get http://localhost:4004/log/AuditLogStore + +### + +get http://localhost:4004/log/Customers + +### + +post http://localhost:4004/log/Customers +Content-Type: application/json + +{ + "ID": "22e718c9-ff99-47f1-8ca3-950c850777d4", + "createdAt": "2019-01-30T00:00:00.000Z", + "createdBy": "admin@business.com", + "modifiedAt": "2019-04-04T00:00:00.000Z", + "modifiedBy": "admin@business.com", + "email": "johanna.doe@company.org", + "firstName": "Queen Johanna", + "lastName": "Doe", + "creditCardNo": "1313-7171-5656-7878", + "dateOfBirth": "2001-11-11" +} diff --git a/orders/db/schema.cds b/orders/db/schema.cds index 048aba28..ba2e4160 100644 --- a/orders/db/schema.cds +++ b/orders/db/schema.cds @@ -1,5 +1,5 @@ -using { sap.capire.bookshop.Books } from '@capire/bookshop'; -using { Currency, managed, cuid } from '@sap/cds/common'; +using { sap.capire.bookshop.Books } from '../../bookshop/db/schema'; +using { Currency, managed, cuid } from '@sap/cds/common'; namespace sap.capire.bookshop; entity Orders : cuid, managed { diff --git a/package.json b/package.json index 7efd36e2..73b2d302 100644 --- a/package.json +++ b/package.json @@ -5,12 +5,12 @@ "repository": "https://github.com/sap-samples/cloud-cap-samples.git", "author": "daniel.hutzel@sap.com", "dependencies": { - "@capire/bookshop": "file:bookshop", - "@capire/common": "file:common", - "@capire/fiori": "file:fiori", - "@capire/media": "file:media", - "@capire/orders": "file:orders", - "@capire/reviews": "file:reviews" + "@capire/bookshop": "./bookshop", + "@capire/common": "./common", + "@capire/fiori": "./fiori", + "@capire/media": "./media", + "@capire/orders": "./orders", + "@capire/reviews": "./reviews" }, "devDependencies": { "chai": "^4.2.0",