diff --git a/cms/lib/handlers/handler_ws.php b/cms/lib/handlers/handler_ws.php index faaa35f..ada5108 100755 --- a/cms/lib/handlers/handler_ws.php +++ b/cms/lib/handlers/handler_ws.php @@ -399,13 +399,45 @@ function _renameFile() { } +/** + * Segunda via de autenticacion del ciclo de vida, para las copias LOCALES de + * Forge (standalone o de test). El WS central solo conoce webs de produccion: + * el token de una web sin dominio real nunca le valida, asi que los ganchos + * (install/uninstall/enable/disable) quedaban inservibles ahi con un + * "Token no valido" que no tenia nada que ver con el token. + * + * No es un bypass: se compara el par token+tokenHash recibido con el que la + * propia web guarda en su .acai, que es exactamente el que Forge envia. Sin + * .acai (produccion), con mode=production, o sin coincidencia exacta, no + * autoriza nada. + */ +function _lifecycleTokenLocalValido($datosAuth) { + $acaiPath = __DIR__ . "/../../../.acai"; + if (!is_file($acaiPath)) { + return false; + } + $acai = @json_decode(@file_get_contents($acaiPath), true); + if (!is_array($acai) || @$acai["mode"] === "production") { + return false; + } + $token = (string) @$datosAuth["token"]; + $hash = (string) @$datosAuth["tokenHash"]; + $tokenLocal = (string) @$acai["token"]; + $hashLocal = (string) @$acai["tokenHash"]; + if ($token === "" || $hash === "" || $tokenLocal === "" || $hashLocal === "") { + return false; + } + return hash_equals($tokenLocal, $token) && hash_equals($hashLocal, $hash); +} + function _pluginLifecycle() { // Fase 0 (2026-09-12): ejecuta los ganchos de ciclo de vida de un plugin // (install/uninstall/enable/disable/status) DENTRO de la web. Unico punto // de entrada para Forge y para el CMS legacy. Misma autenticacion que coreUpdate. $datosAuth = json_decode(file_get_contents('php://input'), true); CocoWS::setToken(@$datosAuth["token"]); - if (!CocoWS::validateUploadToken(@$datosAuth["tokenHash"])) { + if (!CocoWS::validateUploadToken(@$datosAuth["tokenHash"]) + && !_lifecycleTokenLocalValido($datosAuth)) { die(json_encode(['error' => ['message' => 'Token no vĂ¡lido', 'code' => 403]])); } // Solo body JSON (como _saveFile): el token no debe viajar en la query string.