Review-Batch P0: Sicherheit + Schueler-Ergebnisse + Assessments-Aufblaehung

Sicherheit:
- progress.php: IDOR geschlossen (student_id-Zweig verlangt Lehrer-Login + Klassenbesitz)
- licenses.php: ?admin=1 und generate nur noch fuer echten Admin; Collision-Retry mit Cap
- .htaccess: .env/.sh/.sql/.bak Deny (Defense-in-Depth)

Schueler-Ergebnisse (kritisch):
- sim-metrics.js + .ivm-CSS in schueler.html; renderOwnRunCard-Fallback zeigt jetzt
  Sterne + grafische Kennzahlen fuer die 9 bisher leeren Sims
- sim-metrics.js: klima final_budget als "Mio EUR" statt "EUR" (Faktor 10^6)

Skalierung:
- assessment.php: Zwischen-Pings (phase running/started) nicht mehr persistiert
- heli: 30-s-assessmentPing entfernt (Live-Zustand laeuft ueber Heartbeat)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-08-15 16:21:25 +02:00
parent a3c5753cd7
commit 2984d93e7e
7 changed files with 65 additions and 14 deletions
+14 -3
View File
@@ -19,10 +19,21 @@ $db = Database::get();
if ($method === 'GET') {
$simId = $_GET['sim_id'] ?? '';
$studentId = (int)($_GET['student_id'] ?? 0);
$reqStudentId = (int)($_GET['student_id'] ?? 0);
$studentId = 0;
// Schüler*in: eigener Fortschritt
if ($simId && !$studentId) {
if ($reqStudentId) {
// Lehrer-Ansicht: nur eingeloggte Lehrperson, und die:der Schüler:in
// MUSS zu einer Klasse dieser Lehrperson gehören (sonst IDOR).
$teacherId = Session::requireTeacher();
$owned = $db->fetchOne(
'SELECT s.id FROM students s JOIN classes c ON c.id = s.class_id WHERE s.id = ? AND c.teacher_id = ?',
[$reqStudentId, $teacherId]
);
if (!$owned) Response::error('Kein Zugriff auf diese:n Schüler:in', 403);
$studentId = $reqStudentId;
} elseif ($simId) {
// Schüler*in: eigener Fortschritt (aus der Session abgeleitet)
$sessionId = Session::requireStudent();
$session = $db->fetchOne('SELECT class_id, display_name FROM student_sessions WHERE id = ?', [$sessionId]);
if ($session) {