[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',
|
||||
'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 / 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',
|
||||
'authority' => 'Agenzia per la Cybersicurezza Nazionale (ACN)',
|
||||
'url' => 'https://www.acn.gov.it/portale/nis/modalita-specifiche-base',
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/BaseController.php';
|
||||
require_once APP_PATH . '/services/EmailService.php';
|
||||
|
||||
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('+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 = [];
|
||||
$emailSent = 0;
|
||||
foreach ($targets as $g) {
|
||||
$token = bin2hex(random_bytes(24)); // 48 hex
|
||||
Database::update('stk_activity_targets', [
|
||||
@@ -433,6 +440,14 @@ class StakeholderActivityController extends BaseController
|
||||
'sent_at' => date('Y-m-d H:i:s'),
|
||||
'token_expires_at' => $expires,
|
||||
], '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[] = [
|
||||
'target_id' => (int) $g['id'],
|
||||
'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]);
|
||||
|
||||
$emailOn = defined('EMAIL_SENDING_ENABLED') && EMAIL_SENDING_ENABLED;
|
||||
$this->logAudit('stk_activity_sent', 'stk_activity', $id, ['targets' => count($links), 'email_enabled' => $emailOn]);
|
||||
$this->logAudit('stk_activity_sent', 'stk_activity', $id, ['targets' => count($links), 'email_enabled' => $emailOn, 'email_sent' => $emailSent]);
|
||||
$this->jsonSuccess([
|
||||
'sent' => count($links),
|
||||
'email_sent' => false,
|
||||
'email_sent' => $emailSent,
|
||||
'email_enabled' => $emailOn,
|
||||
'links' => $links,
|
||||
'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.',
|
||||
], '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>';
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user