[FEAT] Epic C / C5 — chiusura open-items: email gated + fonti + provenienza 92/87
- #7 (go-live ready): send() aggancia l'invio email del questionario/firma-lettura via EmailService, GATED dal kill-switch EMAIL_SENDING_ENABLED (oggi=false → ZERO invii; pronto al go-live). Magic-link assoluto via APP_URL; invio solo ai target con email valida; i link restano sempre disponibili per la condivisione manuale. Smoke prod: email_sent=0, email_enabled=false (nessun invio). - #2 (prevale Simon): nis2_sources.php — aggiunta NOTA DI PROVENIENZA sul 92 vs 87 (prodotto usa 92 = file referente; ACN ufficiale 87; divergenza nota/accettata). Numero NON modificato per decisione confermata. Note operative (host, fuori repo): - #8: EMAIL_MS_URL spostato sul path interno http://172.21.0.1:8081/api/emails (verificato 400 con X-Internal-Key = funzionante; pre edge-strip). Rollback: URL pubblico. - #9: pre-equip PHP-CA del DB CONFERMATO (CA presente, app connessa in TLS 1.3); enforce ALTER USER nis2_user REQUIRE SSL resta azione DB coordinata con VIGILE (utente @%). v1.21.3. 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
6992bdcf14
commit
69873e21be
@@ -85,7 +85,7 @@ return [
|
|||||||
'key' => 'acn_specifiche_base_2025',
|
'key' => 'acn_specifiche_base_2025',
|
||||||
'short' => 'Specifiche di base ACN (Framework Nazionale 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',
|
'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 / 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.',
|
'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. NOTA DI PROVENIENZA: il prodotto adotta 92 requisiti per i soggetti importanti come da file del referente compliance (Simon); la pagina ufficiale ACN riporta 87 - divergenza nota e accettata, prevale il dato del referente; da riconciliare sul testo ufficiale.',
|
||||||
'file' => 'docs/nis2/Determina164179_apr2025.pdf',
|
'file' => 'docs/nis2/Determina164179_apr2025.pdf',
|
||||||
'authority' => 'Agenzia per la Cybersicurezza Nazionale (ACN)',
|
'authority' => 'Agenzia per la Cybersicurezza Nazionale (ACN)',
|
||||||
'url' => 'https://www.acn.gov.it/portale/nis/modalita-specifiche-base',
|
'url' => 'https://www.acn.gov.it/portale/nis/modalita-specifiche-base',
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
require_once __DIR__ . '/BaseController.php';
|
require_once __DIR__ . '/BaseController.php';
|
||||||
|
require_once APP_PATH . '/services/EmailService.php';
|
||||||
|
|
||||||
class StakeholderActivityController extends BaseController
|
class StakeholderActivityController extends BaseController
|
||||||
{
|
{
|
||||||
@@ -424,7 +425,13 @@ class StakeholderActivityController extends BaseController
|
|||||||
? date('Y-m-d H:i:s', strtotime($a['due_date'] . ' +30 days'))
|
? date('Y-m-d H:i:s', strtotime($a['due_date'] . ' +30 days'))
|
||||||
: date('Y-m-d H:i:s', strtotime('+90 days'));
|
: date('Y-m-d H:i:s', strtotime('+90 days'));
|
||||||
|
|
||||||
|
$emailOn = defined('EMAIL_SENDING_ENABLED') && EMAIL_SENDING_ENABLED;
|
||||||
|
$base = defined('APP_URL') ? rtrim(APP_URL, '/') : 'https://nis2.agile.software';
|
||||||
|
$emailer = $emailOn ? new EmailService() : null; // istanziato solo se l'invio è abilitato
|
||||||
|
$isReadAck = ($a['type'] === 'read_ack');
|
||||||
|
|
||||||
$links = [];
|
$links = [];
|
||||||
|
$emailSent = 0;
|
||||||
foreach ($targets as $g) {
|
foreach ($targets as $g) {
|
||||||
$token = bin2hex(random_bytes(24)); // 48 hex
|
$token = bin2hex(random_bytes(24)); // 48 hex
|
||||||
Database::update('stk_activity_targets', [
|
Database::update('stk_activity_targets', [
|
||||||
@@ -433,6 +440,14 @@ class StakeholderActivityController extends BaseController
|
|||||||
'sent_at' => date('Y-m-d H:i:s'),
|
'sent_at' => date('Y-m-d H:i:s'),
|
||||||
'token_expires_at' => $expires,
|
'token_expires_at' => $expires,
|
||||||
], 'id = ?', [(int) $g['id']]);
|
], 'id = ?', [(int) $g['id']]);
|
||||||
|
$absLink = $base . '/stk-portal.html?t=' . $token;
|
||||||
|
// Invio email gated dal kill-switch: EmailService::send rispetta EMAIL_SENDING_ENABLED
|
||||||
|
// (con kill-switch OFF non parte nulla; pronto per il go-live). Solo se c'è un'email.
|
||||||
|
if ($emailer && !empty($g['contact_email']) && filter_var($g['contact_email'], FILTER_VALIDATE_EMAIL)) {
|
||||||
|
if ($emailer->send($g['contact_email'], $this->mailSubject($isReadAck, $a['title']), $this->mailBody($isReadAck, $a['title'], $g['stakeholder_name'], $absLink))) {
|
||||||
|
$emailSent++;
|
||||||
|
}
|
||||||
|
}
|
||||||
$links[] = [
|
$links[] = [
|
||||||
'target_id' => (int) $g['id'],
|
'target_id' => (int) $g['id'],
|
||||||
'stakeholder_name' => $g['stakeholder_name'],
|
'stakeholder_name' => $g['stakeholder_name'],
|
||||||
@@ -442,16 +457,34 @@ class StakeholderActivityController extends BaseController
|
|||||||
}
|
}
|
||||||
Database::update('stk_activities', ['status' => 'sent'], 'id = ? AND organization_id = ?', [$id, $orgId]);
|
Database::update('stk_activities', ['status' => 'sent'], 'id = ? AND organization_id = ?', [$id, $orgId]);
|
||||||
|
|
||||||
$emailOn = defined('EMAIL_SENDING_ENABLED') && EMAIL_SENDING_ENABLED;
|
$this->logAudit('stk_activity_sent', 'stk_activity', $id, ['targets' => count($links), 'email_enabled' => $emailOn, 'email_sent' => $emailSent]);
|
||||||
$this->logAudit('stk_activity_sent', 'stk_activity', $id, ['targets' => count($links), 'email_enabled' => $emailOn]);
|
|
||||||
$this->jsonSuccess([
|
$this->jsonSuccess([
|
||||||
'sent' => count($links),
|
'sent' => count($links),
|
||||||
'email_sent' => false,
|
'email_sent' => $emailSent,
|
||||||
|
'email_enabled' => $emailOn,
|
||||||
'links' => $links,
|
'links' => $links,
|
||||||
'note' => $emailOn
|
'note' => $emailOn
|
||||||
? 'Invio email non ancora collegato: condividi i magic-link con gli stakeholder.'
|
? ($emailSent . ' email inviate ai contatti disponibili; condividi i magic-link con gli stakeholder senza email.')
|
||||||
: 'Email disattivate (kill-switch): copia e condividi manualmente i magic-link qui sotto.',
|
: 'Email disattivate (kill-switch): copia e condividi manualmente i magic-link qui sotto.',
|
||||||
], 'Attività inviata (magic-link generati)');
|
], 'Attività inviata');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Oggetto email invito (gated): questionario o presa visione. */
|
||||||
|
private function mailSubject(bool $isReadAck, string $title): string
|
||||||
|
{
|
||||||
|
return ($isReadAck ? 'Presa visione richiesta: ' : 'Questionario NIS2: ') . $title;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Corpo HTML email con il magic-link (EmailService applica il wrap/branding). */
|
||||||
|
private function mailBody(bool $isReadAck, string $title, string $name, string $link): string
|
||||||
|
{
|
||||||
|
$esc = static fn($s) => htmlspecialchars((string) $s, ENT_QUOTES, 'UTF-8');
|
||||||
|
$azione = $isReadAck ? 'prendere visione del documento e confermarne la lettura' : 'compilare il questionario';
|
||||||
|
return '<p>Gentile ' . $esc($name) . ',</p>'
|
||||||
|
. '<p>nell\'ambito degli adempimenti NIS2 della nostra organizzazione, ti chiediamo di ' . $azione . ': <strong>' . $esc($title) . '</strong>.</p>'
|
||||||
|
. '<p><a href="' . $esc($link) . '" style="display:inline-block;padding:10px 18px;background:#0066CC;color:#fff;border-radius:8px;text-decoration:none;">Apri</a></p>'
|
||||||
|
. '<p style="font-size:.85rem;color:#666;">Oppure copia questo link: ' . $esc($link) . '</p>'
|
||||||
|
. '<p style="font-size:.8rem;color:#999;">Il link è personale: non inoltrarlo.</p>';
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
|||||||
+1
-1
@@ -13,7 +13,7 @@
|
|||||||
* Il nome cache include la release: va BUMPATO ad ogni rilascio (vedi version.json),
|
* Il nome cache include la release: va BUMPATO ad ogni rilascio (vedi version.json),
|
||||||
* cosi' activate elimina automaticamente la shell vecchia.
|
* cosi' activate elimina automaticamente la shell vecchia.
|
||||||
*/
|
*/
|
||||||
const CACHE = 'nis2-shell-v1.21.2';
|
const CACHE = 'nis2-shell-v1.21.3';
|
||||||
|
|
||||||
const PRECACHE = [
|
const PRECACHE = [
|
||||||
'/offline.html',
|
'/offline.html',
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
{"version": "1.21.2", "build": "2026-06-16-v1.21.2", "date": "2026-06-16", "changelog": "Epic C / C5 — fix open-item post-verifica: upload allegati RIPRISTINATO in prod (ACL host www-data su public/uploads; risolve anche evidence/visure) — collaudato. UI: create+assign attività non lascia più attività orfane su errore (no duplicati, messaggio chiaro) e richiede destinatari per assegnazione individuale; blocco PWA di stakeholder-activities allineato (favicon-16, status-bar, application-name). Additivo."}
|
{"version": "1.21.3", "build": "2026-06-16-v1.21.3", "date": "2026-06-16", "changelog": "Epic C / C5 chiusura open-items: aggancio invio email questionari/firma-lettura in send() (GATED dal kill-switch EMAIL_SENDING_ENABLED=false -> nessun invio finora, pronto per go-live; magic-link assoluto via APP_URL). nis2_sources.php: nota di provenienza 92 vs 87 (prevale dato referente Simon). [host] EMAIL_MS_URL spostato sul path interno; pre-equip PHP-CA DB confermato (TLS1.3 attivo). Additivo."}
|
||||||
|
|||||||
Reference in New Issue
Block a user