fix(core): los ganchos de ciclo de vida de plugins funcionan en las webs locales
_pluginLifecycle validaba el token SOLO contra el WS central, que unicamente conoce webs de produccion: en una copia local de Forge (standalone o de test) el token nunca le valida y instalar, desinstalar, activar o desactivar un plugin moria con "Token no valido", un mensaje que no tenia nada que ver con el token. Segunda via para esas copias: si el central rechaza y la web tiene .acai con mode != production, se compara el par token+tokenHash recibido con el que la propia web guarda en su .acai (hash_equals). No es un bypass — sin .acai, en produccion o sin coincidencia exacta no autoriza nada —, y el secreto es el mismo que Forge ya usa para hablar con la web. Probado en superadmin/claude-es-mi-amigo: con el token del .acai el gancho corre (ok:true); con un token falso o vacio, 403 como siempre. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
0bbf053703
commit
a9ecad4ee1
@@ -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() {
|
function _pluginLifecycle() {
|
||||||
// Fase 0 (2026-09-12): ejecuta los ganchos de ciclo de vida de un plugin
|
// 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
|
// (install/uninstall/enable/disable/status) DENTRO de la web. Unico punto
|
||||||
// de entrada para Forge y para el CMS legacy. Misma autenticacion que coreUpdate.
|
// de entrada para Forge y para el CMS legacy. Misma autenticacion que coreUpdate.
|
||||||
$datosAuth = json_decode(file_get_contents('php://input'), true);
|
$datosAuth = json_decode(file_get_contents('php://input'), true);
|
||||||
CocoWS::setToken(@$datosAuth["token"]);
|
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]]));
|
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.
|
// Solo body JSON (como _saveFile): el token no debe viajar en la query string.
|
||||||
|
|||||||
Reference in New Issue
Block a user