Files
deskhub/DeskHub-Webseite/tests/theme_catalog.php
T
2026-10-16 19:12:24 +00:00

107 lines
5.9 KiB
PHP

<?php
declare(strict_types=1);
/** Prüft die Theme-Installation aus dem Katalog (includes/theme_catalog.php): Prüfsumme, Signatur, Format, Sicherheit. */
$themesDir = sys_get_temp_dir() . '/deskhub-tc-' . bin2hex(random_bytes(4));
mkdir($themesDir, 0755, true);
putenv('DESKHUB_THEMES_DIR=' . $themesDir);
putenv('DESKHUB_TESTING=1');
putenv('DESKHUB_CATALOG_DEFAULTS=' . __DIR__ . '/fixtures/no-catalog.php');
putenv('DESKHUB_SQLITE_PATH=:memory:');
require_once dirname(__DIR__) . '/includes/bootstrap.php';
if (!class_exists('ZipArchive')) { echo "Theme-Katalog-Tests übersprungen: PHP-ZIP fehlt.\n"; exit(0); }
$failures = [];
$check = function (bool $ok, string $what) use (&$failures): void {
if (!$ok) $failures[] = $what;
};
$cnf = tempnam(sys_get_temp_dir(), 'cnf');
file_put_contents($cnf, "[req]\ndistinguished_name=req_dn\n[req_dn]\n");
$k = openssl_pkey_new(['config' => $cnf, 'private_key_bits' => 2048, 'private_key_type' => OPENSSL_KEYTYPE_RSA]);
openssl_pkey_export($k, $priv, null, ['config' => $cnf]);
$pub = openssl_pkey_get_details($k)['key'];
$zipPath = tempnam(sys_get_temp_dir(), 'th') . '.zip';
$z = new ZipArchive();
$z->open($zipPath, ZipArchive::CREATE | ZipArchive::OVERWRITE);
$z->addFromString('theme.css', "/*\n * Author: Studio\n * Version: 1.2.0\n */\n:root { --primary: #0a5; }");
$z->close();
$sha = hash_file('sha256', $zipPath);
openssl_sign(plugin_signature_message('theme-nordlicht', '1.2.0', $sha), $sigRaw, $priv, OPENSSL_ALGO_SHA256);
$sig = base64_encode($sigRaw);
$entry = ['slug' => 'nordlicht', 'name' => 'Nordlicht', 'version' => '1.2.0', 'description' => 'Hell und ruhig', 'author' => 'Studio',
'downloadUrl' => 'https://plugins.example.de/dl/nordlicht-1.2.0.zip', 'sha256' => $sha, 'signature' => $sig];
$served = ['themes' => [$entry, ['slug' => 'kaputt', 'version' => 'x', 'sha256' => 'y'], array_merge($entry, ['slug' => 'httpdl', 'downloadUrl' => 'http://x.de/a.zip'])]];
$body = file_get_contents($zipPath);
$GLOBALS['deskhub_http_get'] = function (string $url, int $max) use (&$served, &$body): array {
if (str_contains($url, 'catalog.php')) return [200, json_encode($served), null];
return [200, $body, null];
};
$check(!theme_catalog_fetch()['ok'], 'ohne Katalog-Adresse gibt es einen Hinweis');
set_setting('theme_catalog_url', 'https://plugins.example.de/catalog.php?type=themes');
$cat = theme_catalog_fetch();
$check($cat['ok'] && count($cat['themes']) === 1 && $cat['themes'][0]['slug'] === 'nordlicht', 'Katalog wird gelesen, ungültige Einträge werden verworfen');
$r = theme_catalog_install('nordlicht');
$check(!$r['ok'] && str_contains($r['error'], 'kein Herausgeber-Schlüssel'), 'Signatur ohne hinterlegten Schlüssel kann nicht geprüft werden');
$check(!theme_catalog_install('gibtesnicht')['ok'], 'Theme außerhalb des Katalogs kann nicht installiert werden');
set_setting('plugin_publisher_key', $pub);
$r = theme_catalog_install('nordlicht');
$check($r['ok'] && $r['signed'] === true, 'mit Herausgeber-Schlüssel wird die Signatur geprüft');
$again = theme_catalog_install('nordlicht');
$check(!$again['ok'] && str_contains($again['error'], 'bereits'), 'ein bereits installiertes Theme lässt sich nicht noch einmal installieren');
$state = theme_catalog_state($served['themes'][0]);
$check($state['installed'] && !$state['update'] && $state['version'] === '1.2.0', 'Katalog erkennt das Theme als installiert (gleicher Name, gleiche Version)');
$check(!theme_catalog_state(['name' => 'Anderes Theme', 'version' => '1.0.0'])['installed'], 'ein nicht installiertes Theme wird nicht als installiert gezeigt');
$check(theme_catalog_state(['name' => 'Nordlicht', 'version' => '1.3.0'])['update'] === true, 'eine neuere Katalogversion wird als Update erkannt');
$themes = list_custom_themes();
$check(count($themes) === 1 && $themes[0]['name'] === 'Nordlicht' && is_file($themesDir . '/' . $themes[0]['id'] . '/theme.css'), 'Theme liegt im Theme-Ordner und wird aufgelistet');
$check($themes && $themes[0]['theme_version'] === '1.2.0' && $themes[0]['author'] === 'Studio', 'Version und Autor stammen aus theme.css');
$check(get_setting('active_custom_theme', '') === '', 'das Theme wird nicht automatisch aktiviert');
foreach (list_custom_themes() as $__t) delete_custom_theme($__t['id']);
$served['themes'][0]['signature'] = base64_encode(random_bytes(256));
$r = theme_catalog_install('nordlicht');
$check(!$r['ok'] && str_contains($r['error'], 'Signatur ist ungültig'), 'ungültige Signatur wird abgelehnt');
// Plugin-Signatur darf nicht als Theme-Signatur durchgehen
openssl_sign(plugin_signature_message('nordlicht', '1.2.0', $sha), $pluginSig, $priv, OPENSSL_ALGO_SHA256);
$served['themes'][0]['signature'] = base64_encode($pluginSig);
$check(!theme_catalog_install('nordlicht')['ok'], 'eine Plugin-Signatur gilt nicht für ein Theme');
$served['themes'][0]['signature'] = $sig;
$body = file_get_contents($zipPath) . 'x'; // Download wurde verändert
$r = theme_catalog_install('nordlicht');
$check(!$r['ok'] && str_contains($r['error'], 'Prüfsumme'), 'veränderter Download wird abgelehnt');
$body = file_get_contents($zipPath);
$served['themes'][0]['signature'] = '';
set_setting('plugin_require_signature', '1');
$r = theme_catalog_install('nordlicht');
$check(!$r['ok'] && str_contains($r['error'], 'müssen signiert sein'), '„Signatur erforderlich“ lehnt unsignierte Themes ab');
set_setting('plugin_require_signature', '0');
$served['themes'][0]['downloadUrl'] = 'https://192.168.1.5/x.zip';
$check(!theme_catalog_install('nordlicht')['ok'], 'Download von interner Adresse wird abgelehnt');
unset($GLOBALS['deskhub_http_get']);
$rm = function (string $d) use (&$rm) {
foreach (scandir($d) ?: [] as $e) { if ($e === '.' || $e === '..') continue; is_dir("$d/$e") ? $rm("$d/$e") : @unlink("$d/$e"); }
@rmdir($d);
};
$rm($themesDir);
@unlink($zipPath);
@unlink($cnf);
if ($failures) {
fwrite(STDERR, "Fehlgeschlagen:\n - " . implode("\n - ", $failures) . "\n");
exit(1);
}
echo "Theme catalog checks passed.\n";