A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.
This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).
Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
295 lines
10 KiB
Markdown
295 lines
10 KiB
Markdown
# BreakPilot Authentifizierung & Autorisierung
|
|
|
|
## Uebersicht
|
|
|
|
BreakPilot verwendet einen **Hybrid-Ansatz** fuer Authentifizierung und Autorisierung:
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
│ AUTHENTIFIZIERUNG │
|
|
│ "Wer bist du?" │
|
|
│ ┌────────────────────────────────────────────────────────────────────┐ │
|
|
│ │ HybridAuthenticator │ │
|
|
│ │ ┌─────────────────────┐ ┌─────────────────────────────────┐ │ │
|
|
│ │ │ Keycloak │ │ Lokales JWT │ │ │
|
|
│ │ │ (Produktion) │ OR │ (Entwicklung) │ │ │
|
|
│ │ │ RS256 + JWKS │ │ HS256 + Secret │ │ │
|
|
│ │ └─────────────────────┘ └─────────────────────────────────┘ │ │
|
|
│ └────────────────────────────────────────────────────────────────────┘ │
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
│ AUTORISIERUNG │
|
|
│ "Was darfst du?" │
|
|
│ ┌────────────────────────────────────────────────────────────────────┐ │
|
|
│ │ rbac.py (Eigenentwicklung) │ │
|
|
│ │ ┌─────────────────┐ ┌─────────────────┐ ┌───────────────────┐ │ │
|
|
│ │ │ Rollen-Hierarchie│ │ PolicySet │ │ DEFAULT_PERMISSIONS│ │ │
|
|
│ │ │ 15+ Rollen │ │ Bundesland- │ │ Matrix │ │ │
|
|
│ │ │ - Erstkorrektor │ │ spezifisch │ │ Rolle→Ressource→ │ │ │
|
|
│ │ │ - Klassenlehrer │ │ - Niedersachsen │ │ Aktion │ │ │
|
|
│ │ │ - Schulleitung │ │ - Bayern │ │ │ │ │
|
|
│ │ └─────────────────┘ └─────────────────┘ └───────────────────┘ │ │
|
|
│ └────────────────────────────────────────────────────────────────────┘ │
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
## Warum dieser Ansatz?
|
|
|
|
### Alternative Loesungen (verworfen)
|
|
|
|
| Tool | Problem fuer BreakPilot |
|
|
|------|-------------------------|
|
|
| **Casbin** | Zu generisch fuer Bundesland-spezifische Policies |
|
|
| **Cerbos** | Overhead: Externer PDP-Service fuer ~15 Rollen ueberdimensioniert |
|
|
| **OpenFGA** | Zanzibar-Modell optimiert fuer Graph-Beziehungen, nicht Hierarchien |
|
|
| **Keycloak RBAC** | Kann keine ressourcen-spezifischen Zuweisungen (User X ist Erstkorrektor fuer Package Y) |
|
|
|
|
### Vorteile des Hybrid-Ansatzes
|
|
|
|
1. **Keycloak fuer Authentifizierung:**
|
|
- Bewährtes IAM-System
|
|
- SSO, Federation, MFA
|
|
- Apache-2.0 Lizenz
|
|
|
|
2. **Eigenes rbac.py fuer Autorisierung:**
|
|
- Domaenenspezifische Logik (Korrekturkette, Zeugnis-Workflow)
|
|
- Bundesland-spezifische Regeln
|
|
- Zeitlich begrenzte Zuweisungen
|
|
- Key-Sharing fuer verschluesselte Klausuren
|
|
|
|
---
|
|
|
|
## Authentifizierung (auth/keycloak_auth.py)
|
|
|
|
### Konfiguration
|
|
|
|
```python
|
|
# Entwicklung: Lokales JWT (Standard)
|
|
JWT_SECRET=your-secret-key
|
|
|
|
# Produktion: Keycloak
|
|
KEYCLOAK_SERVER_URL=https://keycloak.breakpilot.app
|
|
KEYCLOAK_REALM=breakpilot
|
|
KEYCLOAK_CLIENT_ID=breakpilot-backend
|
|
KEYCLOAK_CLIENT_SECRET=your-client-secret
|
|
```
|
|
|
|
### Token-Erkennung
|
|
|
|
Der `HybridAuthenticator` erkennt automatisch den Token-Typ:
|
|
|
|
```python
|
|
# Keycloak-Token (RS256)
|
|
{
|
|
"iss": "https://keycloak.breakpilot.app/realms/breakpilot",
|
|
"sub": "user-uuid",
|
|
"realm_access": {"roles": ["teacher", "admin"]},
|
|
...
|
|
}
|
|
|
|
# Lokales JWT (HS256)
|
|
{
|
|
"iss": "breakpilot",
|
|
"user_id": "user-uuid",
|
|
"role": "admin",
|
|
...
|
|
}
|
|
```
|
|
|
|
### FastAPI Integration
|
|
|
|
```python
|
|
from auth import get_current_user
|
|
|
|
@app.get("/api/protected")
|
|
async def protected_endpoint(user: dict = Depends(get_current_user)):
|
|
# user enthält: user_id, email, role, realm_roles, tenant_id
|
|
return {"user_id": user["user_id"]}
|
|
```
|
|
|
|
---
|
|
|
|
## Autorisierung (klausur-service/backend/rbac.py)
|
|
|
|
### Rollen (15+)
|
|
|
|
| Rolle | Beschreibung | Bereich |
|
|
|-------|--------------|---------|
|
|
| `erstkorrektor` | Erster Prüfer | Klausur |
|
|
| `zweitkorrektor` | Zweiter Prüfer | Klausur |
|
|
| `drittkorrektor` | Dritter Prüfer | Klausur |
|
|
| `klassenlehrer` | Klassenleitung | Zeugnis |
|
|
| `fachlehrer` | Fachlehrkraft | Noten |
|
|
| `fachvorsitz` | Fachkonferenz-Leitung | Fachschaft |
|
|
| `schulleitung` | Schulleiter/in | Schule |
|
|
| `zeugnisbeauftragter` | Zeugnis-Koordination | Zeugnis |
|
|
| `sekretariat` | Verwaltung | Schule |
|
|
| `data_protection_officer` | DSB | DSGVO |
|
|
| ... | | |
|
|
|
|
### Ressourcentypen (25+)
|
|
|
|
```python
|
|
class ResourceType(str, Enum):
|
|
EXAM_PACKAGE = "exam_package" # Klausurpaket
|
|
STUDENT_SUBMISSION = "student_submission"
|
|
CORRECTION = "correction"
|
|
ZEUGNIS = "zeugnis"
|
|
FACHNOTE = "fachnote"
|
|
KOPFNOTE = "kopfnote"
|
|
BEMERKUNG = "bemerkung"
|
|
...
|
|
```
|
|
|
|
### Aktionen (17)
|
|
|
|
```python
|
|
class Action(str, Enum):
|
|
CREATE = "create"
|
|
READ = "read"
|
|
UPDATE = "update"
|
|
DELETE = "delete"
|
|
SIGN_OFF = "sign_off" # Freigabe
|
|
BREAK_GLASS = "break_glass" # Notfall-Zugriff
|
|
SHARE_KEY = "share_key" # Schlüssel teilen
|
|
...
|
|
```
|
|
|
|
### Permission-Pruefung
|
|
|
|
```python
|
|
from klausur_service.backend.rbac import PolicyEngine
|
|
|
|
engine = PolicyEngine()
|
|
|
|
# Pruefe ob User X Klausur Y korrigieren darf
|
|
allowed = engine.check_permission(
|
|
user_id="user-uuid",
|
|
action=Action.UPDATE,
|
|
resource_type=ResourceType.CORRECTION,
|
|
resource_id="klausur-uuid"
|
|
)
|
|
```
|
|
|
|
---
|
|
|
|
## Bundesland-spezifische Policies
|
|
|
|
```python
|
|
@dataclass
|
|
class PolicySet:
|
|
bundesland: str
|
|
abitur_type: str # "landesabitur" | "zentralabitur"
|
|
|
|
# Korrekturkette
|
|
korrektoren_anzahl: int # 2 oder 3
|
|
anonyme_erstkorrektur: bool
|
|
|
|
# Sichtbarkeit
|
|
zk_visibility_mode: ZKVisibilityMode # BLIND | SEMI | FULL
|
|
eh_visibility_mode: EHVisibilityMode
|
|
|
|
# Zeugnis
|
|
kopfnoten_enabled: bool
|
|
...
|
|
```
|
|
|
|
### Beispiel: Niedersachsen
|
|
|
|
```python
|
|
NIEDERSACHSEN_POLICY = PolicySet(
|
|
bundesland="niedersachsen",
|
|
abitur_type="landesabitur",
|
|
korrektoren_anzahl=2,
|
|
anonyme_erstkorrektur=True,
|
|
zk_visibility_mode=ZKVisibilityMode.BLIND,
|
|
eh_visibility_mode=EHVisibilityMode.SUMMARY_ONLY,
|
|
kopfnoten_enabled=True,
|
|
)
|
|
```
|
|
|
|
---
|
|
|
|
## Workflow-Beispiele
|
|
|
|
### Klausurkorrektur-Workflow
|
|
|
|
```
|
|
1. Lehrer laedt Klausuren hoch
|
|
└── Rolle: "lehrer" + Action.CREATE auf EXAM_PACKAGE
|
|
|
|
2. Erstkorrektor korrigiert
|
|
└── Rolle: "erstkorrektor" (ressourcen-spezifisch) + Action.UPDATE auf CORRECTION
|
|
|
|
3. Zweitkorrektor ueberprueft
|
|
└── Rolle: "zweitkorrektor" + Action.READ auf CORRECTION
|
|
└── Policy: zk_visibility_mode bestimmt Sichtbarkeit
|
|
|
|
4. Drittkorrektor (bei Abweichung)
|
|
└── Rolle: "drittkorrektor" + Action.SIGN_OFF
|
|
```
|
|
|
|
### Zeugnis-Workflow
|
|
|
|
```
|
|
1. Fachlehrer traegt Noten ein
|
|
└── Rolle: "fachlehrer" + Action.CREATE auf FACHNOTE
|
|
|
|
2. Klassenlehrer prueft
|
|
└── Rolle: "klassenlehrer" + Action.READ auf ZEUGNIS
|
|
└── Action.SIGN_OFF freigeben
|
|
|
|
3. Zeugnisbeauftragter final
|
|
└── Rolle: "zeugnisbeauftragter" + Action.SIGN_OFF
|
|
|
|
4. Schulleitung unterzeichnet
|
|
└── Rolle: "schulleitung" + Action.SIGN_OFF
|
|
```
|
|
|
|
---
|
|
|
|
## Dateien
|
|
|
|
| Datei | Beschreibung |
|
|
|-------|--------------|
|
|
| `backend/auth/__init__.py` | Auth-Modul Exports |
|
|
| `backend/auth/keycloak_auth.py` | Hybrid-Authentifizierung |
|
|
| `klausur-service/backend/rbac.py` | Autorisierungs-Engine |
|
|
| `backend/rbac_api.py` | REST API fuer Rollenverwaltung |
|
|
|
|
---
|
|
|
|
## Konfiguration
|
|
|
|
### Entwicklung (ohne Keycloak)
|
|
|
|
```bash
|
|
# .env
|
|
ENVIRONMENT=development
|
|
JWT_SECRET=dev-secret-32-chars-minimum-here
|
|
```
|
|
|
|
### Produktion (mit Keycloak)
|
|
|
|
```bash
|
|
# .env
|
|
ENVIRONMENT=production
|
|
JWT_SECRET=<openssl rand -hex 32>
|
|
KEYCLOAK_SERVER_URL=https://keycloak.breakpilot.app
|
|
KEYCLOAK_REALM=breakpilot
|
|
KEYCLOAK_CLIENT_ID=breakpilot-backend
|
|
KEYCLOAK_CLIENT_SECRET=<from keycloak admin console>
|
|
```
|
|
|
|
---
|
|
|
|
## Sicherheitshinweise
|
|
|
|
1. **Secrets niemals im Code** - Immer Umgebungsvariablen verwenden
|
|
2. **JWT_SECRET in Produktion** - Mindestens 32 Bytes, generiert mit `openssl rand -hex 32`
|
|
3. **Keycloak HTTPS** - KEYCLOAK_VERIFY_SSL=true in Produktion
|
|
4. **Token-Expiration** - Keycloak-Tokens kurz halten (5-15 Minuten)
|
|
5. **Audit-Trail** - Alle Berechtigungspruefungen werden geloggt
|