' . '

401 — Token richiesto

Accedi con ?t=Nis2Test2026

'; exit; } // ── Router ──────────────────────────────────────────────────────────────────── $path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); $base = rtrim(dirname($_SERVER['SCRIPT_NAME']), '/'); $slug = ltrim(substr($path, strlen($base)), '/'); if ($slug === 'events') { serveEvents(); exit; } if ($slug === 'status') { serveStatus(); exit; } if ($slug === 'db-stats') { serveDbStats(); exit; } serveUI(); exit; // ── Commands ────────────────────────────────────────────────────────────────── function getCommands(): array { $php = PHP_BINARY ?: 'php'; $root = PROJECT_ROOT; $api = API_BASE; $loginAdmin = "curl -sf -X POST {$api}/auth/login -H 'Content-Type: application/json' -d '{\"email\":\"admin@datacore.demo\",\"password\":\"Demo2026!\"}' 2>/dev/null"; $getToken = "\$({$loginAdmin} | python3 -c \"import sys,json; d=json.load(sys.stdin); print(d.get('data',{}).get('access_token',''))\" 2>/dev/null)"; return [ // ── INFRASTRUCTURE ────────────────────────────────────────────────── 'health' => [ 'label' => 'Health Check — API Status', 'level' => 'infra', 'bash' => "curl -sf {$api}/../api-status.php | python3 -m json.tool", 'cwd' => $root, 'timeout' => 15, 'continue_on_fail' => false, ], // ── L1: AUTH & JWT ────────────────────────────────────────────────── 'l1-auth' => [ 'label' => 'L1 — Auth & JWT', 'level' => 'l1', 'bash' => implode(' && ', [ "echo '━━━ L1.1 Login valido ━━━'", "curl -sf -X POST {$api}/auth/login -H 'Content-Type: application/json' -d '{\"email\":\"admin@datacore.demo\",\"password\":\"Demo2026!\"}' | python3 -m json.tool || echo '[SKIP] utente non trovato'", "echo ''", "echo '━━━ L1.2 Login password errata (401) ━━━'", "curl -sf -o /dev/null -w 'HTTP %{http_code}' -X POST {$api}/auth/login -H 'Content-Type: application/json' -d '{\"email\":\"admin@datacore.demo\",\"password\":\"WRONG\"}'", "echo ''", "echo '━━━ L1.3 /auth/me senza token (401) ━━━'", "curl -sf -o /dev/null -w 'HTTP %{http_code}' {$api}/auth/me", "echo ''", "echo '━━━ L1.4 /auth/me con token valido ━━━'", "TOKEN={$getToken} && [ -n \"\$TOKEN\" ] && curl -sf -H \"Authorization: Bearer \$TOKEN\" {$api}/auth/me | python3 -m json.tool || echo '[SKIP] token non disponibile'", "echo ''", "echo '━━━ L1.5 Rate limiting (5 login rapidi) ━━━'", "for i in 1 2 3 4 5; do curl -sf -o /dev/null -w \"Tentativo \$i → HTTP %{http_code}\\n\" -X POST {$api}/auth/login -H 'Content-Type: application/json' -d '{\"email\":\"ratelimit-test@test.com\",\"password\":\"wrong\"}'; done", "echo '[OK] L1 Auth & JWT completato'", ]), 'cwd' => $root, 'timeout' => 45, 'continue_on_fail' => true, ], // ── L2: MULTI-TENANT ──────────────────────────────────────────────── 'l2-tenant' => [ 'label' => 'L2 — Multi-Tenant Isolation', 'level' => 'l2', 'bash' => implode(' && ', [ "echo '━━━ L2.1 Login utente DataCore ━━━'", "TOKEN1={$getToken} && echo \"Token ottenuto: \${TOKEN1:0:20}...\"", "echo ''", "echo '━━━ L2.2 Organizations dell\'utente ━━━'", "TOKEN={$getToken} && curl -sf -H \"Authorization: Bearer \$TOKEN\" {$api}/organizations/list | python3 -m json.tool", "echo ''", "echo '━━━ L2.3 Accesso org corrente ━━━'", "TOKEN={$getToken} && ORG_ID=\$(curl -sf -H \"Authorization: Bearer \$TOKEN\" {$api}/organizations/list 2>/dev/null | python3 -c \"import sys,json; d=json.load(sys.stdin); orgs=d.get('data',[]); print(orgs[0]['id'] if orgs else '')\" 2>/dev/null) && [ -n \"\$ORG_ID\" ] && curl -sf -H \"Authorization: Bearer \$TOKEN\" -H \"X-Organization-Id: \$ORG_ID\" {$api}/organizations/current | python3 -m json.tool || echo '[SKIP] nessuna org trovata'", "echo ''", "echo '━━━ L2.4 Dashboard overview con org ━━━'", "TOKEN={$getToken} && ORG_ID=\$(curl -sf -H \"Authorization: Bearer \$TOKEN\" {$api}/organizations/list 2>/dev/null | python3 -c \"import sys,json; d=json.load(sys.stdin); orgs=d.get('data',[]); print(orgs[0]['id'] if orgs else '')\" 2>/dev/null) && [ -n \"\$ORG_ID\" ] && curl -sf -H \"Authorization: Bearer \$TOKEN\" -H \"X-Organization-Id: \$ORG_ID\" {$api}/dashboard/overview | python3 -m json.tool || echo '[SKIP]'", "echo '[OK] L2 Multi-Tenant completato'", ]), 'cwd' => $root, 'timeout' => 60, 'continue_on_fail' => true, ], // ── L3: COMPLIANCE CORE ───────────────────────────────────────────── 'l3-compliance' => [ 'label' => 'L3 — Compliance Core', 'level' => 'l3', 'bash' => implode(' && ', [ "echo '━━━ L3.1 Compliance score ━━━'", "TOKEN={$getToken} && ORG_ID=\$(curl -sf -H \"Authorization: Bearer \$TOKEN\" {$api}/organizations/list 2>/dev/null | python3 -c \"import sys,json; d=json.load(sys.stdin); orgs=d.get('data',[]); print(orgs[0]['id'] if orgs else '')\" 2>/dev/null) && [ -n \"\$ORG_ID\" ] && curl -sf -H \"Authorization: Bearer \$TOKEN\" -H \"X-Organization-Id: \$ORG_ID\" {$api}/dashboard/compliance-score | python3 -m json.tool || echo '[SKIP]'", "echo ''", "echo '━━━ L3.2 Risk matrix ━━━'", "TOKEN={$getToken} && ORG_ID=\$(curl -sf -H \"Authorization: Bearer \$TOKEN\" {$api}/organizations/list 2>/dev/null | python3 -c \"import sys,json; d=json.load(sys.stdin); orgs=d.get('data',[]); print(orgs[0]['id'] if orgs else '')\" 2>/dev/null) && [ -n \"\$ORG_ID\" ] && curl -sf -H \"Authorization: Bearer \$TOKEN\" -H \"X-Organization-Id: \$ORG_ID\" {$api}/risks/matrix | python3 -m json.tool || echo '[SKIP]'", "echo ''", "echo '━━━ L3.3 Audit controls (Art.21) ━━━'", "TOKEN={$getToken} && ORG_ID=\$(curl -sf -H \"Authorization: Bearer \$TOKEN\" {$api}/organizations/list 2>/dev/null | python3 -c \"import sys,json; d=json.load(sys.stdin); orgs=d.get('data',[]); print(orgs[0]['id'] if orgs else '')\" 2>/dev/null) && [ -n \"\$ORG_ID\" ] && curl -sf -H \"Authorization: Bearer \$TOKEN\" -H \"X-Organization-Id: \$ORG_ID\" {$api}/audit/controls | python3 -m json.tool || echo '[SKIP]'", "echo ''", "echo '━━━ L3.4 Incidents list ━━━'", "TOKEN={$getToken} && ORG_ID=\$(curl -sf -H \"Authorization: Bearer \$TOKEN\" {$api}/organizations/list 2>/dev/null | python3 -c \"import sys,json; d=json.load(sys.stdin); orgs=d.get('data',[]); print(orgs[0]['id'] if orgs else '')\" 2>/dev/null) && [ -n \"\$ORG_ID\" ] && curl -sf -H \"Authorization: Bearer \$TOKEN\" -H \"X-Organization-Id: \$ORG_ID\" {$api}/incidents/list | python3 -c \"import sys,json; d=json.load(sys.stdin); items=d.get('data',[]); print(f'Incidenti trovati: {len(items)}')\" || echo '[SKIP]'", "echo ''", "echo '━━━ L3.5 Upcoming deadlines ━━━'", "TOKEN={$getToken} && ORG_ID=\$(curl -sf -H \"Authorization: Bearer \$TOKEN\" {$api}/organizations/list 2>/dev/null | python3 -c \"import sys,json; d=json.load(sys.stdin); orgs=d.get('data',[]); print(orgs[0]['id'] if orgs else '')\" 2>/dev/null) && [ -n \"\$ORG_ID\" ] && curl -sf -H \"Authorization: Bearer \$TOKEN\" -H \"X-Organization-Id: \$ORG_ID\" {$api}/dashboard/upcoming-deadlines | python3 -m json.tool || echo '[SKIP]'", "echo '[OK] L3 Compliance Core completato'", ]), 'cwd' => $root, 'timeout' => 90, 'continue_on_fail' => true, ], // ── L4: B2B & SERVICES ────────────────────────────────────────────── 'l4-b2b' => [ 'label' => 'L4 — B2B & Services API', 'level' => 'l4', 'bash' => implode(' && ', [ "echo '━━━ L4.1 Validate invite (endpoint esistenza) ━━━'", "curl -sf -o /dev/null -w 'POST /auth/validate-invite → HTTP %{http_code}\\n' -X POST {$api}/auth/validate-invite -H 'Content-Type: application/json' -d '{\"invite_token\":\"invalid-test-token\"}'", "echo ''", "echo '━━━ L4.2 Supply chain risk overview ━━━'", "TOKEN={$getToken} && ORG_ID=\$(curl -sf -H \"Authorization: Bearer \$TOKEN\" {$api}/organizations/list 2>/dev/null | python3 -c \"import sys,json; d=json.load(sys.stdin); orgs=d.get('data',[]); print(orgs[0]['id'] if orgs else '')\" 2>/dev/null) && [ -n \"\$ORG_ID\" ] && curl -sf -H \"Authorization: Bearer \$TOKEN\" -H \"X-Organization-Id: \$ORG_ID\" {$api}/supply-chain/risk-overview | python3 -m json.tool || echo '[SKIP]'", "echo ''", "echo '━━━ L4.3 NCR stats ━━━'", "TOKEN={$getToken} && ORG_ID=\$(curl -sf -H \"Authorization: Bearer \$TOKEN\" {$api}/organizations/list 2>/dev/null | python3 -c \"import sys,json; d=json.load(sys.stdin); orgs=d.get('data',[]); print(orgs[0]['id'] if orgs else '')\" 2>/dev/null) && [ -n \"\$ORG_ID\" ] && curl -sf -H \"Authorization: Bearer \$TOKEN\" -H \"X-Organization-Id: \$ORG_ID\" {$api}/ncr/stats | python3 -m json.tool || echo '[SKIP]'", "echo ''", "echo '━━━ L4.4 Training compliance status ━━━'", "TOKEN={$getToken} && ORG_ID=\$(curl -sf -H \"Authorization: Bearer \$TOKEN\" {$api}/organizations/list 2>/dev/null | python3 -c \"import sys,json; d=json.load(sys.stdin); orgs=d.get('data',[]); print(orgs[0]['id'] if orgs else '')\" 2>/dev/null) && [ -n \"\$ORG_ID\" ] && curl -sf -H \"Authorization: Bearer \$TOKEN\" -H \"X-Organization-Id: \$ORG_ID\" {$api}/training/compliance-status | python3 -m json.tool || echo '[SKIP]'", "echo '[OK] L4 B2B & Services completato'", ]), 'cwd' => $root, 'timeout' => 60, 'continue_on_fail' => true, ], // ── L5: EXPORT & REPORTS ──────────────────────────────────────────── 'l5-export' => [ 'label' => 'L5 — Export & Reports', 'level' => 'l5', 'bash' => implode(' && ', [ "echo '━━━ L5.1 Export CSV rischi ━━━'", "TOKEN={$getToken} && ORG_ID=\$(curl -sf -H \"Authorization: Bearer \$TOKEN\" {$api}/organizations/list 2>/dev/null | python3 -c \"import sys,json; d=json.load(sys.stdin); orgs=d.get('data',[]); print(orgs[0]['id'] if orgs else '')\" 2>/dev/null) && [ -n \"\$ORG_ID\" ] && curl -sf -o /tmp/risks_export.csv -w 'CSV rischi: %{size_download} bytes (HTTP %{http_code})' -H \"Authorization: Bearer \$TOKEN\" -H \"X-Organization-Id: \$ORG_ID\" \"{$api}/audit/export?type=risks\" && head -3 /tmp/risks_export.csv || echo '[SKIP]'", "echo ''", "echo '━━━ L5.2 Export CSV incidenti ━━━'", "TOKEN={$getToken} && ORG_ID=\$(curl -sf -H \"Authorization: Bearer \$TOKEN\" {$api}/organizations/list 2>/dev/null | python3 -c \"import sys,json; d=json.load(sys.stdin); orgs=d.get('data',[]); print(orgs[0]['id'] if orgs else '')\" 2>/dev/null) && [ -n \"\$ORG_ID\" ] && curl -sf -o /tmp/incidents_export.csv -w 'CSV incidenti: %{size_download} bytes (HTTP %{http_code})' -H \"Authorization: Bearer \$TOKEN\" -H \"X-Organization-Id: \$ORG_ID\" \"{$api}/audit/export?type=incidents\" || echo '[SKIP]'", "echo ''", "echo '━━━ L5.3 ISO 27001 mapping ━━━'", "TOKEN={$getToken} && ORG_ID=\$(curl -sf -H \"Authorization: Bearer \$TOKEN\" {$api}/organizations/list 2>/dev/null | python3 -c \"import sys,json; d=json.load(sys.stdin); orgs=d.get('data',[]); print(orgs[0]['id'] if orgs else '')\" 2>/dev/null) && [ -n \"\$ORG_ID\" ] && curl -sf -H \"Authorization: Bearer \$TOKEN\" -H \"X-Organization-Id: \$ORG_ID\" {$api}/audit/iso27001-mapping | python3 -c \"import sys,json; d=json.load(sys.stdin); items=d.get('data',[]); print(f'Controlli ISO 27001: {len(items)}')\" || echo '[SKIP]'", "echo ''", "echo '━━━ L5.4 Report audit log (ultimi 10) ━━━'", "TOKEN={$getToken} && ORG_ID=\$(curl -sf -H \"Authorization: Bearer \$TOKEN\" {$api}/organizations/list 2>/dev/null | python3 -c \"import sys,json; d=json.load(sys.stdin); orgs=d.get('data',[]); print(orgs[0]['id'] if orgs else '')\" 2>/dev/null) && [ -n \"\$ORG_ID\" ] && curl -sf -H \"Authorization: Bearer \$TOKEN\" -H \"X-Organization-Id: \$ORG_ID\" {$api}/audit/logs | python3 -c \"import sys,json; d=json.load(sys.stdin); logs=d.get('data',[]); print(f'Log audit: {len(logs)} entries')\" || echo '[SKIP]'", "echo '[OK] L5 Export & Reports completato'", ]), 'cwd' => $root, 'timeout' => 60, 'continue_on_fail' => true, ], // ── L6: AI FEATURES ───────────────────────────────────────────────── 'l6-ai' => [ 'label' => 'L6 — AI Features (Cross-Analysis)', 'level' => 'l6', 'bash' => implode(' && ', [ // Login come consultant (ha accesso a cross-analysis) "echo '━━━ L6.0 Setup: login consultant ━━━'", "TOKEN_CONS=\$(curl -sf -X POST {$api}/auth/login -H 'Content-Type: application/json' -d '{\"email\":\"consultant@nis2agile.demo\",\"password\":\"Demo2026!\"}' 2>/dev/null | python3 -c \"import sys,json; d=json.load(sys.stdin); print(d.get('data',{}).get('access_token',''))\" 2>/dev/null) && [ -n \"\$TOKEN_CONS\" ] && echo \"Token consultant: \${TOKEN_CONS:0:25}...\" || echo '[SKIP] consultant non trovato — eseguire SIM-01 prima'", "echo ''", // L6.1: portfolio (senza AI, solo dati aggregati) "echo '━━━ L6.1 Cross-Analysis Portfolio (no AI) ━━━'", "TOKEN_CONS=\$(curl -sf -X POST {$api}/auth/login -H 'Content-Type: application/json' -d '{\"email\":\"consultant@nis2agile.demo\",\"password\":\"Demo2026!\"}' 2>/dev/null | python3 -c \"import sys,json; d=json.load(sys.stdin); print(d.get('data',{}).get('access_token',''))\" 2>/dev/null) && [ -n \"\$TOKEN_CONS\" ] && curl -sf -H \"Authorization: Bearer \$TOKEN_CONS\" {$api}/cross-analysis/portfolio | python3 -m json.tool || echo '[SKIP] token non disponibile'", "echo ''", // L6.2: history (vuota, ma endpoint deve rispondere 200) "echo '━━━ L6.2 Cross-Analysis History ━━━'", "TOKEN_CONS=\$(curl -sf -X POST {$api}/auth/login -H 'Content-Type: application/json' -d '{\"email\":\"consultant@nis2agile.demo\",\"password\":\"Demo2026!\"}' 2>/dev/null | python3 -c \"import sys,json; d=json.load(sys.stdin); print(d.get('data',{}).get('access_token',''))\" 2>/dev/null) && [ -n \"\$TOKEN_CONS\" ] && curl -sf -H \"Authorization: Bearer \$TOKEN_CONS\" {$api}/cross-analysis/history | python3 -c \"import sys,json; d=json.load(sys.stdin); h=d.get('data',{}).get('history',[]); print(f'History entries: {len(h)} — HTTP OK')\" || echo '[SKIP]'", "echo ''", // L6.3: analyze con domanda breve (chiama AI Anthropic) "echo '━━━ L6.3 Cross-Analysis AI Analyze ━━━'", "TOKEN_CONS=\$(curl -sf -X POST {$api}/auth/login -H 'Content-Type: application/json' -d '{\"email\":\"consultant@nis2agile.demo\",\"password\":\"Demo2026!\"}' 2>/dev/null | python3 -c \"import sys,json; d=json.load(sys.stdin); print(d.get('data',{}).get('access_token',''))\" 2>/dev/null) && [ -n \"\$TOKEN_CONS\" ] && curl -sf -X POST -H \"Authorization: Bearer \$TOKEN_CONS\" -H 'Content-Type: application/json' -d '{\"question\":\"Qual e il livello medio di compliance e quali sono le categorie NIS2 piu deboli nel portfolio?\"}' {$api}/cross-analysis/analyze | python3 -c \"import sys,json; d=json.load(sys.stdin); r=d.get('data',{}); ans=r.get('result',{}).get('answer',''); orgs=r.get('org_count',0); print(f'Org analizzate: {orgs}'); print(f'Risposta AI ({len(ans)} chars): {ans[:300]}...' if len(ans)>300 else f'Risposta: {ans}')\" || echo '[SKIP/ERRORE]'", "echo ''", // L6.4: accesso negato a utente normale (403) "echo '━━━ L6.4 Cross-Analysis 403 per utente non-consultant ━━━'", "TOKEN_EMP=\$(curl -sf -X POST {$api}/auth/login -H 'Content-Type: application/json' -d '{\"email\":\"admin@datacore.demo\",\"password\":\"Demo2026!\"}' 2>/dev/null | python3 -c \"import sys,json; d=json.load(sys.stdin); print(d.get('data',{}).get('access_token',''))\" 2>/dev/null) && [ -n \"\$TOKEN_EMP\" ] && curl -sf -o /dev/null -w 'GET /cross-analysis/portfolio (org_admin) → HTTP %{http_code}\\n' -H \"Authorization: Bearer \$TOKEN_EMP\" {$api}/cross-analysis/portfolio || echo '[SKIP]'", "echo ''", // L6.5: normative feed "echo '━━━ L6.5 Normative Feed ━━━'", "TOKEN={$getToken} && ORG_ID=\$(curl -sf -H \"Authorization: Bearer \$TOKEN\" {$api}/organizations/list 2>/dev/null | python3 -c \"import sys,json; d=json.load(sys.stdin); orgs=d.get('data',[]); print(orgs[0]['id'] if orgs else '')\" 2>/dev/null) && [ -n \"\$ORG_ID\" ] && curl -sf -H \"Authorization: Bearer \$TOKEN\" -H \"X-Organization-Id: \$ORG_ID\" {$api}/normative/list | python3 -c \"import sys,json; d=json.load(sys.stdin); items=d.get('data',[]); print(f'Aggiornamenti normativi: {len(items)}')\" || echo '[SKIP]'", "echo ''", // L6.6: whistleblowing stats "echo '━━━ L6.6 Whistleblowing Stats ━━━'", "TOKEN={$getToken} && ORG_ID=\$(curl -sf -H \"Authorization: Bearer \$TOKEN\" {$api}/organizations/list 2>/dev/null | python3 -c \"import sys,json; d=json.load(sys.stdin); orgs=d.get('data',[]); print(orgs[0]['id'] if orgs else '')\" 2>/dev/null) && [ -n \"\$ORG_ID\" ] && curl -sf -H \"Authorization: Bearer \$TOKEN\" -H \"X-Organization-Id: \$ORG_ID\" {$api}/whistleblowing/stats | python3 -m json.tool || echo '[SKIP]'", "echo '[OK] L6 AI Features completato'", ]), 'cwd' => $root, 'timeout' => 120, 'continue_on_fail' => true, ], // ── SMOKE ─────────────────────────────────────────────────────────── 'smoke' => [ 'label' => 'Smoke Tests (curl rapido)', 'level' => 'infra', 'bash' => implode(' && ', [ "echo '=== Login demo ===' && curl -sf -X POST {$api}/auth/login -H 'Content-Type: application/json' -d '{\"email\":\"admin@datacore.demo\",\"password\":\"Demo2026!\"}' | python3 -m json.tool || echo '[SKIP]'", "echo '=== /auth/me senza token (401) ===' && curl -sf -o /dev/null -w 'HTTP %{http_code}' {$api}/auth/me", "echo '=== /dashboard/overview (no token → 401) ===' && curl -sf -o /dev/null -w 'HTTP %{http_code}' {$api}/dashboard/overview", "echo '=== API status ===' && curl -sf {$api}/../api-status.php | python3 -m json.tool", ]), 'cwd' => $root, 'timeout' => 30, 'continue_on_fail' => true, ], // ── SIMULAZIONI ───────────────────────────────────────────────────── 'sim01' => [ 'label' => 'SIM-01 Onboarding + Assessment', 'level' => 'sim', 'bash' => "NIS2_SIM=SIM01 {$php} {$root}/simulate-nis2.php", 'cwd' => $root, 'timeout' => 180, 'continue_on_fail' => false, ], 'sim02' => [ 'label' => 'SIM-02 Ransomware Art.23', 'level' => 'sim', 'bash' => "NIS2_SIM=SIM02 {$php} {$root}/simulate-nis2.php", 'cwd' => $root, 'timeout' => 120, 'continue_on_fail' => false, ], 'sim03' => [ 'label' => 'SIM-03 Data Breach Supply Chain', 'level' => 'sim', 'bash' => "NIS2_SIM=SIM03 {$php} {$root}/simulate-nis2.php", 'cwd' => $root, 'timeout' => 120, 'continue_on_fail' => false, ], 'sim04' => [ 'label' => 'SIM-04 Whistleblowing SCADA', 'level' => 'sim', 'bash' => "NIS2_SIM=SIM04 {$php} {$root}/simulate-nis2.php", 'cwd' => $root, 'timeout' => 90, 'continue_on_fail' => false, ], 'sim05' => [ 'label' => 'SIM-05 Audit Chain Verify', 'level' => 'sim', 'bash' => "NIS2_SIM=SIM05 {$php} {$root}/simulate-nis2.php", 'cwd' => $root, 'timeout' => 60, 'continue_on_fail' => false, ], 'sim06' => [ 'label' => 'SIM-06 B2B License Provisioning', 'level' => 'sim', 'bash' => "NIS2_SIM=SIM06 {$php} {$root}/simulate-nis2.php", 'cwd' => $root, 'timeout' => 90, 'continue_on_fail' => false, ], 'simulate' => [ 'label' => 'Tutte le Simulazioni (SIM-01→06)', 'level' => 'sim', 'bash' => "{$php} {$root}/simulate-nis2.php", 'cwd' => $root, 'timeout' => 720, 'continue_on_fail' => false, ], 'chain-verify' => [ 'label' => 'Verifica Hash Chain Audit', 'level' => 'infra', 'bash' => implode(' && ', [ "echo '=== Verifica chain org demo ==='", "curl -sf -H 'Authorization: Bearer \$(cat /tmp/nis2_chain_token 2>/dev/null || echo \"\")' {$api}/audit/chain-verify | python3 -m json.tool || echo '[INFO] Autenticarsi prima con simulate'", ]), 'cwd' => $root, 'timeout' => 30, 'continue_on_fail' => true, ], 'reset' => [ 'label' => 'Reset Dati Demo', 'level' => 'infra', 'bash' => "mysql -u nis2_agile_user -p\$(grep DB_PASSWORD {$root}/.env | cut -d= -f2) nis2_agile_db < {$root}/docs/sql/reset-demo.sql", 'cwd' => $root, 'timeout' => 30, 'continue_on_fail' => false, ], 'full-suite' => [ 'label' => 'Full Suite L1+L2+L3+L4+L5+L6', 'level' => 'infra', 'bash' => implode(' && ', [ "echo '════════════ L1 AUTH ════════════'", "curl -sf -X POST {$api}/auth/login -H 'Content-Type: application/json' -d '{\"email\":\"admin@datacore.demo\",\"password\":\"Demo2026!\"}' | python3 -c \"import sys,json; d=json.load(sys.stdin); print('Login:', 'OK' if d.get('success') else 'FAIL')\" || echo 'L1 SKIP'", "echo '════════════ L2 TENANT ════════════'", "TOKEN={$getToken} && curl -sf -H \"Authorization: Bearer \$TOKEN\" {$api}/organizations/list | python3 -c \"import sys,json; d=json.load(sys.stdin); orgs=d.get('data',[]); print(f'Orgs: {len(orgs)}')\" || echo 'L2 SKIP'", "echo '════════════ L3 COMPLIANCE ════════════'", "TOKEN={$getToken} && ORG_ID=\$(curl -sf -H \"Authorization: Bearer \$TOKEN\" {$api}/organizations/list 2>/dev/null | python3 -c \"import sys,json; d=json.load(sys.stdin); orgs=d.get('data',[]); print(orgs[0]['id'] if orgs else '')\" 2>/dev/null) && [ -n \"\$ORG_ID\" ] && curl -sf -H \"Authorization: Bearer \$TOKEN\" -H \"X-Organization-Id: \$ORG_ID\" {$api}/dashboard/compliance-score | python3 -c \"import sys,json; d=json.load(sys.stdin); print('Score:', d.get('data',{}).get('score','?'))\" || echo 'L3 SKIP'", "echo '════════════ L5 EXPORT ════════════'", "curl -sf {$api}/../api-status.php | python3 -c \"import sys,json; d=json.load(sys.stdin); print('API:', d.get('status','?'))\"", "echo '════════════ L6 AI CROSS ════════════'", "TOKEN_CONS=\$(curl -sf -X POST {$api}/auth/login -H 'Content-Type: application/json' -d '{\"email\":\"consultant@nis2agile.demo\",\"password\":\"Demo2026!\"}' 2>/dev/null | python3 -c \"import sys,json; d=json.load(sys.stdin); print(d.get('data',{}).get('access_token',''))\" 2>/dev/null) && [ -n \"\$TOKEN_CONS\" ] && curl -sf -H \"Authorization: Bearer \$TOKEN_CONS\" {$api}/cross-analysis/portfolio | python3 -c \"import sys,json; d=json.load(sys.stdin); print('Portfolio orgs:', d.get('data',{}).get('org_count','?'))\" || echo 'L6 SKIP (eseguire SIM-01 prima)'", "echo '[OK] Full Suite L1→L6 completata'", ]), 'cwd' => $root, 'timeout' => 300, 'continue_on_fail' => true, ], 'full-reset-sim' => [ 'label' => 'Reset + Simula + Testa Tutto', 'level' => 'infra', 'bash' => implode(' && ', [ "echo '════════════════════════════════════════'", "echo ' FASE 1 — Reset database demo'", "echo '════════════════════════════════════════'", "mysql -u nis2_agile_user -p\$(grep DB_PASSWORD {$root}/.env | cut -d= -f2) nis2_agile_db < {$root}/docs/sql/reset-demo.sql", "echo '[OK] Reset completato.'", "echo ''", "echo '════════════════════════════════════════'", "echo ' FASE 2 — Simulazioni demo (SIM-01→06)'", "echo '════════════════════════════════════════'", PHP_BINARY . " {$root}/simulate-nis2.php", "echo ''", "echo '════════════════════════════════════════'", "echo ' FASE 3 — Smoke tests API'", "echo '════════════════════════════════════════'", "curl -sf " . API_BASE . "/../api-status.php | python3 -m json.tool", "echo '[OK] Suite completa terminata.'", ]), 'cwd' => $root, 'timeout' => 900, 'continue_on_fail' => false, ], ]; } // ── SSE Events handler ──────────────────────────────────────────────────────── function serveEvents(): void { $cmd = $_GET['cmd'] ?? ''; $commands = getCommands(); if (!isset($commands[$cmd])) { http_response_code(400); echo "Comando sconosciuto: {$cmd}"; return; } $def = $commands[$cmd]; header('Content-Type: text/event-stream'); header('Cache-Control: no-cache'); header('X-Accel-Buffering: no'); if (ob_get_level()) ob_end_clean(); sseWrite('start', json_encode(['cmd' => $cmd, 'label' => $def['label'], 'ts' => date('c')])); $t0 = microtime(true); runCommand($def, function (string $type, string $line) { sseWrite($type, $line); }); $elapsed = round(microtime(true) - $t0, 2); sseWrite('done', json_encode(['cmd' => $cmd, 'elapsed' => $elapsed])); } function runCommand(array $def, callable $emit): void { $descriptors = [ 0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w'], ]; $env = array_merge($_ENV, getenv() ?: [], ['NIS2_API_BASE' => API_BASE]); $proc = proc_open( $def['bash'], $descriptors, $pipes, $def['cwd'] ?? PROJECT_ROOT, $env ); if (!is_resource($proc)) { $emit('stderr', '[ERRORE] Impossibile avviare il processo'); return; } fclose($pipes[0]); stream_set_blocking($pipes[1], false); stream_set_blocking($pipes[2], false); $timeout = $def['timeout'] ?? 120; $deadline = microtime(true) + $timeout; $buf1 = ''; $buf2 = ''; while (true) { if (microtime(true) > $deadline) { $emit('stderr', "[TIMEOUT] Processo terminato dopo {$timeout}s"); proc_terminate($proc, 9); break; } $r = [$pipes[1], $pipes[2]]; $w = null; $e = null; $changed = @stream_select($r, $w, $e, 0, 50000); if ($changed === false) break; foreach ($r as $s) { $chunk = fread($s, 4096); if ($chunk === false || $chunk === '') continue; if ($s === $pipes[1]) { $buf1 .= $chunk; while (($nl = strpos($buf1, "\n")) !== false) { $emit('stdout', rtrim(substr($buf1, 0, $nl))); $buf1 = substr($buf1, $nl + 1); } } else { $buf2 .= $chunk; while (($nl = strpos($buf2, "\n")) !== false) { $emit('stderr', rtrim(substr($buf2, 0, $nl))); $buf2 = substr($buf2, $nl + 1); } } } if (feof($pipes[1]) && feof($pipes[2])) break; if (connection_aborted()) { proc_terminate($proc, 9); break; } if (ob_get_level()) ob_flush(); flush(); } if ($buf1 !== '') $emit('stdout', rtrim($buf1)); if ($buf2 !== '') $emit('stderr', rtrim($buf2)); fclose($pipes[1]); fclose($pipes[2]); proc_close($proc); if (ob_get_level()) ob_flush(); flush(); } function sseWrite(string $event, string $data): void { echo "event: {$event}\ndata: " . $data . "\n\n"; if (ob_get_level()) ob_flush(); flush(); } // ── Status JSON ─────────────────────────────────────────────────────────────── function serveStatus(): void { header('Content-Type: application/json'); $api = API_BASE; $up = false; $info = []; $ch = curl_init("{$api}/../api-status.php"); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 5, CURLOPT_SSL_VERIFYPEER => false, ]); $resp = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($code === 200 && $resp) { $up = true; $info = json_decode($resp, true) ?: []; } echo json_encode([ 'api_up' => $up, 'api_url' => $api, 'api_info' => $info, 'php_version' => PHP_VERSION, 'ts' => date('c'), ], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); } // ── DB Stats JSON ───────────────────────────────────────────────────────────── function serveDbStats(): void { header('Content-Type: application/json'); $envFile = PROJECT_ROOT . '/.env'; if (!file_exists($envFile)) { echo json_encode(['error' => '.env non trovato'], JSON_PRETTY_PRINT); return; } $env = []; foreach (file($envFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) as $line) { if (str_starts_with(trim($line), '#') || !str_contains($line, '=')) continue; [$k, $v] = explode('=', $line, 2); $env[trim($k)] = trim($v); } try { $dsn = sprintf('mysql:host=%s;dbname=%s;charset=utf8mb4', $env['DB_HOST'] ?? '127.0.0.1', $env['DB_NAME'] ?? 'nis2_agile_db' ); $pdo = new PDO($dsn, $env['DB_USER'] ?? '', $env['DB_PASSWORD'] ?? '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_TIMEOUT => 3, ]); $tables = [ 'organizations', 'users', 'user_organizations', 'assessments', 'assessment_responses', 'risks', 'risk_treatments', 'incidents', 'incident_timeline', 'policies', 'suppliers', 'training_courses', 'training_assignments', 'assets', 'compliance_controls', 'evidence_files', 'audit_logs', 'ai_interactions', 'email_log', 'non_conformities', 'corrective_actions', ]; $stats = []; foreach ($tables as $t) { try { $stmt = $pdo->query("SELECT COUNT(*) FROM `{$t}`"); $stats[$t] = (int)$stmt->fetchColumn(); } catch (\Throwable) { $stats[$t] = null; // tabella non esiste } } echo json_encode([ 'stats' => $stats, 'total' => array_sum(array_filter($stats, fn($v) => $v !== null)), 'ts' => date('c'), ], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); } catch (\Throwable $e) { echo json_encode(['error' => $e->getMessage()], JSON_PRETTY_PRINT); } } // ── UI ──────────────────────────────────────────────────────────────────────── function serveUI(): void { $commands = getCommands(); $cmdsJson = json_encode(array_map(fn($k, $v) => [ 'id' => $k, 'label' => $v['label'], 'level' => $v['level'] ?? 'infra', ], array_keys($commands), $commands), JSON_UNESCAPED_UNICODE); $token = ACCESS_TOKEN; $api = API_BASE; $url = "https://nis2.agile.software/test-runner.php?t={$token}"; $demoCredentials = [ ['role' => '★ Super Admin', 'email' => 'cristiano.benassati@gmail.com', 'password' => 'Silvia1978!@', 'org' => 'Tutte'], ['role' => 'Admin (DataCore)', 'email' => 'admin@datacore.demo', 'password' => 'Demo2026!', 'org' => 'DataCore S.r.l.'], ['role' => 'Compliance (MedClinic)', 'email' => 'compliance@medclinic.demo', 'password' => 'Demo2026!', 'org' => 'MedClinic Italia'], ['role' => 'CISO (EnerNet)', 'email' => 'ciso@enernet.demo', 'password' => 'Demo2026!', 'org' => 'EnerNet S.r.l.'], ['role' => 'Consultant', 'email' => 'consultant@nis2agile.demo', 'password' => 'Demo2026!', 'org' => 'Multi-azienda'], ]; $credsRows = ''; foreach ($demoCredentials as $c) { $credsRows .= "{$c['role']}{$c['email']}{$c['password']}{$c['org']}"; } // SIM cards $simCards = ''; $simDefs = [ 'sim01' => ['SIM-01', 'Onboarding + Assessment', '3 aziende, gap analysis 80 domande', 'cyan'], 'sim02' => ['SIM-02', 'Ransomware Art.23', 'Incidente critico, 24h/72h/30d timeline', 'orange'], 'sim03' => ['SIM-03', 'Data Breach Supply Chain', 'Fornitore compromesso, Art.23 parallelo', 'red'], 'sim04' => ['SIM-04', 'Whistleblowing SCADA', 'Segnalazione anonima tracciata a chiusura', 'purple'], 'sim05' => ['SIM-05', 'Audit Chain Verify', 'Verifica integrità SHA-256 audit trail', 'green'], 'sim06' => ['SIM-06', 'B2B License Provisioning', 'Invite token → org creata + SSO + JWT', 'yellow'], ]; foreach ($simDefs as $id => [$code, $name, $desc, $col]) { $simCards .= << {$code}
{$name} {$desc}
HTML; } $simCards .= '
'; $simCards .= ""; // Level buttons $levelBtns = "\n
\n"; $levelDefs = [ 'health' => ['L0', 'Health Check', 'infra'], 'smoke' => ['L0', 'Smoke Tests', 'infra'], 'l1-auth' => ['L1', 'Auth & JWT', 'l1'], 'l2-tenant' => ['L2', 'Multi-Tenant', 'l2'], 'l3-compliance' => ['L3', 'Compliance Core', 'l3'], 'l4-b2b' => ['L4', 'B2B & Services', 'l4'], 'l5-export' => ['L5', 'Export & Reports', 'l5'], 'l6-ai' => ['L6', 'AI Features', 'l6'], 'chain-verify' => ['—', 'Hash Chain Verify', 'infra'], 'full-suite' => ['ALL', 'Full Suite L1→L6', 'infra'], 'reset' => ['⚠', 'Reset Dati Demo', 'danger'], ]; foreach ($levelDefs as $id => [$badge, $label, $cls]) { $danger = $cls === 'danger' ? ' btn-danger' : ''; $levelBtns .= "\n"; } // Coverage endpoint map $coverageEndpoints = [ ['Auth', 'POST /api/auth/login', 'L1', 'ok'], ['Auth', 'POST /api/auth/register', 'L1', 'ok'], ['Auth', 'GET /api/auth/me', 'L1', 'ok'], ['Auth', 'POST /api/auth/refresh', 'L1', 'partial'], ['Auth', 'POST /api/auth/validate-invite', 'L4', 'ok'], ['Org', 'GET /api/organizations/list', 'L2', 'ok'], ['Org', 'GET /api/organizations/current', 'L2', 'ok'], ['Org', 'POST /api/organizations/create', 'L2', 'sim'], ['Dashboard','GET /api/dashboard/overview', 'L2', 'ok'], ['Dashboard','GET /api/dashboard/compliance-score', 'L3', 'ok'], ['Dashboard','GET /api/dashboard/upcoming-deadlines', 'L3', 'ok'], ['Dashboard','GET /api/dashboard/risk-heatmap', 'L3', 'partial'], ['Risk', 'GET /api/risks/list', 'L3', 'ok'], ['Risk', 'GET /api/risks/matrix', 'L3', 'ok'], ['Risk', 'POST /api/risks/create', 'sim','ok'], ['Incident', 'GET /api/incidents/list', 'L3', 'ok'], ['Incident', 'POST /api/incidents/create', 'sim','ok'], ['Policy', 'GET /api/policies/list', 'L3', 'partial'], ['Audit', 'GET /api/audit/controls', 'L3', 'ok'], ['Audit', 'GET /api/audit/export', 'L5', 'ok'], ['Audit', 'GET /api/audit/iso27001-mapping','L5', 'ok'], ['Audit', 'GET /api/audit/logs', 'L5', 'ok'], ['Supply', 'GET /api/supply-chain/risk-overview', 'L4', 'ok'], ['Training', 'GET /api/training/compliance-status', 'L4', 'ok'], ['NCR', 'GET /api/ncr/stats', 'L4', 'ok'], ['Onboard', 'POST /api/onboarding/complete', 'sim','ok'], ['Admin', 'GET /api/admin/stats', 'admin','skip'], ['CrossAI', 'GET /api/cross-analysis/portfolio', 'L6', 'ok'], ['CrossAI', 'GET /api/cross-analysis/history', 'L6', 'ok'], ['CrossAI', 'POST /api/cross-analysis/analyze', 'L6', 'ok'], ['Normative','GET /api/normative/list', 'L6', 'ok'], ['Normative','POST /api/normative/{id}/ack', 'L6', 'sim'], ['Whistle', 'GET /api/whistleblowing/stats', 'L6', 'ok'], ['Whistle', 'POST /api/whistleblowing/submit', 'L6', 'sim'], ]; $covRows = ''; foreach ($coverageEndpoints as [$module, $ep, $level, $status]) { $statusColor = match($status) { 'ok' => 'var(--green)', 'partial' => 'var(--yellow)', 'sim' => 'var(--cyan)', 'skip' => 'var(--muted)', default => 'var(--red)', }; $statusLabel = match($status) { 'ok' => '✓ Testato', 'partial' => '~ Parziale', 'sim' => '⚙ Solo SIM', 'skip' => '— Skip', default => '✗ Mancante', }; $covRows .= "{$module}{$ep}" . "{$level}" . "{$statusLabel}"; } echo << NIS2 Agile — Test Runner v2
Pronto · seleziona un test o una simulazione
NIS2 Agile Test Runner v2 — pronto. ────────────────────────────────────────────────────────────────────
HTML; }