DSGVO-konforme Entwicklung in ABAP Cloud

Kategorie
Security
Veröffentlicht
Autor
Johannes

Die Datenschutz-Grundverordnung (DSGVO) stellt konkrete Anforderungen an die Softwareentwicklung. Als ABAP Cloud Entwickler musst du diese Anforderungen technisch umsetzen. Dieser Artikel zeigt, wie du DSGVO-konforme Anwendungen in ABAP Cloud entwickelst.

DSGVO-Anforderungen für Entwickler

Die DSGVO definiert Rechte für betroffene Personen, die sich direkt auf die Entwicklung auswirken:

RechtArtikelTechnische Umsetzung
AuskunftsrechtArt. 15Datenexport-Funktion
Recht auf BerichtigungArt. 16Änderungsfunktionen
Recht auf LöschungArt. 17Löschkonzept
Recht auf EinschränkungArt. 18Sperrfunktion
DatenübertragbarkeitArt. 20Export in Standardformat
WiderspruchsrechtArt. 21Consent-Verwaltung

Technische Prinzipien

Die DSGVO fordert Privacy by Design und Privacy by Default:

Privacy by Design:
┌─────────────────────────────────────────────────────┐
│ Datenschutz in alle Entwicklungsphasen integrieren │
├─────────────────────────────────────────────────────┤
│ • Minimale Datenerhebung │
│ • Zweckbindung prüfen │
│ • Aufbewahrungsfristen definieren │
│ • Verschlüsselung implementieren │
│ • Zugriffsprotokollierung aktivieren │
└─────────────────────────────────────────────────────┘

Löschkonzept implementieren (Data Retention)

Ein Löschkonzept definiert, wann und wie personenbezogene Daten gelöscht werden. In ABAP Cloud implementierst du dies über Retention Rules und entsprechende Lösch-Jobs.

Retention-Tabelle definieren

@EndUserText.label: 'Datenkategorie Aufbewahrungsfristen'
@AbapCatalog.enhancement.category: #NOT_EXTENSIBLE
define table zretention_rules {
key client : abap.clnt not null;
key data_category : abap.char(30) not null;
retention_period_days : abap.int4;
deletion_method : abap.char(10); -- HARD, SOFT, ANON
legal_basis : abap.char(100);
last_review_date : abap.dats;
responsible_role : abap.char(30);
}

Löschservice implementieren

CLASS zcl_gdpr_deletion_service DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
TYPES:
BEGIN OF ty_deletion_result,
data_category TYPE string,
records_deleted TYPE i,
records_failed TYPE i,
deletion_date TYPE timestampl,
END OF ty_deletion_result,
tt_deletion_results TYPE STANDARD TABLE OF ty_deletion_result WITH EMPTY KEY.
METHODS:
execute_retention_deletion
RETURNING VALUE(rt_results) TYPE tt_deletion_results,
delete_customer_data
IMPORTING iv_customer_id TYPE zcustomer_id
iv_deletion_type TYPE string DEFAULT 'HARD'
RETURNING VALUE(rv_success) TYPE abap_bool,
get_retention_period
IMPORTING iv_data_category TYPE string
RETURNING VALUE(rv_days) TYPE i.
PRIVATE SECTION.
METHODS:
get_deletion_candidates
IMPORTING iv_data_category TYPE string
iv_cutoff_date TYPE d
RETURNING VALUE(rt_keys) TYPE ztt_entity_keys,
perform_hard_delete
IMPORTING it_keys TYPE ztt_entity_keys
RETURNING VALUE(rv_count) TYPE i,
perform_soft_delete
IMPORTING it_keys TYPE ztt_entity_keys
RETURNING VALUE(rv_count) TYPE i,
log_deletion
IMPORTING iv_category TYPE string
iv_count TYPE i
iv_method TYPE string.
ENDCLASS.
CLASS zcl_gdpr_deletion_service IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
DATA(lt_results) = execute_retention_deletion( ).
LOOP AT lt_results INTO DATA(ls_result).
out->write( |Kategorie: { ls_result-data_category }| ).
out->write( |Gelöscht: { ls_result-records_deleted }| ).
out->write( |Fehlgeschlagen: { ls_result-records_failed }| ).
out->write( |---| ).
ENDLOOP.
ENDMETHOD.
METHOD execute_retention_deletion.
" Alle aktiven Retention Rules abrufen
SELECT * FROM zretention_rules
INTO TABLE @DATA(lt_rules).
DATA(lv_today) = cl_abap_context_info=>get_system_date( ).
LOOP AT lt_rules INTO DATA(ls_rule).
" Stichtag berechnen
DATA(lv_cutoff_date) = lv_today - ls_rule-retention_period_days.
" Löschkandidaten ermitteln
DATA(lt_candidates) = get_deletion_candidates(
iv_data_category = ls_rule-data_category
iv_cutoff_date = lv_cutoff_date ).
" Löschung durchführen
DATA(lv_deleted) = COND #(
WHEN ls_rule-deletion_method = 'HARD'
THEN perform_hard_delete( lt_candidates )
WHEN ls_rule-deletion_method = 'SOFT'
THEN perform_soft_delete( lt_candidates )
WHEN ls_rule-deletion_method = 'ANON'
THEN anonymize_records( lt_candidates )
ELSE 0 ).
" Ergebnis protokollieren
log_deletion(
iv_category = ls_rule-data_category
iv_count = lv_deleted
iv_method = ls_rule-deletion_method ).
APPEND VALUE #(
data_category = ls_rule-data_category
records_deleted = lv_deleted
records_failed = lines( lt_candidates ) - lv_deleted
deletion_date = utclong_current( )
) TO rt_results.
ENDLOOP.
ENDMETHOD.
METHOD delete_customer_data.
" Einzellöschung für Betroffenenanfrage
TRY.
CASE iv_deletion_type.
WHEN 'HARD'.
" Unwiderrufliche Löschung
DELETE FROM zcustomer WHERE customer_id = @iv_customer_id.
DELETE FROM zcustomer_contact WHERE customer_id = @iv_customer_id.
DELETE FROM zcustomer_address WHERE customer_id = @iv_customer_id.
WHEN 'SOFT'.
" Markierung als gelöscht
UPDATE zcustomer
SET is_deleted = @abap_true,
deletion_date = @( cl_abap_context_info=>get_system_date( ) )
WHERE customer_id = @iv_customer_id.
WHEN 'ANONYMIZE'.
" Anonymisierung statt Löschung
UPDATE zcustomer
SET first_name = 'ANONYMISIERT',
last_name = 'ANONYMISIERT',
email = 'anonymisiert@example.com',
phone = '000000000',
is_anonymized = @abap_true
WHERE customer_id = @iv_customer_id.
ENDCASE.
rv_success = abap_true.
CATCH cx_sy_open_sql_db.
rv_success = abap_false.
ENDTRY.
ENDMETHOD.
METHOD get_retention_period.
SELECT SINGLE retention_period_days
FROM zretention_rules
WHERE data_category = @iv_data_category
INTO @rv_days.
IF sy-subrc <> 0.
rv_days = 365 * 10. " Default: 10 Jahre
ENDIF.
ENDMETHOD.
" ... weitere Methoden
ENDCLASS.

RAP Action für Löschung

" Behavior Definition
define behavior for ZI_Customer alias Customer
implementation in class zbp_i_customer unique
{
// DSGVO-Löschung als Action
action deletePersonalData result [1] $self;
// Soft Delete statt physischer Löschung
delete ( features : instance );
}
" Behavior Implementation
METHOD deletePersonalData.
DATA(lo_deletion_service) = NEW zcl_gdpr_deletion_service( ).
LOOP AT keys INTO DATA(ls_key).
DATA(lv_success) = lo_deletion_service->delete_customer_data(
iv_customer_id = ls_key-CustomerId
iv_deletion_type = 'ANONYMIZE' ).
IF lv_success = abap_true.
" Ergebnis zurückgeben
READ ENTITIES OF zi_customer IN LOCAL MODE
ENTITY Customer
ALL FIELDS WITH VALUE #( ( %key = ls_key-%key ) )
RESULT DATA(lt_customers).
result = VALUE #( FOR customer IN lt_customers
( %tky = customer-%tky
%param = customer ) ).
ELSE.
APPEND VALUE #(
%tky = ls_key-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'Löschung fehlgeschlagen' )
) TO reported-customer.
ENDIF.
ENDLOOP.
ENDMETHOD.

Anonymisierung von Daten

Anonymisierung macht Daten unwiderruflich nicht mehr einer Person zuordenbar. Sie ist eine Alternative zur Löschung, wenn die Daten für Statistiken benötigt werden.

Anonymisierungsservice

CLASS zcl_gdpr_anonymization DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
TYPES:
BEGIN OF ty_field_rule,
field_name TYPE string,
anon_method TYPE string, -- MASK, HASH, RANDOM, CONSTANT, NULLIFY
constant_value TYPE string,
END OF ty_field_rule,
tt_field_rules TYPE STANDARD TABLE OF ty_field_rule WITH EMPTY KEY.
METHODS:
anonymize_customer
IMPORTING iv_customer_id TYPE zcustomer_id
RETURNING VALUE(rv_success) TYPE abap_bool,
anonymize_field
IMPORTING iv_value TYPE any
iv_method TYPE string
iv_constant TYPE string OPTIONAL
RETURNING VALUE(rv_result) TYPE string,
generate_pseudonym
IMPORTING iv_original TYPE string
RETURNING VALUE(rv_pseudonym) TYPE string.
ENDCLASS.
CLASS zcl_gdpr_anonymization IMPLEMENTATION.
METHOD anonymize_customer.
" Anonymisierungsregeln definieren
DATA(lt_rules) = VALUE tt_field_rules(
( field_name = 'FIRST_NAME' anon_method = 'CONSTANT' constant_value = 'ANON' )
( field_name = 'LAST_NAME' anon_method = 'CONSTANT' constant_value = 'ANON' )
( field_name = 'EMAIL' anon_method = 'HASH' )
( field_name = 'PHONE' anon_method = 'MASK' )
( field_name = 'BIRTH_DATE' anon_method = 'NULLIFY' )
( field_name = 'ADDRESS' anon_method = 'CONSTANT' constant_value = 'Anonymisiert' )
).
" Kundendaten laden
SELECT SINGLE *
FROM zcustomer
WHERE customer_id = @iv_customer_id
INTO @DATA(ls_customer).
IF sy-subrc <> 0.
rv_success = abap_false.
RETURN.
ENDIF.
" Felder anonymisieren
ls_customer-first_name = anonymize_field(
iv_value = ls_customer-first_name
iv_method = 'CONSTANT'
iv_constant = 'ANON' ).
ls_customer-last_name = anonymize_field(
iv_value = ls_customer-last_name
iv_method = 'CONSTANT'
iv_constant = 'ANON' ).
ls_customer-email = anonymize_field(
iv_value = ls_customer-email
iv_method = 'HASH' ).
ls_customer-phone = anonymize_field(
iv_value = ls_customer-phone
iv_method = 'MASK' ).
ls_customer-is_anonymized = abap_true.
ls_customer-anonymization_date = cl_abap_context_info=>get_system_date( ).
" Anonymisierte Daten speichern
UPDATE zcustomer FROM @ls_customer.
rv_success = xsdbool( sy-subrc = 0 ).
ENDMETHOD.
METHOD anonymize_field.
CASE iv_method.
WHEN 'MASK'.
" Teilweise maskieren (z.B. ***1234)
DATA(lv_length) = strlen( iv_value ).
IF lv_length > 4.
rv_result = repeat( val = '*' occ = lv_length - 4 ) &&
substring( val = iv_value off = lv_length - 4 len = 4 ).
ELSE.
rv_result = repeat( val = '*' occ = lv_length ).
ENDIF.
WHEN 'HASH'.
" Einweg-Hash (nicht rückrechenbar)
TRY.
cl_abap_message_digest=>create_md5(
EXPORTING
if_data = cl_abap_codepage=>convert_to( iv_value )
IMPORTING
ef_hashstring = rv_result ).
CATCH cx_abap_message_digest.
rv_result = 'HASH_ERROR'.
ENDTRY.
WHEN 'RANDOM'.
" Zufallswert generieren
rv_result = cl_system_uuid=>create_uuid_x16_static( ).
WHEN 'CONSTANT'.
" Fester Ersatzwert
rv_result = iv_constant.
WHEN 'NULLIFY'.
" Leerwert
rv_result = ''.
WHEN OTHERS.
rv_result = iv_value.
ENDCASE.
ENDMETHOD.
METHOD generate_pseudonym.
" Deterministischer Pseudonym (gleiches Original = gleiches Pseudonym)
DATA(lv_salt) = 'GDPR_PSEUDONYM_SALT_2024'.
DATA(lv_input) = lv_salt && iv_original.
TRY.
cl_abap_message_digest=>create_sha256(
EXPORTING
if_data = cl_abap_codepage=>convert_to( lv_input )
IMPORTING
ef_hashstring = DATA(lv_hash) ).
" Kürzeren, lesbaren Pseudonym erstellen
rv_pseudonym = 'PSN_' && substring( val = lv_hash len = 12 ).
CATCH cx_abap_message_digest.
rv_pseudonym = 'PSN_ERROR'.
ENDTRY.
ENDMETHOD.
ENDCLASS.

Pseudonymisierung vs. Anonymisierung

AspektPseudonymisierungAnonymisierung
RückführbarJa, mit SchlüsselNein
DSGVO-StatusPersonenbezogenNicht personenbezogen
AnwendungVerarbeitungStatistik, Archiv
BeispielPSN_A3F82BANON

Auskunftsrecht umsetzen (Data Export)

Betroffene haben das Recht, alle über sie gespeicherten Daten in einem maschinenlesbaren Format zu erhalten.

Datenexport-Service

CLASS zcl_gdpr_data_export DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
TYPES:
BEGIN OF ty_export_section,
category TYPE string,
data TYPE REF TO data,
END OF ty_export_section,
tt_export_sections TYPE STANDARD TABLE OF ty_export_section WITH EMPTY KEY.
METHODS:
export_customer_data
IMPORTING iv_customer_id TYPE zcustomer_id
RETURNING VALUE(rv_json) TYPE string,
export_as_json
IMPORTING it_sections TYPE tt_export_sections
RETURNING VALUE(rv_json) TYPE string,
create_export_request
IMPORTING iv_customer_id TYPE zcustomer_id
iv_request_type TYPE string
RETURNING VALUE(rv_request_id) TYPE sysuuid_x16.
ENDCLASS.
CLASS zcl_gdpr_data_export IMPLEMENTATION.
METHOD export_customer_data.
DATA: lt_sections TYPE tt_export_sections.
" 1. Stammdaten
SELECT SINGLE customer_id, first_name, last_name, email,
phone, birth_date, created_at
FROM zcustomer
WHERE customer_id = @iv_customer_id
INTO @DATA(ls_customer).
IF sy-subrc = 0.
GET REFERENCE OF ls_customer INTO DATA(lr_customer).
APPEND VALUE #( category = 'Stammdaten' data = lr_customer ) TO lt_sections.
ENDIF.
" 2. Adressen
SELECT address_type, street, city, postal_code, country
FROM zcustomer_address
WHERE customer_id = @iv_customer_id
INTO TABLE @DATA(lt_addresses).
IF sy-subrc = 0.
GET REFERENCE OF lt_addresses INTO DATA(lr_addresses).
APPEND VALUE #( category = 'Adressen' data = lr_addresses ) TO lt_sections.
ENDIF.
" 3. Bestellungen
SELECT order_id, order_date, status, total_amount, currency
FROM zorder
WHERE customer_id = @iv_customer_id
INTO TABLE @DATA(lt_orders).
IF sy-subrc = 0.
GET REFERENCE OF lt_orders INTO DATA(lr_orders).
APPEND VALUE #( category = 'Bestellungen' data = lr_orders ) TO lt_sections.
ENDIF.
" 4. Kommunikation
SELECT communication_date, channel, subject
FROM zcommunication_log
WHERE customer_id = @iv_customer_id
INTO TABLE @DATA(lt_communications).
IF sy-subrc = 0.
GET REFERENCE OF lt_communications INTO DATA(lr_comms).
APPEND VALUE #( category = 'Kommunikation' data = lr_comms ) TO lt_sections.
ENDIF.
" 5. Consent-Historie
SELECT consent_type, consent_date, consent_status, ip_address
FROM zconsent_log
WHERE customer_id = @iv_customer_id
INTO TABLE @DATA(lt_consents).
IF sy-subrc = 0.
GET REFERENCE OF lt_consents INTO DATA(lr_consents).
APPEND VALUE #( category = 'Einwilligungen' data = lr_consents ) TO lt_sections.
ENDIF.
" Als JSON exportieren
rv_json = export_as_json( lt_sections ).
ENDMETHOD.
METHOD export_as_json.
DATA: lo_json TYPE REF TO cl_sxml_string_writer.
" JSON-Struktur aufbauen
DATA(lo_writer) = cl_sxml_string_writer=>create( type = if_sxml=>co_xt_json ).
lo_writer->open_element( name = 'gdpr_export' ).
lo_writer->open_element( name = 'export_date' ).
lo_writer->write_value( |{ cl_abap_context_info=>get_system_date( ) DATE = ISO }| ).
lo_writer->close_element( ).
LOOP AT it_sections INTO DATA(ls_section).
lo_writer->open_element( name = ls_section-category ).
" Daten zu JSON serialisieren
DATA(lv_section_json) = /ui2/cl_json=>serialize(
data = ls_section-data->*
pretty_name = /ui2/cl_json=>pretty_mode-low_case ).
lo_writer->write_value( lv_section_json ).
lo_writer->close_element( ).
ENDLOOP.
lo_writer->close_element( ).
rv_json = cl_abap_codepage=>convert_from( lo_writer->get_output( ) ).
ENDMETHOD.
METHOD create_export_request.
" Export-Anfrage protokollieren
rv_request_id = cl_system_uuid=>create_uuid_x16_static( ).
INSERT INTO zgdpr_requests VALUES @( VALUE #(
request_id = rv_request_id
customer_id = iv_customer_id
request_type = iv_request_type " EXPORT, DELETE, RECTIFY
request_date = cl_abap_context_info=>get_system_date( )
request_time = cl_abap_context_info=>get_system_time( )
status = 'NEW'
requestor = cl_abap_context_info=>get_user_technical_name( )
) ).
ENDMETHOD.
ENDCLASS.

RAP Action für Datenexport

" Behavior Definition
define behavior for ZC_Customer alias Customer
{
// Datenexport als Action mit JSON-Rückgabe
action ( features : instance ) exportMyData
result [1] $self;
}
" In der Projection View einen virtuellen Feld für Export-Link
define view entity ZC_Customer as projection on ZI_Customer
{
key CustomerId,
FirstName,
LastName,
Email,
// Virtuelles Feld für Export-Download
@UI.hidden: true
cast( '' as abap.string ) as ExportData
}

Einwilligungen müssen dokumentiert, widerrufbar und nachweisbar sein.

@EndUserText.label: 'Consent Kategorien'
define table zconsent_types {
key client : abap.clnt not null;
key consent_type : abap.char(30) not null;
description : abap.char(200);
legal_basis : abap.char(100);
is_mandatory : abap_boolean;
default_duration : abap.int4; -- Tage
}
@EndUserText.label: 'Consent Log'
define table zconsent_log {
key client : abap.clnt not null;
key consent_id : sysuuid_x16 not null;
customer_id : zcustomer_id;
consent_type : abap.char(30);
consent_status : abap.char(10); -- GRANTED, WITHDRAWN, EXPIRED
consent_date : abap.dats;
consent_time : abap.tims;
expiry_date : abap.dats;
ip_address : abap.char(45);
user_agent : abap.char(500);
consent_text : abap.string;
withdrawal_date : abap.dats;
withdrawal_reason : abap.char(200);
}
CLASS zcl_consent_manager DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
CONSTANTS:
BEGIN OF gc_consent_status,
granted TYPE string VALUE 'GRANTED',
withdrawn TYPE string VALUE 'WITHDRAWN',
expired TYPE string VALUE 'EXPIRED',
END OF gc_consent_status.
METHODS:
grant_consent
IMPORTING iv_customer_id TYPE zcustomer_id
iv_consent_type TYPE string
iv_ip_address TYPE string OPTIONAL
iv_user_agent TYPE string OPTIONAL
RETURNING VALUE(rv_consent_id) TYPE sysuuid_x16,
withdraw_consent
IMPORTING iv_customer_id TYPE zcustomer_id
iv_consent_type TYPE string
iv_reason TYPE string OPTIONAL
RETURNING VALUE(rv_success) TYPE abap_bool,
check_consent
IMPORTING iv_customer_id TYPE zcustomer_id
iv_consent_type TYPE string
RETURNING VALUE(rv_granted) TYPE abap_bool,
get_consent_status
IMPORTING iv_customer_id TYPE zcustomer_id
RETURNING VALUE(rt_consents) TYPE ztt_consent_status,
expire_old_consents.
ENDCLASS.
CLASS zcl_consent_manager IMPLEMENTATION.
METHOD grant_consent.
" Consent-Typ-Details laden
SELECT SINGLE * FROM zconsent_types
WHERE consent_type = @iv_consent_type
INTO @DATA(ls_type).
IF sy-subrc <> 0.
RETURN.
ENDIF.
" Ablaufdatum berechnen
DATA(lv_today) = cl_abap_context_info=>get_system_date( ).
DATA(lv_expiry) = COND #(
WHEN ls_type-default_duration > 0
THEN lv_today + ls_type-default_duration
ELSE '99991231' ).
" Neuen Consent anlegen
rv_consent_id = cl_system_uuid=>create_uuid_x16_static( ).
INSERT INTO zconsent_log VALUES @( VALUE #(
consent_id = rv_consent_id
customer_id = iv_customer_id
consent_type = iv_consent_type
consent_status = gc_consent_status-granted
consent_date = lv_today
consent_time = cl_abap_context_info=>get_system_time( )
expiry_date = lv_expiry
ip_address = iv_ip_address
user_agent = iv_user_agent
consent_text = ls_type-description
) ).
" Alten Consent invalidieren
UPDATE zconsent_log
SET consent_status = @gc_consent_status-withdrawn
WHERE customer_id = @iv_customer_id
AND consent_type = @iv_consent_type
AND consent_id <> @rv_consent_id
AND consent_status = @gc_consent_status-granted.
ENDMETHOD.
METHOD withdraw_consent.
DATA(lv_today) = cl_abap_context_info=>get_system_date( ).
UPDATE zconsent_log
SET consent_status = @gc_consent_status-withdrawn,
withdrawal_date = @lv_today,
withdrawal_reason = @iv_reason
WHERE customer_id = @iv_customer_id
AND consent_type = @iv_consent_type
AND consent_status = @gc_consent_status-granted.
rv_success = xsdbool( sy-subrc = 0 ).
ENDMETHOD.
METHOD check_consent.
DATA(lv_today) = cl_abap_context_info=>get_system_date( ).
SELECT SINGLE @abap_true
FROM zconsent_log
WHERE customer_id = @iv_customer_id
AND consent_type = @iv_consent_type
AND consent_status = @gc_consent_status-granted
AND expiry_date >= @lv_today
INTO @rv_granted.
ENDMETHOD.
METHOD get_consent_status.
SELECT consent_type, consent_status, consent_date, expiry_date
FROM zconsent_log
WHERE customer_id = @iv_customer_id
ORDER BY consent_date DESCENDING
INTO TABLE @rt_consents.
ENDMETHOD.
METHOD expire_old_consents.
DATA(lv_today) = cl_abap_context_info=>get_system_date( ).
UPDATE zconsent_log
SET consent_status = @gc_consent_status-expired
WHERE expiry_date < @lv_today
AND consent_status = @gc_consent_status-granted.
ENDMETHOD.
ENDCLASS.
METHOD send_marketing_email.
DATA(lo_consent) = NEW zcl_consent_manager( ).
" Prüfen ob Marketing-Einwilligung vorliegt
IF lo_consent->check_consent(
iv_customer_id = iv_customer_id
iv_consent_type = 'MARKETING_EMAIL' ) = abap_false.
" Keine Einwilligung - E-Mail nicht senden
RAISE EXCEPTION TYPE zcx_no_consent
EXPORTING
textid = zcx_no_consent=>no_marketing_consent.
ENDIF.
" Marketing-E-Mail senden
" ...
ENDMETHOD.

SAP Information Lifecycle Management Integration

SAP ILM bietet eine integrierte Lösung für Datenaufbewahrung und Löschung.

ILM-Konfiguration

CLASS zcl_ilm_integration DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
METHODS:
register_business_object
IMPORTING iv_object_name TYPE string
iv_table_name TYPE tabname,
set_retention_rule
IMPORTING iv_object_name TYPE string
iv_retention_days TYPE i,
execute_blocking
IMPORTING iv_object_name TYPE string,
execute_destruction
IMPORTING iv_object_name TYPE string.
ENDCLASS.
CLASS zcl_ilm_integration IMPLEMENTATION.
METHOD register_business_object.
" ILM Objekt registrieren über SARA/ILM_DESTRUCTION
" Hinweis: In ABAP Cloud über APIs
" ...
ENDMETHOD.
METHOD set_retention_rule.
" Aufbewahrungsregel in ILM setzen
" Wird typischerweise über Customizing gemacht
" ...
ENDMETHOD.
METHOD execute_blocking.
" Residence/Retention-Phase
" Daten werden für Änderungen gesperrt
" ...
ENDMETHOD.
METHOD execute_destruction.
" End of Purpose - Daten löschen
" Wird über ILM Job ausgeführt
" ...
ENDMETHOD.
ENDCLASS.

ILM-fähige CDS View

@EndUserText.label: 'Kundenstamm mit ILM'
@ObjectModel.dataCategory: #MASTER
@ObjectModel.lifecycle.dateField: 'CreatedAt' -- Für ILM-Bestimmung
define view entity ZI_Customer_ILM
as select from zcustomer
{
key customer_id as CustomerId,
first_name as FirstName,
last_name as LastName,
@Semantics.systemDateTime.createdAt: true
created_at as CreatedAt,
@ObjectModel.lifecycle.status: #BLOCKED
is_blocked as IsBlocked,
@ObjectModel.lifecycle.status: #DELETED
is_deleted as IsDeleted
}

Checkliste für DSGVO-Compliance

Entwicklungs-Checkliste

PrüfpunktUmgesetztBemerkung
Datenminimierung
Nur notwendige Felder erfassen☐
Pflichtfelder auf Minimum reduziert☐
Keine Erfassung “auf Vorrat”☐
Zweckbindung
Verwendungszweck dokumentiert☐
Consent für jeden Zweck eingeholt☐
Keine Zweckentfremdung☐
Speicherbegrenzung
Aufbewahrungsfristen definiert☐
Löschjobs implementiert☐
Archivierung konfiguriert☐
Betroffenenrechte
Auskunftsfunktion vorhanden☐
Berichtigungsfunktion vorhanden☐
Löschfunktion vorhanden☐
Sperrfunktion vorhanden☐
Export-Funktion vorhanden☐
Sicherheit
Zugriffskontrolle implementiert☐
Verschlüsselung bei Übertragung☐
Protokollierung aktiviert☐
Dokumentation
Verarbeitungsverzeichnis aktuell☐
Technische Dokumentation vorhanden☐
Datenschutzfolgenabschätzung durchgeführt☐

Code-Review-Checkliste für DSGVO

" ❌ NICHT DSGVO-konform
DATA: gv_customer_ssn TYPE string. " Sensible Daten global
" ✅ DSGVO-konform
DATA(lo_customer) = zcl_customer_handler=>get_instance( iv_customer_id ).
DATA(lv_masked_ssn) = lo_customer->get_masked_ssn( ).
" ❌ NICHT DSGVO-konform - Logging mit personenbezogenen Daten
MESSAGE |Kunde { ls_customer-name } hat Bestellung { lv_order_id }| TYPE 'I'.
" ✅ DSGVO-konform - Keine personenbezogenen Daten im Log
MESSAGE |Bestellung { lv_order_id } erstellt| TYPE 'I'.
" ❌ NICHT DSGVO-konform - Unbegrenzter Export
SELECT * FROM zcustomer INTO TABLE @DATA(lt_all_customers).
" ✅ DSGVO-konform - Berechtigungsprüfung und Protokollierung
IF lo_auth->check_export_authorization( ) = abap_true.
lo_audit->log_data_export( iv_reason = 'CUSTOMER_REQUEST' ).
SELECT * FROM zcustomer WHERE customer_id = @iv_customer_id
INTO TABLE @DATA(lt_customer_data).
ENDIF.

Automatisierte DSGVO-Prüfungen mit ATC

" Custom ATC-Check für DSGVO-Compliance
" Prüft auf:
" - Hardcodierte personenbezogene Daten
" - Fehlende Berechtigungsprüfungen
" - Ungeschützte Exporte
" - Fehlende Protokollierung

Best Practices

1. Privacy by Design

" Datenminimierung bereits im Datenmodell
define table zcustomer_minimal {
key customer_id : sysuuid_x16; -- Pseudonym statt Klarname
display_name : abap.char(100); -- Optional
email_hash : abap.char(64); -- Gehashte E-Mail
created_at : timestampl;
" KEINE: Geburtsdatum, Adresse, Telefon - wenn nicht benötigt
}

2. Standardmäßiger Datenschutz

" Default: Minimale Datenverarbeitung
METHOD constructor.
" Consent wird NICHT automatisch angenommen
me->consent_status = gc_consent_status-not_given.
" Marketing ist NICHT standardmäßig aktiviert
me->marketing_enabled = abap_false.
" Daten werden NICHT unbegrenzt gespeichert
me->retention_date = cl_abap_context_info=>get_system_date( ) + 365.
ENDMETHOD.

3. Verschlüsselung

" Sensible Daten verschlüsselt speichern
METHOD encrypt_pii_data.
TRY.
DATA(lo_crypto) = cl_abap_symmetric_cipher=>get_aes256( ).
rv_encrypted = lo_crypto->encrypt(
if_data = cl_abap_codepage=>convert_to( iv_plain_text )
if_key = get_encryption_key( ) ).
CATCH cx_abap_symmetric_cipher.
" Fehlerbehandlung
ENDTRY.
ENDMETHOD.

Zusammenfassung

DSGVO-AnforderungABAP Cloud Lösung
LöschrechtRAP Action + Deletion Service
AnonymisierungAnonymization Service
AuskunftsrechtData Export als JSON
Consent ManagementConsent Service + CDS Views
AufbewahrungILM Integration
ProtokollierungApplication Logging

DSGVO-Compliance erfordert eine durchgängige Berücksichtigung von Datenschutzanforderungen in der gesamten Anwendung – vom Datenmodell über die Geschäftslogik bis zur Benutzeroberfläche.

Verwandte Themen