Privacy Party (Controller & DPO)
Why this exists
Section titled “Why this exists”GDPR Art. 13 §1 obliges every data controller to communicate to data subjects:
- The identity and contact details of the controller (Art. 13 §1(a)) — mandatory.
- The contact details of the Data Protection Officer, when one is designated (Art. 13 §1(b)).
- The contact details of the competent supervisory authority (Art. 13 §2(d), Art. 77).
Hard-coding these in appsettings.json works but pushes a redeploy whenever the
DPO changes role, the email rotates, or a new tenant onboarded with its own
controller. Granit exposes them as runtime-editable settings so the values can
be overridden globally and per tenant via the Granit.Settings admin API, and
read by any template (notification emails, privacy policy page, document
generation) through a {{ privacy }} global context.
Settings
Section titled “Settings”Six settings are auto-registered by GranitPrivacyModule (no manual provider
needed). All are visible to clients (IsVisibleToClients = true) and support
the cascade Global → Tenant.
| Setting name | GDPR | Required |
|---|---|---|
Granit.Privacy.Controller.Name | Art. 13 §1(a) | yes |
Granit.Privacy.Controller.Email | Art. 13 §1(a) | yes |
Granit.Privacy.Controller.PostalAddress | Art. 13 §1(a) | recommended |
Granit.Privacy.Dpo.Name | Art. 13 §1(b), Art. 37 | if DPO designated |
Granit.Privacy.Dpo.Email | Art. 13 §1(b), Art. 38 §4 | if DPO designated |
Granit.Privacy.SupervisoryAuthority.Url | Art. 13 §2(d), Art. 77 | recommended |
The constants live in Granit.Privacy.Settings.PrivacySettingNames — reference
them rather than duplicating the strings.
Configuration
Section titled “Configuration”Static defaults via appsettings.json
Section titled “Static defaults via appsettings.json”The configuration provider (C priority, between Default and Global) reads
settings under the Settings section:
{ "Settings": { "Granit.Privacy.Controller.Name": "Acme Corp SA", "Granit.Privacy.Controller.PostalAddress": "1 rue de la Loi, 1000 Bruxelles, Belgium", "Granit.Privacy.Dpo.Name": "Jane Doe", "Granit.Privacy.SupervisoryAuthority.Url": "https://www.autoriteprotectiondonnees.be" }}Per-tenant override at runtime
Section titled “Per-tenant override at runtime”Tenant administrators can override any of the values through the Granit.Settings
admin API without a redeploy:
PUT /api/{version}/settings/Granit.Privacy.Dpo.EmailContent-Type: application/json
{ "providerName": "T", "providerKey": "<tenant-id>"}The cascade resolves Tenant → Global → Configuration → Default, so a missing tenant value falls back to the host-wide default automatically.
Templating: the {{ privacy }} global context
Section titled “Templating: the {{ privacy }} global context”Granit.Privacy.Notifications registers a PrivacyContactGlobalContext that
exposes the six settings as a {{ privacy }} namespace, available in every
template the app renders — notification emails, document generation, user-defined
templates served by Granit.Templating.
Available variables:
| Template variable | Source setting |
|---|---|
{{ privacy.controller_name }} | Granit.Privacy.Controller.Name |
{{ privacy.controller_email }} | Granit.Privacy.Controller.Email |
{{ privacy.controller_postal_address }} | Granit.Privacy.Controller.PostalAddress |
{{ privacy.dpo_name }} | Granit.Privacy.Dpo.Name |
{{ privacy.dpo_email }} | Granit.Privacy.Dpo.Email |
{{ privacy.supervisory_authority_url }} | Granit.Privacy.SupervisoryAuthority.Url |
Missing settings render as empty strings — use Scriban’s truthiness checks to conditionally render the DPO block:
<p> This message is sent by {{ privacy.controller_name }} ({{ privacy.controller_email }}).</p>
{{ if privacy.dpo_email }}<p> For privacy-related questions, contact our Data Protection Officer: <a href="mailto:{{ privacy.dpo_email }}">{{ privacy.dpo_email }}</a>.</p>{{ end }}
{{ if privacy.supervisory_authority_url }}<p> You may lodge a complaint with the supervisory authority: <a href="{{ privacy.supervisory_authority_url }}">{{ privacy.supervisory_authority_url }}</a>.</p>{{ end }}Typical use cases inside the framework:
- Deletion confirmation (
privacy.deletion_confirmed) — append a footer with controller + DPO contact so the user knows where to follow up. - Deletion reminder (
privacy.deletion_reminder) — same footer, plus the supervisory authority link in case the user disputes the deletion. - Legal document obsolete (
Privacy.LegalDocumentObsolete) — re-consent request signed by the controller. - Application-wide email layout — embed the contact in the standard footer so every transactional email (invoices, security alerts, password resets) carries a privacy contact line. Recommended for full Art. 13 transparency.
Regulation-aware deadlines ({{ model.response_deadline_days }})
Section titled “Regulation-aware deadlines ({{ model.response_deadline_days }})”Templates that quote a legal response window — most prominently
privacy.deletion_acknowledged — must NOT hard-code the deadline. GDPR Art. 12 §3
mandates one calendar month, but other regulations differ (CCPA: 45 days, LGPD: 15
days, PIPEDA: 30 days). Hard-coding “one month” in an English template and copy-pasting
it into the Spanish/Portuguese variants would silently misrepresent the deadline for
LGPD subjects.
The Granit.Privacy.Notifications package solves this by exposing the deadline as
a model field rather than a literal in the template:
<p> We will process your request within <strong>{{ model.response_deadline_days }} days</strong>{{ if model.regulation }} (<code>{{ model.regulation }}</code>){{ end }}.</p>The handler that publishes the notification resolves
PrivacyDeletionAcknowledgedNotificationData.ResponseDeadlineDays by looking up
the data subject’s regulation profile via IRegulationProfileRegistry
(Granit.Privacy.Regulations). Each profile (GDPR, CCPA, LGPD, PIPEDA, …)
declares its own statutory window, so the template renders the correct number of
days for the recipient’s jurisdiction without code changes per locale.
When you author a new privacy notification template that quotes a deadline,
follow this pattern: add a ResponseDeadlineDays field to the notification data
record, populate it from IRegulationProfileRegistry in the Wolverine handler,
and reference it as {{ model.response_deadline_days }} in every culture
template. Doing so keeps the framework regulation-neutral and lets future
regulations be added without touching template wording.
Performance
Section titled “Performance”The context resolves all six settings in a single batched
ISettingProvider.GetAllAsync(...) call per render. Values are cached by the
settings layer (per-tenant), so cold reads cost one DB roundtrip per tenant and
subsequent renders hit the cache.
See also
Section titled “See also”- Application Settings — Cascading settings (Default → Configuration → Global → Tenant → User)
- Templating — Scriban engine, layouts, global contexts
- Notifications — Notification types, channels, layouts
- Legal Agreements — Consent versioning
- Data Deletion — Deletion with cooling-off