Files
GLPI-Plugin-CVE-Prototype/inc/cvealert.class.php
2025-05-31 10:03:48 +02:00

414 lines
12 KiB
PHP

<?php
/**
* GLPI CVE Plugin - Software Vulnerability Alert Class
* Manages alerts for vulnerable software in inventory
*/
if (!defined('GLPI_ROOT')) {
die("Sorry. You can't access this file directly");
}
/**
* PluginCveCveAlert class for managing vulnerability alerts
*/
class PluginCveCveAlert extends CommonDBTM {
static $rightname = 'plugin_cve_alert';
/**
* Get name of this type by language of the user connected
*
* @param integer $nb number of elements
* @return string name of this type
*/
static function getTypeName($nb = 0) {
return _n('Software Vulnerability Alert', 'Software Vulnerability Alerts', $nb, 'cve');
}
/**
* Define tabs to display
*
* @param array $options
* @return array containing the tabs
*/
function defineTabs($options = []) {
$tabs = [];
$this->addDefaultFormTab($tabs);
$this->addStandardTab('Log', $tabs, $options);
return $tabs;
}
/**
* Display the CVE Alert form
*
* @param integer $ID ID of the item
* @param array $options
* @return boolean
*/
function showForm($ID, $options = []) {
global $CFG_GLPI;
$this->initForm($ID, $options);
$this->showFormHeader($options);
$canedit = $this->can($ID, UPDATE);
echo "<tr class='tab_bg_1'>";
// Software
echo "<td>" . __('Software', 'cve') . "</td>";
echo "<td>";
if ($this->fields['softwares_id'] > 0) {
$software = new Software();
if ($software->getFromDB($this->fields['softwares_id'])) {
echo $software->getLink();
} else {
echo __('Unknown software', 'cve');
}
} else {
echo __('N/A', 'cve');
}
echo "</td>";
// Version
echo "<td>" . __('Version', 'cve') . "</td>";
echo "<td>";
if ($this->fields['softwareversions_id'] > 0) {
$version = new SoftwareVersion();
if ($version->getFromDB($this->fields['softwareversions_id'])) {
echo $version->getName();
} else {
echo __('Unknown version', 'cve');
}
} else {
echo __('N/A', 'cve');
}
echo "</td>";
echo "</tr>";
echo "<tr class='tab_bg_1'>";
// CVE
echo "<td>" . __('CVE', 'cve') . "</td>";
echo "<td>";
if ($this->fields['cves_id'] > 0) {
$cve = new PluginCveCve();
if ($cve->getFromDB($this->fields['cves_id'])) {
echo "<a href='" . PluginCveCve::getFormURLWithID($this->fields['cves_id']) . "'>";
echo $cve->fields['cve_id'];
echo "</a>";
} else {
echo __('Unknown CVE', 'cve');
}
} else {
echo __('N/A', 'cve');
}
echo "</td>";
// Severity
echo "<td>" . __('Severity', 'cve') . "</td>";
echo "<td>";
echo "<span class='" . PluginCveCve::getSeverityClass($this->fields['severity']) . "'>";
echo $this->fields['severity'];
echo "</span>";
echo "</td>";
echo "</tr>";
echo "<tr class='tab_bg_1'>";
// Status
echo "<td>" . __('Status', 'cve') . "</td>";
echo "<td>";
if ($canedit) {
$status_options = [
'NEW' => __('New', 'cve'),
'PROCESSED' => __('Processed', 'cve'),
'IGNORED' => __('Ignored', 'cve')
];
Dropdown::showFromArray('status', $status_options,
['value' => $this->fields['status']]);
} else {
echo $this->fields['status'];
}
echo "</td>";
// Associated ticket
echo "<td>" . __('Ticket', 'cve') . "</td>";
echo "<td>";
if ($this->fields['tickets_id'] > 0) {
$ticket = new Ticket();
if ($ticket->getFromDB($this->fields['tickets_id'])) {
echo "<a href='" . Ticket::getFormURLWithID($this->fields['tickets_id']) . "'>";
echo $ticket->fields['name'] . " (" . $this->fields['tickets_id'] . ")";
echo "</a>";
} else {
echo __('Unknown ticket', 'cve');
}
} else {
if ($canedit && $this->fields['status'] == 'NEW') {
echo "<button type='submit' name='create_ticket' value='1' class='submit'>";
echo __('Create Ticket', 'cve');
echo "</button>";
} else {
echo __('No ticket associated', 'cve');
}
}
echo "</td>";
echo "</tr>";
// Add entity dropdown if needed
echo "<tr class='tab_bg_1'>";
echo "<td>" . __('Entity', 'cve') . "</td>";
echo "<td>";
echo Dropdown::getDropdownName('glpi_entities', $this->fields['entities_id']);
echo "</td>";
// Creation date
echo "<td>" . __('Creation Date', 'cve') . "</td>";
echo "<td>" . Html::convDateTime($this->fields['date_creation']) . "</td>";
echo "</tr>";
$this->showFormButtons($options);
return true;
}
/**
* Actions done after the PURGE of the item in the database
*
* @return void
*/
function post_purgeItem() {
// Nothing special
}
/**
* Actions done after the UPDATE of the item in the database
*
* @param integer $history store changes history ?
* @return void
*/
function post_updateItem($history = 1) {
// If status changed to IGNORED, we might want to do something
if (in_array('status', $this->updates) && $this->fields['status'] == 'IGNORED') {
// Add a record to ignore this specific software-CVE combination in the future
// This would be implemented here
}
}
/**
* Get search function for the class
*
* @return array of search options
*/
function rawSearchOptions() {
$tab = [];
$tab[] = [
'id' => 'common',
'name' => self::getTypeName(2)
];
$tab[] = [
'id' => '1',
'table' => $this->getTable(),
'field' => 'id',
'name' => __('ID', 'cve'),
'massiveaction' => false,
'datatype' => 'number'
];
$tab[] = [
'id' => '2',
'table' => 'glpi_softwares',
'field' => 'name',
'name' => __('Software', 'cve'),
'massiveaction' => false,
'datatype' => 'dropdown',
'joinparams' => [
'jointype' => 'child',
'condition' => 'AND NEWTABLE.`id` = REFTABLE.`softwares_id`'
]
];
$tab[] = [
'id' => '3',
'table' => 'glpi_softwareversions',
'field' => 'name',
'name' => __('Version', 'cve'),
'massiveaction' => false,
'datatype' => 'dropdown',
'joinparams' => [
'jointype' => 'child',
'condition' => 'AND NEWTABLE.`id` = REFTABLE.`softwareversions_id`'
]
];
$tab[] = [
'id' => '4',
'table' => 'glpi_plugin_cve_cves',
'field' => 'cve_id',
'name' => __('CVE ID', 'cve'),
'massiveaction' => false,
'datatype' => 'dropdown',
'joinparams' => [
'jointype' => 'child',
'condition' => 'AND NEWTABLE.`id` = REFTABLE.`cves_id`'
]
];
$tab[] = [
'id' => '5',
'table' => $this->getTable(),
'field' => 'severity',
'name' => __('Severity', 'cve'),
'datatype' => 'specific',
'searchtype' => ['equals', 'notequals']
];
$tab[] = [
'id' => '6',
'table' => $this->getTable(),
'field' => 'status',
'name' => __('Status', 'cve'),
'datatype' => 'specific',
'searchtype' => ['equals', 'notequals']
];
$tab[] = [
'id' => '7',
'table' => 'glpi_tickets',
'field' => 'name',
'name' => __('Ticket', 'cve'),
'massiveaction' => false,
'datatype' => 'dropdown',
'joinparams' => [
'jointype' => 'child',
'condition' => 'AND NEWTABLE.`id` = REFTABLE.`tickets_id`'
]
];
$tab[] = [
'id' => '8',
'table' => 'glpi_entities',
'field' => 'completename',
'name' => __('Entity', 'cve'),
'massiveaction' => false,
'datatype' => 'dropdown'
];
$tab[] = [
'id' => '9',
'table' => $this->getTable(),
'field' => 'date_creation',
'name' => __('Creation date', 'cve'),
'datatype' => 'datetime',
'massiveaction' => false
];
$tab[] = [
'id' => '10',
'table' => $this->getTable(),
'field' => 'date_mod',
'name' => __('Last update', 'cve'),
'datatype' => 'datetime',
'massiveaction' => false
];
return $tab;
}
/**
* Get dashboard count of alerts by severity
*
* @return array
*/
static function getAlertStats() {
global $DB;
$stats = [];
// Count by severity
$query = "SELECT severity, status, COUNT(*) as count
FROM `" . self::getTable() . "`
GROUP BY severity, status";
$result = $DB->query($query);
$stats['by_severity'] = [
'CRITICAL' => 0,
'HIGH' => 0,
'MEDIUM' => 0,
'LOW' => 0
];
$stats['by_status'] = [
'NEW' => 0,
'PROCESSED' => 0,
'IGNORED' => 0
];
if ($result) {
while ($data = $DB->fetchAssoc($result)) {
// Add to severity stats
if (isset($stats['by_severity'][$data['severity']])) {
$stats['by_severity'][$data['severity']] += $data['count'];
}
// Add to status stats
if (isset($stats['by_status'][$data['status']])) {
$stats['by_status'][$data['status']] += $data['count'];
}
}
}
// Get total count
$query = "SELECT COUNT(*) as total FROM `" . self::getTable() . "`";
$result = $DB->query($query);
$stats['total'] = 0;
if ($result && $data = $DB->fetchAssoc($result)) {
$stats['total'] = $data['total'];
}
return $stats;
}
/**
* Get alerts for dashboard
*
* @param integer $limit Maximum number of alerts to return
* @return array
*/
static function getRecentAlerts($limit = 10) {
global $DB;
$alerts = [];
$query = "SELECT a.*,
c.cve_id,
c.severity AS cve_severity,
s.name AS software_name,
v.name AS version_name
FROM `" . self::getTable() . "` AS a
LEFT JOIN `glpi_plugin_cve_cves` AS c ON c.id = a.cves_id
LEFT JOIN `glpi_softwares` AS s ON s.id = a.softwares_id
LEFT JOIN `glpi_softwareversions` AS v ON v.id = a.softwareversions_id
ORDER BY a.date_creation DESC
LIMIT $limit";
$result = $DB->query($query);
if ($result) {
while ($data = $DB->fetchAssoc($result)) {
$alerts[] = $data;
}
}
return $alerts;
}
}