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>
161 lines
5.5 KiB
PHP
161 lines
5.5 KiB
PHP
<?php
|
|
/**
|
|
* NIS2 Agile - Asset Controller
|
|
*
|
|
* Inventario asset IT/OT, classificazione, dipendenze.
|
|
*/
|
|
|
|
require_once __DIR__ . '/BaseController.php';
|
|
|
|
class AssetController extends BaseController
|
|
{
|
|
public function list(): void
|
|
{
|
|
$this->requireOrgAccess();
|
|
$pagination = $this->getPagination();
|
|
|
|
$where = 'organization_id = ?';
|
|
$params = [$this->getCurrentOrgId()];
|
|
|
|
if ($this->hasParam('asset_type')) {
|
|
$where .= ' AND asset_type = ?';
|
|
$params[] = $this->getParam('asset_type');
|
|
}
|
|
if ($this->hasParam('criticality')) {
|
|
$where .= ' AND criticality = ?';
|
|
$params[] = $this->getParam('criticality');
|
|
}
|
|
if ($this->hasParam('status')) {
|
|
$where .= ' AND status = ?';
|
|
$params[] = $this->getParam('status');
|
|
}
|
|
|
|
$total = Database::count('assets', $where, $params);
|
|
$assets = Database::fetchAll(
|
|
"SELECT a.*, u.full_name as owner_name
|
|
FROM assets a
|
|
LEFT JOIN users u ON u.id = a.owner_user_id
|
|
WHERE a.{$where}
|
|
ORDER BY a.criticality DESC, a.name
|
|
LIMIT {$pagination['per_page']} OFFSET {$pagination['offset']}",
|
|
$params
|
|
);
|
|
|
|
$this->jsonPaginated($assets, $total, $pagination['page'], $pagination['per_page']);
|
|
}
|
|
|
|
public function create(): void
|
|
{
|
|
$this->requireOrgRole(['org_admin', 'compliance_manager']);
|
|
$this->validateRequired(['name', 'asset_type']);
|
|
|
|
$assetId = Database::insert('assets', [
|
|
'organization_id' => $this->getCurrentOrgId(),
|
|
'name' => trim($this->getParam('name')),
|
|
'asset_type' => $this->getParam('asset_type'),
|
|
'category' => $this->getParam('category'),
|
|
'description' => $this->getParam('description'),
|
|
'criticality' => $this->getParam('criticality', 'medium'),
|
|
'owner_user_id' => $this->getParam('owner_user_id'),
|
|
'location' => $this->getParam('location'),
|
|
'ip_address' => $this->getParam('ip_address'),
|
|
'vendor' => $this->getParam('vendor'),
|
|
'version' => $this->getParam('version'),
|
|
'serial_number' => $this->getParam('serial_number'),
|
|
'purchase_date' => $this->getParam('purchase_date'),
|
|
'warranty_expiry' => $this->getParam('warranty_expiry'),
|
|
'dependencies' => $this->getParam('dependencies') ? json_encode($this->getParam('dependencies')) : null,
|
|
]);
|
|
|
|
$this->logAudit('asset_created', 'asset', $assetId);
|
|
$this->jsonSuccess(['id' => $assetId], 'Asset registrato', 201);
|
|
}
|
|
|
|
public function get(int $id): void
|
|
{
|
|
$this->requireOrgAccess();
|
|
|
|
$asset = Database::fetchOne(
|
|
'SELECT a.*, u.full_name as owner_name
|
|
FROM assets a LEFT JOIN users u ON u.id = a.owner_user_id
|
|
WHERE a.id = ? AND a.organization_id = ?',
|
|
[$id, $this->getCurrentOrgId()]
|
|
);
|
|
|
|
if (!$asset) {
|
|
$this->jsonError('Asset non trovato', 404, 'ASSET_NOT_FOUND');
|
|
}
|
|
|
|
$this->jsonSuccess($asset);
|
|
}
|
|
|
|
public function update(int $id): void
|
|
{
|
|
$this->requireOrgRole(['org_admin', 'compliance_manager']);
|
|
|
|
$updates = [];
|
|
$fields = ['name', 'asset_type', 'category', 'description', 'criticality',
|
|
'owner_user_id', 'location', 'ip_address', 'vendor', 'version',
|
|
'serial_number', 'purchase_date', 'warranty_expiry', 'status'];
|
|
|
|
foreach ($fields as $field) {
|
|
if ($this->hasParam($field)) {
|
|
$updates[$field] = $this->getParam($field);
|
|
}
|
|
}
|
|
|
|
if ($this->hasParam('dependencies')) {
|
|
$updates['dependencies'] = json_encode($this->getParam('dependencies'));
|
|
}
|
|
|
|
if (!empty($updates)) {
|
|
Database::update('assets', $updates, 'id = ? AND organization_id = ?', [$id, $this->getCurrentOrgId()]);
|
|
$this->logAudit('asset_updated', 'asset', $id, $updates);
|
|
}
|
|
|
|
$this->jsonSuccess($updates, 'Asset aggiornato');
|
|
}
|
|
|
|
public function delete(int $id): void
|
|
{
|
|
$this->requireOrgRole(['org_admin']);
|
|
$deleted = Database::delete('assets', 'id = ? AND organization_id = ?', [$id, $this->getCurrentOrgId()]);
|
|
if ($deleted === 0) {
|
|
$this->jsonError('Asset non trovato', 404, 'ASSET_NOT_FOUND');
|
|
}
|
|
$this->logAudit('asset_deleted', 'asset', $id);
|
|
$this->jsonSuccess(null, 'Asset eliminato');
|
|
}
|
|
|
|
public function dependencyMap(): void
|
|
{
|
|
$this->requireOrgAccess();
|
|
|
|
$assets = Database::fetchAll(
|
|
'SELECT id, name, asset_type, criticality, dependencies
|
|
FROM assets
|
|
WHERE organization_id = ? AND status = "active"',
|
|
[$this->getCurrentOrgId()]
|
|
);
|
|
|
|
$nodes = [];
|
|
$edges = [];
|
|
|
|
foreach ($assets as $asset) {
|
|
$nodes[] = [
|
|
'id' => $asset['id'],
|
|
'label' => $asset['name'],
|
|
'type' => $asset['asset_type'],
|
|
'criticality' => $asset['criticality'],
|
|
];
|
|
|
|
$deps = json_decode($asset['dependencies'] ?? '[]', true) ?: [];
|
|
foreach ($deps as $depId) {
|
|
$edges[] = ['from' => $asset['id'], 'to' => (int) $depId];
|
|
}
|
|
}
|
|
|
|
$this->jsonSuccess(['nodes' => $nodes, 'edges' => $edges]);
|
|
}
|
|
}
|