[FEAT] Permessi documentali profilabili per ruolo (Impostazioni > Permessi documentali) — v1.24.2

I diritti sul ciclo di vita dei documenti SGSI sono ora configurabili per ruolo (matrice Ruolo x Azione, org-scoped) invece che cablati.
- mig.061: tabella isms_doc_permissions (org x capability x role x allowed)
- IsmsModelController: requireDocCapability() sostituisce i requireOrgRole cablati nelle 7 transizioni (edit/submit/approve/publish/reject/archive/new_version); default = comportamento precedente; super_admin sempre ok; endpoint GET/PUT /api/isms/docPermissions (canEdit)
- settings.html: tab 'Permessi documentali' con matrice checkbox (lettura per i membri, salvataggio Amministratore/Direzione)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
DevEnv nis2-agile
2026-06-20 14:25:07 +02:00
co-authored by Claude Opus 4.8
parent d29195b2c1
commit e245e76671
6 changed files with 188 additions and 10 deletions
+2
View File
@@ -302,6 +302,8 @@ $actionMap = [
'GET:documents/{subId}/versions' => 'documentVersions',
'GET:documents/{subId}/word' => 'exportDocumentWord', // download .doc Word
'GET:documentsWordAll' => 'exportAllDocumentsWord', // download UNICO di tutto il set
'GET:docPermissions' => 'getDocPermissions', // matrice permessi documentali (Ruolo x Azione)
'PUT:docPermissions' => 'saveDocPermissions',
'GET:documents/{subId}' => 'getDocument', // documento completo (editor) — {subId} numerico
'GET:readiness' => 'readiness',
'GET:export' => 'export',
+60 -2
View File
@@ -229,6 +229,7 @@
<button class="settings-tab" onclick="switchTab('profile')">Profilo</button>
<button class="settings-tab" onclick="switchTab('preferences')">Preferenze</button>
<button class="settings-tab" onclick="switchTab('members')">Membri</button>
<button class="settings-tab" onclick="switchTab('docperms')">Permessi documentali</button>
<button class="settings-tab" onclick="switchTab('security')">Sicurezza</button>
<button class="settings-tab" onclick="switchTab('apikeys')">API Keys</button>
<button class="settings-tab" onclick="switchTab('webhooks')">Webhook</button>
@@ -521,6 +522,21 @@
</div>
</div>
<!-- ══════════════ TAB: Permessi documentali ══════════════ -->
<div class="tab-panel" id="tab-docperms">
<div class="card">
<div class="card-header"><h3>Permessi documentali (ciclo di vita SGSI)</h3></div>
<div class="card-body">
<p class="text-muted" style="margin-top:0;">Definisci quali <strong>ruoli</strong> possono compiere ogni azione del ciclo di vita dei documenti del Modello SGSI (ISO/IEC 27001 cl. 7.5). La configurazione vale per questa organizzazione; il super amministratore ha sempre tutti i permessi.</p>
<div id="docperms-matrix"><div class="spinner" style="margin:30px auto;"></div></div>
<div style="display:flex;justify-content:space-between;align-items:center;margin-top:14px;">
<span id="docperms-hint" class="text-muted" style="font-size:.85rem;"></span>
<button class="btn btn-primary" id="docperms-save" onclick="saveDocPerms()">Salva permessi</button>
</div>
</div>
</div>
</div>
<!-- ══════════════ TAB: Sicurezza ══════════════ -->
<div class="tab-panel" id="tab-security">
<div class="card mb-24">
@@ -718,8 +734,8 @@
document.querySelectorAll('.settings-tab').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
const tabMap = { org: 0, profile: 1, preferences: 2, members: 3, security: 4, apikeys: 5, webhooks: 6, branding: 7 };
const panelMap = { org: 'tab-org', profile: 'tab-profile', preferences: 'tab-preferences', members: 'tab-members', security: 'tab-security', apikeys: 'tab-apikeys', webhooks: 'tab-webhooks', branding: 'tab-branding' };
const tabMap = { org: 0, profile: 1, preferences: 2, members: 3, docperms: 4, security: 5, apikeys: 6, webhooks: 7, branding: 8 };
const panelMap = { org: 'tab-org', profile: 'tab-profile', preferences: 'tab-preferences', members: 'tab-members', docperms: 'tab-docperms', security: 'tab-security', apikeys: 'tab-apikeys', webhooks: 'tab-webhooks', branding: 'tab-branding' };
document.querySelectorAll('.settings-tab')[tabMap[tab]].classList.add('active');
document.getElementById(panelMap[tab]).classList.add('active');
@@ -729,9 +745,51 @@
if (tab === 'security') { loadSessions(); loadAuditLog(); }
if (tab === 'apikeys') loadApiKeys();
if (tab === 'webhooks') { loadWebhooks(); loadDeliveries(); }
if (tab === 'docperms') loadDocPerms();
if (tab === 'branding') loadBranding();
}
// ── Permessi documentali (matrice Ruolo x Azione) ──
const DOCPERM_CAP_LABELS = { edit:'Modifica contenuto', submit:'Invia in revisione', approve:'Approva', publish:'Pubblica', reject:'Rimanda in bozza', archive:'Archivia', new_version:'Nuova versione' };
const DOCPERM_ROLE_LABELS = { org_admin:'Amministratore', compliance_manager:'Resp. SGSI/Compliance', board_member:'Direzione/CdA', auditor:'Auditor', employee:'Dipendente', consultant:'Consulente' };
let _docpermCanEdit = false;
async function loadDocPerms(){
const box = document.getElementById('docperms-matrix');
box.innerHTML = '<div class="spinner" style="margin:30px auto;"></div>';
const r = await api.get('/isms/docPermissions');
if (!r || !r.success){ box.innerHTML = '<p class="text-muted">'+((r&&r.message)||'Errore')+' &mdash; richiede un Modello SGSI avviato.</p>'; return; }
const caps = r.data.capabilities || [];
const roles = r.data.roleList || [];
_docpermCanEdit = !!r.data.canEdit;
let h = '<div style="overflow-x:auto;"><table style="width:100%;border-collapse:collapse;font-size:.88rem;">';
h += '<tr><th style="text-align:left;padding:8px;border-bottom:2px solid #e5e7eb;">Azione</th>';
roles.forEach(function(role){ h += '<th style="padding:8px;border-bottom:2px solid #e5e7eb;text-align:center;">'+(DOCPERM_ROLE_LABELS[role]||role)+'</th>'; });
h += '</tr>';
caps.forEach(function(c){
h += '<tr><td style="padding:8px;border-bottom:1px solid #f1f5f9;font-weight:600;">'+(DOCPERM_CAP_LABELS[c.capability]||c.capability)+'</td>';
roles.forEach(function(role){
const checked = c.roles[role] ? 'checked' : '';
const dis = _docpermCanEdit ? '' : 'disabled';
h += '<td style="padding:8px;border-bottom:1px solid #f1f5f9;text-align:center;"><input type="checkbox" data-cap="'+c.capability+'" data-role="'+role+'" '+checked+' '+dis+' style="width:18px;height:18px;cursor:pointer;"></td>';
});
h += '</tr>';
});
box.innerHTML = h + '</table></div>';
const saveBtn = document.getElementById('docperms-save');
const hint = document.getElementById('docperms-hint');
if (saveBtn) saveBtn.style.display = _docpermCanEdit ? '' : 'none';
if (hint) hint.textContent = _docpermCanEdit ? 'Suggerimento: lascia almeno un ruolo per ogni azione critica (approva, pubblica).' : 'Solo Amministratore o Direzione possono modificare i permessi.';
}
async function saveDocPerms(){
if (!_docpermCanEdit) return;
const boxes = document.querySelectorAll('#docperms-matrix input[type=checkbox]');
const entries = [];
boxes.forEach(function(b){ entries.push({ capability:b.getAttribute('data-cap'), role:b.getAttribute('data-role'), allowed:b.checked }); });
const hint = document.getElementById('docperms-hint'); hint.textContent = 'Salvataggio…';
const r = await api.put('/isms/docPermissions', { entries: entries });
hint.textContent = (r && r.success) ? 'Permessi salvati ✓' : ((r && r.message) || 'Errore nel salvataggio');
}
// ── Preferenze utente (Fase 4 / G12 — UI) ─────────────────
async function loadPreferences() {
try {
+1 -1
View File
@@ -1 +1 @@
{"version": "1.24.1", "build": "2026-06-20-v1.24.1", "date": "2026-06-20", "changelog": "Gestione documentale ISMS — completata con: (1) download UNICO dell'intero set documentale in un solo file Word (.doc) con copertina e indice (endpoint /api/isms/documentsWordAll); (2) editor in-app del contenuto per le bozze e i documenti in revisione (barra strumenti grassetto/titoli/elenchi + modalita HTML), con guard che impedisce la modifica diretta dei documenti pubblicati (creare prima una nuova versione). Endpoint GET /api/isms/documents/{id} per il caricamento del documento nell'editor. Permessi di modifica estesi a board_member."}
{"version": "1.24.2", "build": "2026-06-20-v1.24.2", "date": "2026-06-20", "changelog": "Permessi documentali profilabili per ruolo. I diritti sul ciclo di vita dei documenti SGSI (modifica, invia in revisione, approva, pubblica, rimanda in bozza, archivia, nuova versione) non sono piu cablati: si configurano in Impostazioni > Permessi documentali con una matrice Ruolo x Azione (org_admin, compliance_manager, board_member, auditor, employee, consultant), per organizzazione. Migrazione 061 (tabella isms_doc_permissions). I controlli di ruolo nelle 7 transizioni usano ora requireDocCapability (con default = comportamento precedente); super_admin sempre consentito. Endpoint GET/PUT /api/isms/docPermissions (lettura aperta ai membri, scrittura ad Amministratore/Direzione)."}