nis2-agile/application/controllers/PolicyController.php
Cristiano Benassati ae78a2f7f4 [CORE] Initial project scaffold - NIS2 Agile Compliance Platform
Complete MVP implementation including:
- PHP 8.4 backend with Front Controller pattern (80+ API endpoints)
- Multi-tenant architecture with organization_id isolation
- JWT authentication (HS256, 2h access + 7d refresh tokens)
- 14 controllers: Auth, Organization, Assessment, Dashboard, Risk,
  Incident, Policy, SupplyChain, Training, Asset, Audit, Admin
- AI Service integration (Anthropic Claude API) for gap analysis,
  risk suggestions, policy generation, incident classification
- NIS2 gap analysis questionnaire (~80 questions, 10 categories)
- MySQL schema (20 tables) with NIS2 Art. 21 compliance controls
- NIS2 Art. 23 incident reporting workflow (24h/72h/30d)
- Frontend: login, register, dashboard, assessment wizard, org setup
- Docker configuration (PHP-FPM + Nginx + MySQL)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 17:50:18 +01:00

171 lines
6.2 KiB
PHP

<?php
/**
* NIS2 Agile - Policy Controller
*
* Gestione policy e procedure di sicurezza, con AI generation.
*/
require_once __DIR__ . '/BaseController.php';
require_once APP_PATH . '/services/AIService.php';
class PolicyController extends BaseController
{
public function list(): void
{
$this->requireOrgAccess();
$where = 'organization_id = ?';
$params = [$this->getCurrentOrgId()];
if ($this->hasParam('status')) {
$where .= ' AND status = ?';
$params[] = $this->getParam('status');
}
if ($this->hasParam('category')) {
$where .= ' AND category = ?';
$params[] = $this->getParam('category');
}
$policies = Database::fetchAll(
"SELECT p.*, u.full_name as approved_by_name
FROM policies p
LEFT JOIN users u ON u.id = p.approved_by
WHERE p.{$where}
ORDER BY p.category, p.title",
$params
);
$this->jsonSuccess($policies);
}
public function create(): void
{
$this->requireOrgRole(['org_admin', 'compliance_manager']);
$this->validateRequired(['title', 'category']);
$policyId = Database::insert('policies', [
'organization_id' => $this->getCurrentOrgId(),
'title' => trim($this->getParam('title')),
'category' => $this->getParam('category'),
'nis2_article' => $this->getParam('nis2_article'),
'content' => $this->getParam('content'),
'next_review_date' => $this->getParam('next_review_date'),
'ai_generated' => $this->getParam('ai_generated', 0),
]);
$this->logAudit('policy_created', 'policy', $policyId);
$this->jsonSuccess(['id' => $policyId], 'Policy creata', 201);
}
public function get(int $id): void
{
$this->requireOrgAccess();
$policy = Database::fetchOne(
'SELECT p.*, u.full_name as approved_by_name
FROM policies p
LEFT JOIN users u ON u.id = p.approved_by
WHERE p.id = ? AND p.organization_id = ?',
[$id, $this->getCurrentOrgId()]
);
if (!$policy) {
$this->jsonError('Policy non trovata', 404, 'POLICY_NOT_FOUND');
}
$this->jsonSuccess($policy);
}
public function update(int $id): void
{
$this->requireOrgRole(['org_admin', 'compliance_manager']);
$updates = [];
foreach (['title', 'content', 'category', 'nis2_article', 'status', 'version', 'next_review_date'] as $field) {
if ($this->hasParam($field)) {
$updates[$field] = $this->getParam($field);
}
}
if (!empty($updates)) {
Database::update('policies', $updates, 'id = ? AND organization_id = ?', [$id, $this->getCurrentOrgId()]);
$this->logAudit('policy_updated', 'policy', $id, $updates);
}
$this->jsonSuccess($updates, 'Policy aggiornata');
}
public function delete(int $id): void
{
$this->requireOrgRole(['org_admin']);
$deleted = Database::delete('policies', 'id = ? AND organization_id = ?', [$id, $this->getCurrentOrgId()]);
if ($deleted === 0) {
$this->jsonError('Policy non trovata', 404, 'POLICY_NOT_FOUND');
}
$this->logAudit('policy_deleted', 'policy', $id);
$this->jsonSuccess(null, 'Policy eliminata');
}
public function approve(int $id): void
{
$this->requireOrgRole(['org_admin']);
Database::update('policies', [
'status' => 'approved',
'approved_by' => $this->getCurrentUserId(),
'approved_at' => date('Y-m-d H:i:s'),
], 'id = ? AND organization_id = ?', [$id, $this->getCurrentOrgId()]);
$this->logAudit('policy_approved', 'policy', $id);
$this->jsonSuccess(null, 'Policy approvata');
}
public function aiGeneratePolicy(): void
{
$this->requireOrgRole(['org_admin', 'compliance_manager']);
$this->validateRequired(['category']);
$category = $this->getParam('category');
$org = Database::fetchOne('SELECT * FROM organizations WHERE id = ?', [$this->getCurrentOrgId()]);
try {
$aiService = new AIService();
$generated = $aiService->generatePolicy($category, $org);
$aiService->logInteraction(
$this->getCurrentOrgId(),
$this->getCurrentUserId(),
'policy_draft',
"Generate {$category} policy",
substr($generated['title'] ?? '', 0, 500)
);
$this->jsonSuccess($generated, 'Policy generata dall\'AI');
} catch (Throwable $e) {
$this->jsonError('Errore AI: ' . $e->getMessage(), 500, 'AI_ERROR');
}
}
public function getTemplates(): void
{
$this->requireOrgAccess();
$templates = [
['category' => 'information_security', 'title' => 'Politica di Sicurezza delle Informazioni', 'nis2_article' => '21.2.a'],
['category' => 'access_control', 'title' => 'Politica di Controllo degli Accessi', 'nis2_article' => '21.2.i'],
['category' => 'incident_response', 'title' => 'Piano di Risposta agli Incidenti', 'nis2_article' => '21.2.b'],
['category' => 'business_continuity', 'title' => 'Piano di Continuità Operativa', 'nis2_article' => '21.2.c'],
['category' => 'supply_chain', 'title' => 'Politica di Sicurezza della Supply Chain', 'nis2_article' => '21.2.d'],
['category' => 'encryption', 'title' => 'Politica sulla Crittografia', 'nis2_article' => '21.2.h'],
['category' => 'hr_security', 'title' => 'Politica di Sicurezza delle Risorse Umane', 'nis2_article' => '21.2.i'],
['category' => 'asset_management', 'title' => 'Politica di Gestione degli Asset', 'nis2_article' => '21.2.i'],
['category' => 'network_security', 'title' => 'Politica di Sicurezza della Rete', 'nis2_article' => '21.2.e'],
['category' => 'vulnerability_management', 'title' => 'Politica di Gestione delle Vulnerabilità', 'nis2_article' => '21.2.e'],
];
$this->jsonSuccess($templates);
}
}