From 4570ce3ba1c6429a19c63aa95db7a0f098fecc0b Mon Sep 17 00:00:00 2001 From: DevEnv nis2-agile Date: Tue, 16 Jun 2026 18:18:28 +0200 Subject: [PATCH] =?UTF-8?q?[FEAT]=20Epic=20C=20/=20C1=20operativo=20?= =?UTF-8?q?=E2=80=94=20copia=20procedure=20di=20default=20al=201=C2=B0=20a?= =?UTF-8?q?ccesso=20(mig.048)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PolicyController::list ora, al primo accesso al modulo Procedure di un'org reale, copia le 42 procedure di default (cfg_nis2_procedure) in policies come contenuto editabile/cancellabile (decisione utente: copia per-org al 1° accesso). - organizations.framework_procedures_seeded_at = idempotenza (no ri-copia). - categoria policies assegnata da mappa famiglia NIST->ENUM; nis2_article=misura; content riporta misure coperte + nota 'personalizzabile e cancellabile'. - Salta org demo/sandbox (isDemo) per non inquinare il dataset dimostrativo. - Best-effort: errore nel seed NON blocca la lista (log + return). Smoke prod: org 151 3->45 policy (42 default, 10 categorie), idempotente al 2° accesso, golden 151 poi ripristinato (cleanup). Additivo. Co-Authored-By: Claude Opus 4.8 (1M context) --- application/controllers/PolicyController.php | 73 ++++++++++++++++++++ docs/sql/048_org_framework_proc_seed.sql | 11 +++ 2 files changed, 84 insertions(+) create mode 100644 docs/sql/048_org_framework_proc_seed.sql diff --git a/application/controllers/PolicyController.php b/application/controllers/PolicyController.php index 55fdffb..6c6db86 100644 --- a/application/controllers/PolicyController.php +++ b/application/controllers/PolicyController.php @@ -11,9 +11,25 @@ require_once APP_PATH . '/services/WebhookService.php'; class PolicyController extends BaseController { + // Epic C / C1 operativo: mappa famiglia NIST -> categoria policies (ENUM) + // per assegnare una categoria sensata alle procedure di default copiate. + private const PROC_NIST_CATEGORY = [ + 'GV.OC' => 'information_security', 'GV.RM' => 'information_security', + 'GV.RR' => 'information_security', 'GV.PO' => 'information_security', + 'GV.SC' => 'supply_chain', + 'ID.AM' => 'asset_management', 'ID.RA' => 'vulnerability_management', + 'ID.IM' => 'business_continuity', + 'PR.AA' => 'access_control', 'PR.AT' => 'hr_security', + 'PR.DS' => 'encryption', 'PR.PS' => 'information_security', + 'PR.IR' => 'network_security', 'DE.CM' => 'information_security', + 'RS.MA' => 'incident_response', 'RS.CO' => 'incident_response', + 'RC.RP' => 'business_continuity', 'RC.CO' => 'business_continuity', + ]; + public function list(): void { $this->requireOrgAccess(); + $this->ensureFrameworkProcedures(); $where = 'organization_id = ?'; $params = [$this->getCurrentOrgId()]; @@ -39,6 +55,63 @@ class PolicyController extends BaseController $this->jsonSuccess($policies); } + /** + * Epic C / C1 operativo — copia le 42 procedure di default (cfg_nis2_procedure) + * nell'elenco procedure dell'org al PRIMO accesso al modulo, come contenuto + * editabile/cancellabile. Idempotente (flag organizations.framework_procedures_seeded_at). + * Salta le org demo/sandbox (no inquinamento del dataset dimostrativo). Best-effort: + * un errore NON blocca la lista (log + return). + */ + private function ensureFrameworkProcedures(): void + { + if (!empty($this->isDemo)) { return; } + $orgId = $this->getCurrentOrgId(); + if (!$orgId) { return; } + try { + $org = Database::fetchOne( + 'SELECT framework_procedures_seeded_at FROM organizations WHERE id = ?', + [$orgId] + ); + if (!$org || $org['framework_procedures_seeded_at'] !== null) { return; } + + $procs = Database::fetchAll( + "SELECT p.proc_code, p.proc_descr, + (SELECT m.nist_code FROM cfg_nis2_requisiti q + JOIN cfg_nis2_misure m ON m.misura_code = q.misura_code + WHERE q.proc_code = p.proc_code ORDER BY q.n LIMIT 1) AS nist_code, + (SELECT q2.misura_code FROM cfg_nis2_requisiti q2 + WHERE q2.proc_code = p.proc_code ORDER BY q2.n LIMIT 1) AS first_misura, + (SELECT GROUP_CONCAT(DISTINCT q3.misura_code ORDER BY q3.misura_code SEPARATOR ', ') + 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 + + foreach ($procs as $p) { + $cat = self::PROC_NIST_CATEGORY[$p['nist_code'] ?? ''] ?? 'information_security'; + $content = $p['proc_descr'] + . "\n\nProcedura di default del framework NIS2 (Determinazione ACN n. 164179 del 14 aprile 2025)." + . ($p['misure'] ? ' Copre le misure: ' . $p['misure'] . '.' : '') + . ' Contenuto iniziale personalizzabile e cancellabile.'; + Database::insert('policies', [ + 'organization_id' => $orgId, + 'title' => mb_substr($p['proc_descr'], 0, 255), + 'category' => $cat, + 'nis2_article' => mb_substr($p['first_misura'] ?? '', 0, 20), + 'content' => $content, + 'status' => 'draft', + 'ai_generated' => 0, + ]); + } + Database::query( + 'UPDATE organizations SET framework_procedures_seeded_at = NOW() WHERE id = ?', + [$orgId] + ); + } catch (Throwable $e) { + error_log('[PolicyController] seed procedure default fallito (org ' . $orgId . '): ' . $e->getMessage()); + } + } + public function create(): void { $this->requireOrgRole(['org_admin', 'compliance_manager']); diff --git a/docs/sql/048_org_framework_proc_seed.sql b/docs/sql/048_org_framework_proc_seed.sql new file mode 100644 index 0000000..a83633e --- /dev/null +++ b/docs/sql/048_org_framework_proc_seed.sql @@ -0,0 +1,11 @@ +-- ===================================================================== +-- 048 — Flag idempotenza per la copia procedure di default (Epic C / C1 operativo) +-- ===================================================================== +-- Le 42 procedure di default (cfg_nis2_procedure) vengono copiate nell'elenco +-- procedure (policies) dell'org al PRIMO accesso al modulo, come contenuto +-- editabile/cancellabile. Questo flag evita la ri-copia. +-- Additivo, reversibile (ALTER ... DROP COLUMN framework_procedures_seeded_at). +-- ===================================================================== + +ALTER TABLE organizations + ADD COLUMN framework_procedures_seeded_at DATETIME NULL DEFAULT NULL;