Compare commits
2 Commits
gdpr
...
gdpr-with-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
673fec080a | ||
|
|
6f7b786ca0 |
@@ -1,23 +1,28 @@
|
|||||||
using { managed, cuid, sap.common.CodeList } from '@sap/cds/common';
|
using {
|
||||||
|
managed,
|
||||||
|
cuid,
|
||||||
|
sap.common.CodeList
|
||||||
|
} from '@sap/cds/common';
|
||||||
|
|
||||||
namespace sap.capire.auditLog;
|
namespace sap.capire.auditLog;
|
||||||
|
|
||||||
entity AuditLogStore : cuid {
|
entity AuditLogStore : cuid {
|
||||||
|
|
||||||
Action : String enum{DataAccess; DataModification};
|
Action : String enum {
|
||||||
|
DataAccess;
|
||||||
|
DataModification
|
||||||
|
};
|
||||||
|
|
||||||
User : String;
|
User : String;
|
||||||
Timestamp : Timestamp;
|
Timestamp : Timestamp;
|
||||||
Tenant : String;
|
Tenant : String;
|
||||||
Channel : 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
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,65 +2,37 @@
|
|||||||
using {sap.capire.bookshop} from './schema';
|
using {sap.capire.bookshop} from './schema';
|
||||||
|
|
||||||
// annotations for Data Privacy
|
// annotations for Data Privacy
|
||||||
annotate bookshop.Customers with @PersonalData : {
|
annotate bookshop.Customers with @PersonalData: {
|
||||||
DataSubjectRole : 'Customer',
|
DataSubjectRole: 'Customer',
|
||||||
EntitySemantics : 'DataSubject'
|
EntitySemantics: 'DataSubject'
|
||||||
}
|
} {
|
||||||
{
|
ID @PersonalData.FieldSemantics : 'DataSubjectID';
|
||||||
ID @PersonalData.FieldSemantics : 'DataSubjectID';
|
email @PersonalData.IsPotentiallyPersonal;
|
||||||
email @PersonalData.IsPotentiallyPersonal;
|
firstName @PersonalData.IsPotentiallyPersonal;
|
||||||
firstName @PersonalData.IsPotentiallyPersonal;
|
lastName @PersonalData.IsPotentiallyPersonal;
|
||||||
lastName @PersonalData.IsPotentiallyPersonal;
|
dateOfBirth @PersonalData.IsPotentiallyPersonal;
|
||||||
// creditCardNo @PersonalData.IsPotentiallySensitive;
|
|
||||||
dateOfBirth @PersonalData.IsPotentiallyPersonal;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
annotate bookshop.CustomerBillingData with @PersonalData : {
|
annotate bookshop.BillingData with @PersonalData: {
|
||||||
DataSubjectRole : 'Customer',
|
DataSubjectRole: 'Customer',
|
||||||
EntitySemantics : 'DataSubjectDetails'
|
EntitySemantics: 'DataSubjectDetails'
|
||||||
}
|
} {
|
||||||
{
|
customer @PersonalData.FieldSemantics : 'DataSubjectID';
|
||||||
creditCardNo @PersonalData.IsPotentiallySensitive;
|
creditCardNo @PersonalData.IsPotentiallySensitive;
|
||||||
}
|
}
|
||||||
|
|
||||||
annotate bookshop.CustomerPostalAddress with @PersonalData : {
|
annotate bookshop.Addresses with @PersonalData: {
|
||||||
DataSubjectRole : 'Customer',
|
DataSubjectRole: 'Customer',
|
||||||
EntitySemantics : 'DataSubjectDetails'
|
EntitySemantics: 'DataSubjectDetails'
|
||||||
}
|
} {
|
||||||
{
|
customer @PersonalData.FieldSemantics : 'DataSubjectID';
|
||||||
Customer @PersonalData.FieldSemantics : 'DataSubjectID';
|
|
||||||
street @PersonalData.IsPotentiallyPersonal;
|
street @PersonalData.IsPotentiallyPersonal;
|
||||||
town @PersonalData.IsPotentiallyPersonal;
|
town @PersonalData.IsPotentiallyPersonal;
|
||||||
country @PersonalData.IsPotentiallyPersonal;
|
country @PersonalData.IsPotentiallyPersonal;
|
||||||
}
|
}
|
||||||
|
|
||||||
annotate bookshop.Orders with @PersonalData.EntitySemantics : 'Other'
|
annotate bookshop.Orders with @PersonalData.EntitySemantics: 'Other' {
|
||||||
{
|
ID @PersonalData.FieldSemantics : 'ContractRelatedID';
|
||||||
ID @PersonalData.FieldSemantics : 'ContractRelatedID';
|
customer @PersonalData.FieldSemantics : 'DataSubjectID';
|
||||||
Customer @PersonalData.FieldSemantics : 'DataSubjectID';
|
|
||||||
personalComment @PersonalData.IsPotentiallyPersonal;
|
personalComment @PersonalData.IsPotentiallyPersonal;
|
||||||
}
|
}
|
||||||
|
|
||||||
// annotations for Audit Log
|
|
||||||
annotate bookshop.Customers with @AuditLog.Operation : {
|
|
||||||
Read : true,
|
|
||||||
Insert : true,
|
|
||||||
Update : true,
|
|
||||||
Delete : true
|
|
||||||
};
|
|
||||||
|
|
||||||
// annotations for Audit Log
|
|
||||||
annotate bookshop.CustomerPostalAddress with @AuditLog.Operation : {
|
|
||||||
Read : true,
|
|
||||||
Insert : true,
|
|
||||||
Update : true,
|
|
||||||
Delete : true
|
|
||||||
};
|
|
||||||
|
|
||||||
// annotations for Audit Log
|
|
||||||
annotate bookshop.Orders with @AuditLog.Operation : {
|
|
||||||
Read : true,
|
|
||||||
Insert : true,
|
|
||||||
Update : true,
|
|
||||||
Delete : true
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,37 +1,41 @@
|
|||||||
// Proxy for importing schema from bookshop sample
|
// Proxy for importing schema from bookshop sample
|
||||||
using { sap.capire.bookshop.Books } from '../../bookshop/db/schema';
|
using {sap.capire.bookshop.Books} from '../../bookshop/db/schema';
|
||||||
using { sap.capire.orders.Orders } from '../../orders/db/schema';
|
using {sap.capire.orders.Orders} from '../../orders/db/schema';
|
||||||
using { sap.capire.orders.OrderItems } from '../../orders/db/schema';
|
using {sap.capire.orders.OrderItems} from '../../orders/db/schema';
|
||||||
using { Country, managed, cuid } from '@sap/cds/common';
|
using {
|
||||||
|
Country,
|
||||||
|
managed,
|
||||||
|
cuid
|
||||||
|
} from '@sap/cds/common';
|
||||||
|
|
||||||
namespace sap.capire.bookshop;
|
namespace sap.capire.bookshop;
|
||||||
|
|
||||||
extend Orders with {
|
extend Orders with {
|
||||||
Customer : Association to Customers;
|
customer : Association to Customers;
|
||||||
personalComment : String;
|
personalComment : String;
|
||||||
}
|
|
||||||
|
|
||||||
entity Customers : cuid, managed {
|
|
||||||
email : String;
|
|
||||||
firstName : String;
|
|
||||||
lastName : String;
|
|
||||||
// creditCardNo : String;
|
|
||||||
dateOfBirth : Date;
|
|
||||||
billingData : Composition of one CustomerBillingData on billingData.Customer = $self;
|
|
||||||
postalAddress : Composition of one CustomerPostalAddress on postalAddress.Customer = $self;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
entity CustomerPostalAddress : cuid, managed {
|
entity Customers : cuid, managed {
|
||||||
Customer : Association to one Customers;
|
email : String;
|
||||||
|
firstName : String;
|
||||||
|
lastName : String;
|
||||||
|
dateOfBirth : Date;
|
||||||
|
billingData : Composition of BillingData
|
||||||
|
on billingData.customer = $self;
|
||||||
|
addresses : Composition of Addresses
|
||||||
|
on addresses.customer = $self;
|
||||||
|
}
|
||||||
|
|
||||||
|
entity Addresses : cuid, managed {
|
||||||
|
customer : Association to one Customers;
|
||||||
street : String(128);
|
street : String(128);
|
||||||
town : String(128);
|
town : String(128);
|
||||||
country : Country;
|
country : Country;
|
||||||
someOtherField : String(128);
|
someOtherField : String(128);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
entity CustomerBillingData : cuid, managed {
|
entity BillingData : cuid, managed {
|
||||||
Customer : Association to one Customers;
|
customer : Association to one Customers;
|
||||||
creditCardNo : String;
|
creditCardNo : String;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
const cds = require('@sap/cds')
|
const cds = require('@sap/cds')
|
||||||
|
|
||||||
|
// FIXME: no longer works like this with new audit logging plugin
|
||||||
module.exports = class MyAuditLogService extends cds.AuditLogService {
|
module.exports = class MyAuditLogService extends cds.AuditLogService {
|
||||||
async init() {
|
async init() {
|
||||||
|
|
||||||
// console.log('My Audit Log');
|
// console.log('My Audit Log');
|
||||||
// call AuditLogService's init
|
// call AuditLogService's init
|
||||||
await super.init()
|
await super.init()
|
||||||
@@ -12,132 +12,59 @@ module.exports = class MyAuditLogService extends cds.AuditLogService {
|
|||||||
|
|
||||||
// register custom handlers
|
// register custom handlers
|
||||||
this.on('dataAccessLog', async req => {
|
this.on('dataAccessLog', async req => {
|
||||||
|
const logs = []
|
||||||
const logs = [];
|
|
||||||
|
|
||||||
const action = 'DataAccess';
|
const action = 'DataAccess'
|
||||||
const user = req.user.id;
|
const user = req.user.id
|
||||||
const timestamp = req.timestamp;
|
const timestamp = req.timestamp
|
||||||
const tenant = req.tenant;
|
const tenant = req.tenant
|
||||||
const channel = req.channel;
|
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)
|
req.data.accesses.forEach(dataAccess => {
|
||||||
}
|
logs.push({
|
||||||
)
|
Action: action,
|
||||||
|
User: user,
|
||||||
|
Timestamp: timestamp,
|
||||||
|
Tenant: tenant,
|
||||||
|
Channel: channel,
|
||||||
|
DataSubjectType: dataAccess.data_subject.type,
|
||||||
|
DataSubjectRole: dataAccess.data_subject.role,
|
||||||
|
DataSubjectID: JSON.stringify(dataAccess.data_subject.id),
|
||||||
|
ObjectType: dataAccess.object.type,
|
||||||
|
ObjectKey: JSON.stringify(dataAccess.object.id),
|
||||||
|
Blob: JSON.stringify(dataAccess)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
await INSERT.into(AuditLogStore).entries(logs)
|
||||||
|
})
|
||||||
|
|
||||||
this.on('dataModificationLog', async req => {
|
this.on('dataModificationLog', async req => {
|
||||||
|
const mods = []
|
||||||
|
|
||||||
const mods = [];
|
const action = 'DataModification'
|
||||||
|
const user = req.user.id
|
||||||
|
const timestamp = req.timestamp
|
||||||
|
const tenant = req.tenant
|
||||||
|
const channel = req.channel
|
||||||
|
|
||||||
const action = 'DataModification';
|
req.data.modifications.forEach(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({
|
mods.push({
|
||||||
Action: action,
|
Action: action,
|
||||||
User: user,
|
User: user,
|
||||||
Timestamp: timestamp,
|
Timestamp: timestamp,
|
||||||
Tenant: tenant,
|
Tenant: tenant,
|
||||||
Channel: channel,
|
Channel: channel,
|
||||||
DataSubjectType: dataModification.dataSubject.type,
|
DataSubjectType: dataModification.data_subject.type,
|
||||||
DataSubjectRole: dataModification.dataSubject.role,
|
DataSubjectRole: dataModification.data_subject.role,
|
||||||
DataSubjectID: JSON.stringify(dataModification.dataSubject.id),
|
DataSubjectID: JSON.stringify(dataModification.data_subject.id),
|
||||||
ObjectType: dataModification.dataObject.type,
|
ObjectType: dataModification.object.type,
|
||||||
ObjectKey: JSON.stringify(dataModification.dataObject.id),
|
ObjectKey: JSON.stringify(dataModification.object.id),
|
||||||
Blob: JSON.stringify(dataModification)
|
Blob: JSON.stringify(dataModification)
|
||||||
}) }
|
})
|
||||||
)
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
await INSERT.into(AuditLogStore).entries(mods)
|
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;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
@@ -1,14 +1,13 @@
|
|||||||
using {sap.capire.bookshop as db} from '../db/data-privacy';
|
using {sap.capire.bookshop as db} from '../db/data-privacy';
|
||||||
using {sap.capire.orders as dbo} from '../db/data-privacy';
|
using {sap.capire.orders as dbo} from '../db/data-privacy';
|
||||||
using {sap.capire.auditLog as log} from '../db/AuditLogStore.cds';
|
using {sap.capire.auditLog as log} from '../db/AuditLogStore.cds';
|
||||||
|
|
||||||
//@requires: 'PersonalDataManagerUser' // security check
|
//@requires: 'PersonalDataManagerUser' // security check
|
||||||
service LogService {
|
service LogService {
|
||||||
|
|
||||||
entity Customers as projection on db.Customers;
|
entity Customers as projection on db.Customers;
|
||||||
entity CustomerPostalAddress as projection on db.CustomerPostalAddress;
|
entity Addresses as projection on db.Addresses;
|
||||||
entity Orders as projection on dbo.Orders;
|
entity Orders as projection on dbo.Orders;
|
||||||
|
entity AuditLogStore as projection on log.AuditLogStore;
|
||||||
|
|
||||||
entity AuditLogStore as projection on log.AuditLogStore;
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,44 +1,43 @@
|
|||||||
using {sap.capire.bookshop as db} from '../db/data-privacy';
|
using {sap.capire.bookshop as db} from '../db/data-privacy';
|
||||||
using {sap.capire.bookshop.Books} from '../db/data-privacy';
|
using {sap.capire.bookshop.Books} from '../db/data-privacy';
|
||||||
using {sap.capire.orders.Orders} from '../db/data-privacy';
|
using {sap.capire.orders.Orders} from '../db/data-privacy';
|
||||||
using {sap.capire.orders.OrderItems} from '../db/data-privacy';
|
using {sap.capire.orders.OrderItems} from '../db/data-privacy';
|
||||||
|
|
||||||
//@requires: 'PersonalDataManagerUser' // security check
|
//@requires: 'PersonalDataManagerUser' // security check
|
||||||
service PDMService {
|
service PDMService {
|
||||||
|
|
||||||
entity Customers as projection on db.Customers;
|
// Data Privacy annotations on 'Customers', 'Addresses', and 'BillingData' are derived from original entity definitions
|
||||||
entity CustomerPostalAddress as projection on db.CustomerPostalAddress;
|
entity Customers as projection on db.Customers;
|
||||||
entity CustomerBillingData as projection on db.CustomerBillingData;
|
entity Addresses as projection on db.Addresses;
|
||||||
|
entity BillingData as projection on db.BillingData;
|
||||||
|
|
||||||
// create view on Orders and Items as flat projection
|
// create view on Orders and Items as flat projection
|
||||||
entity OrderItemView as
|
entity OrderItemView as
|
||||||
select from Orders {
|
select from Orders {
|
||||||
ID,
|
ID,
|
||||||
key Items.ID as Item_ID,
|
key Items.ID as item_ID,
|
||||||
OrderNo,
|
OrderNo,
|
||||||
Customer.ID as Customer_ID,
|
customer.ID as customer_ID,
|
||||||
Customer.email as Customer_Email,
|
customer.email as customer_email,
|
||||||
Items.book.ID as Item_Book_ID,
|
Items.book.ID as item_Book_ID,
|
||||||
Items.amount as Item_Amount,
|
Items.amount as item_Amount,
|
||||||
Items.netAmount as Item_NetAmount
|
Items.netAmount as item_NetAmount
|
||||||
};
|
};
|
||||||
|
|
||||||
// annotate new view
|
// annotate new view
|
||||||
annotate PDMService.OrderItemView with @(PersonalData.EntitySemantics : 'Other') {
|
annotate PDMService.OrderItemView with @(PersonalData.EntitySemantics: 'Other') {
|
||||||
Item_ID @PersonalData.FieldSemantics : 'ContractRelatedID';
|
item_ID @PersonalData.FieldSemantics: 'ContractRelatedID';
|
||||||
Customer_ID @PersonalData.FieldSemantics : 'DataSubjectID';
|
customer_ID @PersonalData.FieldSemantics: 'DataSubjectID';
|
||||||
Customer_Email @PersonalData.IsPotentiallyPersonal;
|
customer_email @PersonalData.IsPotentiallyPersonal;
|
||||||
};
|
};
|
||||||
|
|
||||||
// annotations for Personal Data Manager - Search Fields
|
// annotations for Personal Data Manager - Search Fields
|
||||||
annotate Customers with @(Communication.Contact : {
|
annotate Customers with @(Communication.Contact: {
|
||||||
n : {
|
n : {
|
||||||
surname : lastName,
|
surname: lastName,
|
||||||
given : firstName
|
given : firstName
|
||||||
},
|
},
|
||||||
bday : dateOfBirth
|
bday: dateOfBirth
|
||||||
});
|
});
|
||||||
|
|
||||||
// Data Privacy annotations on 'Customers' and 'CustomerPostalAddress' are derived from original entity definitions
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user