feat(audit): platform-tools atualizado (12f26fd) e registo de auditoria ligado
- shared/platform-tools: ea3616a → 12f26fd (27 commits). Traz o módulo de audit (f761d99, o pin da frota), «manter sessão iniciada», o registo de segurança da frota (o gancho que o login já tinha passa a funcionar) e a correção do IP do cliente encontrada nesta auditoria (último X-Forwarded-For / X-Real-IP), promovida ao partilhado. client_ip() da app delega nele. - config/functions_audit.php: audit() sobre platform_audit(); tabela audit_log criada no primeiro uso, segredos ocultados, nunca parte a ação. - Registado: login, falha de login, bloqueio por excesso de tentativas, logout, pedidos de recuperação, mudança/definição de palavra-passe, registo e criação de utilizadores, convites, perfil (antes/depois), todas as definições (db_update_setting, com valor anterior), cada passo da lixeira (apagar, restaurar, eliminar de vez) e ações nas mensagens. - Sistema → Auditoria (views/sysadmin/audit.php), só sysadmin. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5e300fc341
commit
0e8b53f620
65 files changed
+5852
-455
No files matched your search
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
// ─── Front controller das páginas de sistema ─────────────────────────────────
|
||||
//
|
||||
// Um só stub por site, em vez de um por página:
|
||||
//
|
||||
// <?php require __DIR__ . '/../shared/platform-tools/router.php'; // public/platform.php
|
||||
//
|
||||
// e cada página partilhada abre em platform.php?tool=<nome>. Uma ferramenta nova
|
||||
// no platform-tools aparece em todos os sites no próximo update do submódulo,
|
||||
// sem ficheiro nenhum a criar do lado de lá.
|
||||
//
|
||||
// O que os stubs faziam à mão vem agora do platform_connect() do site:
|
||||
//
|
||||
// guard quem pode entrar (obrigatório — sem ele isto recusa)
|
||||
// guards guarda própria de uma ferramenta, p. ex. ['tracking' => 'require_admin']
|
||||
// replaced_tools ferramenta que o site substitui por página sua:
|
||||
// ['smtp_status' => 'views/sysadmin/comms.php']
|
||||
// layout ['header' => …, 'navbar' => …, 'footer' => …]
|
||||
// dashboard_url o «Voltar» das páginas (relativo ao base_url, ou absoluto);
|
||||
// em array, por ferramenta — as outras voltam ao índice
|
||||
// migrations_dir onde vivem as migrações, quando não é database/migrations/
|
||||
// sysadmin_tools cartões próprios do índice (array, ou callable que o devolve)
|
||||
// hide_tools cartões por omissão a esconder, p. ex. ['smtp_status']
|
||||
// page_vars variáveis `platform_*` extra de uma página, p. ex.
|
||||
// ['platform_error_log_file' => …]
|
||||
//
|
||||
// Query string (platform.php?tool=backups) e não /platform/backups: um caminho
|
||||
// bonito precisa de regras de reescrita, e a frota tem Apache em Docker, cPanel
|
||||
// e talvez nginx. Isto funciona em todos sem configurar nada.
|
||||
//
|
||||
// A tabela de rotas é uma lista fechada, e cada página continua a fazer as suas
|
||||
// verificações (CSRF, confirmação escrita). O router só despacha e guarda a
|
||||
// porta — nunca inclui um nome vindo do pedido.
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/hub_bootstrap.php'; // $repo_root, $pdo, config/db.php + functions.php
|
||||
require_once __DIR__ . '/functions_connector.php';
|
||||
require_once __DIR__ . '/functions_routes.php';
|
||||
|
||||
$tool = (string) ($_GET['tool'] ?? 'index');
|
||||
$route = platform_routes()[$tool] ?? null;
|
||||
if ($route === null) {
|
||||
http_response_code(404);
|
||||
exit('Ferramenta desconhecida.');
|
||||
}
|
||||
|
||||
$cfg = platform_config();
|
||||
// Uma guarda por ferramenta quando o site a declara (a Origem das visitas é de
|
||||
// admin na fotografia, não só de sysadmin); senão a geral.
|
||||
$guard = $cfg['guards'][$tool] ?? ($cfg['guard'] ?? null);
|
||||
if ($cfg === [] || !is_callable($guard)) {
|
||||
// Falha fechada: sem guarda declarada não há forma de saber quem pode entrar,
|
||||
// e uma página de sistema aberta a qualquer um é pior do que nenhuma.
|
||||
http_response_code(500);
|
||||
exit('Este site ainda não declara uma guarda de sysadmin no platform_connect() — '
|
||||
. 'as páginas de sistema não abrem sem ela.');
|
||||
}
|
||||
$guard();
|
||||
|
||||
// Um site pode ter página própria no lugar de uma partilhada (as Comunicações
|
||||
// da fotografia em vez do Estado SMTP: dois editores da mesma configuração
|
||||
// acabavam por mostrar um o que o outro não gravou). O URL partilhado continua
|
||||
// a chegar lá.
|
||||
if (!empty($cfg['replaced_tools'][$tool])) {
|
||||
header('Location: ' . platform_route_absolute_url((string) $cfg['replaced_tools'][$tool]), true, 302);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!$pdo instanceof PDO && !empty($route['needs_db'])) {
|
||||
http_response_code(503);
|
||||
exit('Esta ferramenta precisa de base de dados, e este site não tem.');
|
||||
}
|
||||
|
||||
// O que os stubs definiam antes de incluir a página.
|
||||
foreach (['header', 'navbar', 'footer'] as $part) {
|
||||
if (!empty($cfg['layout'][$part])) ${'platform_layout_' . $part} = $cfg['layout'][$part];
|
||||
}
|
||||
// Em array é por ferramenta, e as que lá não estão voltam ao índice do Sistema
|
||||
// (é de lá que se chega a elas): ['index' => 'views/admin/users.php'].
|
||||
if (is_array($cfg['dashboard_url'] ?? null)) {
|
||||
$platform_dashboard_url = isset($cfg['dashboard_url'][$tool])
|
||||
? platform_route_absolute_url((string) $cfg['dashboard_url'][$tool])
|
||||
: platform_route_url('index');
|
||||
} elseif (!empty($cfg['dashboard_url'])) {
|
||||
$platform_dashboard_url = platform_route_absolute_url((string) $cfg['dashboard_url']);
|
||||
}
|
||||
if (!empty($cfg['migrations_dir'])) {
|
||||
$GLOBALS['MIGRATIONS_DIR'] = (string) $cfg['migrations_dir'];
|
||||
}
|
||||
foreach ((array) ($cfg['page_vars'] ?? []) as $name => $value) {
|
||||
if (is_string($name) && str_starts_with($name, 'platform_')) ${$name} = $value;
|
||||
}
|
||||
if ($tool === 'index') {
|
||||
$tools = $cfg['sysadmin_tools'] ?? [];
|
||||
$sysadmin_tools = is_callable($tools) ? (array) $tools() : (array) $tools;
|
||||
foreach ($sysadmin_tools as &$card) {
|
||||
if (isset($card['url'])) $card['url'] = platform_route_absolute_url((string) $card['url']);
|
||||
}
|
||||
unset($card);
|
||||
// Sem o .php: o índice compara com str_contains() no URL do cartão, e o do
|
||||
// router é …?tool=smtp_status, não …/smtp_status.php.
|
||||
$platform_hide_tools = array_map(fn($h) => preg_replace('/\.php$/', '', (string) $h), (array) ($cfg['hide_tools'] ?? []));
|
||||
}
|
||||
if (!empty($route['backup_helpers'])) {
|
||||
hub_load_backup_helpers();
|
||||
}
|
||||
|
||||
unset($cfg, $tools, $part, $name, $value);
|
||||
require __DIR__ . '/' . $route['file'];
|
||||
Reference in new issue
Block a user