From e91e8e9dc7656ce63be7c023f27916749b5861d3 Mon Sep 17 00:00:00 2001 From: Matthias Linhuber Date: Thu, 24 Sep 2026 15:43:08 +0200 Subject: [PATCH 1/3] feat(landing-page): derive privacy-statement facts from the deployment config The landing page's privacy statement hardcodes "2 weeks" for workspace retention and "deleted when the session ends" for sessions. Both are TUM production's numbers. Mannheim runs WORKSPACE_TTL=12960000s (150 days) with ephemeralStorage=false, so its workspaces persist and are reaped 150 days after the last session - the page has been telling Mannheim users something untrue. Emits a `privacy` block into the landing page config, derived from the values that actually produce the behaviour rather than restated next to them: workspacePersistent landingPage.ephemeralStorage, inverted workspaceRetentionDays theia-workspace-garbage-collector.env.WORKSPACE_TTL sessionMaxMinutes appDefinitions.defaults.timeout sessionIdleMinutes appDefinitions.defaults.monitor.activityTracker.timeoutAfter The last two mirror the fallback chain in appdefinitions.yaml, where the operator reads them, so the page cannot disagree with the AppDefinition the sessions actually run under. Restating the figures in a second place is how a privacy statement quietly becomes false, which is the failure this avoids. landingPage.privacy.scientificUse is the one value that is a policy choice and not a derivation: it states that anonymised usage data may also be used for scientific research. Off by default, because it is a processing purpose and needs a legal basis, not a string change. Verified by rendering against the real environment values files on EduIDE-deployment main: tum-production gives 14 days / ephemeral / 1440 min, mannheim gives 150 days / persistent / 180 min. Co-Authored-By: Claude Opus 5 --- charts/eduide/Chart.yaml | 2 +- charts/eduide/README.md | 4 ++- .../templates/landing-page-config-map.yaml | 35 +++++++++++++++++++ charts/eduide/values.yaml | 13 +++++++ 4 files changed, 52 insertions(+), 2 deletions(-) diff --git a/charts/eduide/Chart.yaml b/charts/eduide/Chart.yaml index 558d97a..6789ddf 100644 --- a/charts/eduide/Chart.yaml +++ b/charts/eduide/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 2.1.5 +version: 2.2.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. diff --git a/charts/eduide/README.md b/charts/eduide/README.md index 08c86ce..13ed2ee 100644 --- a/charts/eduide/README.md +++ b/charts/eduide/README.md @@ -1,6 +1,6 @@ # eduide -![Version: 2.1.5](https://img.shields.io/badge/Version-2.1.5-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 1.2.0](https://img.shields.io/badge/AppVersion-1.2.0-informational?style=flat-square) +![Version: 2.2.0](https://img.shields.io/badge/Version-2.2.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 1.2.0](https://img.shields.io/badge/AppVersion-1.2.0-informational?style=flat-square) EduIDE tenant release: operator, REST service, landing page and routes for one environment. Requires eduide-cluster to be installed on the cluster first. @@ -84,6 +84,8 @@ environment. Requires eduide-cluster to be installed on the cluster first. | landingPage.logo | string | `"logos/theiablueprint.svg"` | The logo of the application that should be displayed on the landing pages | | landingPage.logoData | string | `nil` | set landingPage.logoData=$(cat path/to/file.svg | base64 -w 0 -) Another way is to directly add the base64 string to the values file. | | landingPage.logoFileExtension | string | `"svg"` | The file extension of the logo. Must be set to match the logo respectively the logoData. This is required because browsers cannot show a binary image (e.g. png) with a svg ending and vice-versa. | +| landingPage.privacy | object | (see details below) | What the privacy page states about this installation. Only the research clause lives here. The retention and session figures the page shows are DERIVED in the landing page config map from the settings that actually produce them - `theia-workspace-garbage-collector.env.WORKSPACE_TTL`, `landingPage.ephemeralStorage` and `appDefinitions.defaults.timeout` - so changing a retention period cannot leave the privacy statement claiming something untrue. Do not restate those numbers here. | +| landingPage.privacy.scientificUse | bool | `false` | State that anonymised usage data may also be used for scientific research. Leave off unless this installation has a legal basis for it: this is a processing purpose, not a cosmetic string. | | landingPage.sentry | object | (see details below) | Values related to Sentry on the landing page. | | landingPage.sentry.enable | bool | `false` | Set SENTRY_ENABLE=true in the landing page deployment. Off by default: the DSN is compiled into the published images and points at TUM's Sentry, so enabling this outside TUM sends your hostnames and namespace names there. | | monitor | object | (see details below) | Values to influence the monitor initialization on the operator | diff --git a/charts/eduide/templates/landing-page-config-map.yaml b/charts/eduide/templates/landing-page-config-map.yaml index 4d12713..6847d7c 100644 --- a/charts/eduide/templates/landing-page-config-map.yaml +++ b/charts/eduide/templates/landing-page-config-map.yaml @@ -69,6 +69,41 @@ data: loadingText: "{{ tpl (.Values.landingPage.loadingText | toString) . }}", {{- end }} sentryEnable: {{ tpl (.Values.landingPage.sentry.enable | toString) . }}, + {{- /* + What the privacy page is allowed to state about this installation. + + DERIVED, deliberately. Every number below is computed from the value that + actually produces the behaviour it describes, so raising a retention + period cannot leave the privacy statement asserting the old one. The + alternative - restating the figures in a second place - is how a privacy + statement quietly becomes false. + + workspacePersistent landingPage.ephemeralStorage, inverted + workspaceRetentionDays theia-workspace-garbage-collector.env.WORKSPACE_TTL + sessionMaxMinutes appDefinitions.defaults.timeout + sessionIdleMinutes appDefinitions.defaults.monitor.activityTracker.timeoutAfter + + The last two mirror the fallbacks in appdefinitions.yaml, which is where + the operator actually reads them, so the page cannot disagree with the + AppDefinition the sessions run under. + + scientificUse is the one policy choice, not a derivation. + */}} + {{- $gc := (index .Values "theia-workspace-garbage-collector") | default dict }} + {{- $gcEnabled := true }} + {{- if hasKey $gc "enabled" }}{{ $gcEnabled = $gc.enabled }}{{ end }} + {{- $ttl := ((($gc.env).WORKSPACE_TTL) | default "1209600s") | toString | trimSuffix "s" }} + {{- $appDefaults := (.Values.appDefinitions).defaults | default dict }} + privacy: { + workspacePersistent: {{ not .Values.landingPage.ephemeralStorage }}, + workspaceGarbageCollected: {{ $gcEnabled }}, + {{- if $gcEnabled }} + workspaceRetentionDays: {{ div (atoi $ttl) 86400 }}, + {{- end }} + sessionMaxMinutes: {{ $appDefaults.timeout | default 1440 }}, + sessionIdleMinutes: {{ ((($appDefaults.monitor).activityTracker).timeoutAfter) | default 60 }}, + scientificUse: {{ ((.Values.landingPage).privacy).scientificUse | default false }}, + }, {{- if .Values.landingPage.footerLinks }} footerLinks: { {{- if .Values.landingPage.footerLinks.attribution }} diff --git a/charts/eduide/values.yaml b/charts/eduide/values.yaml index 85e2f65..eff4950 100644 --- a/charts/eduide/values.yaml +++ b/charts/eduide/values.yaml @@ -127,6 +127,19 @@ landingPage: # -- If set to true no persisted storage is used when creating sessions on the landing page. # Set to false if you want to use persisted storage. ephemeralStorage: true + # -- What the privacy page states about this installation. + # Only the research clause lives here. The retention and session figures the + # page shows are DERIVED in the landing page config map from the settings that + # actually produce them - `theia-workspace-garbage-collector.env.WORKSPACE_TTL`, + # `landingPage.ephemeralStorage` and `appDefinitions.defaults.timeout` - so + # changing a retention period cannot leave the privacy statement claiming + # something untrue. Do not restate those numbers here. + # @default -- (see details below) + privacy: + # -- State that anonymised usage data may also be used for scientific + # research. Leave off unless this installation has a legal basis for it: + # this is a processing purpose, not a cosmetic string. + scientificUse: false # -- The page may show these additional apps in a drop down. This is a map. # The key maps to the app definition name # The value contains the label shown in the UI and may optionally contain From 54bf5808cc81ea7e3cc7fbb0733e879b69cfc592 Mon Sep 17 00:00:00 2001 From: Matthias Linhuber Date: Thu, 24 Sep 2026 18:48:44 +0200 Subject: [PATCH 2/3] feat(landing-page): make the controller and data protection officer configurable The privacy page names TUM as data controller and TUM's data protection officer in hardcoded text, on every installation. Any other university deploying EduIDE publishes a statement pointing data subjects at the wrong institution, which is worse than a missing contact - the reader has no way to exercise their rights. Unlike the retention figures, these cannot be derived from anything: only the operator knows who its controller is. So they are values, and their defaults are deliberately obvious placeholders - "Example University", "Prof. Dr. Example Person", privacy@example.edu. A statement that visibly has not been filled in is recoverable; one that confidently names somebody else is not. landingPage.privacy.controller.{organisation,representative,address,email} landingPage.privacy.dataProtectionOfficer.{name,email} The officer is separate from the controller because the GDPR requires a distinct contact point, and address is optional because not every institution publishes one. EduIDE's own installations set the real TUM values in EduIDE-deployment, not here, so the chart itself never carries them. Co-Authored-By: Claude Opus 5 --- charts/eduide/README.md | 8 +++++++ .../templates/landing-page-config-map.yaml | 19 ++++++++++++++++ charts/eduide/values.yaml | 22 +++++++++++++++++++ 3 files changed, 49 insertions(+) diff --git a/charts/eduide/README.md b/charts/eduide/README.md index 13ed2ee..5d61592 100644 --- a/charts/eduide/README.md +++ b/charts/eduide/README.md @@ -85,6 +85,14 @@ environment. Requires eduide-cluster to be installed on the cluster first. | landingPage.logoData | string | `nil` | set landingPage.logoData=$(cat path/to/file.svg | base64 -w 0 -) Another way is to directly add the base64 string to the values file. | | landingPage.logoFileExtension | string | `"svg"` | The file extension of the logo. Must be set to match the logo respectively the logoData. This is required because browsers cannot show a binary image (e.g. png) with a svg ending and vice-versa. | | landingPage.privacy | object | (see details below) | What the privacy page states about this installation. Only the research clause lives here. The retention and session figures the page shows are DERIVED in the landing page config map from the settings that actually produce them - `theia-workspace-garbage-collector.env.WORKSPACE_TTL`, `landingPage.ephemeralStorage` and `appDefinitions.defaults.timeout` - so changing a retention period cannot leave the privacy statement claiming something untrue. Do not restate those numbers here. | +| landingPage.privacy.controller | object | (see details below) | Who is accountable for the data, named on the privacy page. The defaults are deliberately obvious placeholders. Whoever deploys this is the controller, and a privacy statement naming somebody else's university is worse than one that visibly has not been filled in. | +| landingPage.privacy.controller.address | string | `"1 Example Street, 00000 Example City"` | Postal address of that person. Optional; omitted when empty. | +| landingPage.privacy.controller.email | string | `"privacy@example.edu"` | Where data protection enquiries go. | +| landingPage.privacy.controller.organisation | string | `"Example University"` | The legal entity responsible under the GDPR. | +| landingPage.privacy.controller.representative | string | `"Prof. Dr. Example Person"` | The person accountable for this service, as named on the imprint. | +| landingPage.privacy.dataProtectionOfficer | object | (see details below) | The data protection officer, named separately from the controller because the GDPR requires a distinct contact point. | +| landingPage.privacy.dataProtectionOfficer.email | string | `"dpo@example.edu"` | Where the data protection officer is reached. | +| landingPage.privacy.dataProtectionOfficer.name | string | `""` | Optional name. Most institutions publish only the address. | | landingPage.privacy.scientificUse | bool | `false` | State that anonymised usage data may also be used for scientific research. Leave off unless this installation has a legal basis for it: this is a processing purpose, not a cosmetic string. | | landingPage.sentry | object | (see details below) | Values related to Sentry on the landing page. | | landingPage.sentry.enable | bool | `false` | Set SENTRY_ENABLE=true in the landing page deployment. Off by default: the DSN is compiled into the published images and points at TUM's Sentry, so enabling this outside TUM sends your hostnames and namespace names there. | diff --git a/charts/eduide/templates/landing-page-config-map.yaml b/charts/eduide/templates/landing-page-config-map.yaml index 6847d7c..661faad 100644 --- a/charts/eduide/templates/landing-page-config-map.yaml +++ b/charts/eduide/templates/landing-page-config-map.yaml @@ -103,6 +103,25 @@ data: sessionMaxMinutes: {{ $appDefaults.timeout | default 1440 }}, sessionIdleMinutes: {{ ((($appDefaults.monitor).activityTracker).timeoutAfter) | default 60 }}, scientificUse: {{ ((.Values.landingPage).privacy).scientificUse | default false }}, + {{- /* + Who is accountable, named on the privacy page. Unlike the figures above + these cannot be derived from anything - only the operator knows who the + controller is - so they are values, and their defaults are obvious + placeholders. A statement naming the wrong institution is worse than + one that visibly has not been filled in. + */}} + {{- $ctrl := ((.Values.landingPage).privacy).controller | default dict }} + {{- $dpo := ((.Values.landingPage).privacy).dataProtectionOfficer | default dict }} + controller: { + organisation: {{ $ctrl.organisation | default "Example University" | quote }}, + representative: {{ $ctrl.representative | default "Prof. Dr. Example Person" | quote }}, + address: {{ $ctrl.address | default "" | quote }}, + email: {{ $ctrl.email | default "privacy@example.edu" | quote }}, + }, + dataProtectionOfficer: { + name: {{ $dpo.name | default "" | quote }}, + email: {{ $dpo.email | default "dpo@example.edu" | quote }}, + }, }, {{- if .Values.landingPage.footerLinks }} footerLinks: { diff --git a/charts/eduide/values.yaml b/charts/eduide/values.yaml index eff4950..4ceb647 100644 --- a/charts/eduide/values.yaml +++ b/charts/eduide/values.yaml @@ -140,6 +140,28 @@ landingPage: # research. Leave off unless this installation has a legal basis for it: # this is a processing purpose, not a cosmetic string. scientificUse: false + # -- Who is accountable for the data, named on the privacy page. + # The defaults are deliberately obvious placeholders. Whoever deploys this + # is the controller, and a privacy statement naming somebody else's + # university is worse than one that visibly has not been filled in. + # @default -- (see details below) + controller: + # -- The legal entity responsible under the GDPR. + organisation: Example University + # -- The person accountable for this service, as named on the imprint. + representative: Prof. Dr. Example Person + # -- Postal address of that person. Optional; omitted when empty. + address: 1 Example Street, 00000 Example City + # -- Where data protection enquiries go. + email: privacy@example.edu + # -- The data protection officer, named separately from the controller + # because the GDPR requires a distinct contact point. + # @default -- (see details below) + dataProtectionOfficer: + # -- Optional name. Most institutions publish only the address. + name: "" + # -- Where the data protection officer is reached. + email: dpo@example.edu # -- The page may show these additional apps in a drop down. This is a map. # The key maps to the app definition name # The value contains the label shown in the UI and may optionally contain From d343770d43ab64ced70f40e2b35bc96a123b9676 Mon Sep 17 00:00:00 2001 From: Matthias Linhuber Date: Thu, 24 Sep 2026 19:01:04 +0200 Subject: [PATCH 3/3] fix(landing-page): keep retention exact, take session limits worst-case Three review findings, all fair. Retention is emitted as seconds, not days. Helm's `div` is integer division, so a WORKSPACE_TTL that is not a whole number of days was rounded down and the page would claim a shorter retention than the deployment keeps - the one direction a retention claim must never be wrong in. 129600s now reaches the page intact instead of collapsing to "1 day". Session limits are the maximum across every selectable AppDefinition rather than the defaults alone. appdefinitions.yaml resolves each app's own `timeout` and `monitor.activityTracker.timeoutAfter` before falling back, so an app overriding either would have made the page understate how long a session can live. Nothing overrides them today, in the chart or in any environment; taking the worst case keeps the statement true if something ever does. Controller and officer now default to empty rather than to "Example University" and privacy@example.edu. A placeholder rendered inside "Verantwortlich im Sinne der DSGVO ist ..." reads as a statement of fact, and the address goes nowhere. The page detects the empty state and says no controller has been configured. The example values live in the values documentation instead, where they are plainly examples. Co-Authored-By: Claude Opus 5 --- charts/eduide/README.md | 12 +++--- .../templates/landing-page-config-map.yaml | 37 +++++++++++++++---- charts/eduide/values.yaml | 28 ++++++++------ 3 files changed, 53 insertions(+), 24 deletions(-) diff --git a/charts/eduide/README.md b/charts/eduide/README.md index 5d61592..f55c9f3 100644 --- a/charts/eduide/README.md +++ b/charts/eduide/README.md @@ -86,12 +86,12 @@ environment. Requires eduide-cluster to be installed on the cluster first. | landingPage.logoFileExtension | string | `"svg"` | The file extension of the logo. Must be set to match the logo respectively the logoData. This is required because browsers cannot show a binary image (e.g. png) with a svg ending and vice-versa. | | landingPage.privacy | object | (see details below) | What the privacy page states about this installation. Only the research clause lives here. The retention and session figures the page shows are DERIVED in the landing page config map from the settings that actually produce them - `theia-workspace-garbage-collector.env.WORKSPACE_TTL`, `landingPage.ephemeralStorage` and `appDefinitions.defaults.timeout` - so changing a retention period cannot leave the privacy statement claiming something untrue. Do not restate those numbers here. | | landingPage.privacy.controller | object | (see details below) | Who is accountable for the data, named on the privacy page. The defaults are deliberately obvious placeholders. Whoever deploys this is the controller, and a privacy statement naming somebody else's university is worse than one that visibly has not been filled in. | -| landingPage.privacy.controller.address | string | `"1 Example Street, 00000 Example City"` | Postal address of that person. Optional; omitted when empty. | -| landingPage.privacy.controller.email | string | `"privacy@example.edu"` | Where data protection enquiries go. | -| landingPage.privacy.controller.organisation | string | `"Example University"` | The legal entity responsible under the GDPR. | -| landingPage.privacy.controller.representative | string | `"Prof. Dr. Example Person"` | The person accountable for this service, as named on the imprint. | -| landingPage.privacy.dataProtectionOfficer | object | (see details below) | The data protection officer, named separately from the controller because the GDPR requires a distinct contact point. | -| landingPage.privacy.dataProtectionOfficer.email | string | `"dpo@example.edu"` | Where the data protection officer is reached. | +| landingPage.privacy.controller.address | string | `""` | Postal address of that person, e.g. "1 Example Street, 00000 Example City". Optional; omitted when empty. | +| landingPage.privacy.controller.email | string | `""` | Where data protection enquiries go, e.g. "privacy@example.edu". | +| landingPage.privacy.controller.organisation | string | `""` | The legal entity responsible under the GDPR, e.g. "Example University". Empty by default on purpose: the page then says plainly that no controller has been configured, rather than presenting a placeholder as though it were this installation's real contact. | +| landingPage.privacy.controller.representative | string | `""` | The person accountable for this service, as named on the imprint, e.g. "Prof. Dr. Example Person". | +| landingPage.privacy.dataProtectionOfficer | object | (see details below) | The data protection officer, named separately from the controller because the GDPR requires a distinct contact point. Empty by default for the same reason as above. | +| landingPage.privacy.dataProtectionOfficer.email | string | `""` | Where the data protection officer is reached, e.g. "dpo@example.edu". | | landingPage.privacy.dataProtectionOfficer.name | string | `""` | Optional name. Most institutions publish only the address. | | landingPage.privacy.scientificUse | bool | `false` | State that anonymised usage data may also be used for scientific research. Leave off unless this installation has a legal basis for it: this is a processing purpose, not a cosmetic string. | | landingPage.sentry | object | (see details below) | Values related to Sentry on the landing page. | diff --git a/charts/eduide/templates/landing-page-config-map.yaml b/charts/eduide/templates/landing-page-config-map.yaml index 661faad..da9c345 100644 --- a/charts/eduide/templates/landing-page-config-map.yaml +++ b/charts/eduide/templates/landing-page-config-map.yaml @@ -94,14 +94,37 @@ data: {{- if hasKey $gc "enabled" }}{{ $gcEnabled = $gc.enabled }}{{ end }} {{- $ttl := ((($gc.env).WORKSPACE_TTL) | default "1209600s") | toString | trimSuffix "s" }} {{- $appDefaults := (.Values.appDefinitions).defaults | default dict }} + {{- /* + Session limits are the WORST CASE across everything a user can pick, not + the defaults. appdefinitions.yaml resolves each app's own timeout before + falling back to the defaults, so an app overriding it would otherwise make + the page understate how long a session can live. Nothing overrides these + today; taking the maximum keeps the statement true if something does. + */}} + {{- $maxSession := ($appDefaults.timeout | default 1440) | int }} + {{- $maxIdle := (((($appDefaults.monitor).activityTracker).timeoutAfter) | default 60) | int }} + {{- range $name, $app := (.Values.appDefinitions).apps | default dict }} + {{- if and $app $app.landingPage }} + {{- $t := ($app.timeout | default $appDefaults.timeout | default 1440) | int }} + {{- if gt $t $maxSession }}{{ $maxSession = $t }}{{ end }} + {{- $i := ((($app.monitor).activityTracker).timeoutAfter) | default ((($appDefaults.monitor).activityTracker).timeoutAfter) | default 60 | int }} + {{- if gt $i $maxIdle }}{{ $maxIdle = $i }}{{ end }} + {{- end }} + {{- end }} privacy: { workspacePersistent: {{ not .Values.landingPage.ephemeralStorage }}, workspaceGarbageCollected: {{ $gcEnabled }}, {{- if $gcEnabled }} - workspaceRetentionDays: {{ div (atoi $ttl) 86400 }}, + {{- /* + Seconds, not days: helm's `div` is integer division, so a TTL that is + not a whole number of days would be silently rounded down and the page + would claim a shorter retention than the deployment actually keeps. + The page formats it. + */}} + workspaceRetentionSeconds: {{ atoi $ttl }}, {{- end }} - sessionMaxMinutes: {{ $appDefaults.timeout | default 1440 }}, - sessionIdleMinutes: {{ ((($appDefaults.monitor).activityTracker).timeoutAfter) | default 60 }}, + sessionMaxMinutes: {{ $maxSession }}, + sessionIdleMinutes: {{ $maxIdle }}, scientificUse: {{ ((.Values.landingPage).privacy).scientificUse | default false }}, {{- /* Who is accountable, named on the privacy page. Unlike the figures above @@ -113,14 +136,14 @@ data: {{- $ctrl := ((.Values.landingPage).privacy).controller | default dict }} {{- $dpo := ((.Values.landingPage).privacy).dataProtectionOfficer | default dict }} controller: { - organisation: {{ $ctrl.organisation | default "Example University" | quote }}, - representative: {{ $ctrl.representative | default "Prof. Dr. Example Person" | quote }}, + organisation: {{ $ctrl.organisation | quote }}, + representative: {{ $ctrl.representative | quote }}, address: {{ $ctrl.address | default "" | quote }}, - email: {{ $ctrl.email | default "privacy@example.edu" | quote }}, + email: {{ $ctrl.email | quote }}, }, dataProtectionOfficer: { name: {{ $dpo.name | default "" | quote }}, - email: {{ $dpo.email | default "dpo@example.edu" | quote }}, + email: {{ $dpo.email | quote }}, }, }, {{- if .Values.landingPage.footerLinks }} diff --git a/charts/eduide/values.yaml b/charts/eduide/values.yaml index 4ceb647..a63d25d 100644 --- a/charts/eduide/values.yaml +++ b/charts/eduide/values.yaml @@ -146,22 +146,28 @@ landingPage: # university is worse than one that visibly has not been filled in. # @default -- (see details below) controller: - # -- The legal entity responsible under the GDPR. - organisation: Example University - # -- The person accountable for this service, as named on the imprint. - representative: Prof. Dr. Example Person - # -- Postal address of that person. Optional; omitted when empty. - address: 1 Example Street, 00000 Example City - # -- Where data protection enquiries go. - email: privacy@example.edu + # -- The legal entity responsible under the GDPR, e.g. "Example University". + # Empty by default on purpose: the page then says plainly that no + # controller has been configured, rather than presenting a placeholder as + # though it were this installation's real contact. + organisation: "" + # -- The person accountable for this service, as named on the imprint, + # e.g. "Prof. Dr. Example Person". + representative: "" + # -- Postal address of that person, e.g. "1 Example Street, 00000 Example + # City". Optional; omitted when empty. + address: "" + # -- Where data protection enquiries go, e.g. "privacy@example.edu". + email: "" # -- The data protection officer, named separately from the controller - # because the GDPR requires a distinct contact point. + # because the GDPR requires a distinct contact point. Empty by default for + # the same reason as above. # @default -- (see details below) dataProtectionOfficer: # -- Optional name. Most institutions publish only the address. name: "" - # -- Where the data protection officer is reached. - email: dpo@example.edu + # -- Where the data protection officer is reached, e.g. "dpo@example.edu". + email: "" # -- The page may show these additional apps in a drop down. This is a map. # The key maps to the app definition name # The value contains the label shown in the UI and may optionally contain