From 45e6c3599c6d05679c489f189311a9890d7638b8 Mon Sep 17 00:00:00 2001 From: DevEnv nis2-agile Date: Tue, 16 Jun 2026 18:40:31 +0200 Subject: [PATCH] =?UTF-8?q?[FIX]=20Epic=20C=20/=20C1=20=E2=80=94=20correzi?= =?UTF-8?q?oni=20post-verifica=20flotta=20(1=20major=20+=205=20minor)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flotta di verifica (14 agenti): sicurezza pulita, fedeltà del dato perfetta (codifica Simon intatta, 0 scostamenti). Corretti i 6 finding confermati: - MAJOR: PolicyController::ensureFrameworkProcedures ora in transazione + SELECT...FOR UPDATE sulla riga org -> no procedure duplicate su fallimento parziale (rollback) o richieste concorrenti. - MINOR: importer idempotente committato application/cli/seed_framework.php (DDL + upsert da nis2_framework_seed.json) -> re-seed riproducibile/versionato (verificato 2 run = 0 duplicati). - MINOR: allineato '92 requisiti' importanti (dato autoritativo Simon) in help.js + nis2_sources.php (era 87, contraddizione interna); coerente '24 requisiti aggiuntivi essenziali' (116-92). - MINOR (UI): troncamento anteprima valutazione sul RAW prima di esc() (no entita HTML spezzate). - MINOR (UI a11y/AGID): modale valutazione con focus iniziale, ripristino focus, focus-trap. Cache-buster ?v=20260623, version 1.18.2, SW nis2-shell-v1.18.2. NB residuo da verificare: data esatta Determinazione 333017/2025 (mese senza giorno) in nis2_sources.php. Co-Authored-By: Claude Opus 4.8 (1M context) --- application/cli/seed_framework.php | 121 +++++++++++++++++++ application/config/nis2_sources.php | 2 +- application/controllers/PolicyController.php | 14 ++- public/_app-bi-demo.html | 4 +- public/acn-gap.html | 8 +- public/architecture.html | 8 +- public/assessment.html | 8 +- public/assets.html | 8 +- public/companies.html | 8 +- public/competenze.html | 8 +- public/cross-analysis.html | 6 +- public/dashboard.html | 8 +- public/forgot-password.html | 2 +- public/guida.html | 8 +- public/incidents.html | 8 +- public/integrazioniext.html | 4 +- public/isms.html | 8 +- public/js/help.js | 6 +- public/kb.html | 8 +- public/licenseExt.html | 2 +- public/login.html | 4 +- public/misure-requisiti.html | 35 ++++-- public/mktg-api-doc.html | 2 +- public/normative.html | 8 +- public/onboarding.html | 4 +- public/organigramma.html | 8 +- public/policies.html | 8 +- public/raci.html | 8 +- public/register.html | 4 +- public/reports.html | 8 +- public/review-schedule.html | 8 +- public/risks.html | 8 +- public/settings.html | 8 +- public/setup-org.html | 4 +- public/stakeholders.html | 8 +- public/supply-chain.html | 8 +- public/sw.js | 2 +- public/training.html | 8 +- public/version.json | 2 +- public/whistleblowing.html | 8 +- public/workflow.html | 6 +- 41 files changed, 278 insertions(+), 130 deletions(-) create mode 100644 application/cli/seed_framework.php diff --git a/application/cli/seed_framework.php b/application/cli/seed_framework.php new file mode 100644 index 0000000..3ea7fce --- /dev/null +++ b/application/cli/seed_framework.php @@ -0,0 +1,121 @@ +exec("SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci"); + +$ddl = [ +"CREATE TABLE IF NOT EXISTS cfg_nis2_ambiti ( + nist_code VARCHAR(16) NOT NULL, nist_descr TEXT NOT NULL, + PRIMARY KEY (nist_code) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci", +"CREATE TABLE IF NOT EXISTS cfg_nis2_procedure ( + proc_code VARCHAR(16) NOT NULL, proc_descr TEXT NOT NULL, + PRIMARY KEY (proc_code) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci", +"CREATE TABLE IF NOT EXISTS cfg_nis2_rischi ( + risk_code VARCHAR(16) NOT NULL, risk_descr TEXT NOT NULL, + PRIMARY KEY (risk_code) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci", +"CREATE TABLE IF NOT EXISTS cfg_nis2_misure ( + misura_code VARCHAR(16) NOT NULL, nist_code VARCHAR(16) NOT NULL, + area_politica VARCHAR(255) NOT NULL, misura_descr TEXT NOT NULL, ord INT NOT NULL, + applies_important TINYINT(1) NOT NULL DEFAULT 1, applies_essential TINYINT(1) NOT NULL DEFAULT 1, + PRIMARY KEY (misura_code), KEY idx_misura_nist (nist_code), KEY idx_misura_ord (ord), + CONSTRAINT fk_misura_ambito FOREIGN KEY (nist_code) REFERENCES cfg_nis2_ambiti (nist_code) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci", +"CREATE TABLE IF NOT EXISTS cfg_nis2_requisiti ( + id INT NOT NULL AUTO_INCREMENT, n INT NULL, misura_code VARCHAR(16) NOT NULL, + requisito_code VARCHAR(32) NOT NULL, requisito_descr TEXT NOT NULL, + proc_code VARCHAR(16) NULL, risk_code VARCHAR(16) NULL, + applies_important TINYINT(1) NOT NULL DEFAULT 0, applies_essential TINYINT(1) NOT NULL DEFAULT 0, + PRIMARY KEY (id), UNIQUE KEY uq_req (misura_code, requisito_code), + KEY idx_req_misura (misura_code), KEY idx_req_proc (proc_code), KEY idx_req_risk (risk_code), + CONSTRAINT fk_req_misura FOREIGN KEY (misura_code) REFERENCES cfg_nis2_misure (misura_code), + CONSTRAINT fk_req_proc FOREIGN KEY (proc_code) REFERENCES cfg_nis2_procedure (proc_code), + CONSTRAINT fk_req_risk FOREIGN KEY (risk_code) REFERENCES cfg_nis2_rischi (risk_code) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci", +]; +foreach ($ddl as $stmt) { + try { $pdo->exec($stmt); } + catch (PDOException $e) { if (!in_array($e->errorInfo[1] ?? 0, [1050, 1061], true)) { throw $e; } } +} + +$seedPath = __DIR__ . '/../data/nis2_framework_seed.json'; +$seed = json_decode(@file_get_contents($seedPath), true); +if (!$seed) { fwrite(STDERR, "SEED non leggibile: $seedPath\n"); exit(1); } + +$pdo->beginTransaction(); +try { + $st = $pdo->prepare("INSERT INTO cfg_nis2_ambiti (nist_code,nist_descr) VALUES (?,?) + ON DUPLICATE KEY UPDATE nist_descr=VALUES(nist_descr)"); + foreach ($seed['ambiti_nist'] as $a) { $st->execute([$a['nist_code'], $a['nist_descr']]); } + + $st = $pdo->prepare("INSERT INTO cfg_nis2_procedure (proc_code,proc_descr) VALUES (?,?) + ON DUPLICATE KEY UPDATE proc_descr=VALUES(proc_descr)"); + foreach ($seed['procedure'] as $p) { $st->execute([$p['proc_code'], $p['proc_descr']]); } + + $st = $pdo->prepare("INSERT INTO cfg_nis2_rischi (risk_code,risk_descr) VALUES (?,?) + ON DUPLICATE KEY UPDATE risk_descr=VALUES(risk_descr)"); + foreach ($seed['rischi'] as $r) { $st->execute([$r['risk_code'], $r['risk_descr']]); } + + $st = $pdo->prepare("INSERT INTO cfg_nis2_misure + (misura_code,nist_code,area_politica,misura_descr,ord,applies_important,applies_essential) + VALUES (?,?,?,?,?,?,?) + ON DUPLICATE KEY UPDATE nist_code=VALUES(nist_code),area_politica=VALUES(area_politica), + misura_descr=VALUES(misura_descr),ord=VALUES(ord), + applies_important=VALUES(applies_important),applies_essential=VALUES(applies_essential)"); + foreach ($seed['misure'] as $m) { + $st->execute([$m['misura_code'], $m['nist_code'], $m['area_politica'], $m['misura_descr'], + $m['ord'], $m['applies_important'], $m['applies_essential']]); + } + + $st = $pdo->prepare("INSERT INTO cfg_nis2_requisiti + (n,misura_code,requisito_code,requisito_descr,proc_code,risk_code,applies_important,applies_essential) + VALUES (?,?,?,?,?,?,?,?) + ON DUPLICATE KEY UPDATE n=VALUES(n),requisito_descr=VALUES(requisito_descr), + proc_code=VALUES(proc_code),risk_code=VALUES(risk_code), + applies_important=VALUES(applies_important),applies_essential=VALUES(applies_essential)"); + foreach ($seed['requisiti'] as $q) { + $st->execute([$q['n'], $q['misura_code'], $q['requisito_code'], $q['requisito_descr'], + $q['proc_code'], $q['risk_code'], $q['applies_important'], $q['applies_essential']]); + } + $pdo->commit(); +} catch (Throwable $e) { + $pdo->rollBack(); + fwrite(STDERR, "SEED FALLITO: " . $e->getMessage() . "\n"); + exit(1); +} + +$counts = [ + 'ambiti' => (int) $pdo->query("SELECT COUNT(*) FROM cfg_nis2_ambiti")->fetchColumn(), + 'procedure' => (int) $pdo->query("SELECT COUNT(*) FROM cfg_nis2_procedure")->fetchColumn(), + 'rischi' => (int) $pdo->query("SELECT COUNT(*) FROM cfg_nis2_rischi")->fetchColumn(), + 'misure' => (int) $pdo->query("SELECT COUNT(*) FROM cfg_nis2_misure")->fetchColumn(), + 'requisiti' => (int) $pdo->query("SELECT COUNT(*) FROM cfg_nis2_requisiti")->fetchColumn(), +]; +echo "OK seed-framework — " . json_encode($counts, JSON_UNESCAPED_UNICODE) . "\n"; diff --git a/application/config/nis2_sources.php b/application/config/nis2_sources.php index bc42f5e..84503ad 100644 --- a/application/config/nis2_sources.php +++ b/application/config/nis2_sources.php @@ -76,7 +76,7 @@ return [ 'key' => 'acn_specifiche_base_2025', 'short' => 'Specifiche di base ACN (Framework Nazionale 2025)', 'citation' => 'ACN, Misure di sicurezza di base NIS - Allegati 1 e 2 alla Determinazione n. 164179/2025', - 'full' => 'Misure di sicurezza di base (Determinazione ACN 164179/2025) articolate secondo il Framework Nazionale per la Cybersecurity e la Data Protection (ed. 2025): funzioni Governance (GV), Identificazione (ID), Protezione (PR), Rilevazione (DE), Risposta (RS), Ripristino (RC), poi categorie, sottocategorie e requisiti. Soggetti importanti: 37 misure / 87 requisiti (Allegato 1). Soggetti essenziali: 43 misure / 116 requisiti (Allegato 2), codifica identica con requisiti aggiuntivi. Esempi: GV.RR-04 (cybersicurezza nelle risorse umane, adeguatezza al ruolo), GV.PO-01 (policy di gestione del rischio). Adozione: 18 mesi dalla notifica di inserimento nell elenco NIS.', + 'full' => 'Misure di sicurezza di base (Determinazione ACN 164179/2025) articolate secondo il Framework Nazionale per la Cybersecurity e la Data Protection (ed. 2025): funzioni Governance (GV), Identificazione (ID), Protezione (PR), Rilevazione (DE), Risposta (RS), Ripristino (RC), poi categorie, sottocategorie e requisiti. Soggetti importanti: 37 misure / 92 requisiti (Allegato 1). Soggetti essenziali: 43 misure / 116 requisiti (Allegato 2), codifica identica con requisiti aggiuntivi. Esempi: GV.RR-04 (cybersicurezza nelle risorse umane, adeguatezza al ruolo), GV.PO-01 (policy di gestione del rischio). Adozione: 18 mesi dalla notifica di inserimento nell elenco NIS.', 'file' => 'docs/nis2/Determina164179_apr2025.pdf', 'authority' => 'Agenzia per la Cybersicurezza Nazionale (ACN)', 'url' => 'https://www.acn.gov.it/portale/nis/modalita-specifiche-base', diff --git a/application/controllers/PolicyController.php b/application/controllers/PolicyController.php index 6c6db86..422c579 100644 --- a/application/controllers/PolicyController.php +++ b/application/controllers/PolicyController.php @@ -67,12 +67,18 @@ class PolicyController extends BaseController if (!empty($this->isDemo)) { return; } $orgId = $this->getCurrentOrgId(); if (!$orgId) { return; } + $pdo = Database::getInstance(); try { + // Transazione + lock di riga: serializza richieste concorrenti (niente doppio + // seed) e, in caso di fallimento parziale, il rollback annulla gli insert già + // fatti (niente procedure duplicate alla ri-prova). Il flag si scrive nello + // stesso commit degli insert. + $pdo->beginTransaction(); $org = Database::fetchOne( - 'SELECT framework_procedures_seeded_at FROM organizations WHERE id = ?', + 'SELECT framework_procedures_seeded_at FROM organizations WHERE id = ? FOR UPDATE', [$orgId] ); - if (!$org || $org['framework_procedures_seeded_at'] !== null) { return; } + if (!$org || $org['framework_procedures_seeded_at'] !== null) { $pdo->commit(); return; } $procs = Database::fetchAll( "SELECT p.proc_code, p.proc_descr, @@ -85,7 +91,7 @@ class PolicyController extends BaseController FROM cfg_nis2_requisiti q3 WHERE q3.proc_code = p.proc_code) AS misure FROM cfg_nis2_procedure p ORDER BY p.proc_code" ); - if (!$procs) { return; } // framework non ancora seedato: riprova al prossimo accesso + if (!$procs) { $pdo->commit(); return; } // framework non ancora seedato: riprova al prossimo accesso foreach ($procs as $p) { $cat = self::PROC_NIST_CATEGORY[$p['nist_code'] ?? ''] ?? 'information_security'; @@ -107,7 +113,9 @@ class PolicyController extends BaseController 'UPDATE organizations SET framework_procedures_seeded_at = NOW() WHERE id = ?', [$orgId] ); + $pdo->commit(); } catch (Throwable $e) { + if ($pdo->inTransaction()) { $pdo->rollBack(); } error_log('[PolicyController] seed procedure default fallito (org ' . $orgId . '): ' . $e->getMessage()); } } diff --git a/public/_app-bi-demo.html b/public/_app-bi-demo.html index 1ce054f..dac6415 100644 --- a/public/_app-bi-demo.html +++ b/public/_app-bi-demo.html @@ -70,8 +70,8 @@ - - + + - + + @@ -164,8 +164,8 @@ } - - + + - - - + + + + - + + @@ -166,8 +166,8 @@ } - - + + - + + @@ -378,8 +378,8 @@ } - - + + - + + - - + + - + + - - + + diff --git a/public/cross-analysis.html b/public/cross-analysis.html index ec57205..4fc054c 100644 --- a/public/cross-analysis.html +++ b/public/cross-analysis.html @@ -382,8 +382,8 @@ - - + + @@ -394,7 +394,7 @@ } - + - + + @@ -155,8 +155,8 @@ } - - + + - + - + + @@ -1153,8 +1153,8 @@ } - - + + - + + @@ -363,8 +363,8 @@ } - - + + - + + - + + @@ -196,8 +196,8 @@ } - - + + diff --git a/public/js/help.js b/public/js/help.js index 2dcc1e1..d2f0788 100644 --- a/public/js/help.js +++ b/public/js/help.js @@ -121,8 +121,8 @@ const HelpSystem = (function () { { heading: 'Importanti vs Essenziali', items: [ - 'Il perimetro dipende dalla classificazione del soggetto: importante (Allegato 1) = 37 misure / 87 requisiti; essenziale (Allegato 2) = 43 misure / 116 requisiti.', - 'La codifica dei requisiti e\' identica per i due livelli (stessi codici GV/ID/PR/DE/RS/RC): agli essenziali si aggiungono 6 misure e 29 requisiti, oltre a punti aggiuntivi su misure gia\' presenti.', + 'Il perimetro dipende dalla classificazione del soggetto: importante (Allegato 1) = 37 misure / 92 requisiti; essenziale (Allegato 2) = 43 misure / 116 requisiti.', + 'La codifica dei requisiti e\' identica per i due livelli (stessi codici GV/ID/PR/DE/RS/RC): agli essenziali si aggiungono 6 misure e 24 requisiti, oltre a punti aggiuntivi su misure gia\' presenti.', 'L\'app filtra automaticamente i requisiti applicabili in base al livello del soggetto (Impostazioni › Classificazione NIS2).' ] }, @@ -152,7 +152,7 @@ const HelpSystem = (function () { ], references: [ 'Determinazione ACN n. 164179 del 14/04/2025 - Misure di sicurezza di base per i soggetti NIS', - 'Allegato 1 - Misure di base per i soggetti IMPORTANTI (37 misure, 87 requisiti)', + 'Allegato 1 - Misure di base per i soggetti IMPORTANTI (37 misure, 92 requisiti)', 'Allegato 2 - Misure di base per i soggetti ESSENZIALI (43 misure, 116 requisiti)', 'Framework Nazionale per la Cybersecurity e la Data Protection - funzioni GV/ID/PR/DE/RS/RC', 'Art. 24 D.Lgs. 138/2024 (recepisce l\'art. 21 della Direttiva (UE) 2022/2555) - base normativa primaria' diff --git a/public/kb.html b/public/kb.html index c699498..2ee8573 100644 --- a/public/kb.html +++ b/public/kb.html @@ -151,8 +151,8 @@ - - + + @@ -162,8 +162,8 @@ } - - + + + - - + + - + + - - + + diff --git a/public/mktg-api-doc.html b/public/mktg-api-doc.html index 171f10e..3b16d16 100644 --- a/public/mktg-api-doc.html +++ b/public/mktg-api-doc.html @@ -424,6 +424,6 @@ curl https://nis2.agile.software/api/services/status - + diff --git a/public/normative.html b/public/normative.html index 0f9e041..dc61af5 100644 --- a/public/normative.html +++ b/public/normative.html @@ -112,8 +112,8 @@ - - + + @@ -124,8 +124,8 @@ } - - + + - - + + - + + - - + + diff --git a/public/policies.html b/public/policies.html index 80e2847..540a4f5 100644 --- a/public/policies.html +++ b/public/policies.html @@ -333,8 +333,8 @@ - - + + @@ -345,8 +345,8 @@ } - - + + - + + - - + + diff --git a/public/register.html b/public/register.html index 362c014..105e89f 100644 --- a/public/register.html +++ b/public/register.html @@ -268,8 +268,8 @@ - - + + - + + @@ -455,8 +455,8 @@ } - - + + - + + - - + + - + + @@ -506,8 +506,8 @@ } - - + + - + + @@ -684,8 +684,8 @@ } - - + + - + + - + + - - + + - + + @@ -489,8 +489,8 @@ } - - + + - + + @@ -304,8 +304,8 @@ } - - + + - + + @@ -219,8 +219,8 @@ } - - + + - - + + +