Skip to content
bitzorcas
中EN

Concept

GDPR and data subject rights

A source-verified GDPR manual covering consent, SAR, erasure, tenancy, persistence, authorization, evidence, and production gaps.

Last updated

The GDPR module manages consent history, subject access requests (SARs), and erasure requests. It already has provider-neutral persistence, SqlSugar/EF Core parity, a 30-day erasure cooling-off period, tenant predicates, and Identity user anonymization. It is not yet a complete commercial privacy-orchestration platform.

1. Current end-to-end path

Authenticated user

Generated /api/privacy/* endpoints

Mediator pipeline
authentication / authorization / transaction / activity

GDPR commands and queries

IGdprStore

SysDataSubjectRequest

SysConsent

IAuditQueryPort
first page only

IIdentityPersonalDataEraser
SysUser only

Every [GenerateEndpoint] route uses RequireAuthorization() by default. Commands that implement IAuthorizedRequest also enter the framework authorization decision. Plain queries and RecordConsent only have the HTTP authentication boundary.

2. Current capability matrix

CapabilityCurrent implementationGuarantee that does not exist
Grant consentAppend a SysConsent history rowNo purpose registry, notice snapshot, active-consent uniqueness, or processor propagation
Revoke consentOwner or role named admin sets RevokedAtNo withdrawal event or downstream acknowledgement
Create SARQuery then insert by tenant and RequestIdNo unique constraint; concurrency is not idempotent
Process SARStore UserId and at most 1,000 audit itemsNo domain collection, file, download, encryption, or expiry
Create erasureStart a 30-day CoolingOff requestNo identity re-proofing, approval, or legal hold
Execute erasureAnonymize and soft-delete the current tenant’s SysUser rowNo credentials, sessions, business owners, or external processors
PersistenceProvider-neutral IEntitySet<T> with dual-provider contractsDoes not prove workflow semantics
Activity auditShared pipeline records command resultBest-effort delivery, not an immutable compliance ledger

3. HTTP use cases and actual decisions

Method and routeUse caseAdditional decision
POST /api/privacy/consentsRecordConsentno IAuthorizedRequest
GET /api/privacy/consentsGetConsentsno IAuthorizedRequest
DELETE /api/privacy/consents/{id}RevokeConsentgdpr.own-data.delete
POST /api/privacy/sarCreateSargdpr.own-data.create
GET /api/privacy/sarGetSarRequestsno IAuthorizedRequest
POST /api/privacy/sar/{id}/processProcessSarprivacy.sar.update
POST /api/privacy/erasureCreateErasuregdpr.own-data.create
DELETE /api/privacy/erasure/{id}CancelErasuregdpr.own-data.delete
POST /api/privacy/erasure/{id}/executeExecuteErasureprivacy.erasure.delete

The decision code comes from ResourceDescriptor + AuthorizationAction. The source catalog instead declares privacy.consent.view, privacy.consent.manage, privacy.sar.manage, and privacy.erasure.manage. This mismatch can prevent roles from receiving the permission that the request actually evaluates.

4. Two state machines in one table

SARErasureretryafter 30 days

Submitted

Processing

Completed

Failed

Rejected

CoolingOff

Cancelled

Executing

This is the intended model, not an enforced state machine. Both SmartEnums persist integers in StatusCode, while ID-based handlers do not check RequestType. A request passed to the wrong endpoint can have the same integer interpreted as a different workflow status.

5. Tenant data plane

GdprStore uses ICurrentTenant.Tenant.EffectiveTenantId as its final predicate. Application handlers generally pass currentUser.User.TenantId:

Caller tenant and store tenant intersection
var actorTenant = currentUser.User.TenantId;
var rows = await store.GetConsentsByUserAsync(
actorTenant,
currentUser.User.UserId?.ToString() ?? "0",
cancellationToken);
// The store also requires row.TenantId == EffectiveTenantId.
// During operate-as, the intersection can be empty.

The two values normally match for an ordinary user. Host operations, background execution, and tenant impersonation need explicit contracts and tests.

6. Persistence and composition

The module owns two tenant-scoped soft-delete tables:

  • SysConsent stores purpose, notice version, grant/revocation times, plus IP and User-Agent columns that current commands do not populate.
  • SysDataSubjectRequest stores request type, status, cooling-off deadline, rejection reason, DetailsJson, and reserved ExportFileId.

GdprStore depends on IEntitySet<T>, not SqlSugar or EF Core. With production persistence prerequisites, generated registration selects the configured ORM adapter. Shell mode retains an explicit unavailable port and fails closed on use.

7. Highest-priority delivery gaps

  1. Process handlers do not validate RequestType; status integers can cross workflow boundaries.
  2. RequestId lookup does not include type, and the table has no unique request index.
  3. ProcessSar writes Failed and then returns Result.Failure; the transaction pipeline rolls the Failed update back.
  4. Business code never assigns ExportFileId or produces a downloadable artifact.
  5. Erasure changes only selected SysUser fields; it does not orchestrate other data owners.
  6. Permission catalog, runtime decisions, and feature privacy.operations have no closed-loop evidence.
  7. There is no subject re-proofing, legal hold, deadline escalation, download authorization, or immutable per-step record.

8. Reading path

  1. Consent and evidence
  2. SAR and export
  3. Erasure workflow
  4. Storage, security, and authorization
  5. Testing and operations

Related foundations: Auditing, Identity, Multitenancy, and Authorization.

9. Minimal source review

Terminal window
# List all GDPR routes and handlers.
rg -n "GenerateEndpoint|class Handler" src/Platform/Gdpr -g '*.cs'
# Inspect export fields and prove where business code assigns them.
rg -n "ExportFileId|DetailsJson" src/Platform/Gdpr tests -g '*.cs'
# Detect the mixed gdpr/privacy authorization resources.
rg -n 'new\("gdpr"|GdprPermissions\.' src/Platform/Gdpr -g '*.cs'

Back to module catalog

100%

Scroll or use controls to zoom · drag when enlarged · double-click for 100% / 200%