Skip to content

Commit bf1e970

Browse files
committed
Initial commit: YForm Encryption AddOn für FriendsOfREDAXO
0 parents  commit bf1e970

28 files changed

+4329
-0
lines changed

.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Composer-Vendor (wird beim Build via composer install erzeugt)
2+
/vendor/
3+
4+
# macOS
5+
.DS_Store
6+
7+
# IDE
8+
.idea/
9+
.vscode/
10+
*.swp

LICENSE.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 Thomas Skerbis / Friends Of REDAXO
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 356 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,356 @@
1+
# YForm Encryption
2+
3+
**REDAXO-Addon zur transparenten Feldverschlüsselung in YForm-Tabellen.**
4+
5+
Verschlüsselt sensible YForm-Felder serverseitig mit libsodium (XSalsa20-Poly1305). Die Daten werden verschlüsselt in der Datenbank gespeichert und im Backend automatisch entschlüsselt angezeigt. Der Schlüssel liegt außerhalb des Webroots.
6+
7+
---
8+
9+
## Voraussetzungen
10+
11+
- REDAXO ≥ 5.18
12+
- PHP ≥ 8.1 mit aktivierter `sodium`-Extension
13+
- YForm ≥ 5.0
14+
- **Inkompatibel mit dem Addon `yform_export`** (eigener Export ist integriert)
15+
16+
---
17+
18+
## Installation
19+
20+
1. Addon über den REDAXO-Installer installieren oder manuell in `redaxo/src/addons/yform_encryption/` ablegen.
21+
2. Im REDAXO-Backend unter **YForm Encryption → Einstellungen** den Schlüsselpfad konfigurieren (Verzeichnis **außerhalb** des Webroots empfohlen).
22+
3. Schlüssel generieren lassen oder vorhandenen Schlüssel hinterlegen.
23+
24+
---
25+
26+
## Konfiguration
27+
28+
| Einstellung | Beschreibung |
29+
|---|---|
30+
| Schlüsselpfad | Absoluter Pfad zur Schlüsseldatei, idealerweise außerhalb des Webroots |
31+
| Schlüssel generieren | Neuen libsodium-Schlüssel erzeugen und speichern |
32+
33+
---
34+
35+
## Schlüssel als Umgebungsvariable (empfohlen)
36+
37+
Statt den Schlüssel in einer Datei zu speichern, kann er als Systemumgebungsvariable `YFORM_ENCRYPTION_KEY` hinterlegt werden. Das Addon prüft diese Variable **zuerst** – vor dem Dateipfad.
38+
39+
Der Wert muss der **Base64-kodierte** 32-Byte-Schlüssel sein (wird im Backend unter „Einstellungen → Schlüssel anzeigen" angezeigt).
40+
41+
### Plesk
42+
43+
1. Plesk → Domain → **Apache & nginx-Einstellungen**
44+
2. Im Feld **„Zusätzliche Apache-Direktiven"** (HTTP oder HTTPS):
45+
```apache
46+
SetEnv YFORM_ENCRYPTION_KEY "IhrBase64SchluesselHier=="
47+
```
48+
3. **Speichern** – Plesk schreibt die Direktive in die vHost-Konfiguration.
49+
50+
Alternativ über die Plesk-Erweiterung **„Node.js/PHP Environment Variables"** oder direkt in der `php.ini` der Domain:
51+
```ini
52+
; Plesk → PHP-Einstellungen → Zusätzliche Direktiven
53+
env[YFORM_ENCRYPTION_KEY] = "IhrBase64SchluesselHier=="
54+
```
55+
56+
---
57+
58+
### Docker / docker-compose
59+
60+
In `docker-compose.yml`:
61+
```yaml
62+
services:
63+
web:
64+
image: your-redaxo-image
65+
environment:
66+
YFORM_ENCRYPTION_KEY: "IhrBase64SchluesselHier=="
67+
```
68+
69+
Oder mit einer `.env`-Datei (niemals ins Repository einchecken!):
70+
```dotenv
71+
# .env
72+
YFORM_ENCRYPTION_KEY=IhrBase64SchluesselHier==
73+
```
74+
```yaml
75+
# docker-compose.yml
76+
services:
77+
web:
78+
env_file:
79+
- .env
80+
```
81+
82+
Beim direkten `docker run`:
83+
```bash
84+
docker run -e YFORM_ENCRYPTION_KEY="IhrBase64SchluesselHier==" your-redaxo-image
85+
```
86+
87+
---
88+
89+
### Apache (ohne Plesk)
90+
91+
In der **VirtualHost-Konfiguration** oder `.htaccess`:
92+
```apache
93+
# /etc/apache2/sites-available/ihre-domain.conf ODER .htaccess
94+
SetEnv YFORM_ENCRYPTION_KEY "IhrBase64SchluesselHier=="
95+
```
96+
97+
> `.htaccess` ist weniger sicher als die VirtualHost-Konfiguration, da sie im Webroot liegen kann. Den `.htaccess`-Eintrag unbedingt **außerhalb des öffentlich zugänglichen Verzeichnisses** setzen oder per `Deny from all` schützen.
98+
99+
---
100+
101+
### Nginx + PHP-FPM
102+
103+
Nginx selbst unterstützt `SetEnv` nicht – die Variable muss im **PHP-FPM-Pool** gesetzt werden:
104+
105+
```ini
106+
# /etc/php/8.x/fpm/pool.d/ihre-domain.conf
107+
env[YFORM_ENCRYPTION_KEY] = "IhrBase64SchluesselHier=="
108+
```
109+
110+
Nach dem Bearbeiten PHP-FPM neu starten:
111+
```bash
112+
systemctl restart php8.x-fpm
113+
```
114+
115+
---
116+
117+
### Linux-Server (systemd / global)
118+
119+
Für systemd-verwaltete PHP-FPM-Dienste in der Override-Konfiguration:
120+
```bash
121+
systemctl edit php8.x-fpm
122+
```
123+
```ini
124+
[Service]
125+
Environment="YFORM_ENCRYPTION_KEY=IhrBase64SchluesselHier=="
126+
```
127+
128+
Global für alle Prozesse (weniger empfohlen):
129+
```bash
130+
# /etc/environment
131+
YFORM_ENCRYPTION_KEY="IhrBase64SchluesselHier=="
132+
```
133+
134+
---
135+
136+
### Sicherheitshinweis zu Umgebungsvariablen
137+
138+
- `.env`-Dateien **niemals** in Git einchecken – `.gitignore`-Eintrag prüfen.
139+
- Berechtigungen von Konfigurationsdateien mit dem Schlüssel auf `640` oder `600` setzen.
140+
- In Shared-Hosting-Umgebungen lieber die **Schlüsseldatei außerhalb des Webroots** verwenden, da Umgebungsvariablen u.U. durch `phpinfo()` sichtbar werden.
141+
142+
---
143+
144+
## Felder verschlüsseln
145+
146+
Unter **YForm Encryption → Feldzuordnung** können pro YForm-Tabelle einzelne Felder zur Verschlüsselung markiert werden.
147+
148+
Folgende YForm-Feldtypen können verschlüsselt werden:
149+
150+
| Feldtyp | Beschreibung | Typischer Anwendungsfall |
151+
|---|---|---|
152+
| `text` | Einzeiliges Textfeld | Name, Vorname, Ausweisnummer, Tokens |
153+
| `textarea` | Mehrzeiliges Textfeld | Notizen, Anamnese, Freitexte mit sensiblem Inhalt |
154+
| `email` | E-Mail-Adresse | Kontaktdaten mit erhöhtem Schutzbedarf |
155+
| `phone` | Telefonnummer | Mobil-/Festnetznummern |
156+
| `url` | URL-Feld | Interne Links, Token-URLs |
157+
| `ip` | IP-Adresse | Logging, DSGVO-relevante Netzwerkdaten |
158+
| `fields_iban` | IBAN (fields-Addon) | Bankverbindungen – **besonders schützenswert** |
159+
| `fields_inline` | Inline-Gruppe (fields-Addon) | Kombinierte Felder z.B. Adresse + IBAN |
160+
161+
**Nicht verschlüsselbar** (und auch nicht sinnvoll):
162+
- Auswahlfelder (`select`, `choice`, `checkbox`, `radio`) – zu wenige diskrete Werte, Verschlüsselung bringt keinen Sicherheitsgewinn
163+
- Relationsfelder (`be_relation`, `relation`) – nur IDs, kein personenbezogener Inhalt
164+
- Zahlenfelder (`integer`, `float`) – Datenbanktyp inkompatibel mit Ciphertext-String
165+
- Datumsfelder (`date`, `datetime`) – oft für Sortierung/Filterung nötig, Verschlüsselung würde Abfragen komplett blockieren
166+
- `be_media`, `upload` – Dateiname/Pfad, Dateiinhalt selbst liegt im Dateisystem
167+
168+
Nach dem Speichern der Feldzuordnung werden **neue Einträge** automatisch verschlüsselt gespeichert. Bestehende Daten können über die **Migration**-Funktion nachträglich verschlüsselt werden.
169+
170+
---
171+
172+
## Export (CSV / Excel)
173+
174+
Da das Addon mit `yform_export` inkompatibel ist, bringt es einen eigenen Exporter mit:
175+
176+
- In der YForm-Datenliste erscheinen **CSV**- und **Excel**-Buttons.
177+
- Die Daten werden beim Export automatisch entschlüsselt.
178+
- Die Excel-Datei enthält Metadaten (Autor = eingeloggter User, Exportzeitpunkt, Domain).
179+
180+
---
181+
182+
## ⚠️ Einschränkung: YForm-Suche
183+
184+
**Die integrierte YForm-Datenlisten-Suche kann verschlüsselte Felder nicht durchsuchen.**
185+
186+
Der Grund: Die Suche arbeitet mit SQL-`LIKE`-Abfragen direkt auf der Datenbank. Verschlüsselte Werte liegen als Ciphertext vor – ein Klartext-Suchbegriff kann dort keine Treffer finden.
187+
188+
**Betroffen sind alle als verschlüsselt markierten Felder.** Nicht-verschlüsselte Felder derselben Tabelle (z.B. `id`, Datumsfelder, Status-Felder) sind weiterhin normal durchsuchbar.
189+
190+
**Workaround**: Den **CSV- oder Excel-Export** nutzen und lokal in der Tabellenkalkulation suchen – die exportierten Daten sind vollständig entschlüsselt.
191+
192+
> **Empfehlung**: Nur wirklich sensible Felder verschlüsseln (IBAN, Ausweisdaten, Gesundheitsdaten, Tokens). Felder, nach denen häufig gesucht wird (z.B. Name, E-Mail), nur verschlüsseln wenn ein erhöhtes Breach-Risiko besteht – dann ist der Komfortverlust durch die eingeschränkte Suche bewusst in Kauf zu nehmen.
193+
194+
---
195+
196+
## Autorisierung für Bulk-Operationen (SessionGuard)
197+
198+
Massenoperationen wie das **nachträgliche Ver- oder Entschlüsseln bestehender Datensätze** (Migration) sind durch einen zusätzlichen Authentifizierungsschritt gesichert – den **SessionGuard**.
199+
200+
**Ablauf:**
201+
1. Unter **YForm Encryption → Feldzuordnung** eine Bulk-Aktion auslösen (z.B. „Alle bestehenden Datensätze verschlüsseln").
202+
2. Das System fordert eine **erneute Eingabe** von REDAXO-Benutzername und Passwort.
203+
3. Nach erfolgreicher Eingabe ist die Session für **30 Minuten** freigeschaltet.
204+
4. Weitere Bulk-Aktionen innerhalb dieses Zeitfensters erfordern keine erneute Eingabe.
205+
5. Nach Ablauf des Timeouts (oder manueller Sperrung) muss erneut authentifiziert werden.
206+
207+
> Der Timeout ist unter **YForm Encryption → Einstellungen** konfigurierbar.
208+
209+
**Warum dieser zusätzliche Schutz?**
210+
Bulk-Operationen schreiben alle Datensätze einer Tabelle um. Ein versehentlicher Klick – oder ein kompromittiertes Admin-Konto das gerade in der Session läuft – könnte sonst alle verschlüsselten Daten im Klartext in die DB schreiben. Die erneute Passwortabfrage stellt sicher, dass die Aktion bewusst von einer autorisierten Person ausgelöst wird.
211+
212+
---
213+
214+
## Sicherheitshinweise
215+
216+
- Den Schlüssel **regelmäßig sichern** – ohne Schlüssel sind verschlüsselte Daten unwiederbringlich verloren.
217+
- Den Schlüssel **niemals** im Webroot ablegen.
218+
- Den Schlüssel **nicht** in der Versionsverwaltung tracken.
219+
- Bei einem Server-Umzug den Schlüssel separat übertragen.
220+
221+
---
222+
223+
## API-Referenz
224+
225+
Alle Klassen liegen im Namespace `FriendsOfREDAXO\YFormEncryption\` und werden von REDAXO automatisch geladen.
226+
227+
---
228+
229+
### `Helper` – Einstiegspunkt für externe Nutzung
230+
231+
Die einfachste Klasse für den Zugriff aus Modulen, Addons oder Templates.
232+
233+
```php
234+
use FriendsOfREDAXO\YFormEncryption\Helper;
235+
```
236+
237+
| Methode | Rückgabe | Beschreibung |
238+
|---|---|---|
239+
| `Helper::getDecryptedRow(string $tableName, int $id)` | `?array` | Einzelnen Datensatz laden und alle verschlüsselten Felder entschlüsseln |
240+
| `Helper::getDecryptedTable(string $tableName, string $where, string $orderBy, int $limit, int $offset)` | `array` | Mehrere Datensätze laden und entschlüsseln |
241+
| `Helper::decryptValue(string $value)` | `string` | Einzelnen Wert entschlüsseln (gibt Klartext zurück, auch wenn nicht verschlüsselt) |
242+
| `Helper::encryptValue(string $value)` | `string` | Einzelnen Wert verschlüsseln |
243+
| `Helper::isEncrypted(string $value)` | `bool` | Prüfen ob ein Wert verschlüsselt ist |
244+
| `Helper::getEncryptedFieldsForTable(string $tableName)` | `array` | Liste der verschlüsselten Feldnamen für eine Tabelle |
245+
246+
**Beispiel:**
247+
```php
248+
$row = Helper::getDecryptedRow('rex_my_table', 42);
249+
echo $row['iban']; // entschlüsselt
250+
251+
$rows = Helper::getDecryptedTable('rex_my_table', 'status = 1', 'id DESC', 50);
252+
```
253+
254+
---
255+
256+
### `EncryptionService` – Kern-Verschlüsselung
257+
258+
```php
259+
use FriendsOfREDAXO\YFormEncryption\EncryptionService;
260+
$enc = EncryptionService::getInstance();
261+
```
262+
263+
| Methode | Rückgabe | Beschreibung |
264+
|---|---|---|
265+
| `getInstance()` | `self` | Singleton-Instanz |
266+
| `encrypt(string $plaintext)` | `string` | Text verschlüsseln (mit Prefix) |
267+
| `decrypt(string $encrypted)` | `string` | Text entschlüsseln |
268+
| `decryptSafe(string $value)` | `string` | Entschlüsseln ohne Exception – gibt Originalwert bei Fehler zurück |
269+
| `isEncrypted(string $value)` | `bool` | Präfix-Prüfung |
270+
| `encryptFields(array $data, array $fields)` | `array` | Mehrere Felder eines Datensatzes verschlüsseln |
271+
| `decryptFields(array $data, array $fields)` | `array` | Mehrere Felder eines Datensatzes entschlüsseln |
272+
| `getPrefix()` | `string` | Verschlüsselungs-Präfix (statisch) |
273+
274+
---
275+
276+
### `FieldMapper` – Feldzuordnungen
277+
278+
```php
279+
use FriendsOfREDAXO\YFormEncryption\FieldMapper;
280+
$mapper = FieldMapper::getInstance();
281+
```
282+
283+
| Methode | Rückgabe | Beschreibung |
284+
|---|---|---|
285+
| `getInstance()` | `self` | Singleton-Instanz |
286+
| `getEncryptedFields(string $tableName)` | `array` | Alle verschlüsselten Feldnamen einer Tabelle |
287+
| `hasEncryptedFields(string $tableName)` | `bool` | Hat die Tabelle verschlüsselte Felder? |
288+
| `isFieldEncrypted(string $tableName, string $fieldName)` | `bool` | Ist ein bestimmtes Feld verschlüsselt? |
289+
| `addField(string $tableName, string $fieldName)` | `void` | Feld zur Verschlüsselung hinzufügen |
290+
| `removeField(string $tableName, string $fieldName)` | `void` | Feld aus der Verschlüsselung entfernen (Mapping löschen) |
291+
| `getAllMappings()` | `array` | Alle Zuordnungen als `['table' => ['field1', 'field2']]` |
292+
| `getAvailableTablesAndFields()` | `array` | Alle YForm-Tabellen mit verschlüsselbaren Feldern |
293+
294+
---
295+
296+
### `KeyManager` – Schlüsselverwaltung
297+
298+
```php
299+
use FriendsOfREDAXO\YFormEncryption\KeyManager;
300+
$km = KeyManager::getInstance();
301+
```
302+
303+
| Methode | Rückgabe | Beschreibung |
304+
|---|---|---|
305+
| `getInstance()` | `self` | Singleton-Instanz |
306+
| `hasKey()` | `bool` | Ist ein Schlüssel verfügbar? |
307+
| `getKey()` | `string` | Rohen Schlüssel-Binärstring liefern |
308+
| `getKeySource()` | `string` | Herkunft des Schlüssels: `environment`, `file`, `data_dir` |
309+
| `getKeyFilePath()` | `string` | Pfad zur konfigurierten Schlüsseldatei |
310+
| `generateKey(string $location)` | `string` | Neuen Schlüssel erzeugen (`'file'` oder `'data_dir'`) |
311+
312+
**Schlüsselpriorität**: `YFORM_ENCRYPTION_KEY` (Env) → konfigurierter Dateipfad → `data/`-Verzeichnis
313+
314+
---
315+
316+
### `SessionGuard` – Bulk-Autorisierung
317+
318+
```php
319+
use FriendsOfREDAXO\YFormEncryption\SessionGuard;
320+
$guard = SessionGuard::getInstance();
321+
```
322+
323+
| Methode | Rückgabe | Beschreibung |
324+
|---|---|---|
325+
| `getInstance()` | `self` | Singleton-Instanz |
326+
| `isUnlocked()` | `bool` | Ist die Session aktuell entsperrt? |
327+
| `authenticate(string $login, string $password)` | `bool` | Authentifizieren und Session entsperren |
328+
| `unlock()` | `void` | Session manuell entsperren (ohne Passwort) |
329+
| `lock()` | `void` | Session sofort sperren |
330+
| `getRemainingTime()` | `int` | Verbleibende Sekunden bis zum Timeout |
331+
| `getTimeout()` | `int` | Konfigurierten Timeout in Sekunden liefern |
332+
333+
---
334+
335+
### `ColumnMigrator` – Spaltentyp-Migration
336+
337+
```php
338+
use FriendsOfREDAXO\YFormEncryption\ColumnMigrator;
339+
```
340+
341+
| Methode | Rückgabe | Beschreibung |
342+
|---|---|---|
343+
| `ColumnMigrator::checkColumns(string $tableName, array $fields)` | `array` | Prüfen welche Spalten auf `TEXT` erweitert werden müssen |
344+
| `ColumnMigrator::migrateColumns(string $tableName, array $fields)` | `int` | Spalten auf `TEXT` migrieren, gibt Anzahl geänderter Spalten zurück |
345+
| `ColumnMigrator::getWarnings()` | `array` | Warnungen der letzten Migration abrufen |
346+
347+
> Ciphertexte sind länger als Klartexte – `VARCHAR`-Spalten werden bei Bedarf automatisch auf `TEXT` erweitert.
348+
349+
---
350+
351+
## Lizenz
352+
353+
MIT – siehe [LICENSE.md](LICENSE.md)
354+
355+
**Autor**: [Thomas Skerbis](https://github.com/skerbis) / [FriendsOfREDAXO](https://github.com/FriendsOfREDAXO)
356+
**Support**: https://github.com/FriendsOfREDAXO/yform_encryption

0 commit comments

Comments
 (0)