Upload via GUI (194 Dateien)

This commit is contained in:
2026-10-07 07:32:04 +00:00
parent 3ac5a6c14d
commit d976cb4595
8 changed files with 6244 additions and 6054 deletions
+181 -180
View File
@@ -1,180 +1,181 @@
<?php
/**
* Plugin Name: AeroMC Freunde
* Description: Backend für das Freundesystem des AeroMC Launchers - meldet, wer gerade online/im Spiel ist, verwaltet Freundschaftsanfragen, Einladungen, Kurznachrichten, per Code geteilte Instanzen und Fehlerberichte aus dem Absturz-Assistenten sowie eigene Umhänge (auch HD) aus dem Skin-Editor. Anmeldung über Mojangs Sitzungsprüfung (wie bei Minecraft-Servern) - ohne eigenes Passwort.
* Version: 1.5.1
* Author: M_Viper
*/
if (!defined('ABSPATH')) {
exit; // kein direkter Aufruf der Datei
}
define('AEROMC_FRIENDS_VERSION', '1.5.1');
define('AEROMC_FRIENDS_PRESENCE_TABLE', 'aeromc_presence');
define('AEROMC_FRIENDS_FRIENDS_TABLE', 'aeromc_friends');
define('AEROMC_FRIENDS_INVITES_TABLE', 'aeromc_invites');
define('AEROMC_FRIENDS_SESSIONS_TABLE', 'aeromc_sessions');
define('AEROMC_FRIENDS_SHARES_TABLE', 'aeromc_shares');
define('AEROMC_FRIENDS_MESSAGES_TABLE', 'aeromc_messages');
define('AEROMC_FRIENDS_REPORTS_TABLE', 'aeromc_reports');
define('AEROMC_FRIENDS_CAPES_TABLE', 'aeromc_capes');
// Übergangsmodus: false = Anfragen ohne Anmeldung werden noch angenommen, aber
// nur für Spieler, die sich noch NIE über die neue Anmeldung gemeldet haben
// (ältere Launcher-Versionen funktionieren weiter). Sobald alle Nutzer
// aktualisiert haben, in der wp-config.php auf true setzen:
// define('AEROMC_FRIENDS_REQUIRE_AUTH', true);
if (!defined('AEROMC_FRIENDS_REQUIRE_AUTH')) {
define('AEROMC_FRIENDS_REQUIRE_AUTH', false);
}
/**
* Legt die beiden Tabellen an (bzw. bringt sie bei einem Update auf den
* neuesten Stand - dbDelta erkennt bestehende Tabellen/Spalten selbst).
*/
function aeromc_friends_install() {
global $wpdb;
$charset_collate = $wpdb->get_charset_collate();
$presence_table = $wpdb->prefix . AEROMC_FRIENDS_PRESENCE_TABLE;
$friends_table = $wpdb->prefix . AEROMC_FRIENDS_FRIENDS_TABLE;
$invites_table = $wpdb->prefix . AEROMC_FRIENDS_INVITES_TABLE;
$sessions_table = $wpdb->prefix . AEROMC_FRIENDS_SESSIONS_TABLE;
$shares_table = $wpdb->prefix . AEROMC_FRIENDS_SHARES_TABLE;
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
// uuid ohne Bindestriche gespeichert (siehe aeromc_friends_normalize_uuid
// in includes/routes.php) - vermeidet doppelte Zeilen durch
// unterschiedliche Schreibweisen derselben UUID
$sql_presence = "CREATE TABLE $presence_table (
uuid varchar(32) NOT NULL,
username varchar(32) NOT NULL,
instance_name varchar(128) DEFAULT NULL,
server_label varchar(128) DEFAULT NULL,
mc_version varchar(32) DEFAULT NULL,
last_seen datetime NOT NULL,
PRIMARY KEY (uuid)
) $charset_collate;";
dbDelta($sql_presence);
$sql_friends = "CREATE TABLE $friends_table (
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
uuid_a varchar(32) NOT NULL,
uuid_b varchar(32) NOT NULL,
status varchar(20) NOT NULL DEFAULT 'pending',
created_at datetime NOT NULL,
PRIMARY KEY (id),
KEY uuid_a (uuid_a),
KEY uuid_b (uuid_b)
) $charset_collate;";
dbDelta($sql_friends);
// Einladungen "Komm auf meinen Server" - gelten nur ein paar Minuten
$sql_invites = "CREATE TABLE $invites_table (
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
from_uuid varchar(32) NOT NULL,
to_uuid varchar(32) NOT NULL,
server varchar(128) NOT NULL,
mc_version varchar(32) DEFAULT NULL,
created_at datetime NOT NULL,
PRIMARY KEY (id),
KEY to_uuid (to_uuid)
) $charset_collate;";
dbDelta($sql_invites);
// Anmelde-Sitzungen: nur der SHA-256 des Tokens wird gespeichert
$sql_sessions = "CREATE TABLE $sessions_table (
token_hash char(64) NOT NULL,
uuid varchar(32) NOT NULL,
username varchar(32) NOT NULL,
created_at datetime NOT NULL,
last_used datetime NOT NULL,
PRIMARY KEY (token_hash),
KEY uuid (uuid)
) $charset_collate;";
dbDelta($sql_sessions);
// per Code geteilte Instanzen (Mod-Liste, keine Dateien)
$sql_shares = "CREATE TABLE $shares_table (
code varchar(9) NOT NULL,
uuid varchar(32) NOT NULL,
manifest longtext NOT NULL,
created_at datetime NOT NULL,
PRIMARY KEY (code),
KEY uuid (uuid)
) $charset_collate;";
dbDelta($sql_shares);
// Kurznachrichten zwischen Freunden (werden nach einigen Tagen gelöscht)
$messages_table = $wpdb->prefix . AEROMC_FRIENDS_MESSAGES_TABLE;
$sql_messages = "CREATE TABLE $messages_table (
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
from_uuid varchar(32) NOT NULL,
to_uuid varchar(32) NOT NULL,
text varchar(400) NOT NULL,
image_url varchar(255) DEFAULT NULL,
created_at datetime NOT NULL,
read_at datetime DEFAULT NULL,
PRIMARY KEY (id),
KEY to_uuid (to_uuid),
KEY from_uuid (from_uuid)
) $charset_collate;";
dbDelta($sql_messages);
// Fehlerberichte aus dem Absturz-Assistenten (Ansicht: Werkzeuge → AeroMC Fehlerberichte)
$reports_table = $wpdb->prefix . AEROMC_FRIENDS_REPORTS_TABLE;
$sql_reports = "CREATE TABLE $reports_table (
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
created_at datetime NOT NULL,
uuid varchar(32) DEFAULT NULL,
username varchar(32) DEFAULT NULL,
launcher_version varchar(32) DEFAULT NULL,
mc_version varchar(32) DEFAULT NULL,
loader varchar(32) DEFAULT NULL,
loader_version varchar(64) DEFAULT NULL,
system varchar(128) DEFAULT NULL,
funde text,
mods longtext,
bericht longtext,
kommentar text,
status varchar(20) NOT NULL DEFAULT 'neu',
PRIMARY KEY (id),
KEY status (status)
) $charset_collate;";
dbDelta($sql_reports);
// eigene Umhänge (Bild liegt unter wp-content/uploads/aeromc-umhaenge)
$capes_table = $wpdb->prefix . AEROMC_FRIENDS_CAPES_TABLE;
$sql_capes = "CREATE TABLE $capes_table (
uuid varchar(32) NOT NULL,
username varchar(32) NOT NULL,
datei varchar(64) NOT NULL DEFAULT '',
breite smallint(5) unsigned NOT NULL DEFAULT 0,
hoehe smallint(5) unsigned NOT NULL DEFAULT 0,
hash varchar(40) NOT NULL DEFAULT '',
status varchar(16) NOT NULL DEFAULT 'aktiv',
updated_at datetime NOT NULL,
PRIMARY KEY (uuid),
KEY status (status)
) $charset_collate;";
dbDelta($sql_capes);
update_option('aeromc_friends_db_version', AEROMC_FRIENDS_VERSION);
}
register_activation_hook(__FILE__, 'aeromc_friends_install');
// Beim Ersetzen der Plugin-Dateien (Update per Upload) läuft der
// Aktivierungs-Hook NICHT erneut - neue Spalten/Tabellen deshalb anlegen,
// sobald die gespeicherte DB-Version nicht mehr zur Plugin-Version passt.
add_action('plugins_loaded', function () {
if (get_option('aeromc_friends_db_version') !== AEROMC_FRIENDS_VERSION) {
aeromc_friends_install();
}
});
require_once plugin_dir_path(__FILE__) . 'includes/routes.php';
require_once plugin_dir_path(__FILE__) . 'includes/capes.php';
if (is_admin()) {
require_once plugin_dir_path(__FILE__) . 'includes/admin.php';
}
<?php
/**
* Plugin Name: AeroMC Freunde
* Description: Backend für das Freundesystem des AeroMC Launchers - meldet, wer gerade online/im Spiel ist, verwaltet Freundschaftsanfragen, Einladungen, Kurznachrichten, per Code geteilte Instanzen und Fehlerberichte aus dem Absturz-Assistenten sowie eigene Umhänge (auch HD) aus dem Skin-Editor. Anmeldung über Mojangs Sitzungsprüfung (wie bei Minecraft-Servern) - ohne eigenes Passwort.
* Version: 1.5.5
* Author: M_Viper
*/
if (!defined('ABSPATH')) {
exit; // kein direkter Aufruf der Datei
}
define('AEROMC_FRIENDS_VERSION', '1.5.5');
define('AEROMC_FRIENDS_PRESENCE_TABLE', 'aeromc_presence');
define('AEROMC_FRIENDS_FRIENDS_TABLE', 'aeromc_friends');
define('AEROMC_FRIENDS_INVITES_TABLE', 'aeromc_invites');
define('AEROMC_FRIENDS_SESSIONS_TABLE', 'aeromc_sessions');
define('AEROMC_FRIENDS_SHARES_TABLE', 'aeromc_shares');
define('AEROMC_FRIENDS_MESSAGES_TABLE', 'aeromc_messages');
define('AEROMC_FRIENDS_REPORTS_TABLE', 'aeromc_reports');
define('AEROMC_FRIENDS_CAPES_TABLE', 'aeromc_capes');
// Anmeldung ist Pflicht (seit Plugin 1.5.5): Jede Anfrage "als Spieler X" braucht
// das Sitzungs-Token aus der Mojang-Anmeldung (Launcher ab 1.1.5). Launcher vor
// 1.1.5 können das Freundesystem damit nicht mehr nutzen. Nur falls doch noch
// nötig, lässt sich der alte Übergangsmodus in der wp-config.php zurückholen:
// define('AEROMC_FRIENDS_REQUIRE_AUTH', false);
if (!defined('AEROMC_FRIENDS_REQUIRE_AUTH')) {
define('AEROMC_FRIENDS_REQUIRE_AUTH', true);
}
/**
* Legt die beiden Tabellen an (bzw. bringt sie bei einem Update auf den
* neuesten Stand - dbDelta erkennt bestehende Tabellen/Spalten selbst).
*/
function aeromc_friends_install() {
global $wpdb;
$charset_collate = $wpdb->get_charset_collate();
$presence_table = $wpdb->prefix . AEROMC_FRIENDS_PRESENCE_TABLE;
$friends_table = $wpdb->prefix . AEROMC_FRIENDS_FRIENDS_TABLE;
$invites_table = $wpdb->prefix . AEROMC_FRIENDS_INVITES_TABLE;
$sessions_table = $wpdb->prefix . AEROMC_FRIENDS_SESSIONS_TABLE;
$shares_table = $wpdb->prefix . AEROMC_FRIENDS_SHARES_TABLE;
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
// uuid ohne Bindestriche gespeichert (siehe aeromc_friends_normalize_uuid
// in includes/routes.php) - vermeidet doppelte Zeilen durch
// unterschiedliche Schreibweisen derselben UUID
$sql_presence = "CREATE TABLE $presence_table (
uuid varchar(32) NOT NULL,
username varchar(32) NOT NULL,
instance_name varchar(128) DEFAULT NULL,
server_label varchar(128) DEFAULT NULL,
mc_version varchar(32) DEFAULT NULL,
last_seen datetime NOT NULL,
PRIMARY KEY (uuid)
) $charset_collate;";
dbDelta($sql_presence);
$sql_friends = "CREATE TABLE $friends_table (
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
uuid_a varchar(32) NOT NULL,
uuid_b varchar(32) NOT NULL,
status varchar(20) NOT NULL DEFAULT 'pending',
created_at datetime NOT NULL,
PRIMARY KEY (id),
KEY uuid_a (uuid_a),
KEY uuid_b (uuid_b)
) $charset_collate;";
dbDelta($sql_friends);
// Einladungen "Komm auf meinen Server" - gelten nur ein paar Minuten
$sql_invites = "CREATE TABLE $invites_table (
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
from_uuid varchar(32) NOT NULL,
to_uuid varchar(32) NOT NULL,
server varchar(128) NOT NULL,
mc_version varchar(32) DEFAULT NULL,
created_at datetime NOT NULL,
PRIMARY KEY (id),
KEY to_uuid (to_uuid)
) $charset_collate;";
dbDelta($sql_invites);
// Anmelde-Sitzungen: nur der SHA-256 des Tokens wird gespeichert
$sql_sessions = "CREATE TABLE $sessions_table (
token_hash char(64) NOT NULL,
uuid varchar(32) NOT NULL,
username varchar(32) NOT NULL,
created_at datetime NOT NULL,
last_used datetime NOT NULL,
PRIMARY KEY (token_hash),
KEY uuid (uuid)
) $charset_collate;";
dbDelta($sql_sessions);
// per Code geteilte Instanzen (Mod-Liste, keine Dateien)
$sql_shares = "CREATE TABLE $shares_table (
code varchar(9) NOT NULL,
uuid varchar(32) NOT NULL,
manifest longtext NOT NULL,
created_at datetime NOT NULL,
PRIMARY KEY (code),
KEY uuid (uuid)
) $charset_collate;";
dbDelta($sql_shares);
// Kurznachrichten zwischen Freunden (werden nach einigen Tagen gelöscht)
$messages_table = $wpdb->prefix . AEROMC_FRIENDS_MESSAGES_TABLE;
$sql_messages = "CREATE TABLE $messages_table (
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
from_uuid varchar(32) NOT NULL,
to_uuid varchar(32) NOT NULL,
text varchar(400) NOT NULL,
image_url varchar(255) DEFAULT NULL,
created_at datetime NOT NULL,
read_at datetime DEFAULT NULL,
PRIMARY KEY (id),
KEY to_uuid (to_uuid),
KEY from_uuid (from_uuid)
) $charset_collate;";
dbDelta($sql_messages);
// Fehlerberichte aus dem Absturz-Assistenten (Ansicht: Werkzeuge → AeroMC Fehlerberichte)
$reports_table = $wpdb->prefix . AEROMC_FRIENDS_REPORTS_TABLE;
$sql_reports = "CREATE TABLE $reports_table (
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
created_at datetime NOT NULL,
uuid varchar(32) DEFAULT NULL,
username varchar(32) DEFAULT NULL,
launcher_version varchar(32) DEFAULT NULL,
mc_version varchar(32) DEFAULT NULL,
loader varchar(32) DEFAULT NULL,
loader_version varchar(64) DEFAULT NULL,
system varchar(128) DEFAULT NULL,
funde text,
mods longtext,
bericht longtext,
kommentar text,
status varchar(20) NOT NULL DEFAULT 'neu',
PRIMARY KEY (id),
KEY status (status)
) $charset_collate;";
dbDelta($sql_reports);
// eigene Umhänge (Bild liegt unter wp-content/uploads/aeromc-umhaenge)
$capes_table = $wpdb->prefix . AEROMC_FRIENDS_CAPES_TABLE;
$sql_capes = "CREATE TABLE $capes_table (
uuid varchar(32) NOT NULL,
username varchar(32) NOT NULL,
datei varchar(64) NOT NULL DEFAULT '',
breite smallint(5) unsigned NOT NULL DEFAULT 0,
hoehe smallint(5) unsigned NOT NULL DEFAULT 0,
hash varchar(40) NOT NULL DEFAULT '',
status varchar(16) NOT NULL DEFAULT 'aktiv',
updated_at datetime NOT NULL,
PRIMARY KEY (uuid),
KEY status (status)
) $charset_collate;";
dbDelta($sql_capes);
update_option('aeromc_friends_db_version', AEROMC_FRIENDS_VERSION);
}
register_activation_hook(__FILE__, 'aeromc_friends_install');
// Beim Ersetzen der Plugin-Dateien (Update per Upload) läuft der
// Aktivierungs-Hook NICHT erneut - neue Spalten/Tabellen deshalb anlegen,
// sobald die gespeicherte DB-Version nicht mehr zur Plugin-Version passt.
add_action('plugins_loaded', function () {
if (get_option('aeromc_friends_db_version') !== AEROMC_FRIENDS_VERSION) {
aeromc_friends_install();
}
});
require_once plugin_dir_path(__FILE__) . 'includes/routes.php';
require_once plugin_dir_path(__FILE__) . 'includes/capes.php';
require_once plugin_dir_path(__FILE__) . 'includes/signaturen.php';
if (is_admin()) {
require_once plugin_dir_path(__FILE__) . 'includes/admin.php';
}
@@ -0,0 +1,246 @@
<?php
/**
* Update-Signaturen für den AeroMC Launcher
* -----------------------------------------
* Der Launcher installiert ein Update nur, wenn es mit dem Release-Schlüssel des
* Entwicklers unterschrieben ist (Ed25519). Die Unterschriften liegen hier statt
* im Gitea-Release; die EXE selbst kommt weiter aus Gitea.
*
* GET /wp-json/aeromc/v1/update-signatur?datei=AeroMC-Setup.exe&version=1.2.2
* öffentlich – liefert die Unterschrift (sie ist nicht geheim)
* POST /wp-json/aeromc/v1/update-signatur
* nur Administratoren (Anwendungspasswort) – vom Build-Skript
* scripts/sign-release.js automatisch aufgerufen
* GET /wp-json/aeromc/v1/update-signatur/zugang
* prüft beim Einrichten, ob der Zugang stimmt, und sagt sonst, woran es scheitert
*
* Wichtig: Der geheime Schlüssel liegt NIE auf diesem Server. Hier wird nur
* mit dem öffentlichen Schlüssel geprüft, dass eine hochgeladene Unterschrift
* echt ist – selbst ein übernommener Admin-Zugang kann keine gültige erzeugen.
*/
if (!defined('ABSPATH')) {
exit;
}
define('AEROMC_SIG_OPTION', 'aeromc_update_signaturen');
define('AEROMC_SIG_FORMAT', 'AeroMC-Update/1');
// öffentlicher Release-Schlüssel (identisch mit src/update-schluessel.pem im Launcher)
define('AEROMC_SIG_SCHLUESSEL', 'MCowBQYDK2VwAyEAXxt1cCntDQzvej4hjyVZt6Onj6Uv3hSlneJaOxxmsds=');
define('AEROMC_SIG_DATEIEN', ['AeroMC-Setup.exe', 'AeroMC-Portable.exe']);
define('AEROMC_SIG_MAX', 40); // so viele Einträge werden aufgehoben (älteste fliegen raus)
add_action('rest_api_init', function () {
register_rest_route('aeromc/v1', '/update-signatur', [
[
'methods' => 'GET',
'callback' => 'aeromc_sig_route_get',
'permission_callback' => '__return_true',
],
[
'methods' => 'POST',
'callback' => 'aeromc_sig_route_post',
'permission_callback' => 'aeromc_sig_ist_admin',
],
]);
// Zugangsprüfung mit Diagnose: sagt dem Build-Skript genau, woran eine
// Anmeldung scheitert (Header kommt nicht an, falsches Passwort, Rolle fehlt …)
register_rest_route('aeromc/v1', '/update-signatur/zugang', [
'methods' => 'GET',
'callback' => 'aeromc_sig_route_zugang',
'permission_callback' => '__return_true',
]);
});
/*
* Eigene Rolle "AeroMC Release": darf NUR Signaturen hochladen (sonst nichts
* außer dem eigenen Profil). Für das Build-Skript gedacht – so muss kein
* Administrator-Zugang auf dem Entwickler-PC liegen.
*/
define('AEROMC_SIG_RECHT', 'aeromc_signaturen_hochladen');
add_action('init', function () {
if (!get_role('aeromc_release')) {
add_role('aeromc_release', 'AeroMC Release', ['read' => true, AEROMC_SIG_RECHT => true]);
}
});
function aeromc_sig_route_zugang() {
$authorization = !empty($_SERVER['HTTP_AUTHORIZATION']) || !empty($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])
|| !empty($_SERVER['PHP_AUTH_USER']);
$eigener = !empty($_SERVER['HTTP_X_AEROMC_AUTH']);
$grund = '';
$user = is_user_logged_in() ? wp_get_current_user() : null;
if (!$user && $eigener) {
$roh = base64_decode((string) $_SERVER['HTTP_X_AEROMC_AUTH'], true);
if ($roh && strpos($roh, ':') !== false) {
list($benutzer, $passwort) = explode(':', $roh, 2);
$r = wp_authenticate_application_password(null, $benutzer, $passwort);
if ($r instanceof WP_User) {
$user = $r;
wp_set_current_user($r->ID);
} elseif (is_wp_error($r)) {
$grund = $r->get_error_code();
} else {
$grund = 'nicht_geprueft';
}
} else {
$grund = 'kopf_ungueltig';
}
}
$darf = $user && (user_can($user, AEROMC_SIG_RECHT) || user_can($user, 'manage_options'));
return new WP_REST_Response([
'ok' => (bool) $darf,
'version' => AEROMC_FRIENDS_VERSION,
'authorization_kommt_an' => $authorization,
'eigener_kopf_kommt_an' => $eigener,
'anwendungspasswoerter' => function_exists('wp_is_application_passwords_available') && wp_is_application_passwords_available(),
'benutzer' => $user ? $user->user_login : null,
'darf_hochladen' => (bool) $darf,
'grund' => $grund,
], $darf ? 200 : 401);
}
function aeromc_sig_ist_admin() {
aeromc_sig_anmelden_ueber_kopf();
return current_user_can(AEROMC_SIG_RECHT) || current_user_can('manage_options');
}
/*
* Manche Server (Apache mit FastCGI/PHP-FPM, Proxys) geben den Header
* "Authorization" nicht an PHP weiter – die normale Anmeldung per
* Anwendungspasswort kommt dann nie an. Das Build-Skript schickt dieselben
* Daten deshalb zusätzlich als "X-AeroMC-Auth: base64(benutzer:passwort)".
* Geprüft wird mit der WordPress-eigenen Funktion für Anwendungspasswörter –
* das normale Login-Passwort wird hier NICHT angenommen.
*/
function aeromc_sig_anmelden_ueber_kopf() {
if (is_user_logged_in()) {
return;
}
$kopf = isset($_SERVER['HTTP_X_AEROMC_AUTH']) ? (string) $_SERVER['HTTP_X_AEROMC_AUTH'] : '';
$roh = $kopf !== '' ? base64_decode($kopf, true) : false;
if (!$roh || strpos($roh, ':') === false) {
return;
}
list($benutzer, $passwort) = explode(':', $roh, 2);
$user = wp_authenticate_application_password(null, $benutzer, $passwort);
if ($user instanceof WP_User) {
wp_set_current_user($user->ID);
}
}
function aeromc_sig_version_norm($v) {
return preg_replace('/^v/i', '', trim((string) $v));
}
function aeromc_sig_schluessel_von($datei, $version) {
return $datei . '|' . aeromc_sig_version_norm($version);
}
function aeromc_sig_alle() {
$alle = get_option(AEROMC_SIG_OPTION, []);
return is_array($alle) ? $alle : [];
}
function aeromc_sig_route_get($request) {
$datei = (string) $request->get_param('datei');
$version = aeromc_sig_version_norm($request->get_param('version'));
$alle = aeromc_sig_alle();
$eintrag = $alle[aeromc_sig_schluessel_von($datei, $version)] ?? null;
if (!$eintrag) {
return new WP_REST_Response(['ok' => false, 'message' => 'Keine Signatur für diese Datei/Version.'], 404);
}
unset($eintrag['hochgeladen']);
$antwort = new WP_REST_Response($eintrag, 200);
$antwort->header('Cache-Control', 'no-store');
return $antwort;
}
/**
* Prüft Aufbau und – sofern PHP libsodium mitbringt – die Unterschrift selbst.
* Gibt null zurück, wenn alles stimmt, sonst die Fehlermeldung.
*/
function aeromc_sig_pruefe(array $s) {
if (($s['format'] ?? '') !== AEROMC_SIG_FORMAT) return 'Unbekanntes Format.';
if (!in_array($s['datei'] ?? '', AEROMC_SIG_DATEIEN, true)) return 'Unbekannter Dateiname.';
if (!preg_match('/^\d+\.\d+\.\d+(-[0-9A-Za-z.\-]+)?$/', aeromc_sig_version_norm($s['version'] ?? ''))) return 'Ungültige Version.';
if (!preg_match('/^[0-9a-f]{64}$/', (string) ($s['sha256'] ?? ''))) return 'Ungültige Prüfsumme.';
$unterschrift = base64_decode((string) ($s['signatur'] ?? ''), true);
if ($unterschrift === false || strlen($unterschrift) !== 64) return 'Ungültige Signatur.';
if (function_exists('sodium_crypto_sign_verify_detached')) {
$der = base64_decode(AEROMC_SIG_SCHLUESSEL);
$oeffentlich = substr($der, -32); // SPKI-Hülle (12 Byte) + 32 Byte Schlüssel
$text = implode("\n", [AEROMC_SIG_FORMAT, $s['datei'], aeromc_sig_version_norm($s['version']), $s['sha256']]);
if (!sodium_crypto_sign_verify_detached($unterschrift, $text, $oeffentlich)) {
return 'Signatur passt nicht zum Release-Schlüssel.';
}
}
return null;
}
function aeromc_sig_route_post($request) {
$s = $request->get_json_params();
if (!is_array($s)) {
return new WP_REST_Response(['ok' => false, 'message' => 'Kein JSON erhalten.'], 400);
}
$s = [
'format' => (string) ($s['format'] ?? ''),
'datei' => (string) ($s['datei'] ?? ''),
'version' => aeromc_sig_version_norm($s['version'] ?? ''),
'sha256' => strtolower((string) ($s['sha256'] ?? '')),
'signatur' => (string) ($s['signatur'] ?? ''),
];
$fehler = aeromc_sig_pruefe($s);
if ($fehler) {
return new WP_REST_Response(['ok' => false, 'message' => $fehler], 400);
}
$alle = aeromc_sig_alle();
$s['hochgeladen'] = current_time('mysql');
$alle[aeromc_sig_schluessel_von($s['datei'], $s['version'])] = $s;
// nur die neuesten Einträge behalten
uasort($alle, function ($a, $b) { return strcmp($b['hochgeladen'], $a['hochgeladen']); });
$alle = array_slice($alle, 0, AEROMC_SIG_MAX, true);
update_option(AEROMC_SIG_OPTION, $alle, false);
return ['ok' => true, 'datei' => $s['datei'], 'version' => $s['version'],
'geprueft' => function_exists('sodium_crypto_sign_verify_detached')];
}
// Übersicht im Admin-Menü (unter "AeroMC Fehler")
add_action('admin_menu', function () {
add_submenu_page('aeromc-reports', 'AeroMC Update-Signaturen', 'Update-Signaturen', 'manage_options',
'aeromc-signaturen', 'aeromc_sig_admin_seite');
}, 20);
function aeromc_sig_admin_seite() {
if (!current_user_can('manage_options')) {
return;
}
if (isset($_POST['aeromc_sig_loeschen']) && check_admin_referer('aeromc_signaturen')) {
$alle = aeromc_sig_alle();
unset($alle[(string) wp_unslash($_POST['aeromc_sig_loeschen'])]);
update_option(AEROMC_SIG_OPTION, $alle, false);
echo '<div class="notice notice-success"><p>Signatur entfernt.</p></div>';
}
echo '<div class="wrap"><h1>AeroMC Update-Signaturen</h1>';
echo '<p>Diese Unterschriften lädt das Build-Skript automatisch hoch. Der Launcher installiert ein Update nur, '
. 'wenn hier eine passende, gültige Signatur liegt. Sie sind nicht geheim – der geheime Schlüssel bleibt auf dem Entwickler-PC.</p>';
if (!function_exists('sodium_crypto_sign_verify_detached')) {
echo '<div class="notice notice-warning"><p>PHP hat kein libsodium – hochgeladene Signaturen werden hier nicht gegengeprüft '
. '(der Launcher prüft sie trotzdem selbst).</p></div>';
}
$alle = aeromc_sig_alle();
if (!$alle) {
echo '<p><em>Noch keine Signaturen vorhanden.</em></p></div>';
return;
}
echo '<table class="widefat striped"><thead><tr><th>Datei</th><th>Version</th><th>SHA-256</th><th>Hochgeladen</th><th></th></tr></thead><tbody>';
foreach ($alle as $schl => $s) {
echo '<tr><td>' . esc_html($s['datei']) . '</td><td>' . esc_html($s['version']) . '</td>'
. '<td><code>' . esc_html(substr($s['sha256'], 0, 16)) . '…</code></td><td>' . esc_html($s['hochgeladen']) . '</td><td>'
. '<form method="post" onsubmit="return confirm(\'Signatur wirklich entfernen? Dann bietet der Launcher dieses Update nicht mehr an.\');">';
wp_nonce_field('aeromc_signaturen');
echo '<input type="hidden" name="aeromc_sig_loeschen" value="' . esc_attr($schl) . '" />'
. '<button class="button button-link-delete">Entfernen</button></form></td></tr>';
}
echo '</tbody></table></div>';
}