From 6928ae907a0920f0af4894e61703af3bb469ade7 Mon Sep 17 00:00:00 2001 From: sjvans Date: Thu, 3 Feb 2022 17:57:35 +0100 Subject: [PATCH 1/4] initial --- gdpr/.cdsrc.json | 26 ++ gdpr/.env | 1 + gdpr/.etc/deploy.sh | 4 + gdpr/.etc/undeploy.sh | 7 + gdpr/.pdm/pdm-binding-config.json | 16 + gdpr/.pdm/pdm-instance-config.json | 8 + gdpr/app/fiori.cds | 325 ++++++++++++++++++ gdpr/db/data-privacy.cds | 56 +++ ...ap.capire.gdpr-CustomerPostalAddresses.csv | 3 + gdpr/db/data/sap.capire.gdpr-Customers.csv | 3 + .../data/sap.capire.orders-Orders.Items.csv | 4 + gdpr/db/data/sap.capire.orders-Orders.csv | 3 + gdpr/db/schema.cds | 29 ++ gdpr/db/src/.hdiconfig | 136 ++++++++ gdpr/manifest.yml | 31 ++ gdpr/package.json | 45 +++ gdpr/readme.md | 34 ++ gdpr/services-manifest.yml | 20 ++ gdpr/srv/auth.js | 42 +++ gdpr/srv/gdpr-service.cds | 10 + gdpr/srv/pdm-service.cds | 24 ++ gdpr/xs-security.json | 14 + orders/app/orders/webapp/manifest.json | 321 +++++++++-------- package.json | 2 + 24 files changed, 1003 insertions(+), 161 deletions(-) create mode 100644 gdpr/.cdsrc.json create mode 100644 gdpr/.env create mode 100644 gdpr/.etc/deploy.sh create mode 100644 gdpr/.etc/undeploy.sh create mode 100644 gdpr/.pdm/pdm-binding-config.json create mode 100644 gdpr/.pdm/pdm-instance-config.json create mode 100644 gdpr/app/fiori.cds create mode 100644 gdpr/db/data-privacy.cds create mode 100644 gdpr/db/data/sap.capire.gdpr-CustomerPostalAddresses.csv create mode 100644 gdpr/db/data/sap.capire.gdpr-Customers.csv create mode 100644 gdpr/db/data/sap.capire.orders-Orders.Items.csv create mode 100644 gdpr/db/data/sap.capire.orders-Orders.csv create mode 100644 gdpr/db/schema.cds create mode 100644 gdpr/db/src/.hdiconfig create mode 100644 gdpr/manifest.yml create mode 100644 gdpr/package.json create mode 100644 gdpr/readme.md create mode 100644 gdpr/services-manifest.yml create mode 100644 gdpr/srv/auth.js create mode 100644 gdpr/srv/gdpr-service.cds create mode 100644 gdpr/srv/pdm-service.cds create mode 100644 gdpr/xs-security.json diff --git a/gdpr/.cdsrc.json b/gdpr/.cdsrc.json new file mode 100644 index 00000000..513273ea --- /dev/null +++ b/gdpr/.cdsrc.json @@ -0,0 +1,26 @@ +{ + "build": { + "target": "gen", + "tasks": [{ + "for": "hana", + "src": "db", + "options": { + "model": [ + "db", + "srv" + ] + } + }, + { + "for": "node-cf", + "src": "srv", + "options": { + "model": [ + "db", + "srv" + ] + } + } + ] + } +} diff --git a/gdpr/.env b/gdpr/.env new file mode 100644 index 00000000..6009b480 --- /dev/null +++ b/gdpr/.env @@ -0,0 +1 @@ +PORT = 4007 diff --git a/gdpr/.etc/deploy.sh b/gdpr/.etc/deploy.sh new file mode 100644 index 00000000..41f29d05 --- /dev/null +++ b/gdpr/.etc/deploy.sh @@ -0,0 +1,4 @@ +npm run build +cf create-service-push +cf bind-service gdpr-srv gdpr-pdm -c .pdm/pdm-binding-config.json +cf restage gdpr-srv diff --git a/gdpr/.etc/undeploy.sh b/gdpr/.etc/undeploy.sh new file mode 100644 index 00000000..ea3a6d40 --- /dev/null +++ b/gdpr/.etc/undeploy.sh @@ -0,0 +1,7 @@ +cf delete gdpr-srv -f +cf delete gdpr-db-deployer -f +cf delete-service gdpr-pdm -f +cf delete-service gdpr-auditlog -f +cf delete-service gdpr-uaa -f +cf delete-service gdpr-hdi -f +cf delete-service gdpr-logs -f diff --git a/gdpr/.pdm/pdm-binding-config.json b/gdpr/.pdm/pdm-binding-config.json new file mode 100644 index 00000000..92dc50e6 --- /dev/null +++ b/gdpr/.pdm/pdm-binding-config.json @@ -0,0 +1,16 @@ +{ + "fullyQualifiedApplicationName": "capire-gdpr", + "fullyQualifiedModuleName": "gdpr-srv", + "applicationTitle": "Capire GDPR Sample App", + "applicationTitleKey": "Capire GDPR Sample App", + "applicationURL": "https://capire-gdpr-srv.cfapps.eu10.hana.ondemand.com", + "endPoints": [{ + "type": "odatav4", + "serviceName": "PDMService", + "serviceURI": "/pdm", + "serviceTitle": "Capire GDPR Sample App PDM Service", + "serviceTitleKey": "Capire GDPR Sample App PDM Service", + "hasGdprV4Annotations": true, + "cacheControl": "no-cache" + }] +} diff --git a/gdpr/.pdm/pdm-instance-config.json b/gdpr/.pdm/pdm-instance-config.json new file mode 100644 index 00000000..7596bc2e --- /dev/null +++ b/gdpr/.pdm/pdm-instance-config.json @@ -0,0 +1,8 @@ +{ + "xs-security": { + "xsappname": "capire-gdpr", + "authorities": ["$ACCEPT_GRANTED_AUTHORITIES"] + }, + "fullyQualifiedApplicationName": "capire-gdpr", + "appConsentServiceEnabled": true +} diff --git a/gdpr/app/fiori.cds b/gdpr/app/fiori.cds new file mode 100644 index 00000000..38a70b78 --- /dev/null +++ b/gdpr/app/fiori.cds @@ -0,0 +1,325 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Note: this is designed for the GDPRService being co-located with +// orders. It does not work if GDPRService is run as a separate +// process, and is not intended to do so. +// +//////////////////////////////////////////////////////////////////////////// + + +using {GDPRService} from '../srv/gdpr-service'; + +annotate cds.UUID with @Core.Computed; + +/* + * Orders + */ +@odata.draft.enabled +annotate GDPRService.Orders with @(UI : { + SelectionFields : [ + createdAt, + createdBy + ], + LineItem : [ + { + Value : OrderNo, + Label : 'Order number' + }, + { + Value : customer.firstName, + Label : 'First Name' + }, + { + Value : customer.lastName, + Label : 'Last Name' + } + ], + HeaderInfo : { + TypeName : 'Order', + TypeNamePlural : 'Orders', + Title : { + Value : OrderNo, + Label : 'Order number' + // }, + // Description : { + // Value : createdBy, + // Label : 'Created by' + } + }, + Identification : [ + { + Value : createdBy, + Label : 'Created by' + }, + { + Value : createdAt, + Label : 'Created at' + // }, + // { + // Value : OrderNo, + // Label : 'Order number' + } + ], + HeaderFacets : [ + { + $Type : 'UI.ReferenceFacet', + Label : '{i18n>Created}', + Target : '@UI.FieldGroup#Created' + }, + { + $Type : 'UI.ReferenceFacet', + Label : '{i18n>Modified}', + Target : '@UI.FieldGroup#Modified' + }, + ], + Facets : [ + { + $Type : 'UI.ReferenceFacet', + Label : '{i18n>Details}', + Target : '@UI.FieldGroup#Details' + }, + { + $Type : 'UI.ReferenceFacet', + Label : '{i18n>OrderItems}', + Target : 'Items/@UI.LineItem' + }, + ], + FieldGroup #Details : {Data : [ + { + Value : customer_ID, + Label : 'Customer' + }, + { + Value : customer.firstName, + Label : 'First Name' + }, + { + Value : customer.lastName, + Label : 'Last Name' + }, + { + Value : currency_code, + Label : 'Currency' + } + ]}, + FieldGroup #Created : {Data : [ + { + Value : createdBy, + Label : 'Created by' + }, + { + Value : createdAt, + Label : 'Created at' + } + ]}, + FieldGroup #Modified : {Data : [ + { + Value : modifiedBy, + Label : 'Modified by' + }, + { + Value : modifiedAt, + Label : 'Modified at' + } + ]}, +}, ) { + createdAt @UI.HiddenFilter : false; + createdBy @UI.HiddenFilter : false; + customer @ValueList.entity : 'Customers'; +}; + +/* + * TODO: Order Items are not really maintainable in Fiori preview app + */ +annotate GDPRService.Orders.Items with @(UI : { + LineItem : [ + { + Value : product_ID, + Label : 'Product ID' + }, + { + Value : title, + Label : 'Product Name' + }, + { + Value : price, + Label : 'Price' + }, + { + Value : quantity, + Label : 'Quantity' + }, + ], + Identification : [ + { + Value : product_ID, + Label : 'Product ID' + }, + { + Value : title, + Label : 'Product Name' + }, + { + Value : quantity, + Label : 'Quantity' + }, + { + Value : price, + Label : 'Price' + }, + ], + Facets : [{ + $Type : 'UI.ReferenceFacet', + Label : 'Order Items', + Target : '@UI.Identification' + }, ], +}, ) { + ID @Core.Computed @UI.Hidden : true; + title @Core.Computed; + price @Core.Computed; + quantity @(Common.FieldControl : #Mandatory); +}; + +/* + * Customers + */ +@odata.draft.enabled +annotate GDPRService.Customers with @(UI : { + SelectionFields : [ + firstName, + lastName + ], + LineItem : [ + { + Value : firstName, + Label : 'First Name' + }, + { + Value : lastName, + Label : 'Last Name' + }, + { + Value : dateOfBirth, + Label : 'Date of Birth' + } + ], + HeaderInfo : { + TypeName : 'Customer', + TypeNamePlural : 'Customers', + Title : { + Value : lastName, + Label : 'Last Name' + }, + Description : { + Value : firstName, + Label : 'First Name' + } + }, + Identification : [ + { + Value : createdBy, + Label : 'Created by' + }, + { + Value : createdAt, + Label : 'Created at' + } + ], + HeaderFacets : [ + { + $Type : 'UI.ReferenceFacet', + Label : '{i18n>Created}', + Target : '@UI.FieldGroup#Created' + }, + { + $Type : 'UI.ReferenceFacet', + Label : '{i18n>Modified}', + Target : '@UI.FieldGroup#Modified' + }, + ], + Facets : [ + { + $Type : 'UI.ReferenceFacet', + Label : '{i18n>Details}', + Target : '@UI.FieldGroup#Details' + }, + { + $Type : 'UI.ReferenceFacet', + Label : '{i18n>Addresses}', + Target : 'addresses/@UI.LineItem' + }, + ], + FieldGroup #Details : {Data : [ + { + Value : dateOfBirth, + Label : 'Date of Birth' + }, + { + Value : email, + Label : 'E-Mail' + }, + { + Value : creditCardNo, + Label : 'Credit Card Number' + } + ]}, + FieldGroup #Created : {Data : [ + { + Value : createdBy, + Label : 'Created by' + }, + { + Value : createdAt, + Label : 'Created at' + } + ]}, + FieldGroup #Modified : {Data : [ + { + Value : modifiedBy, + Label : 'Modified by' + }, + { + Value : modifiedAt, + Label : 'Modified at' + } + ]}, +}, ) { + createdAt @UI.HiddenFilter : false; + createdBy @UI.HiddenFilter : false; +}; + +annotate GDPRService.CustomerPostalAddresses with @(UI : { + LineItem : [ + { + Value : town, + Label : 'Town' + }, + { + Value : street, + Label : 'Street' + }, + { + Value : country.name, + Label : 'Country' + } + ], + Identification : [ + { + Value : town, + Label : 'Town' + }, + { + Value : street, + Label : 'Street' + }, + { + Value : country_code, + Label : 'Country Code' + } + ], + Facets : [{ + $Type : 'UI.ReferenceFacet', + Label : 'Customer Postal Address', + Target : '@UI.Identification' + }, ], +}, ); diff --git a/gdpr/db/data-privacy.cds b/gdpr/db/data-privacy.cds new file mode 100644 index 00000000..03fd8e5f --- /dev/null +++ b/gdpr/db/data-privacy.cds @@ -0,0 +1,56 @@ +using {sap.capire.orders} from '@capire/orders'; +using {sap.capire.gdpr} from './schema'; + +/* + * annotations for Data Privacy (Personal Data Manager and Audit Logging) + */ +annotate gdpr.Customers with @PersonalData : { + DataSubjectRole : 'Customer', + EntitySemantics : 'DataSubject' +}{ + ID @PersonalData.FieldSemantics : 'DataSubjectID'; + emailAddress @PersonalData.IsPotentiallyPersonal; + firstName @PersonalData.IsPotentiallyPersonal; + lastName @PersonalData.IsPotentiallyPersonal; + creditCardNo @PersonalData.IsPotentiallySensitive; + dateOfBirth @PersonalData.IsPotentiallyPersonal; +} + +annotate gdpr.CustomerPostalAddresses with @PersonalData : { + DataSubjectRole : 'Customer', + EntitySemantics : 'DataSubjectDetails' +}{ + customer @PersonalData.FieldSemantics : 'DataSubjectID'; + street @PersonalData.IsPotentiallyPersonal; + town @PersonalData.IsPotentiallyPersonal; + country @PersonalData.IsPotentiallyPersonal; +} + +/* + * TODO: Personal Data Manager doesn't know EntitySemantics: 'Other' and FieldSemantics: 'ContractRelatedID' + * see: https://help.sap.com/viewer/620a3ea6aaf64610accdd05cca9e3de2/Cloud/en-US/5a55fae1eb7c496c92c56071186d76b3.html + */ +annotate orders.Orders with @PersonalData : { + DataSubjectRole : 'Customer', + EntitySemantics : 'LegalGround' +}{ + ID @PersonalData.FieldSemantics : 'LegalGroundID'; + customer @PersonalData.FieldSemantics : 'DataSubjectID'; +} + +/* + * additional annotations for Audit Logging + */ +annotate gdpr.Customers with @AuditLog.Operation : { + Read : true, + Insert : true, + Update : true, + Delete : true +}; + +annotate gdpr.CustomerPostalAddresses with @AuditLog.Operation : { + Read : true, + Insert : true, + Update : true, + Delete : true +}; diff --git a/gdpr/db/data/sap.capire.gdpr-CustomerPostalAddresses.csv b/gdpr/db/data/sap.capire.gdpr-CustomerPostalAddresses.csv new file mode 100644 index 00000000..4aafe541 --- /dev/null +++ b/gdpr/db/data/sap.capire.gdpr-CustomerPostalAddresses.csv @@ -0,0 +1,3 @@ +ID;modifiedAt;createdAt;createdBy;modifiedBy;customer_ID;street;town;country_code +1e2f2640-6866-4dcf-8f4d-3027aa831cad;2019-04-04;2019-01-31;admin@business.com;admin@business.com;8e2f2640-6866-4dcf-8f4d-3027aa831cad;Hauptstrasse 11;Berlin;DE +24e718c9-ff99-47f1-8ca3-950c850777d4;2019-04-04;2019-01-30;admin@business.com;admin@business.com;74e718c9-ff99-47f1-8ca3-950c850777d4;Main Street 22;London;GB diff --git a/gdpr/db/data/sap.capire.gdpr-Customers.csv b/gdpr/db/data/sap.capire.gdpr-Customers.csv new file mode 100644 index 00000000..c94c4431 --- /dev/null +++ b/gdpr/db/data/sap.capire.gdpr-Customers.csv @@ -0,0 +1,3 @@ +ID;modifiedAt;createdAt;createdBy;modifiedBy;email;firstName;lastName;creditCardNo;dateOfBirth +8e2f2640-6866-4dcf-8f4d-3027aa831cad;2019-04-04;2019-01-31;admin@business.com;admin@business.com;john.doe@test.com;John;Doe;9977-6655-4433-2211;1970-01-01 +74e718c9-ff99-47f1-8ca3-950c850777d4;2019-04-04;2019-01-30;admin@business.com;admin@business.com;jane.doe@sap.com;Jane;Doe;2211-3344-5566-7788;1980-11-11 \ No newline at end of file diff --git a/gdpr/db/data/sap.capire.orders-Orders.Items.csv b/gdpr/db/data/sap.capire.orders-Orders.Items.csv new file mode 100644 index 00000000..c8702065 --- /dev/null +++ b/gdpr/db/data/sap.capire.orders-Orders.Items.csv @@ -0,0 +1,4 @@ +ID;up__ID;quantity;product_ID;title;price +4bd2c9df-c19f-47b8-a921-3cde0d863b52;29f15ef6-4a13-47d4-aef4-329a403b49eb;1;201;Wuthering Heights;11.11 +6c42a40d-5f7c-4c2f-816b-a73c7c28d722;29f15ef6-4a13-47d4-aef4-329a403b49eb;1;271;Catweazle;15 +748555fc-2cb0-42b5-a361-dd19a50bd682;31c2bd15-5146-4418-b574-866a08911de7;2;252;Eleonora;28 diff --git a/gdpr/db/data/sap.capire.orders-Orders.csv b/gdpr/db/data/sap.capire.orders-Orders.csv new file mode 100644 index 00000000..f7e151d6 --- /dev/null +++ b/gdpr/db/data/sap.capire.orders-Orders.csv @@ -0,0 +1,3 @@ +ID;createdAt;createdBy;customer_ID;OrderNo;currency_code +29f15ef6-4a13-47d4-aef4-329a403b49eb;2019-01-31;john.doe@test.com;8e2f2640-6866-4dcf-8f4d-3027aa831cad;1;EUR +31c2bd15-5146-4418-b574-866a08911de7;2019-01-30;jane.doe@test.com;74e718c9-ff99-47f1-8ca3-950c850777d4;2;EUR diff --git a/gdpr/db/schema.cds b/gdpr/db/schema.cds new file mode 100644 index 00000000..033f838a --- /dev/null +++ b/gdpr/db/schema.cds @@ -0,0 +1,29 @@ +using { + Country, + managed, + cuid +} from '@sap/cds/common'; +using {sap.capire.orders} from '@capire/orders'; + +namespace sap.capire.gdpr; + +extend orders.Orders with { + customer : Association to Customers; +} + +entity Customers : cuid, managed { + email : String; + firstName : String; + lastName : String; + creditCardNo : String; + dateOfBirth : Date; + addresses : Composition of many CustomerPostalAddresses + on addresses.customer = $self; +} + +entity CustomerPostalAddresses : cuid, managed { + customer : Association to Customers; + street : String(128); + town : String(128); + country : Country; +}; diff --git a/gdpr/db/src/.hdiconfig b/gdpr/db/src/.hdiconfig new file mode 100644 index 00000000..b1a1e281 --- /dev/null +++ b/gdpr/db/src/.hdiconfig @@ -0,0 +1,136 @@ +{ + "file_suffixes": { + "csv": { + "plugin_name": "com.sap.hana.di.tabledata.source" + }, + "hdbafllangprocedure": { + "plugin_name": "com.sap.hana.di.afllangprocedure" + }, + "hdbanalyticprivilege": { + "plugin_name": "com.sap.hana.di.analyticprivilege" + }, + "hdbcalculationview": { + "plugin_name": "com.sap.hana.di.calculationview" + }, + "hdbcollection": { + "plugin_name": "com.sap.hana.di.collection" + }, + "hdbconstraint": { + "plugin_name": "com.sap.hana.di.constraint" + }, + "hdbdropcreatetable": { + "plugin_name": "com.sap.hana.di.dropcreatetable" + }, + "hdbflowgraph": { + "plugin_name": "com.sap.hana.di.flowgraph" + }, + "hdbfunction": { + "plugin_name": "com.sap.hana.di.function" + }, + "hdbgraphworkspace": { + "plugin_name": "com.sap.hana.di.graphworkspace" + }, + "hdbhadoopmrjob": { + "plugin_name": "com.sap.hana.di.virtualfunctionpackage.hadoop" + }, + "hdbindex": { + "plugin_name": "com.sap.hana.di.index" + }, + "hdblibrary": { + "plugin_name": "com.sap.hana.di.library" + }, + "hdbmigrationtable": { + "plugin_name": "com.sap.hana.di.table.migration" + }, + "hdbprocedure": { + "plugin_name": "com.sap.hana.di.procedure" + }, + "hdbprojectionview": { + "plugin_name": "com.sap.hana.di.projectionview" + }, + "hdbprojectionviewconfig": { + "plugin_name": "com.sap.hana.di.projectionview.config" + }, + "hdbreptask": { + "plugin_name": "com.sap.hana.di.reptask" + }, + "hdbresultcache": { + "plugin_name": "com.sap.hana.di.resultcache" + }, + "hdbrole": { + "plugin_name": "com.sap.hana.di.role" + }, + "hdbroleconfig": { + "plugin_name": "com.sap.hana.di.role.config" + }, + "hdbsearchruleset": { + "plugin_name": "com.sap.hana.di.searchruleset" + }, + "hdbsequence": { + "plugin_name": "com.sap.hana.di.sequence" + }, + "hdbstatistics": { + "plugin_name": "com.sap.hana.di.statistics" + }, + "hdbstructuredprivilege": { + "plugin_name": "com.sap.hana.di.structuredprivilege" + }, + "hdbsynonym": { + "plugin_name": "com.sap.hana.di.synonym" + }, + "hdbsynonymconfig": { + "plugin_name": "com.sap.hana.di.synonym.config" + }, + "hdbsystemversioning": { + "plugin_name": "com.sap.hana.di.systemversioning" + }, + "hdbtable": { + "plugin_name": "com.sap.hana.di.table" + }, + "hdbtabledata": { + "plugin_name": "com.sap.hana.di.tabledata" + }, + "hdbtabletype": { + "plugin_name": "com.sap.hana.di.tabletype" + }, + "hdbtrigger": { + "plugin_name": "com.sap.hana.di.trigger" + }, + "hdbview": { + "plugin_name": "com.sap.hana.di.view" + }, + "hdbvirtualfunction": { + "plugin_name": "com.sap.hana.di.virtualfunction" + }, + "hdbvirtualfunctionconfig": { + "plugin_name": "com.sap.hana.di.virtualfunction.config" + }, + "hdbvirtualpackagehadoop": { + "plugin_name": "com.sap.hana.di.virtualpackage.hadoop" + }, + "hdbvirtualpackagesparksql": { + "plugin_name": "com.sap.hana.di.virtualpackage.sparksql" + }, + "hdbvirtualprocedure": { + "plugin_name": "com.sap.hana.di.virtualprocedure" + }, + "hdbvirtualprocedureconfig": { + "plugin_name": "com.sap.hana.di.virtualprocedure.config" + }, + "hdbvirtualtable": { + "plugin_name": "com.sap.hana.di.virtualtable" + }, + "hdbvirtualtableconfig": { + "plugin_name": "com.sap.hana.di.virtualtable.config" + }, + "properties": { + "plugin_name": "com.sap.hana.di.tabledata.properties" + }, + "tags": { + "plugin_name": "com.sap.hana.di.tabledata.properties" + }, + "txt": { + "plugin_name": "com.sap.hana.di.copyonly" + } + } +} \ No newline at end of file diff --git a/gdpr/manifest.yml b/gdpr/manifest.yml new file mode 100644 index 00000000..6151c264 --- /dev/null +++ b/gdpr/manifest.yml @@ -0,0 +1,31 @@ +--- +applications: +# ----------------------------------------------------------------------------------- +# HANA Database Content Deployer App +# ----------------------------------------------------------------------------------- +- name: gdpr-db-deployer + path: gen/db + no-route: true + health-check-type: process + memory: 256M + buildpack: nodejs_buildpack + services: + - gdpr-logs + - gdpr-hdi +# ----------------------------------------------------------------------------------- +# Backend Service +# ----------------------------------------------------------------------------------- +- name: gdpr-srv + path: gen/srv + memory: 256M + buildpack: nodejs_buildpack + routes: + - route: capire-gdpr-srv.cfapps.eu10.hana.ondemand.com + services: + - gdpr-logs + - gdpr-hdi + - gdpr-uaa + - gdpr-auditlog + # binding with parameters not yet supported -> binding done manually in .etc/deploy.sh + #- name: gdpr-pdm + # parameters: ./pdm-binding-config.json diff --git a/gdpr/package.json b/gdpr/package.json new file mode 100644 index 00000000..3615611b --- /dev/null +++ b/gdpr/package.json @@ -0,0 +1,45 @@ +{ + "name": "@capire/gdpr", + "version": "0.0.1", + "dependencies": { + "@capire/orders": "../orders", + "@sap/audit-logging": "^5.1.0", + "@sap/cds": "^5.7", + "express": "^4.17.1", + "hdb": "^0.19.0" + }, + "scripts": { + "build": "rm -rf gen && cds build --production", + "deploy": "sh .etc/deploy.sh", + "undeploy": "sh .etc/undeploy.sh", + "start": "cds run" + }, + "cds": { + "requires": { + "auth": { + "__comment__": "workaround to avoid approuter et al. setup", + "impl": "srv/auth.js" + }, + "audit-log": { + "[development]": { + "credentials": { + "logToConsole": true + } + } + }, + "db": { + "kind": "sql" + }, + "uaa": { + "kind": "xsuaa" + } + }, + "features": { + "audit_personal_data": true, + "fiori_preview": true + }, + "hana": { + "deploy-format": "hdbtable" + } + } +} diff --git a/gdpr/readme.md b/gdpr/readme.md new file mode 100644 index 00000000..0a18125b --- /dev/null +++ b/gdpr/readme.md @@ -0,0 +1,34 @@ +# how-to + +## required services and subscriptions + +services: +- Audit Log Service +- SAP HANA Cloud +- SAP HANA Schemas & HDI Containers +- Application Logging Service +- Personal Data Manager +- Authorization and Trust Management Service + +subscriptions: +- Audit Log Viewer Service +- Personal Data Manager + +## deploy + +after adding the necessary entitlements, do: +- `cf l` to log into the respective account +- `cd gdpr` (if still in root of `cloud-cap-samples`) +- `npm run deploy`, which executes build and deployment via `.etc/deploy.sh` + +## authorization + +create roles for Audit Log Viewer Service and Personal Data Manager, and assign the roles to the respective users + +# open issues + +- deploy via mta, which can bind with parameters, and get rid of scripts in `.etc` +- use approuter to remove hacky custom auth impl (`srv/auth.js`) +- clarify annotation `EntitySemantics`, which differs between audit logging (`Other`) and personal data manager (`LegalGround`) +- annotations for order items Fiori preview app + + `Products` has `@cds.persistence.skip:'always'` diff --git a/gdpr/services-manifest.yml b/gdpr/services-manifest.yml new file mode 100644 index 00000000..c1e9dbe3 --- /dev/null +++ b/gdpr/services-manifest.yml @@ -0,0 +1,20 @@ +--- +create-services: + - name: gdpr-logs # > for kibana + broker: application-logs + plan: standard + - name: gdpr-hdi # > hana + broker: hana + plan: hdi-shared + - name: gdpr-auditlog # > audit log sink + broker: auditlog + plan: standard + # gdpr-pdm needs to exist before creating gdpr-uaa for authorization grant + - name: gdpr-pdm # > personal data manager + broker: personal-data-manager-service + plan: standard + parameters: ./.pdm/pdm-instance-config.json + - name: gdpr-uaa # > uaa for authentication + broker: xsuaa + plan: application + parameters: xs-security.json diff --git a/gdpr/srv/auth.js b/gdpr/srv/auth.js new file mode 100644 index 00000000..caa17447 --- /dev/null +++ b/gdpr/srv/auth.js @@ -0,0 +1,42 @@ +/* + * workaround to avoid approuter et al. setup + * + * DO NOT USE FOR PRODUCTION! + * - no token validation + * - no xsappname check + */ + +const jwt = require('jsonwebtoken') +const tenant = process.env.VCAP_SERVICES + ? JSON.parse(process.env.VCAP_SERVICES).xsuaa[0].credentials.tenantid + : 'anonymous' + +module.exports = (req, res, next) => { + // decode JWT coming from Personal Data Manager + const bearer = req.headers.authorization && req.headers.authorization.split('Bearer ')[1] + if (bearer) { + const { client_id: id, zid: tenant, scope: roles } = jwt.decode(bearer) + req.user = { + id, + tenant, + is: role => roles.some(r => r.endsWith(`.${role}`)) + } + return next() + } + + // mock user that has every role EXCEPT PersonalDataManagerUser + const basic = req.headers.authorization && req.headers.authorization.split('Basic ')[1] + if (basic) { + const [id] = Buffer.from(basic, 'base64').toString('utf-8').split(':') + req.user = { + id, + tenant, + // is: role => role !== 'PersonalDataManagerUser' + is: role => true + } + return next() + } + + // no bearer & no basic -> 401 + res.set('WWW-Authenticate', 'Basic realm="Users"').status(401).end() +} diff --git a/gdpr/srv/gdpr-service.cds b/gdpr/srv/gdpr-service.cds new file mode 100644 index 00000000..cf8dccc5 --- /dev/null +++ b/gdpr/srv/gdpr-service.cds @@ -0,0 +1,10 @@ +using { + sap.capire.orders, + sap.capire.gdpr +} from '../db/schema'; + +@requires : 'admin' // > authorization check +service GDPRService { + entity Customers as projection on gdpr.Customers; + entity Orders as projection on orders.Orders; +} diff --git a/gdpr/srv/pdm-service.cds b/gdpr/srv/pdm-service.cds new file mode 100644 index 00000000..4fcd0024 --- /dev/null +++ b/gdpr/srv/pdm-service.cds @@ -0,0 +1,24 @@ +using { + sap.capire.gdpr as gdpr, + sap.capire.orders as orders +} from '../db/data-privacy'; + +@requires : 'PersonalDataManagerUser' // > authorization check +service PDMService { + + entity Customers as projection on gdpr.Customers; + entity CustomerPostalAddresses as projection on gdpr.CustomerPostalAddresses; + entity Orders as projection on orders.Orders; + + /* + * additional annotations for Personal Data Manager's Search Fields + */ + annotate Customers with @(Communication.Contact : { + n : { + surname : lastName, + given : firstName + }, + bday : dateOfBirth + }); + +}; diff --git a/gdpr/xs-security.json b/gdpr/xs-security.json new file mode 100644 index 00000000..2530f291 --- /dev/null +++ b/gdpr/xs-security.json @@ -0,0 +1,14 @@ +{ + "xsappname": "capire-gdpr", + "tenant-mode": "shared", + "scopes": [{ + "name": "$XSAPPNAME.PersonalDataManagerUser", + "description": "Authority for Personal Data Manager", + "grant-as-authority-to-apps": [ + "$XSSERVICENAME(gdpr-pdm)" + ] + }, { + "name": "$XSAPPNAME.admin", + "description": "Administrator" + }] +} diff --git a/orders/app/orders/webapp/manifest.json b/orders/app/orders/webapp/manifest.json index 045fa70a..756cb452 100644 --- a/orders/app/orders/webapp/manifest.json +++ b/orders/app/orders/webapp/manifest.json @@ -1,170 +1,169 @@ { - "_version": "1.8.0", - "sap.app": { - "id": "orders", - "type": "application", - "title": "Order Books", - "description": "Sample Application", - "i18n": "i18n/i18n.properties", - "dataSources": { - "OrdersService": { - "uri": "/orders/", - "type": "OData", - "settings": { - "odataVersion": "4.0" - } - } - }, - "-sourceTemplate": { - "id": "ui5template.basicSAPUI5ApplicationProject", - "-id": "ui5template.smartTemplate", - "-version": "1.40.12" - } - }, - "sap.ui5": { - "dependencies": { - "libs": { - "sap.fe.templates": {} + "_version": "1.8.0", + "sap.app": { + "id": "orders", + "type": "application", + "title": "Order Books", + "description": "Sample Application", + "i18n": "i18n/i18n.properties", + "dataSources": { + "OrdersService": { + "uri": "/orders/", + "type": "OData", + "settings": { + "odataVersion": "4.0" + } + } + }, + "-sourceTemplate": { + "id": "ui5template.basicSAPUI5ApplicationProject", + "-id": "ui5template.smartTemplate", + "-version": "1.40.12" + } + }, + "sap.ui5": { + "dependencies": { + "libs": { + "sap.fe.templates": {} + } + }, + "models": { + "i18n": { + "type": "sap.ui.model.resource.ResourceModel", + "uri": "i18n/i18n.properties" + }, + "": { + "dataSource": "OrdersService", + "settings": { + "synchronizationMode": "None", + "operationMode": "Server", + "autoExpandSelect": true, + "earlyRequests": true, + "groupProperties": { + "default": { + "submit": "Auto" } + } + } + } + }, + "routing": { + "routes": [{ + "pattern": ":?query:", + "name": "OrdersList", + "target": "OrdersList" }, - "models": { - "i18n": { - "type": "sap.ui.model.resource.ResourceModel", - "uri": "i18n/i18n.properties" - }, - "": { - "dataSource": "OrdersService", - "settings": { - "synchronizationMode": "None", - "operationMode": "Server", - "autoExpandSelect" : true, - "earlyRequests": true, - "groupProperties": { - "default": { - "submit": "Auto" - } - } - } - } - }, - "routing": { - "routes": [ - { - "pattern": ":?query:", - "name": "OrdersList", - "target": "OrdersList" - }, - { - "pattern": "Orders({key}):?query:", - "name": "OrdersDetails", - "target": "OrdersDetails" - }, - { - "pattern": "Orders({boo})/Items({boo2}):?query:", - "name": "OrderItemsDetails", - "target": "OrderItemsDetails" - }, - { - "pattern": "Books({key}):?query:", - "name": "BooksDetails", - "target": "BooksDetails" + { + "pattern": "Orders({key}):?query:", + "name": "OrdersDetails", + "target": "OrdersDetails" + }, + { + "pattern": "Orders({boo})/Items({boo2}):?query:", + "name": "OrderItemsDetails", + "target": "OrderItemsDetails" + }, + { + "pattern": "Books({key}):?query:", + "name": "BooksDetails", + "target": "BooksDetails" + } + ], + "targets": { + "OrdersList": { + "type": "Component", + "id": "OrdersList", + "name": "sap.fe.templates.ListReport", + "options": { + "settings": { + "entitySet": "Orders", + "navigation": { + "Orders": { + "detail": { + "route": "OrdersDetails" + } } - ], - "targets": { - "OrdersList": { - "type": "Component", - "id": "OrdersList", - "name": "sap.fe.templates.ListReport", - "options": { - "settings" : { - "entitySet" : "Orders", - "navigation" : { - "Orders" : { - "detail" : { - "route" : "OrdersDetails" - } - } - } - } - } - }, - "OrdersDetails": { - "type": "Component", - "id": "OrdersDetails", - "name": "sap.fe.templates.ObjectPage", - "options": { - "settings" : { - "entitySet": "Orders", - "navigation" : { - "Items": { - "detail": { - "route": "OrderItemsDetails" - } - }, - "book": { - "detail": { - "route": "BooksDetails" - } - }, - "dummy": { - "detail": { - "route": "BooksDetails" - } - } - } - } - } - }, - "OrderItemsDetails": { - "type": "Component", - "id": "OrderItemsDetails", - "name": "sap.fe.templates.ObjectPage", - "options": { - "settings" : { - "entitySet": "Orders_Items" - } - } + } + } + } + }, + "OrdersDetails": { + "type": "Component", + "id": "OrdersDetails", + "name": "sap.fe.templates.ObjectPage", + "options": { + "settings": { + "entitySet": "Orders", + "navigation": { + "Items": { + "detail": { + "route": "OrderItemsDetails" + } }, - "BooksDetails": { - "type": "Component", - "id": "BooksDetails", - "name": "sap.fe.templates.ObjectPage", - "options": { - "settings" : { - "entitySet": "Books", - "navigation": { - "author": { - "detail": { - "route": "AuthorsDetails" - } - } - } - } - } + "book": { + "detail": { + "route": "BooksDetails" + } }, - "AuthorsDetails": { - "type": "Component", - "id": "AuthorsDetails", - "name": "sap.fe.templates.ObjectPage", - "options": { - "settings" : { - "entitySet": "Authors" - } - } + "dummy": { + "detail": { + "route": "BooksDetails" + } } + } + } + } + }, + "OrderItemsDetails": { + "type": "Component", + "id": "OrderItemsDetails", + "name": "sap.fe.templates.ObjectPage", + "options": { + "settings": { + "entitySet": "Orders_Items" } + } }, - "contentDensities": { - "compact": true, - "cozy": true - } - }, - "sap.ui": { - "technology": "UI5", - "fullWidth": false - }, - "sap.fiori": { - "registrationIds": [], - "archeType": "transactional" - } -} \ No newline at end of file + "BooksDetails": { + "type": "Component", + "id": "BooksDetails", + "name": "sap.fe.templates.ObjectPage", + "options": { + "settings": { + "entitySet": "Books", + "navigation": { + "author": { + "detail": { + "route": "AuthorsDetails" + } + } + } + } + } + }, + "AuthorsDetails": { + "type": "Component", + "id": "AuthorsDetails", + "name": "sap.fe.templates.ObjectPage", + "options": { + "settings": { + "entitySet": "Authors" + } + } + } + } + }, + "contentDensities": { + "compact": true, + "cozy": true + } + }, + "sap.ui": { + "technology": "UI5", + "fullWidth": false + }, + "sap.fiori": { + "registrationIds": [], + "archeType": "transactional" + } +} diff --git a/package.json b/package.json index 5e0dfd83..586e4b23 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "@capire/common": "./common", "@capire/data-viewer": "./data-viewer", "@capire/fiori": "./fiori", + "@capire/gdpr": "./gdpr", "@capire/hello": "./hello", "@capire/media": "./media", "@capire/orders": "./orders", @@ -30,6 +31,7 @@ "registry": "node .registry/server.js", "bookshop": "cds watch bookshop", "fiori": "cds watch fiori", + "gdpr": "cds watch gdpr", "hello": "cds watch hello", "media": "cds watch media", "mocha": "npx mocha || echo", From 3c6d49b88e335c0f78bc24f712c653dda67880a2 Mon Sep 17 00:00:00 2001 From: sjvans Date: Tue, 8 Feb 2022 13:41:30 +0100 Subject: [PATCH 2/4] in development, write audit logs to custom sink --- gdpr/.cdsrc.json | 6 ++++-- gdpr/db/data-privacy.cds | 2 +- gdpr/db/schema.cds | 1 + gdpr/package.json | 5 ++++- gdpr/readme.md | 1 + gdpr/srv/auth.js | 15 ++++++++------- gdpr/srv/server.js | 26 ++++++++++++++++++++++++++ 7 files changed, 45 insertions(+), 11 deletions(-) create mode 100644 gdpr/srv/server.js diff --git a/gdpr/.cdsrc.json b/gdpr/.cdsrc.json index 513273ea..bdc1b712 100644 --- a/gdpr/.cdsrc.json +++ b/gdpr/.cdsrc.json @@ -7,7 +7,8 @@ "options": { "model": [ "db", - "srv" + "srv", + "app" ] } }, @@ -17,7 +18,8 @@ "options": { "model": [ "db", - "srv" + "srv", + "app" ] } } diff --git a/gdpr/db/data-privacy.cds b/gdpr/db/data-privacy.cds index 03fd8e5f..d6cc056d 100644 --- a/gdpr/db/data-privacy.cds +++ b/gdpr/db/data-privacy.cds @@ -9,7 +9,7 @@ annotate gdpr.Customers with @PersonalData : { EntitySemantics : 'DataSubject' }{ ID @PersonalData.FieldSemantics : 'DataSubjectID'; - emailAddress @PersonalData.IsPotentiallyPersonal; + email @PersonalData.IsPotentiallyPersonal; firstName @PersonalData.IsPotentiallyPersonal; lastName @PersonalData.IsPotentiallyPersonal; creditCardNo @PersonalData.IsPotentiallySensitive; diff --git a/gdpr/db/schema.cds b/gdpr/db/schema.cds index 033f838a..6a4d80d8 100644 --- a/gdpr/db/schema.cds +++ b/gdpr/db/schema.cds @@ -25,5 +25,6 @@ entity CustomerPostalAddresses : cuid, managed { customer : Association to Customers; street : String(128); town : String(128); + @assert.integrity : false country : Country; }; diff --git a/gdpr/package.json b/gdpr/package.json index 3615611b..aa99e72e 100644 --- a/gdpr/package.json +++ b/gdpr/package.json @@ -36,7 +36,10 @@ }, "features": { "audit_personal_data": true, - "fiori_preview": true + "fiori_preview": true, + "[production]": { + "kibana_formatter": true + } }, "hana": { "deploy-format": "hdbtable" diff --git a/gdpr/readme.md b/gdpr/readme.md index 0a18125b..22c0eb8e 100644 --- a/gdpr/readme.md +++ b/gdpr/readme.md @@ -32,3 +32,4 @@ create roles for Audit Log Viewer Service and Personal Data Manager, and assign - clarify annotation `EntitySemantics`, which differs between audit logging (`Other`) and personal data manager (`LegalGround`) - annotations for order items Fiori preview app + `Products` has `@cds.persistence.skip:'always'` +- how to reuse intial data from `common`? diff --git a/gdpr/srv/auth.js b/gdpr/srv/auth.js index caa17447..7ca86d8a 100644 --- a/gdpr/srv/auth.js +++ b/gdpr/srv/auth.js @@ -1,9 +1,5 @@ /* * workaround to avoid approuter et al. setup - * - * DO NOT USE FOR PRODUCTION! - * - no token validation - * - no xsappname check */ const jwt = require('jsonwebtoken') @@ -12,7 +8,13 @@ const tenant = process.env.VCAP_SERVICES : 'anonymous' module.exports = (req, res, next) => { - // decode JWT coming from Personal Data Manager + /* + * decode JWT coming from Personal Data Manager + * + * DO NOT USE FOR PRODUCTION! + * - no token validation + * - no xsappname check + */ const bearer = req.headers.authorization && req.headers.authorization.split('Bearer ')[1] if (bearer) { const { client_id: id, zid: tenant, scope: roles } = jwt.decode(bearer) @@ -31,8 +33,7 @@ module.exports = (req, res, next) => { req.user = { id, tenant, - // is: role => role !== 'PersonalDataManagerUser' - is: role => true + is: role => role !== 'PersonalDataManagerUser' } return next() } diff --git a/gdpr/srv/server.js b/gdpr/srv/server.js new file mode 100644 index 00000000..99378311 --- /dev/null +++ b/gdpr/srv/server.js @@ -0,0 +1,26 @@ +const cds = require('@sap/cds') + +/* + * in development, write audit logs to custom sink (i.e., to console in this example) + */ +cds.on('served', async () => { + if (process.env.NODE_ENV === 'production') return + + const auditLogService = await cds.connect.to('audit-log') + // use prepend to get called before the generic implementation + auditLogService.prepend(function() { + const LOG = cds.log('my custom audit logging impl') + // triggered when reading sensitive personal data + this.on('dataAccessLog', function(req) { + const { accesses } = req.data + for (const access of accesses) LOG.info(access) + }) + // triggered when modifying personal data + this.on('dataModificationLog', function(req) { + const { modifications } = req.data + for (const modification of modifications) LOG.info(modification) + }) + }) +}) + +module.exports = cds.server From b932637400c29ad4c761e96945cd46d1392f786a Mon Sep 17 00:00:00 2001 From: sjvans <30337871+sjvans@users.noreply.github.com> Date: Tue, 8 Feb 2022 13:45:36 +0100 Subject: [PATCH 3/4] Update manifest.json --- orders/app/orders/webapp/manifest.json | 319 +++++++++++++------------ 1 file changed, 160 insertions(+), 159 deletions(-) diff --git a/orders/app/orders/webapp/manifest.json b/orders/app/orders/webapp/manifest.json index 756cb452..c30c4cc5 100644 --- a/orders/app/orders/webapp/manifest.json +++ b/orders/app/orders/webapp/manifest.json @@ -1,169 +1,170 @@ { - "_version": "1.8.0", - "sap.app": { - "id": "orders", - "type": "application", - "title": "Order Books", - "description": "Sample Application", - "i18n": "i18n/i18n.properties", - "dataSources": { - "OrdersService": { - "uri": "/orders/", - "type": "OData", - "settings": { - "odataVersion": "4.0" - } - } - }, - "-sourceTemplate": { - "id": "ui5template.basicSAPUI5ApplicationProject", - "-id": "ui5template.smartTemplate", - "-version": "1.40.12" - } - }, - "sap.ui5": { - "dependencies": { - "libs": { - "sap.fe.templates": {} - } - }, - "models": { - "i18n": { - "type": "sap.ui.model.resource.ResourceModel", - "uri": "i18n/i18n.properties" - }, - "": { - "dataSource": "OrdersService", - "settings": { - "synchronizationMode": "None", - "operationMode": "Server", - "autoExpandSelect": true, - "earlyRequests": true, - "groupProperties": { - "default": { - "submit": "Auto" + "_version": "1.8.0", + "sap.app": { + "id": "orders", + "type": "application", + "title": "Order Books", + "description": "Sample Application", + "i18n": "i18n/i18n.properties", + "dataSources": { + "OrdersService": { + "uri": "/orders/", + "type": "OData", + "settings": { + "odataVersion": "4.0" + } + } + }, + "-sourceTemplate": { + "id": "ui5template.basicSAPUI5ApplicationProject", + "-id": "ui5template.smartTemplate", + "-version": "1.40.12" + } + }, + "sap.ui5": { + "dependencies": { + "libs": { + "sap.fe.templates": {} } - } - } - } - }, - "routing": { - "routes": [{ - "pattern": ":?query:", - "name": "OrdersList", - "target": "OrdersList" }, - { - "pattern": "Orders({key}):?query:", - "name": "OrdersDetails", - "target": "OrdersDetails" - }, - { - "pattern": "Orders({boo})/Items({boo2}):?query:", - "name": "OrderItemsDetails", - "target": "OrderItemsDetails" - }, - { - "pattern": "Books({key}):?query:", - "name": "BooksDetails", - "target": "BooksDetails" - } - ], - "targets": { - "OrdersList": { - "type": "Component", - "id": "OrdersList", - "name": "sap.fe.templates.ListReport", - "options": { - "settings": { - "entitySet": "Orders", - "navigation": { - "Orders": { - "detail": { - "route": "OrdersDetails" - } - } - } - } - } - }, - "OrdersDetails": { - "type": "Component", - "id": "OrdersDetails", - "name": "sap.fe.templates.ObjectPage", - "options": { - "settings": { - "entitySet": "Orders", - "navigation": { - "Items": { - "detail": { - "route": "OrderItemsDetails" - } + "models": { + "i18n": { + "type": "sap.ui.model.resource.ResourceModel", + "uri": "i18n/i18n.properties" + }, + "": { + "dataSource": "OrdersService", + "settings": { + "synchronizationMode": "None", + "operationMode": "Server", + "autoExpandSelect" : true, + "earlyRequests": true, + "groupProperties": { + "default": { + "submit": "Auto" + } + } + } + } + }, + "routing": { + "routes": [ + { + "pattern": ":?query:", + "name": "OrdersList", + "target": "OrdersList" + }, + { + "pattern": "Orders({key}):?query:", + "name": "OrdersDetails", + "target": "OrdersDetails" }, - "book": { - "detail": { - "route": "BooksDetails" - } + { + "pattern": "Orders({boo})/Items({boo2}):?query:", + "name": "OrderItemsDetails", + "target": "OrderItemsDetails" }, - "dummy": { - "detail": { - "route": "BooksDetails" - } + { + "pattern": "Books({key}):?query:", + "name": "BooksDetails", + "target": "BooksDetails" } - } - } - } - }, - "OrderItemsDetails": { - "type": "Component", - "id": "OrderItemsDetails", - "name": "sap.fe.templates.ObjectPage", - "options": { - "settings": { - "entitySet": "Orders_Items" - } - } - }, - "BooksDetails": { - "type": "Component", - "id": "BooksDetails", - "name": "sap.fe.templates.ObjectPage", - "options": { - "settings": { - "entitySet": "Books", - "navigation": { - "author": { - "detail": { - "route": "AuthorsDetails" - } + ], + "targets": { + "OrdersList": { + "type": "Component", + "id": "OrdersList", + "name": "sap.fe.templates.ListReport", + "options": { + "settings" : { + "entitySet" : "Orders", + "navigation" : { + "Orders" : { + "detail" : { + "route" : "OrdersDetails" + } + } + } + } + } + }, + "OrdersDetails": { + "type": "Component", + "id": "OrdersDetails", + "name": "sap.fe.templates.ObjectPage", + "options": { + "settings" : { + "entitySet": "Orders", + "navigation" : { + "Items": { + "detail": { + "route": "OrderItemsDetails" + } + }, + "book": { + "detail": { + "route": "BooksDetails" + } + }, + "dummy": { + "detail": { + "route": "BooksDetails" + } + } + } + } + } + }, + "OrderItemsDetails": { + "type": "Component", + "id": "OrderItemsDetails", + "name": "sap.fe.templates.ObjectPage", + "options": { + "settings" : { + "entitySet": "Orders_Items" + } + } + }, + "BooksDetails": { + "type": "Component", + "id": "BooksDetails", + "name": "sap.fe.templates.ObjectPage", + "options": { + "settings" : { + "entitySet": "Books", + "navigation": { + "author": { + "detail": { + "route": "AuthorsDetails" + } + } + } + } + } + }, + "AuthorsDetails": { + "type": "Component", + "id": "AuthorsDetails", + "name": "sap.fe.templates.ObjectPage", + "options": { + "settings" : { + "entitySet": "Authors" + } + } } - } } - } }, - "AuthorsDetails": { - "type": "Component", - "id": "AuthorsDetails", - "name": "sap.fe.templates.ObjectPage", - "options": { - "settings": { - "entitySet": "Authors" - } - } - } - } - }, - "contentDensities": { - "compact": true, - "cozy": true - } - }, - "sap.ui": { - "technology": "UI5", - "fullWidth": false - }, - "sap.fiori": { - "registrationIds": [], - "archeType": "transactional" - } + "contentDensities": { + "compact": true, + "cozy": true + } + }, + "sap.ui": { + "technology": "UI5", + "fullWidth": false + }, + "sap.fiori": { + "registrationIds": [], + "archeType": "transactional" + } } From 46f1be4395a206cedd0f4358359a87570c3b3623 Mon Sep 17 00:00:00 2001 From: sjvans <30337871+sjvans@users.noreply.github.com> Date: Tue, 8 Feb 2022 13:47:32 +0100 Subject: [PATCH 4/4] cleanup --- gdpr/app/fiori.cds | 8 -------- 1 file changed, 8 deletions(-) diff --git a/gdpr/app/fiori.cds b/gdpr/app/fiori.cds index 38a70b78..df2f32d2 100644 --- a/gdpr/app/fiori.cds +++ b/gdpr/app/fiori.cds @@ -40,10 +40,6 @@ annotate GDPRService.Orders with @(UI : { Title : { Value : OrderNo, Label : 'Order number' - // }, - // Description : { - // Value : createdBy, - // Label : 'Created by' } }, Identification : [ @@ -54,10 +50,6 @@ annotate GDPRService.Orders with @(UI : { { Value : createdAt, Label : 'Created at' - // }, - // { - // Value : OrderNo, - // Label : 'Order number' } ], HeaderFacets : [