Harden published curriculum and student flows

This commit is contained in:
Hamza-Ayed
2026-09-30 08:22:17 +03:00
parent e8163fe265
commit ce7b0fcf14
53 changed files with 4723 additions and 3317 deletions
@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
// In-memory fixture only; no writes to repository, external database or network.
require_once dirname(__DIR__) . '/app/Core/Database.php';
require_once dirname(__DIR__) . '/app/Services/StudentAccessControlService.php';
require_once dirname(__DIR__) . '/app/Services/PublishedContentService.php';
require_once dirname(__DIR__) . '/app/Services/CurriculumService.php';
use App\Core\Database;
use App\Services\PublishedContentService;
use App\Services\CurriculumService;
$pdo = class_exists('Pdo\\Sqlite') ? new \Pdo\Sqlite('sqlite::memory:') : new PDO('sqlite::memory:');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
(new ReflectionProperty(Database::class, 'instance'))->setValue(null, $pdo);
$pdo->exec('CREATE TABLE auth_identities (id INTEGER PRIMARY KEY, status TEXT)');
$pdo->exec('CREATE TABLE students (id INTEGER PRIMARY KEY, grade_level TEXT, identity_id INTEGER)');
$pdo->exec("INSERT INTO auth_identities VALUES (1, 'active'), (2, 'disabled')");
$pdo->exec("INSERT INTO students VALUES (7, 'grade_10', 1), (8, 'grade_9', 1), (10, 'grade_10', 2)");
function checkContent(bool $condition, string $message): void
{
if (!$condition) throw new RuntimeException($message);
}
$asset = ['grade_key' => 'grade_10', 'storage_driver' => 'local', 'storage_key' => 'manifest.json'];
checkContent(PublishedContentService::studentMayRead(7, $asset), 'Same-grade student denied');
checkContent(!PublishedContentService::studentMayRead(8, $asset), 'Other-grade student allowed');
checkContent(!PublishedContentService::studentMayRead(9, $asset), 'Unknown student allowed');
checkContent(!PublishedContentService::studentMayRead(10, $asset), 'Disabled identity allowed');
checkContent(PublishedContentService::readLocalAsset($asset) !== null, 'Exact in-root asset not found');
checkContent(PublishedContentService::readLocalAsset(array_merge($asset, ['storage_key' => '../../database_schema.sql'])) === null, 'Traversal escaped storage root');
checkContent(PublishedContentService::readLocalAsset(array_merge($asset, ['storage_key' => 'missing.md'])) === null, 'Missing file substituted');
checkContent(CurriculumService::getLessonMarkdown('../manifest.json') === '', 'Unsafe lesson read returned content');
checkContent(!CurriculumService::safeMarkdownPath('../grade_10/a.md'), 'Traversal path accepted');
checkContent(CurriculumService::safeMarkdownPath('grade_10/math_10/semester_1/unit_01/lesson_01.md'), 'Valid markdown path rejected');
echo "PASS 10 local content-access checks (not an HTTP/tenant test)\n";