[FEAT] Epic C / C1 operativo — copia procedure di default al 1° accesso (mig.048)
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) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
b34bf3f9f6
commit
4570ce3ba1
@@ -11,9 +11,25 @@ require_once APP_PATH . '/services/WebhookService.php';
|
|||||||
|
|
||||||
class PolicyController extends BaseController
|
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
|
public function list(): void
|
||||||
{
|
{
|
||||||
$this->requireOrgAccess();
|
$this->requireOrgAccess();
|
||||||
|
$this->ensureFrameworkProcedures();
|
||||||
|
|
||||||
$where = 'organization_id = ?';
|
$where = 'organization_id = ?';
|
||||||
$params = [$this->getCurrentOrgId()];
|
$params = [$this->getCurrentOrgId()];
|
||||||
@@ -39,6 +55,63 @@ class PolicyController extends BaseController
|
|||||||
$this->jsonSuccess($policies);
|
$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
|
public function create(): void
|
||||||
{
|
{
|
||||||
$this->requireOrgRole(['org_admin', 'compliance_manager']);
|
$this->requireOrgRole(['org_admin', 'compliance_manager']);
|
||||||
|
|||||||
@@ -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;
|
||||||
Reference in New Issue
Block a user