Start repository

This commit is contained in:
tips-of-mine
2025-05-31 10:03:48 +02:00
commit 194322c9fc
57 changed files with 14723 additions and 0 deletions

3
.bolt/config.json Normal file
View File

@ -0,0 +1,3 @@
{
"template": "bolt-vite-react-ts"
}

5
.bolt/prompt Normal file
View File

@ -0,0 +1,5 @@
For all designs I ask you to make, have them be beautiful, not cookie cutter. Make webpages that are fully featured and worthy for production.
By default, this template supports JSX syntax with Tailwind CSS classes, React hooks, and Lucide React for icons. Do not install other packages for UI themes, icons, etc unless absolutely necessary or I request them.
Use icons from lucide-react for logos.

25
.gitignore vendored Normal file
View File

@ -0,0 +1,25 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
.env

1
README.md Normal file
View File

@ -0,0 +1 @@
GLPI-Plugin-CVE-Prototype

66
ajax/sync_now.php Normal file
View File

@ -0,0 +1,66 @@
<?php
/**
* GLPI CVE Plugin - Immediate Sync Handler
* This file handles immediate synchronization requests
*/
include ("../../../inc/includes.php");
// Check CSRF token
Session::checkLoginUser();
Session::checkRight("config", UPDATE);
// Force no time limit for long-running operations
set_time_limit(0);
// Start synchronization of all active sources
$source = new PluginCveCveSource();
$sources = $source->find(['is_active' => 1]);
$success = true;
$count = 0;
foreach ($sources as $data) {
$source->getFromDB($data['id']);
// Log the start of sync
Toolbox::logInFile('cve_plugin', sprintf('Starting synchronization of source: %s (ID: %d)',
$data['name'], $data['id']));
// Update source status to in progress
$source->update([
'id' => $data['id'],
'sync_status' => 'IN_PROGRESS',
]);
try {
$result = $source->syncNow($data['id']);
$count += $result ? 1 : 0;
$success = $success && $result;
// Log the result
Toolbox::logInFile('cve_plugin', sprintf('Sync of source %s %s',
$data['name'], ($result ? 'succeeded' : 'failed')));
} catch (Exception $e) {
$success = false;
// Log the error
Toolbox::logInFile('cve_plugin', sprintf('Error during sync of source %s: %s',
$data['name'], $e->getMessage()), true);
// Update source status to failed
$source->update([
'id' => $data['id'],
'sync_status' => 'FAILED',
'last_sync' => $_SESSION['glpi_currenttime']
]);
}
}
// Log the overall result
if ($count > 0) {
Toolbox::logInFile('cve_plugin', sprintf('CVE synchronization completed: %d sources processed with %s',
$count, ($success ? 'success' : 'some failures')));
}
// No HTML output needed for background task

28
eslint.config.js Normal file
View File

@ -0,0 +1,28 @@
import js from '@eslint/js';
import globals from 'globals';
import reactHooks from 'eslint-plugin-react-hooks';
import reactRefresh from 'eslint-plugin-react-refresh';
import tseslint from 'typescript-eslint';
export default tseslint.config(
{ ignores: ['dist'] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
}
);

95
front/alert.form.php Normal file
View File

@ -0,0 +1,95 @@
<?php
/**
* GLPI CVE Plugin - Vulnerability Alert Form
*/
include ("../../../inc/includes.php");
Session::checkRight("plugin_cve_alert", READ);
$alert = new PluginCveCveAlert();
if (isset($_POST['create_ticket']) && isset($_POST['id'])) {
$alert->getFromDB($_POST['id']);
// Process alert to create a ticket
PluginCveCveInventory::processAlert($_POST['id']);
Html::back();
} else if (isset($_POST["add"])) {
$alert->check(-1, CREATE, $_POST);
if ($alert->add($_POST)) {
Event::log(
$alert->fields['id'],
"plugin_cve_alert",
4,
"inventory",
sprintf(__('%1$s adds the vulnerability alert %2$s'), $_SESSION["glpiname"], $_POST["name"])
);
}
Html::back();
} else if (isset($_POST["delete"])) {
$alert->check($_POST["id"], DELETE);
if ($alert->delete($_POST)) {
Event::log(
$_POST["id"],
"plugin_cve_alert",
4,
"inventory",
sprintf(__('%1$s deletes the vulnerability alert %2$s'), $_SESSION["glpiname"], $_POST["id"])
);
}
$alert->redirectToList();
} else if (isset($_POST["restore"])) {
$alert->check($_POST["id"], DELETE);
if ($alert->restore($_POST)) {
Event::log(
$_POST["id"],
"plugin_cve_alert",
4,
"inventory",
sprintf(__('%1$s restores the vulnerability alert %2$s'), $_SESSION["glpiname"], $_POST["id"])
);
}
$alert->redirectToList();
} else if (isset($_POST["purge"])) {
$alert->check($_POST["id"], PURGE);
if ($alert->delete($_POST, 1)) {
Event::log(
$_POST["id"],
"plugin_cve_alert",
4,
"inventory",
sprintf(__('%1$s purges the vulnerability alert %2$s'), $_SESSION["glpiname"], $_POST["id"])
);
}
$alert->redirectToList();
} else if (isset($_POST["update"])) {
$alert->check($_POST["id"], UPDATE);
if ($alert->update($_POST)) {
Event::log(
$_POST["id"],
"plugin_cve_alert",
4,
"inventory",
sprintf(__('%1$s updates the vulnerability alert %2$s'), $_SESSION["glpiname"], $_POST["id"])
);
}
Html::back();
} else {
Html::header(
PluginCveCveAlert::getTypeName(Session::getPluralNumber()),
$_SERVER['PHP_SELF'],
"tools",
"PluginCveCveMenu",
"alert"
);
$id = 0;
if (isset($_GET["id"])) {
$id = $_GET["id"];
}
$alert->display(['id' => $id]);
Html::footer();
}

20
front/alert.php Normal file
View File

@ -0,0 +1,20 @@
<?php
/**
* GLPI CVE Plugin - Vulnerability Alerts List
*/
include ("../../../inc/includes.php");
Session::checkRight("plugin_cve_alert", READ);
Html::header(
PluginCveCveAlert::getTypeName(Session::getPluralNumber()),
$_SERVER['PHP_SELF'],
"tools",
"PluginCveCveMenu",
"alert"
);
Search::show('PluginCveCveAlert');
Html::footer();

88
front/cvesource.form.php Normal file
View File

@ -0,0 +1,88 @@
<?php
/**
* GLPI CVE Plugin - CVE Source Form
*/
include ("../../../inc/includes.php");
Session::checkRight("plugin_cve_source", READ);
$source = new PluginCveCveSource();
if (isset($_POST["add"])) {
$source->check(-1, CREATE, $_POST);
if ($source->add($_POST)) {
Event::log(
$source->fields['id'],
"plugin_cve_source",
4,
"setup",
sprintf(__('%1$s adds the CVE source %2$s'), $_SESSION["glpiname"], $_POST["name"])
);
}
Html::back();
} else if (isset($_POST["delete"])) {
$source->check($_POST["id"], DELETE);
if ($source->delete($_POST)) {
Event::log(
$_POST["id"],
"plugin_cve_source",
4,
"setup",
sprintf(__('%1$s deletes the CVE source %2$s'), $_SESSION["glpiname"], $_POST["id"])
);
}
$source->redirectToList();
} else if (isset($_POST["restore"])) {
$source->check($_POST["id"], DELETE);
if ($source->restore($_POST)) {
Event::log(
$_POST["id"],
"plugin_cve_source",
4,
"setup",
sprintf(__('%1$s restores the CVE source %2$s'), $_SESSION["glpiname"], $_POST["id"])
);
}
$source->redirectToList();
} else if (isset($_POST["purge"])) {
$source->check($_POST["id"], PURGE);
if ($source->delete($_POST, 1)) {
Event::log(
$_POST["id"],
"plugin_cve_source",
4,
"setup",
sprintf(__('%1$s purges the CVE source %2$s'), $_SESSION["glpiname"], $_POST["id"])
);
}
$source->redirectToList();
} else if (isset($_POST["update"])) {
$source->check($_POST["id"], UPDATE);
if ($source->update($_POST)) {
Event::log(
$_POST["id"],
"plugin_cve_source",
4,
"setup",
sprintf(__('%1$s updates the CVE source %2$s'), $_SESSION["glpiname"], $_POST["id"])
);
}
Html::back();
} else {
Html::header(
PluginCveCveSource::getTypeName(Session::getPluralNumber()),
$_SERVER['PHP_SELF'],
"tools",
"PluginCveCveMenu",
"cvesource"
);
$id = 0;
if (isset($_GET["id"])) {
$id = $_GET["id"];
}
$source->display(['id' => $id]);
Html::footer();
}

20
front/cvesource.php Normal file
View File

@ -0,0 +1,20 @@
<?php
/**
* GLPI CVE Plugin - CVE Sources List
*/
include ("../../../inc/includes.php");
Session::checkRight("plugin_cve_source", READ);
Html::header(
PluginCveCveSource::getTypeName(Session::getPluralNumber()),
$_SERVER['PHP_SELF'],
"tools",
"PluginCveCveMenu",
"cvesource"
);
Search::show('PluginCveCveSource');
Html::footer();

162
front/inventory.php Normal file
View File

@ -0,0 +1,162 @@
<?php
/**
* GLPI CVE Plugin - Software Inventory Analysis Page
*/
include ("../../../inc/includes.php");
Session::checkRight("plugin_cve_inventory", READ);
Html::header(
PluginCveCveInventory::getTypeName(Session::getPluralNumber()),
$_SERVER['PHP_SELF'],
"tools",
"PluginCveCveMenu",
"inventory"
);
// Manual scan trigger
if (isset($_POST['scan_now']) && Session::haveRight("plugin_cve_inventory", UPDATE)) {
$task = new CronTask();
if ($task->getFromDBbyName('PluginCveCveInventory', 'AnalyzeInventory')) {
$task_id = $task->fields['id'];
// Execute the task
$result = PluginCveCveInventory::cronAnalyzeInventory($task);
if ($result) {
Session::addMessageAfterRedirect(
__('Software vulnerability analysis completed successfully.', 'cve'),
true,
INFO
);
} else {
Session::addMessageAfterRedirect(
__('Software vulnerability analysis completed with no new alerts.', 'cve'),
true,
INFO
);
}
} else {
Session::addMessageAfterRedirect(
__('Software vulnerability analysis task not found.', 'cve'),
true,
ERROR
);
}
Html::redirect($_SERVER['PHP_SELF']);
} else {
$alert = new PluginCveCveAlert();
// Get alert statistics
$stats = PluginCveCveAlert::getAlertStats();
echo "<div class='center'>";
echo "<table class='tab_cadre_fixe'>";
echo "<tr class='tab_bg_2'><th colspan='2'>" . __('Software Vulnerability Analysis', 'cve') . "</th></tr>";
// Show manual scan button
if (Session::haveRight("plugin_cve_inventory", UPDATE)) {
echo "<tr class='tab_bg_1'><td colspan='2' class='center'>";
echo "<form method='post' action='" . $_SERVER['PHP_SELF'] . "'>";
echo "<input type='submit' name='scan_now' value=\"" . __('Scan Software Inventory Now', 'cve') . "\" class='submit'>";
Html::closeForm();
echo "</td></tr>";
}
// Show statistics
echo "<tr class='tab_bg_2'>";
echo "<td>" . __('Total Vulnerability Alerts', 'cve') . "</td>";
echo "<td>" . $stats['total'] . "</td>";
echo "</tr>";
echo "<tr class='tab_bg_2'>";
echo "<td>" . __('New Alerts', 'cve') . "</td>";
echo "<td>" . $stats['by_status']['NEW'] . "</td>";
echo "</tr>";
echo "<tr class='tab_bg_2'>";
echo "<td>" . __('Critical Vulnerabilities', 'cve') . "</td>";
echo "<td>" . $stats['by_severity']['CRITICAL'] . "</td>";
echo "</tr>";
echo "<tr class='tab_bg_2'>";
echo "<td>" . __('High Vulnerabilities', 'cve') . "</td>";
echo "<td>" . $stats['by_severity']['HIGH'] . "</td>";
echo "</tr>";
echo "</table>";
echo "</div>";
// Show recent alerts
$alerts = PluginCveCveAlert::getRecentAlerts(10);
echo "<div class='center'>";
echo "<table class='tab_cadre_fixe'>";
echo "<tr class='tab_bg_2'><th colspan='6'>" . __('Recent Vulnerability Alerts', 'cve') . "</th></tr>";
echo "<tr class='tab_bg_1'>";
echo "<th>" . __('Software', 'cve') . "</th>";
echo "<th>" . __('Version', 'cve') . "</th>";
echo "<th>" . __('CVE ID', 'cve') . "</th>";
echo "<th>" . __('Severity', 'cve') . "</th>";
echo "<th>" . __('Status', 'cve') . "</th>";
echo "<th>" . __('Date', 'cve') . "</th>";
echo "</tr>";
if (empty($alerts)) {
echo "<tr class='tab_bg_1'><td colspan='6' class='center'>" . __('No alerts found', 'cve') . "</td></tr>";
} else {
foreach ($alerts as $alert_data) {
echo "<tr class='tab_bg_1'>";
// Software
echo "<td>";
echo $alert_data['software_name'];
echo "</td>";
// Version
echo "<td>";
echo $alert_data['version_name'];
echo "</td>";
// CVE ID
echo "<td>";
echo "<a href='" . PluginCveCve::getFormURLWithID($alert_data['cves_id']) . "'>";
echo $alert_data['cve_id'];
echo "</a>";
echo "</td>";
// Severity
echo "<td>";
echo "<span class='" . PluginCveCve::getSeverityClass($alert_data['severity']) . "'>";
echo $alert_data['severity'];
echo "</span>";
echo "</td>";
// Status
echo "<td>";
echo $alert_data['status'];
echo "</td>";
// Date
echo "<td>";
echo Html::convDateTime($alert_data['date_creation']);
echo "</td>";
echo "</tr>";
}
}
echo "</table>";
// Link to all alerts
echo "<div class='center'>";
echo "<a href='alert.php'>" . __('View all vulnerability alerts', 'cve') . "</a>";
echo "</div>";
echo "</div>";
}
Html::footer();

171
hook.php Normal file
View File

@ -0,0 +1,171 @@
<?php
/**
* GLPI CVE Plugin - Hook functions
* Contains installation and uninstallation functions
*/
if (!defined('GLPI_ROOT')) {
die("Sorry. You can't access this file directly");
}
/**
* Hook called when a ticket is created
*
* @param Ticket $ticket
*/
function plugin_cve_item_add_ticket(Ticket $ticket) {
// Check if the ticket is related to a CVE
// This would be implemented with actual logic to link tickets with CVEs
}
/**
* Hook called when a ticket is updated
*
* @param Ticket $ticket
*/
function plugin_cve_item_update_ticket(Ticket $ticket) {
// Check if the ticket is related to a CVE and update the CVE status if needed
// This would be implemented with actual status update logic
}
/**
* Hook called before a ticket is purged
*
* @param Ticket $ticket
*/
function plugin_cve_pre_item_purge_ticket(Ticket $ticket) {
// Remove any CVE associations before the ticket is purged
$cveTicket = new PluginCveCveTicket();
$cveTicket->deleteByCriteria(['tickets_id' => $ticket->getID()]);
// Also remove any alert associations
$alert = new PluginCveCveAlert();
$alert->deleteByCriteria(['tickets_id' => $ticket->getID()]);
}
/**
* Create dashboard cards for the plugin
*
* @param array $cards
* @return array
*/
function plugin_cve_dashboard_cards($cards) {
$new_cards = [];
// Add CVE statistics card
$new_cards['plugin_cve_stats'] = [
'widgettype' => 'statscard',
'title' => __('CVE Statistics', 'cve'),
'icon' => 'ti ti-shield',
'provider' => 'PluginCveCve::getCVEStatsDashboard'
];
// Add CVE severity distribution card
$new_cards['plugin_cve_severity'] = [
'widgettype' => 'donut',
'title' => __('CVE Severity Distribution', 'cve'),
'icon' => 'ti ti-chart-pie',
'provider' => 'PluginCveCve::getCVESeverityDashboard'
];
// Add recent CVEs card
$new_cards['plugin_cve_recent'] = [
'widgettype' => 'table',
'title' => __('Recent CVEs', 'cve'),
'icon' => 'ti ti-list',
'provider' => 'PluginCveCve::getRecentCVEsDashboard'
];
// Add software vulnerability statistics card
$new_cards['plugin_cve_software_vulnerabilities'] = [
'widgettype' => 'statscard',
'title' => __('Software Vulnerability Alerts', 'cve'),
'icon' => 'ti ti-alert-triangle',
'provider' => 'PluginCveCveAlert::getAlertStats'
];
return array_merge($cards, $new_cards);
}
/**
* Create the tables needed to run plugin
*
* @return boolean
*/
function plugin_cve_createTables() {
plugin_cve_install();
return true;
}
/**
* Migration function
*
* @param string $version Version to upgrade from
* @return boolean
*/
function plugin_cve_upgradeTables($version) {
// Handle migrations based on version
// This would be implemented with version-specific upgrades
return true;
}
/**
* Add plugin entries to GLPI's search engine
*
* @param string $itemtype
* @return array
*/
function plugin_cve_getAddSearchOptions($itemtype) {
$options = [];
if ($itemtype == 'Ticket') {
$options[9100] = [
'table' => 'glpi_plugin_cve_tickets',
'field' => 'id',
'name' => __('Associated CVEs', 'cve'),
'massiveaction' => false,
'joinparams' => [
'jointype' => 'child',
'condition' => "AND `REFTABLE`.`id` = `TABLE`.`tickets_id`"
]
];
}
if ($itemtype == 'Software') {
$options[9200] = [
'table' => 'glpi_plugin_cve_alerts',
'field' => 'id',
'name' => __('Vulnerability Alerts', 'cve'),
'massiveaction' => false,
'joinparams' => [
'jointype' => 'child',
'condition' => "AND `REFTABLE`.`id` = `TABLE`.`softwares_id`"
]
];
$options[9201] = [
'table' => 'glpi_plugin_cve_alerts',
'field' => 'severity',
'name' => __('Vulnerability Severity', 'cve'),
'massiveaction' => false,
'joinparams' => [
'jointype' => 'child',
'condition' => "AND `REFTABLE`.`id` = `TABLE`.`softwares_id`"
]
];
}
return $options;
}
/**
* Datainjection hook
*
* @param array $data
* @return array
*/
function plugin_datainjection_populate_cve($data) {
// Logic for data injection support
// This would be implemented with actual data mapping logic
return $data;
}

752
inc/cve.class.php Normal file
View File

@ -0,0 +1,752 @@
<?php
/**
* GLPI CVE Plugin - Main CVE Class
* Handles the CVE entity operations
*/
if (!defined('GLPI_ROOT')) {
die("Sorry. You can't access this file directly");
}
/**
* PluginCveCve class for managing CVEs
*/
class PluginCveCve extends CommonDBTM {
// Rights management constants
const RIGHT_NONE = 0;
const RIGHT_READ = 1;
const RIGHT_WRITE = 2;
static $rightname = 'plugin_cve_cve';
/**
* 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('CVE', 'CVEs', $nb, 'cve');
}
/**
* Define tabs to display
*
* @param array $options
* @return array containing the tabs
*/
function defineTabs($options = []) {
$tabs = [];
$this->addDefaultFormTab($tabs);
$this->addStandardTab('PluginCveTicket', $tabs, $options);
$this->addStandardTab('Log', $tabs, $options);
return $tabs;
}
/**
* Display the CVE 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'>";
// CVE ID
echo "<td>" . __('CVE ID', 'cve') . "</td>";
echo "<td>";
echo Html::input('cve_id', ['value' => $this->fields['cve_id'], 'size' => 20]);
echo "</td>";
// Severity
echo "<td>" . __('Severity', 'cve') . "</td>";
echo "<td>";
$severity_options = [
'CRITICAL' => __('Critical', 'cve'),
'HIGH' => __('High', 'cve'),
'MEDIUM' => __('Medium', 'cve'),
'LOW' => __('Low', 'cve')
];
Dropdown::showFromArray('severity', $severity_options,
['value' => $this->fields['severity']]);
echo "</td>";
echo "</tr>";
echo "<tr class='tab_bg_1'>";
// CVSS Score
echo "<td>" . __('CVSS Score', 'cve') . "</td>";
echo "<td>";
echo Html::input('cvss_score', ['value' => $this->fields['cvss_score'], 'size' => 5]);
echo "</td>";
// CVSS Vector
echo "<td>" . __('CVSS Vector', 'cve') . "</td>";
echo "<td>";
echo Html::input('cvss_vector', ['value' => $this->fields['cvss_vector'], 'size' => 40]);
echo "</td>";
echo "</tr>";
echo "<tr class='tab_bg_1'>";
// Published date
echo "<td>" . __('Published Date', 'cve') . "</td>";
echo "<td>";
Html::showDateField('published_date', ['value' => $this->fields['published_date']]);
echo "</td>";
// Modified date
echo "<td>" . __('Modified Date', 'cve') . "</td>";
echo "<td>";
Html::showDateField('modified_date', ['value' => $this->fields['modified_date']]);
echo "</td>";
echo "</tr>";
echo "<tr class='tab_bg_1'>";
// Status
echo "<td>" . __('Status', 'cve') . "</td>";
echo "<td>";
$status_options = [
'NEW' => __('New', 'cve'),
'ANALYZED' => __('Analyzed', 'cve'),
'ASSIGNED' => __('Assigned', 'cve'),
'RESOLVED' => __('Resolved', 'cve')
];
Dropdown::showFromArray('status', $status_options,
['value' => $this->fields['status']]);
echo "</td>";
// Add entity dropdown if needed
echo "<td>" . __('Entity', 'cve') . "</td>";
echo "<td>";
Entity::dropdown(['value' => $this->fields['entities_id']]);
echo "</td>";
echo "</tr>";
echo "<tr class='tab_bg_1'>";
// Description
echo "<td>" . __('Description', 'cve') . "</td>";
echo "<td colspan='3'>";
echo "<textarea name='description' cols='90' rows='4'>".$this->fields['description']."</textarea>";
echo "</td>";
echo "</tr>";
echo "<tr class='tab_bg_1'>";
// References
echo "<td>" . __('References', 'cve') . "</td>";
echo "<td colspan='3'>";
$references = json_decode($this->fields['references'], true) ?: [];
echo "<textarea name='references' cols='90' rows='4'>".implode("\n", $references)."</textarea>";
echo "<br><i>" . __('Enter one URL per line', 'cve') . "</i>";
echo "</td>";
echo "</tr>";
echo "<tr class='tab_bg_1'>";
// Affected Products
echo "<td>" . __('Affected Products', 'cve') . "</td>";
echo "<td colspan='3'>";
$affected_products = json_decode($this->fields['affected_products'], true) ?: [];
echo "<textarea name='affected_products' cols='90' rows='4'>".implode("\n", $affected_products)."</textarea>";
echo "<br><i>" . __('Enter one product per line', 'cve') . "</i>";
echo "</td>";
echo "</tr>";
$this->showFormButtons($options);
return true;
}
/**
* Pre-process data before add or update
*
* @param array $input Data to process
* @return array Processed data
*/
function prepareInputForAddOrUpdate($input) {
// Process references from textarea to JSON
if (isset($input['references'])) {
$references = explode("\n", $input['references']);
$references = array_map('trim', $references);
$references = array_filter($references);
$input['references'] = json_encode(array_values($references));
}
// Process affected products from textarea to JSON
if (isset($input['affected_products'])) {
$products = explode("\n", $input['affected_products']);
$products = array_map('trim', $products);
$products = array_filter($products);
$input['affected_products'] = json_encode(array_values($products));
}
return $input;
}
/**
* Pre-process input data before adding
*
* @param array $input Input data
* @return array|false Processed input data or false on error
*/
function prepareInputForAdd($input) {
// Set creation date if not provided
if (!isset($input['date_creation'])) {
$input['date_creation'] = $_SESSION['glpi_currenttime'];
}
// Set default entity if not provided
if (!isset($input['entities_id'])) {
$input['entities_id'] = $_SESSION['glpiactive_entity'];
}
// Process the input for references and affected products
$input = $this->prepareInputForAddOrUpdate($input);
return $input;
}
/**
* Pre-process input data before updating
*
* @param array $input Input data
* @return array|false Processed input data or false on error
*/
function prepareInputForUpdate($input) {
// Set modification date
$input['date_mod'] = $_SESSION['glpi_currenttime'];
// Process the input for references and affected products
$input = $this->prepareInputForAddOrUpdate($input);
return $input;
}
/**
* 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' => 'cve_id',
'name' => __('CVE ID', 'cve'),
'datatype' => 'itemlink',
'massiveaction' => false
];
$tab[] = [
'id' => '2',
'table' => $this->getTable(),
'field' => 'description',
'name' => __('Description', 'cve'),
'datatype' => 'text',
'massiveaction' => false
];
$tab[] = [
'id' => '3',
'table' => $this->getTable(),
'field' => 'cvss_score',
'name' => __('CVSS Score', 'cve'),
'datatype' => 'decimal',
'massiveaction' => false
];
$tab[] = [
'id' => '4',
'table' => $this->getTable(),
'field' => 'severity',
'name' => __('Severity', 'cve'),
'datatype' => 'specific',
'searchtype' => ['equals', 'notequals']
];
$tab[] = [
'id' => '5',
'table' => $this->getTable(),
'field' => 'published_date',
'name' => __('Published Date', 'cve'),
'datatype' => 'datetime',
'massiveaction' => false
];
$tab[] = [
'id' => '6',
'table' => $this->getTable(),
'field' => 'status',
'name' => __('Status', 'cve'),
'datatype' => 'specific',
'searchtype' => ['equals', 'notequals']
];
$tab[] = [
'id' => '16',
'table' => $this->getTable(),
'field' => 'date_creation',
'name' => __('Creation date', 'cve'),
'datatype' => 'datetime',
'massiveaction' => false
];
$tab[] = [
'id' => '19',
'table' => $this->getTable(),
'field' => 'date_mod',
'name' => __('Last update', 'cve'),
'datatype' => 'datetime',
'massiveaction' => false
];
return $tab;
}
/**
* Check if user has the right to perform an action
*
* @param $action integer ID of the action
* @param $right string|integer Expected right [default READ]
*
* @return boolean
*/
static function canAction($action, $right = READ) {
// Get the active entity
$active_entity = $_SESSION['glpiactive_entity'];
// Check if the user can perform the action
return Session::haveRight(self::$rightname, $right);
}
/**
* Create a ticket from a CVE
*
* @param integer $cves_id ID of the CVE
* @param array $options Additional options
*
* @return boolean|integer ID of the created ticket or false
*/
function createTicket($cves_id, $options = []) {
global $DB;
// Load the CVE
$cve = new self();
if (!$cve->getFromDB($cves_id)) {
return false;
}
// Create a new ticket
$ticket = new Ticket();
// Set ticket fields
$ticketData = [
'entities_id' => $cve->fields['entities_id'],
'name' => __('Vulnerability', 'cve') . ' ' . $cve->fields['cve_id'],
'content' => __('Vulnerability details', 'cve') . ":\n\n" .
$cve->fields['description'] . "\n\n" .
__('References', 'cve') . ":\n" . $cve->fields['references'],
'status' => Ticket::INCOMING,
'date' => $_SESSION['glpi_currenttime'],
'type' => Ticket::INCIDENT_TYPE,
'urgency' => $this->getCVSStoPriority($cve->fields['cvss_score']),
'impact' => $this->getCVSStoPriority($cve->fields['cvss_score']),
'priority' => $this->getCVSStoPriority($cve->fields['cvss_score']),
'itilcategories_id' => 0, // Default or security category if configured
'users_id_recipient' => Session::getLoginUserID(),
];
// Apply any custom options
if (count($options)) {
foreach ($options as $key => $val) {
$ticketData[$key] = $val;
}
}
// Create the ticket
$tickets_id = $ticket->add($ticketData);
if ($tickets_id) {
// Create the link between CVE and ticket
$cveTicket = new PluginCveTicket();
$cveTicketData = [
'cves_id' => $cves_id,
'tickets_id' => $tickets_id,
'creation_type' => 'MANUAL', // Or AUTO if created by rules
'date_creation' => $_SESSION['glpi_currenttime']
];
$cveTicket->add($cveTicketData);
// Update CVE status to ASSIGNED if it was NEW
if ($cve->fields['status'] == 'NEW') {
$cve->update([
'id' => $cves_id,
'status' => 'ASSIGNED'
]);
}
return $tickets_id;
}
return false;
}
/**
* Convert CVSS score to GLPI priority
*
* @param float $cvss_score CVSS Score
*
* @return integer GLPI priority
*/
private function getCVSStoPriority($cvss_score) {
// Convert CVSS score to GLPI priority (1-5)
if ($cvss_score >= 9) {
return 5; // Very high
} else if ($cvss_score >= 7) {
return 4; // High
} else if ($cvss_score >= 4) {
return 3; // Medium
} else if ($cvss_score >= 1) {
return 2; // Low
} else {
return 1; // Very low
}
}
/**
* Get the severity class for display
*
* @param string $severity Severity level
*
* @return string CSS class
*/
static function getSeverityClass($severity) {
switch ($severity) {
case 'CRITICAL':
return 'cve-severity-critical';
case 'HIGH':
return 'cve-severity-high';
case 'MEDIUM':
return 'cve-severity-medium';
case 'LOW':
return 'cve-severity-low';
default:
return '';
}
}
/**
* Get the status class for display
*
* @param string $status Status value
*
* @return string CSS class
*/
static function getStatusClass($status) {
switch ($status) {
case 'NEW':
return 'cve-status-new';
case 'ANALYZED':
return 'cve-status-analyzed';
case 'ASSIGNED':
return 'cve-status-assigned';
case 'RESOLVED':
return 'cve-status-resolved';
default:
return '';
}
}
/**
* Get dashboard statistics data
*
* @return array Dashboard data
*/
static function getCVEStatsDashboard() {
global $DB;
$stats = [];
// Count by severity
$query = "SELECT severity, COUNT(*) as count
FROM `" . self::getTable() . "`
GROUP BY severity";
$result = $DB->query($query);
$stats['severity'] = [
'CRITICAL' => 0,
'HIGH' => 0,
'MEDIUM' => 0,
'LOW' => 0
];
if ($result) {
while ($data = $DB->fetchAssoc($result)) {
$stats['severity'][$data['severity']] = $data['count'];
}
}
// Count by status
$query = "SELECT status, COUNT(*) as count
FROM `" . self::getTable() . "`
GROUP BY status";
$result = $DB->query($query);
$stats['status'] = [
'NEW' => 0,
'ANALYZED' => 0,
'ASSIGNED' => 0,
'RESOLVED' => 0
];
if ($result) {
while ($data = $DB->fetchAssoc($result)) {
$stats['status'][$data['status']] = $data['count'];
}
}
// Get recent CVEs (last 30 days)
$query = "SELECT COUNT(*) as count
FROM `" . self::getTable() . "`
WHERE date_creation > DATE_SUB(NOW(), INTERVAL 30 DAY)";
$result = $DB->query($query);
$stats['recent'] = 0;
if ($result && $data = $DB->fetchAssoc($result)) {
$stats['recent'] = $data['count'];
}
return $stats;
}
/**
* Get severity distribution for dashboard
*
* @return array Dashboard data
*/
static function getCVESeverityDashboard() {
global $DB;
$data = [];
// Count by severity
$query = "SELECT severity, COUNT(*) as count
FROM `" . self::getTable() . "`
GROUP BY severity";
$result = $DB->query($query);
$labels = [
'CRITICAL' => __('Critical', 'cve'),
'HIGH' => __('High', 'cve'),
'MEDIUM' => __('Medium', 'cve'),
'LOW' => __('Low', 'cve')
];
$colors = [
'CRITICAL' => '#d32f2f',
'HIGH' => '#f57c00',
'MEDIUM' => '#fbc02d',
'LOW' => '#2196f3'
];
if ($result) {
$series = [];
$series_labels = [];
while ($row = $DB->fetchAssoc($result)) {
$series[] = [
'name' => $labels[$row['severity']] ?? $row['severity'],
'data' => [(int)$row['count']],
'color' => $colors[$row['severity']] ?? '#999999'
];
$series_labels[] = $labels[$row['severity']] ?? $row['severity'];
}
$data = [
'labels' => $series_labels,
'series' => $series
];
}
return $data;
}
/**
* Get recent CVEs for dashboard
*
* @return array Dashboard data
*/
static function getRecentCVEsDashboard() {
global $DB;
$data = [];
// Get recent CVEs
$query = "SELECT id, cve_id, severity, cvss_score, published_date, status
FROM `" . self::getTable() . "`
ORDER BY date_creation DESC
LIMIT 10";
$result = $DB->query($query);
if ($result) {
$data['headers'] = [
__('CVE ID', 'cve'),
__('Severity', 'cve'),
__('CVSS', 'cve'),
__('Published', 'cve'),
__('Status', 'cve')
];
$data['rows'] = [];
while ($row = $DB->fetchAssoc($result)) {
$data['rows'][] = [
'cve_id' => $row['cve_id'],
'severity' => $row['severity'],
'cvss_score' => $row['cvss_score'],
'published' => $row['published_date'],
'status' => $row['status']
];
}
}
return $data;
}
/**
* Cron task for cleaning old CVEs
*
* @param CronTask $task CronTask object
* @return integer
*/
static function cronCleanOldCVEs($task) {
global $DB;
// Default to cleaning CVEs older than 1 year
$retention_days = 365;
// Get retention configuration if exists
$config = new Config();
$config->getFromDBByCrit(['context' => 'plugin:cve', 'name' => 'retention_days']);
if (isset($config->fields['value'])) {
$retention_days = (int)$config->fields['value'];
}
// Only clean resolved CVEs
$query = "DELETE FROM `" . self::getTable() . "`
WHERE `status` = 'RESOLVED'
AND `date_mod` < DATE_SUB(NOW(), INTERVAL $retention_days DAY)";
$result = $DB->query($query);
if ($result) {
$affected = $DB->affectedRows();
$task->addVolume($affected);
Toolbox::logInFile('cve_plugin', "Cleaned $affected old resolved CVEs older than $retention_days days");
return ($affected > 0) ? 1 : 0;
}
return 0;
}
/**
* Install the plugin database schema
*
* @return boolean
*/
static function install(Migration $migration) {
global $DB;
$table = self::getTable();
if (!$DB->tableExists($table)) {
$migration->displayMessage("Installing $table");
$query = "CREATE TABLE IF NOT EXISTS `$table` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`cve_id` varchar(20) NOT NULL,
`description` text DEFAULT NULL,
`cvss_score` decimal(3,1) DEFAULT NULL,
`cvss_vector` varchar(100) DEFAULT NULL,
`severity` enum('LOW','MEDIUM','HIGH','CRITICAL') DEFAULT NULL,
`published_date` datetime DEFAULT NULL,
`modified_date` datetime DEFAULT NULL,
`status` enum('NEW','ANALYZED','ASSIGNED','RESOLVED') DEFAULT 'NEW',
`references` text DEFAULT NULL,
`affected_products` text DEFAULT NULL,
`entities_id` int(11) NOT NULL DEFAULT '0',
`is_recursive` tinyint(1) NOT NULL DEFAULT '0',
`date_creation` datetime DEFAULT NULL,
`date_mod` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `cve_id` (`cve_id`),
KEY `severity` (`severity`),
KEY `status` (`status`),
KEY `published_date` (`published_date`),
KEY `cvss_score` (`cvss_score`),
KEY `entities_id` (`entities_id`),
KEY `date_creation` (`date_creation`),
KEY `date_mod` (`date_mod`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci";
$DB->query($query) or die("Error creating $table " . $DB->error());
}
return true;
}
/**
* Uninstall the plugin database schema
*
* @return boolean
*/
static function uninstall(Migration $migration) {
global $DB;
$table = self::getTable();
if ($DB->tableExists($table)) {
$migration->displayMessage("Uninstalling $table");
$migration->dropTable($table);
}
return true;
}
}

414
inc/cvealert.class.php Normal file
View File

@ -0,0 +1,414 @@
<?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;
}
}

358
inc/cveinventory.class.php Normal file
View File

@ -0,0 +1,358 @@
<?php
/**
* GLPI CVE Plugin - Software Inventory Analysis Class
* Analyzes GLPI software inventory and matches it with known CVEs
*/
if (!defined('GLPI_ROOT')) {
die("Sorry. You can't access this file directly");
}
/**
* PluginCveCveInventory class for analyzing software inventory for vulnerabilities
*/
class PluginCveCveInventory extends CommonDBTM {
static $rightname = 'plugin_cve_inventory';
/**
* 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 Analysis', 'Software Vulnerability Analyses', $nb, 'cve');
}
/**
* Cron task for software inventory analysis
*
* @param CronTask $task CronTask object
* @return integer
*/
static function cronAnalyzeInventory($task) {
global $DB;
$task->log("Starting software vulnerability analysis");
$task->setVolume(0);
// Get all active entities
$entity = new Entity();
$entities = $entity->find(['is_recursive' => 1]);
$entity_ids = array_column($entities, 'id');
// Get all software from inventory
$software = new Software();
$software_versions = new SoftwareVersion();
$matched_count = 0;
$alert_count = 0;
// For each entity, process software inventory
foreach ($entity_ids as $entity_id) {
$task->log("Processing entity ID: $entity_id");
// Get software in this entity
$software_list = $software->find(['entities_id' => $entity_id]);
foreach ($software_list as $sw) {
// Get versions of this software
$versions = $software_versions->find(['softwares_id' => $sw['id']]);
foreach ($versions as $version) {
// Search for vulnerabilities for this software/version
$vulnerabilities = self::findVulnerabilities($sw['name'], $version['name']);
if (!empty($vulnerabilities)) {
$matched_count += count($vulnerabilities);
// Process each vulnerability
foreach ($vulnerabilities as $cve_id) {
// Create alert if it doesn't already exist
if (self::createAlert($sw['id'], $version['id'], $cve_id, $entity_id)) {
$alert_count++;
}
}
}
}
}
}
$task->setVolume($matched_count);
$task->log("Analysis completed. Found $matched_count potential vulnerabilities, created $alert_count new alerts");
return ($alert_count > 0) ? 1 : 0;
}
/**
* Find vulnerabilities for a given software and version
*
* @param string $software_name Software name
* @param string $version_name Version string
* @return array Array of matching CVE IDs
*/
private static function findVulnerabilities($software_name, $version_name) {
global $DB;
$matches = [];
// Normalize software name for better matching
$normalized_name = strtolower(trim($software_name));
$normalized_version = trim($version_name);
// Search in affected_products field of CVEs
$query = "SELECT id, cve_id, affected_products
FROM `glpi_plugin_cve_cves`
WHERE `status` != 'RESOLVED'";
$result = $DB->query($query);
if ($result) {
while ($row = $DB->fetchAssoc($result)) {
$affected_products = json_decode($row['affected_products'], true) ?: [];
foreach ($affected_products as $product) {
// Simple matching for demonstration
// In a real implementation, this would use CPE matching or more sophisticated algorithms
if (self::matchesSoftware($normalized_name, $normalized_version, $product)) {
$matches[] = $row['id'];
break; // Found a match for this CVE
}
}
}
}
return $matches;
}
/**
* Check if software and version match an affected product string
*
* @param string $name Normalized software name
* @param string $version Normalized version
* @param string $product Affected product string
* @return boolean True if matches
*/
private static function matchesSoftware($name, $version, $product) {
// Normalize product string
$product = strtolower(trim($product));
// Check if product string contains both software name and version
// This is a simple implementation and would need to be more sophisticated in real use
if (strpos($product, $name) !== false) {
// If version is part of an affected range
if (strpos($product, ' < ' . $version) !== false ||
strpos($product, '<=' . $version) !== false ||
strpos($product, $version) !== false) {
return true;
}
}
return false;
}
/**
* Create a vulnerability alert
*
* @param integer $software_id Software ID
* @param integer $version_id Version ID
* @param integer $cve_id CVE ID
* @param integer $entity_id Entity ID
* @return boolean True if new alert was created
*/
private static function createAlert($software_id, $version_id, $cve_id, $entity_id) {
global $DB;
// Check if alert already exists
$query = "SELECT id FROM `glpi_plugin_cve_alerts`
WHERE `softwares_id` = $software_id
AND `softwareversions_id` = $version_id
AND `cves_id` = $cve_id";
$result = $DB->query($query);
if ($result && $DB->numrows($result) > 0) {
// Alert already exists
return false;
}
// Get CVE details
$cve = new PluginCveCve();
$cve->getFromDB($cve_id);
// Create new alert
$alert = new PluginCveCveAlert();
$alert_id = $alert->add([
'softwares_id' => $software_id,
'softwareversions_id' => $version_id,
'cves_id' => $cve_id,
'entities_id' => $entity_id,
'status' => 'NEW',
'severity' => $cve->fields['severity'],
'date_creation' => $_SESSION['glpi_currenttime']
]);
if ($alert_id) {
// Process the alert according to rules
self::processAlert($alert_id);
return true;
}
return false;
}
/**
* Process a vulnerability alert based on rules
*
* @param integer $alert_id Alert ID
* @return boolean Success
*/
private static function processAlert($alert_id) {
// Get alert details
$alert = new PluginCveCveAlert();
if (!$alert->getFromDB($alert_id)) {
return false;
}
// Get associated CVE
$cve = new PluginCveCve();
if (!$cve->getFromDB($alert->fields['cves_id'])) {
return false;
}
// Get software details
$software = new Software();
if (!$software->getFromDB($alert->fields['softwares_id'])) {
return false;
}
$version = new SoftwareVersion();
if (!$version->getFromDB($alert->fields['softwareversions_id'])) {
return false;
}
// Apply rules based on severity
if ($alert->fields['severity'] == 'CRITICAL' || $alert->fields['severity'] == 'HIGH') {
// Create a ticket for high/critical vulnerabilities
$ticket = new Ticket();
$content = __('A vulnerability has been detected in your software inventory', 'cve') . "\n\n";
$content .= __('Software', 'cve') . ': ' . $software->fields['name'] . ' ' . $version->fields['name'] . "\n";
$content .= __('CVE', 'cve') . ': ' . $cve->fields['cve_id'] . "\n";
$content .= __('Severity', 'cve') . ': ' . $cve->fields['severity'] . "\n";
$content .= __('CVSS Score', 'cve') . ': ' . $cve->fields['cvss_score'] . "\n\n";
$content .= __('Description', 'cve') . ":\n" . $cve->fields['description'] . "\n\n";
$affected_products = json_decode($cve->fields['affected_products'], true) ?: [];
if (!empty($affected_products)) {
$content .= __('Affected Products', 'cve') . ":\n" . implode("\n", $affected_products) . "\n\n";
}
$references = json_decode($cve->fields['references'], true) ?: [];
if (!empty($references)) {
$content .= __('References', 'cve') . ":\n" . implode("\n", $references) . "\n";
}
$ticket_id = $ticket->add([
'name' => __('Vulnerability', 'cve') . ' ' . $cve->fields['cve_id'] . ' - ' . $software->fields['name'],
'content' => $content,
'status' => Ticket::INCOMING,
'priority' => ($alert->fields['severity'] == 'CRITICAL') ? 5 : 4,
'urgency' => ($alert->fields['severity'] == 'CRITICAL') ? 5 : 4,
'impact' => ($alert->fields['severity'] == 'CRITICAL') ? 5 : 4,
'entities_id' => $alert->fields['entities_id'],
'date' => $_SESSION['glpi_currenttime'],
'itilcategories_id' => 0, // Default or security category if configured
'type' => Ticket::INCIDENT_TYPE
]);
if ($ticket_id) {
// Link the CVE to the ticket
$cveTicket = new PluginCveCveTicket();
$cveTicket->add([
'cves_id' => $alert->fields['cves_id'],
'tickets_id' => $ticket_id,
'creation_type' => 'AUTO',
'date_creation' => $_SESSION['glpi_currenttime']
]);
// Update alert status
$alert->update([
'id' => $alert_id,
'status' => 'PROCESSED',
'tickets_id' => $ticket_id
]);
}
} else {
// For medium/low severity, just mark as processed without ticket
$alert->update([
'id' => $alert_id,
'status' => 'PROCESSED'
]);
}
return true;
}
/**
* Install the plugin database schema
*
* @return boolean
*/
static function install(Migration $migration) {
global $DB;
$table = 'glpi_plugin_cve_alerts';
if (!$DB->tableExists($table)) {
$migration->displayMessage("Installing $table");
$query = "CREATE TABLE IF NOT EXISTS `$table` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`softwares_id` int(11) NOT NULL,
`softwareversions_id` int(11) NOT NULL,
`cves_id` int(11) NOT NULL,
`entities_id` int(11) NOT NULL DEFAULT '0',
`status` enum('NEW','PROCESSED','IGNORED') DEFAULT 'NEW',
`severity` enum('LOW','MEDIUM','HIGH','CRITICAL') DEFAULT NULL,
`tickets_id` int(11) DEFAULT NULL,
`date_creation` datetime DEFAULT NULL,
`date_mod` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `unique_alert` (`softwares_id`, `softwareversions_id`, `cves_id`),
KEY `softwares_id` (`softwares_id`),
KEY `softwareversions_id` (`softwareversions_id`),
KEY `cves_id` (`cves_id`),
KEY `entities_id` (`entities_id`),
KEY `status` (`status`),
KEY `severity` (`severity`),
KEY `tickets_id` (`tickets_id`),
KEY `date_creation` (`date_creation`),
KEY `date_mod` (`date_mod`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci";
$DB->query($query) or die("Error creating $table " . $DB->error());
}
return true;
}
/**
* Uninstall the plugin database schema
*
* @return boolean
*/
static function uninstall(Migration $migration) {
global $DB;
$table = 'glpi_plugin_cve_alerts';
if ($DB->tableExists($table)) {
$migration->displayMessage("Uninstalling $table");
$migration->dropTable($table);
}
return true;
}
}

497
inc/cverule.class.php Normal file
View File

@ -0,0 +1,497 @@
<?php
/**
* GLPI CVE Plugin - Rule Class
* Manages CVE processing rules
*/
if (!defined('GLPI_ROOT')) {
die("Sorry. You can't access this file directly");
}
/**
* PluginCveCveRule class for managing CVE processing rules
*/
class PluginCveCveRule extends CommonDBTM {
static $rightname = 'plugin_cve_rule';
/**
* 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('CVE Rule', 'CVE Rules', $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 Rule 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'>";
// Rule Name
echo "<td>" . __('Rule Name', 'cve') . "</td>";
echo "<td>";
echo Html::input('name', ['value' => $this->fields['name'], 'size' => 40]);
echo "</td>";
// Priority
echo "<td>" . __('Priority', 'cve') . "</td>";
echo "<td>";
echo Html::input('priority', ['value' => $this->fields['priority'], 'size' => 5, 'type' => 'number', 'min' => 1]);
echo "<br><i>" . __('Lower numbers are processed first', 'cve') . "</i>";
echo "</td>";
echo "</tr>";
echo "<tr class='tab_bg_1'>";
// Criteria - Severity
echo "<td>" . __('Severity', 'cve') . "</td>";
echo "<td>";
$criteria = json_decode($this->fields['criteria'], true) ?: [];
$severity = $criteria['severity'] ?? 'CRITICAL';
$severity_options = [
'CRITICAL' => __('Critical', 'cve'),
'HIGH' => __('High', 'cve'),
'MEDIUM' => __('Medium', 'cve'),
'LOW' => __('Low', 'cve')
];
Dropdown::showFromArray('criteria_severity', $severity_options,
['value' => $severity]);
echo "</td>";
// Status
echo "<td>" . __('Status', 'cve') . "</td>";
echo "<td>";
$status_options = [
'NEW' => __('New', 'cve'),
'ANALYZED' => __('Analyzed', 'cve'),
'ASSIGNED' => __('Assigned', 'cve'),
'RESOLVED' => __('Resolved', 'cve')
];
Dropdown::showFromArray('rule_status', $status_options,
['value' => $this->fields['status'] ?? 'NEW']);
echo "</td>";
echo "</tr>";
echo "<tr class='tab_bg_1'>";
// Actions - Create Ticket
echo "<td>" . __('Create Ticket', 'cve') . "</td>";
echo "<td>";
$actions = json_decode($this->fields['actions'], true) ?: [];
$create_ticket = isset($actions['create_ticket']) ? $actions['create_ticket'] : true;
Dropdown::showYesNo('actions_create_ticket', $create_ticket);
echo "</td>";
// Ticket Priority
echo "<td>" . __('Ticket Priority', 'cve') . "</td>";
echo "<td>";
$ticket_priority = $actions['ticket_priority'] ?? 'NORMAL';
$priority_options = [
'VERY HIGH' => __('Very High', 'cve'),
'HIGH' => __('High', 'cve'),
'NORMAL' => __('Normal', 'cve'),
'LOW' => __('Low', 'cve')
];
Dropdown::showFromArray('actions_ticket_priority', $priority_options,
['value' => $ticket_priority]);
echo "</td>";
echo "</tr>";
echo "<tr class='tab_bg_1'>";
// Actions - Notify Admins
echo "<td>" . __('Notify Administrators', 'cve') . "</td>";
echo "<td>";
$notify_admins = isset($actions['notify_admins']) ? $actions['notify_admins'] : false;
Dropdown::showYesNo('actions_notify_admins', $notify_admins);
echo "</td>";
// Actions - Add to Report
echo "<td>" . __('Add to Vulnerability Report', 'cve') . "</td>";
echo "<td>";
$add_to_report = isset($actions['add_to_report']) ? $actions['add_to_report'] : false;
Dropdown::showYesNo('actions_add_to_report', $add_to_report);
echo "</td>";
echo "</tr>";
echo "<tr class='tab_bg_1'>";
// Is Active
echo "<td>" . __('Active Rule', 'cve') . "</td>";
echo "<td>";
Dropdown::showYesNo('is_active', $this->fields['is_active']);
echo "</td>";
echo "<td colspan='2'></td>";
echo "</tr>";
$this->showFormButtons($options);
return true;
}
/**
* Process a CVE with rules
*
* @param PluginCveCve $cve CVE to process
* @return boolean True if any rule was applied
*/
static function processCVE(PluginCveCve $cve) {
$rule = new self();
// Get active rules sorted by priority
$rules = $rule->find(['is_active' => 1], ['priority' => 'ASC']);
$rule_applied = false;
foreach ($rules as $rule_data) {
// Load the rule
$rule->getFromDB($rule_data['id']);
// Check if the CVE matches the criteria
if (self::matchesCriteria($cve, $rule_data)) {
// Apply the actions
self::applyActions($cve, $rule_data);
$rule_applied = true;
}
}
return $rule_applied;
}
/**
* Check if a CVE matches rule criteria
*
* @param PluginCveCve $cve CVE to check
* @param array $rule_data Rule data
* @return boolean True if the CVE matches the criteria
*/
private static function matchesCriteria(PluginCveCve $cve, array $rule_data) {
$criteria = json_decode($rule_data['criteria'], true) ?: [];
// Check severity
if (isset($criteria['severity'])) {
// Get the numeric values for comparison
$severity_values = [
'CRITICAL' => 4,
'HIGH' => 3,
'MEDIUM' => 2,
'LOW' => 1
];
$rule_severity = $severity_values[$criteria['severity']] ?? 0;
$cve_severity = $severity_values[$cve->fields['severity']] ?? 0;
// Match if CVE severity is greater than or equal to rule severity
if ($cve_severity < $rule_severity) {
return false;
}
}
// Check affected products if specified
if (isset($criteria['affected_products']) && !empty($criteria['affected_products'])) {
$cve_products = json_decode($cve->fields['affected_products'], true) ?: [];
$rule_products = $criteria['affected_products'];
$found = false;
foreach ($rule_products as $product) {
if (in_array($product, $cve_products)) {
$found = true;
break;
}
}
if (!$found) {
return false;
}
}
// Check CVSS score range if specified
if (isset($criteria['cvss_min']) && $cve->fields['cvss_score'] < $criteria['cvss_min']) {
return false;
}
if (isset($criteria['cvss_max']) && $cve->fields['cvss_score'] > $criteria['cvss_max']) {
return false;
}
// All criteria matched or no criteria specified
return true;
}
/**
* Apply rule actions to a CVE
*
* @param PluginCveCve $cve CVE to process
* @param array $rule_data Rule data
* @return boolean True if actions were applied successfully
*/
private static function applyActions(PluginCveCve $cve, array $rule_data) {
$actions = json_decode($rule_data['actions'], true) ?: [];
// Create a ticket if needed
if (isset($actions['create_ticket']) && $actions['create_ticket']) {
$ticket_options = [];
// Set ticket priority
if (isset($actions['ticket_priority'])) {
switch ($actions['ticket_priority']) {
case 'VERY HIGH':
$ticket_options['priority'] = 5;
break;
case 'HIGH':
$ticket_options['priority'] = 4;
break;
case 'NORMAL':
$ticket_options['priority'] = 3;
break;
case 'LOW':
$ticket_options['priority'] = 2;
break;
default:
$ticket_options['priority'] = 3; // Default to normal
}
}
// Create the ticket
$cve->createTicket($cve->getID(), $ticket_options);
}
// Send notifications if needed
if (isset($actions['notify_admins']) && $actions['notify_admins']) {
// This would implement the notification logic
// For example: NotificationEvent::raiseEvent('new_cve', $cve);
}
// Add to report if needed
if (isset($actions['add_to_report']) && $actions['add_to_report']) {
// This would implement the reporting logic
}
return true;
}
/**
* 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' => 'name',
'name' => __('Rule Name', 'cve'),
'datatype' => 'itemlink',
'massiveaction' => false
];
$tab[] = [
'id' => '2',
'table' => $this->getTable(),
'field' => 'priority',
'name' => __('Priority', 'cve'),
'datatype' => 'number',
'min' => 1,
'massiveaction' => true
];
$tab[] = [
'id' => '3',
'table' => $this->getTable(),
'field' => 'criteria',
'name' => __('Criteria', 'cve'),
'datatype' => 'text',
'massiveaction' => false
];
$tab[] = [
'id' => '4',
'table' => $this->getTable(),
'field' => 'actions',
'name' => __('Actions', 'cve'),
'datatype' => 'text',
'massiveaction' => false
];
$tab[] = [
'id' => '5',
'table' => $this->getTable(),
'field' => 'is_active',
'name' => __('Active', 'cve'),
'datatype' => 'bool',
'massiveaction' => true
];
$tab[] = [
'id' => '19',
'table' => $this->getTable(),
'field' => 'date_mod',
'name' => __('Last update', 'cve'),
'datatype' => 'datetime',
'massiveaction' => false
];
$tab[] = [
'id' => '16',
'table' => $this->getTable(),
'field' => 'date_creation',
'name' => __('Creation date', 'cve'),
'datatype' => 'datetime',
'massiveaction' => false
];
return $tab;
}
/**
* Install the plugin database schema
*
* @return boolean
*/
static function install(Migration $migration) {
global $DB;
$table = self::getTable();
if (!$DB->tableExists($table)) {
$migration->displayMessage("Installing $table");
$query = "CREATE TABLE IF NOT EXISTS `$table` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL,
`criteria` json DEFAULT NULL,
`actions` json DEFAULT NULL,
`priority` int(11) NOT NULL DEFAULT '1',
`is_active` tinyint(1) NOT NULL DEFAULT '0',
`status` varchar(20) DEFAULT 'NEW',
`date_creation` datetime DEFAULT NULL,
`date_mod` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `name` (`name`),
KEY `is_active` (`is_active`),
KEY `priority` (`priority`),
KEY `date_creation` (`date_creation`),
KEY `date_mod` (`date_mod`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci";
$DB->query($query) or die("Error creating $table " . $DB->error());
// Add default rules
$default_rules = [
[
'name' => 'Critical Vulnerabilities - Immediate Ticket',
'criteria' => json_encode(['severity' => 'CRITICAL']),
'actions' => json_encode([
'create_ticket' => true,
'ticket_priority' => 'VERY HIGH',
'notify_admins' => true
]),
'priority' => 1,
'is_active' => 1,
'status' => 'NEW',
'date_creation' => $_SESSION['glpi_currenttime'],
'date_mod' => $_SESSION['glpi_currenttime']
],
[
'name' => 'High Risk Vulnerabilities - Create Ticket',
'criteria' => json_encode(['severity' => 'HIGH']),
'actions' => json_encode([
'create_ticket' => true,
'ticket_priority' => 'HIGH',
'notify_admins' => false
]),
'priority' => 2,
'is_active' => 1,
'status' => 'NEW',
'date_creation' => $_SESSION['glpi_currenttime'],
'date_mod' => $_SESSION['glpi_currenttime']
],
[
'name' => 'Medium Risk - Add to Report',
'criteria' => json_encode(['severity' => 'MEDIUM']),
'actions' => json_encode([
'create_ticket' => false,
'add_to_report' => true
]),
'priority' => 3,
'is_active' => 1,
'status' => 'NEW',
'date_creation' => $_SESSION['glpi_currenttime'],
'date_mod' => $_SESSION['glpi_currenttime']
]
];
$rule = new self();
foreach ($default_rules as $rule_data) {
$rule->add($rule_data);
}
}
return true;
}
/**
* Uninstall the plugin database schema
*
* @return boolean
*/
static function uninstall(Migration $migration) {
global $DB;
$table = self::getTable();
if ($DB->tableExists($table)) {
$migration->displayMessage("Uninstalling $table");
$migration->dropTable($table);
}
return true;
}
}

1095
inc/cvesource.class.php Normal file

File diff suppressed because it is too large Load Diff

494
inc/cveticket.class.php Normal file
View File

@ -0,0 +1,494 @@
<?php
/**
* GLPI CVE Plugin - CVE Ticket Class
* Manages links between CVEs and GLPI Tickets
*/
if (!defined('GLPI_ROOT')) {
die("Sorry. You can't access this file directly");
}
/**
* PluginCveCveTicket class for managing CVE-Ticket relations
*/
class PluginCveCveTicket extends CommonDBRelation {
// From CommonDBRelation
static public $itemtype_1 = 'PluginCveCve';
static public $items_id_1 = 'cves_id';
static public $itemtype_2 = 'Ticket';
static public $items_id_2 = 'tickets_id';
static $rightname = 'plugin_cve_ticket';
/**
* 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('CVE Ticket', 'CVE Tickets', $nb, 'cve');
}
/**
* 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_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' => '3',
'table' => 'glpi_tickets',
'field' => 'name',
'name' => __('Ticket', 'cve'),
'massiveaction' => false,
'datatype' => 'dropdown',
'joinparams' => [
'jointype' => 'child',
'condition' => 'AND NEWTABLE.`id` = REFTABLE.`tickets_id`'
]
];
$tab[] = [
'id' => '4',
'table' => $this->getTable(),
'field' => 'creation_type',
'name' => __('Creation Type', 'cve'),
'massiveaction' => false,
'datatype' => 'specific',
'searchtype' => ['equals', 'notequals']
];
$tab[] = [
'id' => '5',
'table' => $this->getTable(),
'field' => 'date_creation',
'name' => __('Creation date', 'cve'),
'datatype' => 'datetime',
'massiveaction' => false
];
return $tab;
}
/**
* Show tickets for a CVE
*
* @param PluginCveCve $cve CVE object
* @return void
*/
static function showForCVE(PluginCveCve $cve) {
global $DB;
$ID = $cve->getField('id');
if (!$cve->can($ID, READ)) {
return false;
}
$canedit = $cve->can($ID, UPDATE);
$rand = mt_rand();
$iterator = $DB->request([
'SELECT' => [
'glpi_plugin_cve_tickets.*',
'glpi_tickets.name AS ticket_name',
'glpi_tickets.status AS ticket_status',
'glpi_tickets.date AS ticket_date',
'glpi_tickets.priority AS ticket_priority'
],
'FROM' => 'glpi_plugin_cve_tickets',
'LEFT JOIN' => [
'glpi_tickets' => [
'ON' => [
'glpi_plugin_cve_tickets' => 'tickets_id',
'glpi_tickets' => 'id'
]
]
],
'WHERE' => [
'glpi_plugin_cve_tickets.cves_id' => $ID
],
'ORDER' => [
'glpi_tickets.date DESC'
]
]);
$tickets = [];
$used = [];
foreach ($iterator as $data) {
$tickets[$data['id']] = $data;
$used[$data['tickets_id']] = $data['tickets_id'];
}
if ($canedit) {
echo "<div class='firstbloc'>";
echo "<form name='cveticket_form$rand' id='cveticket_form$rand' method='post'
action='" . Toolbox::getItemTypeFormURL(__CLASS__) . "'>";
echo "<table class='tab_cadre_fixe'>";
echo "<tr class='tab_bg_2'><th colspan='2'>" . __('Add a ticket', 'cve') . "</th></tr>";
echo "<tr class='tab_bg_1'><td class='right'>";
echo "<input type='hidden' name='cves_id' value='$ID'>";
Ticket::dropdown([
'used' => $used,
'entity' => $cve->getEntityID(),
'entity_sons' => $cve->isRecursive(),
'displaywith' => ['id']
]);
echo "</td><td class='center'>";
echo "<input type='submit' name='add' value=\"" . _sx('button', 'Add') . "\" class='submit'>";
echo "</td></tr>";
echo "</table>";
Html::closeForm();
echo "</div>";
}
if ($canedit && count($tickets)) {
$massiveactionparams = [
'num_displayed' => min($_SESSION['glpilist_limit'], count($tickets)),
'container' => 'mass' . __CLASS__ . $rand,
'specific_actions' => [
'purge' => _x('button', 'Delete permanently')
]
];
Html::showMassiveActions($massiveactionparams);
}
echo "<table class='tab_cadre_fixehov'>";
$header_begin = "<tr>";
$header_top = '';
$header_bottom = '';
$header_end = '';
if ($canedit && count($tickets)) {
$header_top .= "<th width='10'>" . Html::getCheckAllAsCheckbox('mass' . __CLASS__ . $rand) . "</th>";
$header_bottom .= "<th width='10'>" . Html::getCheckAllAsCheckbox('mass' . __CLASS__ . $rand) . "</th>";
}
$header_end .= "<th>" . __('Ticket', 'cve') . "</th>";
$header_end .= "<th>" . __('Status', 'cve') . "</th>";
$header_end .= "<th>" . __('Priority', 'cve') . "</th>";
$header_end .= "<th>" . __('Opening date', 'cve') . "</th>";
$header_end .= "<th>" . __('Creation type', 'cve') . "</th>";
$header_end .= "</tr>";
echo $header_begin . $header_top . $header_end;
foreach ($tickets as $data) {
echo "<tr class='tab_bg_1'>";
if ($canedit) {
echo "<td width='10'>";
Html::showMassiveActionCheckBox(__CLASS__, $data['id']);
echo "</td>";
}
$ticket = new Ticket();
$ticket->getFromDB($data['tickets_id']);
echo "<td class='center'>";
if ($ticket->can($data['tickets_id'], READ)) {
echo "<a href=\"" . Ticket::getFormURLWithID($data['tickets_id']) . "\">";
echo $data['ticket_name'] . " (" . $data['tickets_id'] . ")";
echo "</a>";
} else {
echo $data['ticket_name'] . " (" . $data['tickets_id'] . ")";
}
echo "</td>";
// Status
echo "<td class='center'>";
echo Ticket::getStatus($data['ticket_status']);
echo "</td>";
// Priority
echo "<td class='center'>";
echo Ticket::getPriorityName($data['ticket_priority']);
echo "</td>";
// Date
echo "<td class='center'>";
echo Html::convDateTime($data['ticket_date']);
echo "</td>";
// Creation type
echo "<td class='center'>";
echo $data['creation_type'] == 'AUTO' ? __('Automatic', 'cve') : __('Manual', 'cve');
echo "</td>";
echo "</tr>";
}
if ($header_bottom) {
echo $header_begin . $header_bottom . $header_end;
}
echo "</table>";
if ($canedit && count($tickets)) {
$massiveactionparams['ontop'] = false;
Html::showMassiveActions($massiveactionparams);
Html::closeForm();
}
}
/**
* Show CVEs for a ticket
*
* @param Ticket $ticket Ticket object
* @return void
*/
static function showForTicket(Ticket $ticket) {
global $DB;
$ID = $ticket->getField('id');
if (!$ticket->can($ID, READ)) {
return false;
}
$canedit = $ticket->can($ID, UPDATE);
$rand = mt_rand();
$iterator = $DB->request([
'SELECT' => [
'glpi_plugin_cve_tickets.*',
'glpi_plugin_cve_cves.cve_id',
'glpi_plugin_cve_cves.severity',
'glpi_plugin_cve_cves.cvss_score',
'glpi_plugin_cve_cves.status AS cve_status'
],
'FROM' => 'glpi_plugin_cve_tickets',
'LEFT JOIN' => [
'glpi_plugin_cve_cves' => [
'ON' => [
'glpi_plugin_cve_tickets' => 'cves_id',
'glpi_plugin_cve_cves' => 'id'
]
]
],
'WHERE' => [
'glpi_plugin_cve_tickets.tickets_id' => $ID
]
]);
$cvetickets = [];
$used = [];
foreach ($iterator as $data) {
$cvetickets[$data['id']] = $data;
$used[$data['cves_id']] = $data['cves_id'];
}
if ($canedit) {
echo "<div class='firstbloc'>";
echo "<form name='ticketcve_form$rand' id='ticketcve_form$rand' method='post'
action='" . Toolbox::getItemTypeFormURL(__CLASS__) . "'>";
echo "<table class='tab_cadre_fixe'>";
echo "<tr class='tab_bg_2'><th colspan='2'>" . __('Add a CVE', 'cve') . "</th></tr>";
echo "<tr class='tab_bg_1'><td class='right'>";
echo "<input type='hidden' name='tickets_id' value='$ID'>";
$cve = new PluginCveCve();
$cve->dropdown([
'name' => 'cves_id',
'entity' => $ticket->getEntityID(),
'used' => $used
]);
echo "</td><td class='center'>";
echo "<input type='submit' name='add' value=\"" . _sx('button', 'Add') . "\" class='submit'>";
echo "</td></tr>";
echo "</table>";
Html::closeForm();
echo "</div>";
}
if ($canedit && count($cvetickets)) {
$massiveactionparams = [
'num_displayed' => min($_SESSION['glpilist_limit'], count($cvetickets)),
'container' => 'mass' . __CLASS__ . $rand,
'specific_actions' => [
'purge' => _x('button', 'Delete permanently')
]
];
Html::showMassiveActions($massiveactionparams);
}
echo "<table class='tab_cadre_fixehov'>";
$header_begin = "<tr>";
$header_top = '';
$header_bottom = '';
$header_end = '';
if ($canedit && count($cvetickets)) {
$header_top .= "<th width='10'>" . Html::getCheckAllAsCheckbox('mass' . __CLASS__ . $rand) . "</th>";
$header_bottom .= "<th width='10'>" . Html::getCheckAllAsCheckbox('mass' . __CLASS__ . $rand) . "</th>";
}
$header_end .= "<th>" . __('CVE ID', 'cve') . "</th>";
$header_end .= "<th>" . __('Severity', 'cve') . "</th>";
$header_end .= "<th>" . __('CVSS Score', 'cve') . "</th>";
$header_end .= "<th>" . __('Status', 'cve') . "</th>";
$header_end .= "<th>" . __('Creation Type', 'cve') . "</th>";
$header_end .= "</tr>";
echo $header_begin . $header_top . $header_end;
foreach ($cvetickets as $data) {
echo "<tr class='tab_bg_1'>";
if ($canedit) {
echo "<td width='10'>";
Html::showMassiveActionCheckBox(__CLASS__, $data['id']);
echo "</td>";
}
$cve = new PluginCveCve();
$cve->getFromDB($data['cves_id']);
echo "<td class='center'>";
if ($cve->can($data['cves_id'], READ)) {
echo "<a href=\"" . PluginCveCve::getFormURLWithID($data['cves_id']) . "\">";
echo $data['cve_id'];
echo "</a>";
} else {
echo $data['cve_id'];
}
echo "</td>";
// Severity
echo "<td class='center'>";
echo "<span class='" . PluginCveCve::getSeverityClass($data['severity']) . "'>";
echo $data['severity'];
echo "</span>";
echo "</td>";
// CVSS Score
echo "<td class='center'>";
echo $data['cvss_score'];
echo "</td>";
// Status
echo "<td class='center'>";
echo "<span class='" . PluginCveCve::getStatusClass($data['cve_status']) . "'>";
echo $data['cve_status'];
echo "</span>";
echo "</td>";
// Creation type
echo "<td class='center'>";
echo $data['creation_type'] == 'AUTO' ? __('Automatic', 'cve') : __('Manual', 'cve');
echo "</td>";
echo "</tr>";
}
if ($header_bottom) {
echo $header_begin . $header_bottom . $header_end;
}
echo "</table>";
if ($canedit && count($cvetickets)) {
$massiveactionparams['ontop'] = false;
Html::showMassiveActions($massiveactionparams);
Html::closeForm();
}
}
/**
* Add events to ticket notifications
*
* @param array $events Events array
* @return array Modified events array
*/
static function addEvents(&$events) {
$events['cve_added'] = __('CVE linked to ticket', 'cve');
return $events;
}
/**
* Install the plugin database schema
*
* @return boolean
*/
static function install(Migration $migration) {
global $DB;
$table = self::getTable();
if (!$DB->tableExists($table)) {
$migration->displayMessage("Installing $table");
$query = "CREATE TABLE IF NOT EXISTS `$table` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`cves_id` int(11) NOT NULL,
`tickets_id` int(11) NOT NULL,
`creation_type` enum('AUTO','MANUAL') DEFAULT 'MANUAL',
`date_creation` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `cves_id_tickets_id` (`cves_id`,`tickets_id`),
KEY `cves_id` (`cves_id`),
KEY `tickets_id` (`tickets_id`),
KEY `creation_type` (`creation_type`),
KEY `date_creation` (`date_creation`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci";
$DB->query($query) or die("Error creating $table " . $DB->error());
}
return true;
}
/**
* Uninstall the plugin database schema
*
* @return boolean
*/
static function uninstall(Migration $migration) {
global $DB;
$table = self::getTable();
if ($DB->tableExists($table)) {
$migration->displayMessage("Uninstalling $table");
$migration->dropTable($table);
}
return true;
}
}

78
inc/define.php Normal file
View File

@ -0,0 +1,78 @@
<?php
/**
* GLPI CVE Plugin - Language Definition
* Handles plugin language loading
*/
if (!defined('GLPI_ROOT')) {
die("Sorry. You can't access this file directly");
}
// Translation function for the plugin
function plugin_cve_gettext($text) {
return $text;
}
// Translation function with plurals
function plugin_cve_gettextn($singular, $plural, $number) {
if ($number > 1) {
return $plural;
}
return $singular;
}
/**
* Get the available languages for the plugin
*
* @return array
*/
function plugin_cve_getLanguages() {
return [
'en_GB' => 'English',
'fr_FR' => 'Français',
'de_DE' => 'Deutsch',
'it_IT' => 'Italiano',
'pl_PL' => 'Polski',
'es_ES' => 'Español',
'pt_PT' => 'Português'
];
}
/**
* Initialize plugin localization
*
* @param string $hook_param Hook parameter
* @return void
*/
function plugin_cve_load_language($hook_param) {
global $CFG_GLPI;
// Get user language
$user_language = Session::getLanguage();
// Path to plugin locales
$langpath = PLUGIN_CVE_DIR . '/locales/';
// Available languages for the plugin
$languages = plugin_cve_getLanguages();
// If user language is not available, fallback to English
if (!array_key_exists($user_language, $languages)) {
$user_language = 'en_GB';
}
// Load .mo file for the selected language
$mofile = $langpath . $user_language . '.mo';
// Check if the file exists
if (file_exists($mofile)) {
// Load the translation
load_plugin_textdomain('cve', false, $mofile);
} else {
// Try to fallback to English
$mofile = $langpath . 'en_GB.mo';
if (file_exists($mofile)) {
load_plugin_textdomain('cve', false, $mofile);
}
}
}

273
inc/menu.class.php Normal file
View File

@ -0,0 +1,273 @@
<?php
/**
* GLPI CVE Plugin - Menu Class
* Manages the plugin menu entries
*/
if (!defined('GLPI_ROOT')) {
die("Sorry. You can't access this file directly");
}
/**
* PluginCveCveMenu class for managing plugin menu entries
*/
class PluginCveCveMenu extends CommonGLPI {
static $rightname = 'plugin_cve_cve';
/**
* 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 __('Vulnérabilité', 'cve');
}
/**
* Get menu name
*
* @return string
*/
static function getMenuName() {
return __('Vulnérabilité', 'cve');
}
/**
* Get menu comment
*
* @return string
*/
static function getMenuComment() {
return __('Common Vulnerabilities and Exposures', 'cve');
}
/**
* Check plugin's rights
*
* @return boolean
*/
static function canView() {
return Session::haveRight(self::$rightname, READ);
}
/**
* Check plugin's rights for creation
*
* @return boolean
*/
static function canCreate() {
return Session::haveRight(self::$rightname, CREATE);
}
/**
* Get plugin menu items
*
* @param string $menu Menu name
* @return array Menu entry
*/
static function getMenuContent() {
$menu = [];
if (PluginCveCve::canView()) {
$menu['title'] = self::getMenuName();
$menu['page'] = '/plugins/cve/front/cve.php';
$menu['icon'] = 'fas fa-shield-alt';
$menu['options'] = [
'cve' => [
'title' => PluginCveCve::getTypeName(),
'page' => '/plugins/cve/front/cve.php',
'icon' => 'fas fa-shield-alt',
],
'cvesource' => [
'title' => PluginCveCveSource::getTypeName(),
'page' => '/plugins/cve/front/cvesource.php',
'icon' => 'fas fa-database',
],
'cverule' => [
'title' => PluginCveCveRule::getTypeName(),
'page' => '/plugins/cve/front/cverule.php',
'icon' => 'fas fa-cogs',
]
];
$menu['options']['dashboard'] = [
'title' => __('Dashboard', 'cve'),
'page' => '/plugins/cve/front/dashboard.php',
'icon' => 'fas fa-tachometer-alt',
];
// Add inventory and alerts menu items
if (Session::haveRight('plugin_cve_inventory', READ)) {
$menu['options']['inventory'] = [
'title' => PluginCveCveInventory::getTypeName(),
'page' => '/plugins/cve/front/inventory.php',
'icon' => 'fas fa-laptop',
];
}
if (Session::haveRight('plugin_cve_alert', READ)) {
$menu['options']['alert'] = [
'title' => PluginCveCveAlert::getTypeName(),
'page' => '/plugins/cve/front/alert.php',
'icon' => 'fas fa-exclamation-triangle',
];
}
}
return $menu;
}
/**
* Get main tabs
*
* @param array $options
* @return array
*/
function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) {
if ($item->getType() == 'Ticket') {
if (PluginCveCve::canView()) {
return [1 => __('CVEs', 'cve')];
}
}
// Add tab to software
if ($item->getType() == 'Software' && Session::haveRight('plugin_cve_inventory', READ)) {
return [1 => __('Vulnerabilities', 'cve')];
}
return [];
}
/**
* Display tabs content
*
* @param CommonGLPI $item
* @param int $tabnum
* @param int $withtemplate
* @return boolean
*/
static function displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $withtemplate = 0) {
if ($item->getType() == 'Ticket') {
PluginCveCveTicket::showForTicket($item);
return true;
}
if ($item->getType() == 'Software') {
self::showVulnerabilitiesForSoftware($item);
return true;
}
return false;
}
/**
* Show vulnerabilities for a software
*
* @param Software $software Software object
* @return void
*/
static function showVulnerabilitiesForSoftware(Software $software) {
global $DB;
$ID = $software->getField('id');
echo "<div class='center'>";
// Get vulnerabilities for this software
$query = "SELECT a.*,
c.cve_id,
c.severity AS cve_severity,
c.cvss_score,
c.description,
v.name AS version_name
FROM `glpi_plugin_cve_alerts` AS a
LEFT JOIN `glpi_plugin_cve_cves` AS c ON c.id = a.cves_id
LEFT JOIN `glpi_softwareversions` AS v ON v.id = a.softwareversions_id
WHERE a.softwares_id = $ID
ORDER BY c.severity DESC, c.cvss_score DESC";
$result = $DB->query($query);
if ($result && $DB->numrows($result) > 0) {
echo "<table class='tab_cadre_fixe'>";
echo "<tr class='tab_bg_2'><th colspan='6'>" . __('Vulnerabilities', 'cve') . "</th></tr>";
echo "<tr class='tab_bg_1'>";
echo "<th>" . __('CVE ID', 'cve') . "</th>";
echo "<th>" . __('Version', 'cve') . "</th>";
echo "<th>" . __('Severity', 'cve') . "</th>";
echo "<th>" . __('CVSS Score', 'cve') . "</th>";
echo "<th>" . __('Description', 'cve') . "</th>";
echo "<th>" . __('Status', 'cve') . "</th>";
echo "</tr>";
while ($data = $DB->fetchAssoc($result)) {
echo "<tr class='tab_bg_1'>";
// CVE ID
echo "<td>";
echo "<a href='" . PluginCveCve::getFormURLWithID($data['cves_id']) . "'>";
echo $data['cve_id'];
echo "</a>";
echo "</td>";
// Version
echo "<td>";
echo $data['version_name'];
echo "</td>";
// Severity
echo "<td>";
echo "<span class='" . PluginCveCve::getSeverityClass($data['severity']) . "'>";
echo $data['severity'];
echo "</span>";
echo "</td>";
// CVSS Score
echo "<td>";
echo $data['cvss_score'];
echo "</td>";
// Description
echo "<td>";
echo Html::resume_text($data['description'], 100);
echo "</td>";
// Status
echo "<td>";
echo $data['status'];
if ($data['tickets_id'] > 0) {
echo " (";
echo "<a href='" . Ticket::getFormURLWithID($data['tickets_id']) . "'>";
echo __('Ticket', 'cve') . " #" . $data['tickets_id'];
echo "</a>";
echo ")";
}
echo "</td>";
echo "</tr>";
}
echo "</table>";
} else {
echo "<table class='tab_cadre_fixe'>";
echo "<tr class='tab_bg_2'><th>" . __('Vulnerabilities', 'cve') . "</th></tr>";
echo "<tr class='tab_bg_1'><td class='center'>" . __('No vulnerabilities found for this software', 'cve') . "</td></tr>";
echo "</table>";
}
// Manual scan button
if (Session::haveRight("plugin_cve_inventory", UPDATE)) {
echo "<div class='center' style='margin-top: 10px;'>";
echo "<form method='post' action='/plugins/cve/front/inventory.php'>";
echo "<input type='submit' name='scan_now' value=\"" . __('Scan for vulnerabilities now', 'cve') . "\" class='submit'>";
Html::closeForm();
echo "</div>";
}
echo "</div>";
}
}

13
index.html Normal file
View File

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>GLPI CVE Plugin Prototype</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

330
locales/de_DE.po Normal file
View File

@ -0,0 +1,330 @@
msgid ""
msgstr ""
"Project-Id-Version: GLPI CVE Plugin 1.0.0\n"
"POT-Creation-Date: 2024-01-15 12:00+0100\n"
"PO-Revision-Date: 2024-01-15 12:00+0100\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: de_DE\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
msgid "Vulnérabilité"
msgstr "Schwachstelle"
msgid "CVE"
msgid_plural "CVEs"
msgstr[0] "CVE"
msgstr[1] "CVEs"
msgid "CVE ID"
msgstr "CVE ID"
msgid "Severity"
msgstr "Schweregrad"
msgid "Critical"
msgstr "Kritisch"
msgid "High"
msgstr "Hoch"
msgid "Medium"
msgstr "Mittel"
msgid "Low"
msgstr "Niedrig"
msgid "CVSS Score"
msgstr "CVSS Bewertung"
msgid "CVSS Vector"
msgstr "CVSS Vektor"
msgid "Published Date"
msgstr "Veröffentlichungsdatum"
msgid "Modified Date"
msgstr "Änderungsdatum"
msgid "Status"
msgstr "Status"
msgid "New"
msgstr "Neu"
msgid "Analyzed"
msgstr "Analysiert"
msgid "Assigned"
msgstr "Zugewiesen"
msgid "Resolved"
msgstr "Gelöst"
msgid "Entity"
msgstr "Entität"
msgid "Description"
msgstr "Beschreibung"
msgid "References"
msgstr "Referenzen"
msgid "Enter one URL per line"
msgstr "Eine URL pro Zeile eingeben"
msgid "Affected Products"
msgstr "Betroffene Produkte"
msgid "Enter one product per line"
msgstr "Ein Produkt pro Zeile eingeben"
msgid "Creation date"
msgstr "Erstellungsdatum"
msgid "Last update"
msgstr "Letzte Aktualisierung"
msgid "Common Vulnerabilities and Exposures"
msgstr "Common Vulnerabilities and Exposures"
msgid "Dashboard"
msgstr "Dashboard"
msgid "CVE Rule"
msgid_plural "CVE Rules"
msgstr[0] "CVE-Regel"
msgstr[1] "CVE-Regeln"
msgid "CVE Source"
msgid_plural "CVE Sources"
msgstr[0] "CVE-Quelle"
msgstr[1] "CVE-Quellen"
msgid "Source Name"
msgstr "Quellenname"
msgid "Active"
msgstr "Aktiv"
msgid "API URL"
msgstr "API URL"
msgid "Sync Frequency (hours)"
msgstr "Synchronisierungsfrequenz (Stunden)"
msgid "API Key"
msgstr "API-Schlüssel"
msgid "Last Sync"
msgstr "Letzte Synchronisierung"
msgid "Never"
msgstr "Nie"
msgid "Sync Status"
msgstr "Synchronisierungsstatus"
msgid "Success"
msgstr "Erfolg"
msgid "Failed"
msgstr "Fehlgeschlagen"
msgid "In Progress"
msgstr "In Bearbeitung"
msgid "Pending"
msgstr "Ausstehend"
msgid "Unknown"
msgstr "Unbekannt"
msgid "Sync Now"
msgstr "Jetzt synchronisieren"
msgid "Synchronization completed successfully"
msgstr "Synchronisierung erfolgreich abgeschlossen"
msgid "Rule Name"
msgstr "Regelname"
msgid "Priority"
msgstr "Priorität"
msgid "Lower numbers are processed first"
msgstr "Niedrigere Zahlen werden zuerst verarbeitet"
msgid "Create Ticket"
msgstr "Ticket erstellen"
msgid "Ticket Priority"
msgstr "Ticket-Priorität"
msgid "Very High"
msgstr "Sehr hoch"
msgid "Normal"
msgstr "Normal"
msgid "Notify Administrators"
msgstr "Administratoren benachrichtigen"
msgid "Add to Vulnerability Report"
msgstr "Zum Schwachstellenbericht hinzufügen"
msgid "Active Rule"
msgstr "Aktive Regel"
msgid "CVE Ticket"
msgid_plural "CVE Tickets"
msgstr[0] "CVE-Ticket"
msgstr[1] "CVE-Tickets"
msgid "Add a ticket"
msgstr "Ticket hinzufügen"
msgid "Ticket"
msgstr "Ticket"
msgid "Opening date"
msgstr "Eröffnungsdatum"
msgid "Creation type"
msgstr "Erstellungsart"
msgid "Automatic"
msgstr "Automatisch"
msgid "Manual"
msgstr "Manuell"
msgid "Add a CVE"
msgstr "CVE hinzufügen"
msgid "CVE linked to ticket"
msgstr "Mit Ticket verknüpfte CVE"
msgid "Software Vulnerability Analysis"
msgid_plural "Software Vulnerability Analyses"
msgstr[0] "Software-Schwachstellenanalyse"
msgstr[1] "Software-Schwachstellenanalysen"
msgid "Software Vulnerability Alert"
msgid_plural "Software Vulnerability Alerts"
msgstr[0] "Software-Schwachstellenalarm"
msgstr[1] "Software-Schwachstellenalarme"
msgid "Software"
msgstr "Software"
msgid "Version"
msgstr "Version"
msgid "N/A"
msgstr "N/A"
msgid "Unknown software"
msgstr "Unbekannte Software"
msgid "Unknown version"
msgstr "Unbekannte Version"
msgid "Unknown CVE"
msgstr "Unbekannte CVE"
msgid "Unknown ticket"
msgstr "Unbekanntes Ticket"
msgid "No ticket associated"
msgstr "Kein Ticket zugeordnet"
msgid "Creation Date"
msgstr "Erstellungsdatum"
msgid "ID"
msgstr "ID"
msgid "Processed"
msgstr "Bearbeitet"
msgid "Ignored"
msgstr "Ignoriert"
msgid "CVE Statistics"
msgstr "CVE-Statistiken"
msgid "CVE Severity Distribution"
msgstr "CVE-Schweregradverteilung"
msgid "Recent CVEs"
msgstr "Aktuelle CVEs"
msgid "Software Vulnerability Alerts"
msgstr "Software-Schwachstellenalarme"
msgid "Scan Software Inventory Now"
msgstr "Software-Inventar jetzt scannen"
msgid "Total Vulnerability Alerts"
msgstr "Gesamtzahl der Schwachstellenalarme"
msgid "New Alerts"
msgstr "Neue Alarme"
msgid "Critical Vulnerabilities"
msgstr "Kritische Schwachstellen"
msgid "High Vulnerabilities"
msgstr "Schwerwiegende Schwachstellen"
msgid "Recent Vulnerability Alerts"
msgstr "Aktuelle Schwachstellenalarme"
msgid "No alerts found"
msgstr "Keine Alarme gefunden"
msgid "View all vulnerability alerts"
msgstr "Alle Schwachstellenalarme anzeigen"
msgid "Software vulnerability analysis completed successfully."
msgstr "Software-Schwachstellenanalyse erfolgreich abgeschlossen."
msgid "Software vulnerability analysis completed with no new alerts."
msgstr "Software-Schwachstellenanalyse ohne neue Alarme abgeschlossen."
msgid "Software vulnerability analysis task not found."
msgstr "Software-Schwachstellenanalyseaufgabe nicht gefunden."
msgid "A vulnerability has been detected in your software inventory"
msgstr "In Ihrem Software-Inventar wurde eine Schwachstelle erkannt"
msgid "Vulnerability"
msgstr "Schwachstelle"
msgid "No vulnerabilities found for this software"
msgstr "Keine Schwachstellen für diese Software gefunden"
msgid "Scan for vulnerabilities now"
msgstr "Jetzt nach Schwachstellen scannen"
msgid "Vulnerabilities"
msgstr "Schwachstellen"
msgid "Plugin activated successfully!"
msgstr "Plugin erfolgreich aktiviert!"
msgid "CVE Management plugin has been activated."
msgstr "Das CVE-Management-Plugin wurde aktiviert."
msgid "Initial CVE data is being synchronized in the background."
msgstr "Die ersten CVE-Daten werden im Hintergrund synchronisiert."
msgid "Software inventory will be analyzed for vulnerabilities automatically."
msgstr "Das Software-Inventar wird automatisch auf Schwachstellen analysiert."
msgid "You can now configure the data sources and rules."
msgstr "Sie können jetzt die Datenquellen und Regeln konfigurieren."

351
locales/en_GB.po Normal file
View File

@ -0,0 +1,351 @@
msgid ""
msgstr ""
"Project-Id-Version: GLPI CVE Plugin 1.0.0\n"
"POT-Creation-Date: 2024-01-15 12:00+0100\n"
"PO-Revision-Date: 2024-01-15 12:00+0100\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: en_GB\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
msgid "Vulnérabilité"
msgstr "Vulnerability"
msgid "CVE"
msgid_plural "CVEs"
msgstr[0] "CVE"
msgstr[1] "CVEs"
msgid "CVE ID"
msgstr "CVE ID"
msgid "Severity"
msgstr "Severity"
msgid "Critical"
msgstr "Critical"
msgid "High"
msgstr "High"
msgid "Medium"
msgstr "Medium"
msgid "Low"
msgstr "Low"
msgid "CVSS Score"
msgstr "CVSS Score"
msgid "CVSS Vector"
msgstr "CVSS Vector"
msgid "Published Date"
msgstr "Published Date"
msgid "Modified Date"
msgstr "Modified Date"
msgid "Status"
msgstr "Status"
msgid "New"
msgstr "New"
msgid "Analyzed"
msgstr "Analyzed"
msgid "Assigned"
msgstr "Assigned"
msgid "Resolved"
msgstr "Resolved"
msgid "Entity"
msgstr "Entity"
msgid "Description"
msgstr "Description"
msgid "References"
msgstr "References"
msgid "Enter one URL per line"
msgstr "Enter one URL per line"
msgid "Affected Products"
msgstr "Affected Products"
msgid "Enter one product per line"
msgstr "Enter one product per line"
msgid "Creation date"
msgstr "Creation date"
msgid "Last update"
msgstr "Last update"
msgid "Common Vulnerabilities and Exposures"
msgstr "Common Vulnerabilities and Exposures"
msgid "Dashboard"
msgstr "Dashboard"
msgid "CVE Rule"
msgid_plural "CVE Rules"
msgstr[0] "CVE Rule"
msgstr[1] "CVE Rules"
msgid "CVE Source"
msgid_plural "CVE Sources"
msgstr[0] "CVE Source"
msgstr[1] "CVE Sources"
msgid "Source Name"
msgstr "Source Name"
msgid "Active"
msgstr "Active"
msgid "API URL"
msgstr "API URL"
msgid "Sync Frequency (hours)"
msgstr "Sync Frequency (hours)"
msgid "API Key"
msgstr "API Key"
msgid "Last Sync"
msgstr "Last Sync"
msgid "Never"
msgstr "Never"
msgid "Sync Status"
msgstr "Sync Status"
msgid "Success"
msgstr "Success"
msgid "Failed"
msgstr "Failed"
msgid "In Progress"
msgstr "In Progress"
msgid "Pending"
msgstr "Pending"
msgid "Unknown"
msgstr "Unknown"
msgid "Sync Now"
msgstr "Sync Now"
msgid "Synchronization completed successfully"
msgstr "Synchronization completed successfully"
msgid "Rule Name"
msgstr "Rule Name"
msgid "Priority"
msgstr "Priority"
msgid "Lower numbers are processed first"
msgstr "Lower numbers are processed first"
msgid "Create Ticket"
msgstr "Create Ticket"
msgid "Ticket Priority"
msgstr "Ticket Priority"
msgid "Very High"
msgstr "Very High"
msgid "Normal"
msgstr "Normal"
msgid "Notify Administrators"
msgstr "Notify Administrators"
msgid "Add to Vulnerability Report"
msgstr "Add to Vulnerability Report"
msgid "Active Rule"
msgstr "Active Rule"
msgid "CVE Ticket"
msgid_plural "CVE Tickets"
msgstr[0] "CVE Ticket"
msgstr[1] "CVE Tickets"
msgid "Add a ticket"
msgstr "Add a ticket"
msgid "Ticket"
msgstr "Ticket"
msgid "Opening date"
msgstr "Opening date"
msgid "Creation type"
msgstr "Creation type"
msgid "Automatic"
msgstr "Automatic"
msgid "Manual"
msgstr "Manual"
msgid "Add a CVE"
msgstr "Add a CVE"
msgid "CVE linked to ticket"
msgstr "CVE linked to ticket"
msgid "Software Vulnerability Analysis"
msgid_plural "Software Vulnerability Analyses"
msgstr[0] "Software Vulnerability Analysis"
msgstr[1] "Software Vulnerability Analyses"
msgid "Software Vulnerability Alert"
msgid_plural "Software Vulnerability Alerts"
msgstr[0] "Software Vulnerability Alert"
msgstr[1] "Software Vulnerability Alerts"
msgid "Software"
msgstr "Software"
msgid "Version"
msgstr "Version"
msgid "N/A"
msgstr "N/A"
msgid "Unknown software"
msgstr "Unknown software"
msgid "Unknown version"
msgstr "Unknown version"
msgid "Unknown CVE"
msgstr "Unknown CVE"
msgid "Unknown ticket"
msgstr "Unknown ticket"
msgid "No ticket associated"
msgstr "No ticket associated"
msgid "Creation Date"
msgstr "Creation Date"
msgid "ID"
msgstr "ID"
msgid "Processed"
msgstr "Processed"
msgid "Ignored"
msgstr "Ignored"
msgid "CVE Statistics"
msgstr "CVE Statistics"
msgid "CVE Severity Distribution"
msgstr "CVE Severity Distribution"
msgid "Recent CVEs"
msgstr "Recent CVEs"
msgid "Software Vulnerability Alerts"
msgstr "Software Vulnerability Alerts"
msgid "Scan Software Inventory Now"
msgstr "Scan Software Inventory Now"
msgid "Total Vulnerability Alerts"
msgstr "Total Vulnerability Alerts"
msgid "New Alerts"
msgstr "New Alerts"
msgid "Critical Vulnerabilities"
msgstr "Critical Vulnerabilities"
msgid "High Vulnerabilities"
msgstr "High Vulnerabilities"
msgid "Recent Vulnerability Alerts"
msgstr "Recent Vulnerability Alerts"
msgid "No alerts found"
msgstr "No alerts found"
msgid "View all vulnerability alerts"
msgstr "View all vulnerability alerts"
msgid "Software vulnerability analysis completed successfully."
msgstr "Software vulnerability analysis completed successfully."
msgid "Software vulnerability analysis completed with no new alerts."
msgstr "Software vulnerability analysis completed with no new alerts."
msgid "Software vulnerability analysis task not found."
msgstr "Software vulnerability analysis task not found."
msgid "A vulnerability has been detected in your software inventory"
msgstr "A vulnerability has been detected in your software inventory"
msgid "Vulnerability"
msgstr "Vulnerability"
msgid "No vulnerabilities found for this software"
msgstr "No vulnerabilities found for this software"
msgid "Scan for vulnerabilities now"
msgstr "Scan for vulnerabilities now"
msgid "Vulnerabilities"
msgstr "Vulnerabilities"
msgid "Plugin activated successfully!"
msgstr "Plugin activated successfully!"
msgid "CVE Management plugin has been activated."
msgstr "CVE Management plugin has been activated."
msgid "Initial CVE data is being synchronized in the background."
msgstr "Initial CVE data is being synchronized in the background."
msgid "Software inventory will be analyzed for vulnerabilities automatically."
msgstr "Software inventory will be analyzed for vulnerabilities automatically."
msgid "You can now configure the data sources and rules."
msgstr "You can now configure the data sources and rules."
msgid "Source name is required"
msgstr "Source name is required"
msgid "API URL is required"
msgstr "API URL is required"
msgid "Invalid URL format"
msgstr "Invalid URL format"
msgid "Leave empty to keep current value"
msgstr "Leave empty to keep current value"
msgid "Source Type"
msgstr "Source Type"
msgid "Custom"
msgstr "Custom"
msgid "Data Format"
msgstr "Data Format"

330
locales/es_ES.po Normal file
View File

@ -0,0 +1,330 @@
msgid ""
msgstr ""
"Project-Id-Version: GLPI CVE Plugin 1.0.0\n"
"POT-Creation-Date: 2024-01-15 12:00+0100\n"
"PO-Revision-Date: 2024-01-15 12:00+0100\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: es_ES\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
msgid "Vulnérabilité"
msgstr "Vulnerabilidad"
msgid "CVE"
msgid_plural "CVEs"
msgstr[0] "CVE"
msgstr[1] "CVEs"
msgid "CVE ID"
msgstr "ID CVE"
msgid "Severity"
msgstr "Gravedad"
msgid "Critical"
msgstr "Crítica"
msgid "High"
msgstr "Alta"
msgid "Medium"
msgstr "Media"
msgid "Low"
msgstr "Baja"
msgid "CVSS Score"
msgstr "Puntuación CVSS"
msgid "CVSS Vector"
msgstr "Vector CVSS"
msgid "Published Date"
msgstr "Fecha de publicación"
msgid "Modified Date"
msgstr "Fecha de modificación"
msgid "Status"
msgstr "Estado"
msgid "New"
msgstr "Nueva"
msgid "Analyzed"
msgstr "Analizada"
msgid "Assigned"
msgstr "Asignada"
msgid "Resolved"
msgstr "Resuelta"
msgid "Entity"
msgstr "Entidad"
msgid "Description"
msgstr "Descripción"
msgid "References"
msgstr "Referencias"
msgid "Enter one URL per line"
msgstr "Introduzca una URL por línea"
msgid "Affected Products"
msgstr "Productos afectados"
msgid "Enter one product per line"
msgstr "Introduzca un producto por línea"
msgid "Creation date"
msgstr "Fecha de creación"
msgid "Last update"
msgstr "Última actualización"
msgid "Common Vulnerabilities and Exposures"
msgstr "Vulnerabilidades y exposiciones comunes"
msgid "Dashboard"
msgstr "Panel de control"
msgid "CVE Rule"
msgid_plural "CVE Rules"
msgstr[0] "Regla CVE"
msgstr[1] "Reglas CVE"
msgid "CVE Source"
msgid_plural "CVE Sources"
msgstr[0] "Fuente CVE"
msgstr[1] "Fuentes CVE"
msgid "Source Name"
msgstr "Nombre de la fuente"
msgid "Active"
msgstr "Activa"
msgid "API URL"
msgstr "URL de API"
msgid "Sync Frequency (hours)"
msgstr "Frecuencia de sincronización (horas)"
msgid "API Key"
msgstr "Clave API"
msgid "Last Sync"
msgstr "Última sincronización"
msgid "Never"
msgstr "Nunca"
msgid "Sync Status"
msgstr "Estado de sincronización"
msgid "Success"
msgstr "Éxito"
msgid "Failed"
msgstr "Fallido"
msgid "In Progress"
msgstr "En progreso"
msgid "Pending"
msgstr "Pendiente"
msgid "Unknown"
msgstr "Desconocido"
msgid "Sync Now"
msgstr "Sincronizar ahora"
msgid "Synchronization completed successfully"
msgstr "Sincronización completada con éxito"
msgid "Rule Name"
msgstr "Nombre de la regla"
msgid "Priority"
msgstr "Prioridad"
msgid "Lower numbers are processed first"
msgstr "Los números más bajos se procesan primero"
msgid "Create Ticket"
msgstr "Crear ticket"
msgid "Ticket Priority"
msgstr "Prioridad del ticket"
msgid "Very High"
msgstr "Muy alta"
msgid "Normal"
msgstr "Normal"
msgid "Notify Administrators"
msgstr "Notificar a los administradores"
msgid "Add to Vulnerability Report"
msgstr "Añadir al informe de vulnerabilidades"
msgid "Active Rule"
msgstr "Regla activa"
msgid "CVE Ticket"
msgid_plural "CVE Tickets"
msgstr[0] "Ticket CVE"
msgstr[1] "Tickets CVE"
msgid "Add a ticket"
msgstr "Añadir un ticket"
msgid "Ticket"
msgstr "Ticket"
msgid "Opening date"
msgstr "Fecha de apertura"
msgid "Creation type"
msgstr "Tipo de creación"
msgid "Automatic"
msgstr "Automática"
msgid "Manual"
msgstr "Manual"
msgid "Add a CVE"
msgstr "Añadir una CVE"
msgid "CVE linked to ticket"
msgstr "CVE vinculada al ticket"
msgid "Software Vulnerability Analysis"
msgid_plural "Software Vulnerability Analyses"
msgstr[0] "Análisis de vulnerabilidad de software"
msgstr[1] "Análisis de vulnerabilidades de software"
msgid "Software Vulnerability Alert"
msgid_plural "Software Vulnerability Alerts"
msgstr[0] "Alerta de vulnerabilidad de software"
msgstr[1] "Alertas de vulnerabilidad de software"
msgid "Software"
msgstr "Software"
msgid "Version"
msgstr "Versión"
msgid "N/A"
msgstr "N/D"
msgid "Unknown software"
msgstr "Software desconocido"
msgid "Unknown version"
msgstr "Versión desconocida"
msgid "Unknown CVE"
msgstr "CVE desconocida"
msgid "Unknown ticket"
msgstr "Ticket desconocido"
msgid "No ticket associated"
msgstr "Ningún ticket asociado"
msgid "Creation Date"
msgstr "Fecha de creación"
msgid "ID"
msgstr "ID"
msgid "Processed"
msgstr "Procesada"
msgid "Ignored"
msgstr "Ignorada"
msgid "CVE Statistics"
msgstr "Estadísticas CVE"
msgid "CVE Severity Distribution"
msgstr "Distribución de gravedad CVE"
msgid "Recent CVEs"
msgstr "CVEs recientes"
msgid "Software Vulnerability Alerts"
msgstr "Alertas de vulnerabilidad de software"
msgid "Scan Software Inventory Now"
msgstr "Escanear inventario de software ahora"
msgid "Total Vulnerability Alerts"
msgstr "Total de alertas de vulnerabilidad"
msgid "New Alerts"
msgstr "Nuevas alertas"
msgid "Critical Vulnerabilities"
msgstr "Vulnerabilidades críticas"
msgid "High Vulnerabilities"
msgstr "Vulnerabilidades altas"
msgid "Recent Vulnerability Alerts"
msgstr "Alertas de vulnerabilidad recientes"
msgid "No alerts found"
msgstr "No se encontraron alertas"
msgid "View all vulnerability alerts"
msgstr "Ver todas las alertas de vulnerabilidad"
msgid "Software vulnerability analysis completed successfully."
msgstr "Análisis de vulnerabilidad de software completado con éxito."
msgid "Software vulnerability analysis completed with no new alerts."
msgstr "Análisis de vulnerabilidad de software completado sin nuevas alertas."
msgid "Software vulnerability analysis task not found."
msgstr "Tarea de análisis de vulnerabilidad de software no encontrada."
msgid "A vulnerability has been detected in your software inventory"
msgstr "Se ha detectado una vulnerabilidad en su inventario de software"
msgid "Vulnerability"
msgstr "Vulnerabilidad"
msgid "No vulnerabilities found for this software"
msgstr "No se encontraron vulnerabilidades para este software"
msgid "Scan for vulnerabilities now"
msgstr "Buscar vulnerabilidades ahora"
msgid "Vulnerabilities"
msgstr "Vulnerabilidades"
msgid "Plugin activated successfully!"
msgstr "¡Plugin activado con éxito!"
msgid "CVE Management plugin has been activated."
msgstr "El plugin de gestión de CVE ha sido activado."
msgid "Initial CVE data is being synchronized in the background."
msgstr "Los datos iniciales de CVE se están sincronizando en segundo plano."
msgid "Software inventory will be analyzed for vulnerabilities automatically."
msgstr "El inventario de software será analizado automáticamente en busca de vulnerabilidades."
msgid "You can now configure the data sources and rules."
msgstr "Ahora puede configurar las fuentes de datos y las reglas."

330
locales/fr_FR.po Normal file
View File

@ -0,0 +1,330 @@
msgid ""
msgstr ""
"Project-Id-Version: GLPI CVE Plugin 1.0.0\n"
"POT-Creation-Date: 2024-01-15 12:00+0100\n"
"PO-Revision-Date: 2024-01-15 12:00+0100\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: fr_FR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
msgid "Vulnérabilité"
msgstr "Vulnérabilité"
msgid "CVE"
msgid_plural "CVEs"
msgstr[0] "CVE"
msgstr[1] "CVEs"
msgid "CVE ID"
msgstr "ID CVE"
msgid "Severity"
msgstr "Sévérité"
msgid "Critical"
msgstr "Critique"
msgid "High"
msgstr "Haute"
msgid "Medium"
msgstr "Moyenne"
msgid "Low"
msgstr "Faible"
msgid "CVSS Score"
msgstr "Score CVSS"
msgid "CVSS Vector"
msgstr "Vecteur CVSS"
msgid "Published Date"
msgstr "Date de publication"
msgid "Modified Date"
msgstr "Date de modification"
msgid "Status"
msgstr "Statut"
msgid "New"
msgstr "Nouveau"
msgid "Analyzed"
msgstr "Analysé"
msgid "Assigned"
msgstr "Assigné"
msgid "Resolved"
msgstr "Résolu"
msgid "Entity"
msgstr "Entité"
msgid "Description"
msgstr "Description"
msgid "References"
msgstr "Références"
msgid "Enter one URL per line"
msgstr "Entrez une URL par ligne"
msgid "Affected Products"
msgstr "Produits affectés"
msgid "Enter one product per line"
msgstr "Entrez un produit par ligne"
msgid "Creation date"
msgstr "Date de création"
msgid "Last update"
msgstr "Dernière mise à jour"
msgid "Common Vulnerabilities and Exposures"
msgstr "Vulnérabilités et expositions communes"
msgid "Dashboard"
msgstr "Tableau de bord"
msgid "CVE Rule"
msgid_plural "CVE Rules"
msgstr[0] "Règle CVE"
msgstr[1] "Règles CVE"
msgid "CVE Source"
msgid_plural "CVE Sources"
msgstr[0] "Source CVE"
msgstr[1] "Sources CVE"
msgid "Source Name"
msgstr "Nom de la source"
msgid "Active"
msgstr "Actif"
msgid "API URL"
msgstr "URL de l'API"
msgid "Sync Frequency (hours)"
msgstr "Fréquence de synchronisation (heures)"
msgid "API Key"
msgstr "Clé d'API"
msgid "Last Sync"
msgstr "Dernière synchronisation"
msgid "Never"
msgstr "Jamais"
msgid "Sync Status"
msgstr "Statut de synchronisation"
msgid "Success"
msgstr "Succès"
msgid "Failed"
msgstr "Échec"
msgid "In Progress"
msgstr "En cours"
msgid "Pending"
msgstr "En attente"
msgid "Unknown"
msgstr "Inconnu"
msgid "Sync Now"
msgstr "Synchroniser maintenant"
msgid "Synchronization completed successfully"
msgstr "Synchronisation terminée avec succès"
msgid "Rule Name"
msgstr "Nom de la règle"
msgid "Priority"
msgstr "Priorité"
msgid "Lower numbers are processed first"
msgstr "Les chiffres bas sont traités en premier"
msgid "Create Ticket"
msgstr "Créer un ticket"
msgid "Ticket Priority"
msgstr "Priorité du ticket"
msgid "Very High"
msgstr "Très haute"
msgid "Normal"
msgstr "Normale"
msgid "Notify Administrators"
msgstr "Notifier les administrateurs"
msgid "Add to Vulnerability Report"
msgstr "Ajouter au rapport de vulnérabilité"
msgid "Active Rule"
msgstr "Règle active"
msgid "CVE Ticket"
msgid_plural "CVE Tickets"
msgstr[0] "Ticket CVE"
msgstr[1] "Tickets CVE"
msgid "Add a ticket"
msgstr "Ajouter un ticket"
msgid "Ticket"
msgstr "Ticket"
msgid "Opening date"
msgstr "Date d'ouverture"
msgid "Creation type"
msgstr "Type de création"
msgid "Automatic"
msgstr "Automatique"
msgid "Manual"
msgstr "Manuel"
msgid "Add a CVE"
msgstr "Ajouter une CVE"
msgid "CVE linked to ticket"
msgstr "CVE liée au ticket"
msgid "Software Vulnerability Analysis"
msgid_plural "Software Vulnerability Analyses"
msgstr[0] "Analyse de vulnérabilité logicielle"
msgstr[1] "Analyses de vulnérabilité logicielle"
msgid "Software Vulnerability Alert"
msgid_plural "Software Vulnerability Alerts"
msgstr[0] "Alerte de vulnérabilité logicielle"
msgstr[1] "Alertes de vulnérabilité logicielle"
msgid "Software"
msgstr "Logiciel"
msgid "Version"
msgstr "Version"
msgid "N/A"
msgstr "N/A"
msgid "Unknown software"
msgstr "Logiciel inconnu"
msgid "Unknown version"
msgstr "Version inconnue"
msgid "Unknown CVE"
msgstr "CVE inconnue"
msgid "Unknown ticket"
msgstr "Ticket inconnu"
msgid "No ticket associated"
msgstr "Aucun ticket associé"
msgid "Creation Date"
msgstr "Date de création"
msgid "ID"
msgstr "ID"
msgid "Processed"
msgstr "Traitée"
msgid "Ignored"
msgstr "Ignorée"
msgid "CVE Statistics"
msgstr "Statistiques CVE"
msgid "CVE Severity Distribution"
msgstr "Répartition des sévérités CVE"
msgid "Recent CVEs"
msgstr "CVEs récentes"
msgid "Software Vulnerability Alerts"
msgstr "Alertes de vulnérabilité logicielle"
msgid "Scan Software Inventory Now"
msgstr "Analyser l'inventaire logiciel maintenant"
msgid "Total Vulnerability Alerts"
msgstr "Total des alertes de vulnérabilité"
msgid "New Alerts"
msgstr "Nouvelles alertes"
msgid "Critical Vulnerabilities"
msgstr "Vulnérabilités critiques"
msgid "High Vulnerabilities"
msgstr "Vulnérabilités hautes"
msgid "Recent Vulnerability Alerts"
msgstr "Alertes de vulnérabilité récentes"
msgid "No alerts found"
msgstr "Aucune alerte trouvée"
msgid "View all vulnerability alerts"
msgstr "Voir toutes les alertes de vulnérabilité"
msgid "Software vulnerability analysis completed successfully."
msgstr "Analyse de vulnérabilité logicielle terminée avec succès."
msgid "Software vulnerability analysis completed with no new alerts."
msgstr "Analyse de vulnérabilité logicielle terminée sans nouvelles alertes."
msgid "Software vulnerability analysis task not found."
msgstr "Tâche d'analyse de vulnérabilité logicielle non trouvée."
msgid "A vulnerability has been detected in your software inventory"
msgstr "Une vulnérabilité a été détectée dans votre inventaire logiciel"
msgid "Vulnerability"
msgstr "Vulnérabilité"
msgid "No vulnerabilities found for this software"
msgstr "Aucune vulnérabilité trouvée pour ce logiciel"
msgid "Scan for vulnerabilities now"
msgstr "Rechercher des vulnérabilités maintenant"
msgid "Vulnerabilities"
msgstr "Vulnérabilités"
msgid "Plugin activated successfully!"
msgstr "Plugin activé avec succès !"
msgid "CVE Management plugin has been activated."
msgstr "Le plugin de gestion des CVE a été activé."
msgid "Initial CVE data is being synchronized in the background."
msgstr "Les données CVE initiales sont en cours de synchronisation en arrière-plan."
msgid "Software inventory will be analyzed for vulnerabilities automatically."
msgstr "L'inventaire logiciel sera analysé automatiquement pour détecter les vulnérabilités."
msgid "You can now configure the data sources and rules."
msgstr "Vous pouvez maintenant configurer les sources de données et les règles."

330
locales/it_IT.po Normal file
View File

@ -0,0 +1,330 @@
msgid ""
msgstr ""
"Project-Id-Version: GLPI CVE Plugin 1.0.0\n"
"POT-Creation-Date: 2024-01-15 12:00+0100\n"
"PO-Revision-Date: 2024-01-15 12:00+0100\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: it_IT\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
msgid "Vulnérabilité"
msgstr "Vulnerabilità"
msgid "CVE"
msgid_plural "CVEs"
msgstr[0] "CVE"
msgstr[1] "CVE"
msgid "CVE ID"
msgstr "ID CVE"
msgid "Severity"
msgstr "Gravità"
msgid "Critical"
msgstr "Critica"
msgid "High"
msgstr "Alta"
msgid "Medium"
msgstr "Media"
msgid "Low"
msgstr "Bassa"
msgid "CVSS Score"
msgstr "Punteggio CVSS"
msgid "CVSS Vector"
msgstr "Vettore CVSS"
msgid "Published Date"
msgstr "Data di pubblicazione"
msgid "Modified Date"
msgstr "Data di modifica"
msgid "Status"
msgstr "Stato"
msgid "New"
msgstr "Nuova"
msgid "Analyzed"
msgstr "Analizzata"
msgid "Assigned"
msgstr "Assegnata"
msgid "Resolved"
msgstr "Risolta"
msgid "Entity"
msgstr "Entità"
msgid "Description"
msgstr "Descrizione"
msgid "References"
msgstr "Riferimenti"
msgid "Enter one URL per line"
msgstr "Inserisci un URL per riga"
msgid "Affected Products"
msgstr "Prodotti interessati"
msgid "Enter one product per line"
msgstr "Inserisci un prodotto per riga"
msgid "Creation date"
msgstr "Data di creazione"
msgid "Last update"
msgstr "Ultimo aggiornamento"
msgid "Common Vulnerabilities and Exposures"
msgstr "Vulnerabilità ed esposizioni comuni"
msgid "Dashboard"
msgstr "Dashboard"
msgid "CVE Rule"
msgid_plural "CVE Rules"
msgstr[0] "Regola CVE"
msgstr[1] "Regole CVE"
msgid "CVE Source"
msgid_plural "CVE Sources"
msgstr[0] "Fonte CVE"
msgstr[1] "Fonti CVE"
msgid "Source Name"
msgstr "Nome fonte"
msgid "Active"
msgstr "Attiva"
msgid "API URL"
msgstr "URL API"
msgid "Sync Frequency (hours)"
msgstr "Frequenza di sincronizzazione (ore)"
msgid "API Key"
msgstr "Chiave API"
msgid "Last Sync"
msgstr "Ultima sincronizzazione"
msgid "Never"
msgstr "Mai"
msgid "Sync Status"
msgstr "Stato sincronizzazione"
msgid "Success"
msgstr "Successo"
msgid "Failed"
msgstr "Fallito"
msgid "In Progress"
msgstr "In corso"
msgid "Pending"
msgstr "In attesa"
msgid "Unknown"
msgstr "Sconosciuto"
msgid "Sync Now"
msgstr "Sincronizza ora"
msgid "Synchronization completed successfully"
msgstr "Sincronizzazione completata con successo"
msgid "Rule Name"
msgstr "Nome regola"
msgid "Priority"
msgstr "Priorità"
msgid "Lower numbers are processed first"
msgstr "I numeri più bassi vengono elaborati per primi"
msgid "Create Ticket"
msgstr "Crea ticket"
msgid "Ticket Priority"
msgstr "Priorità ticket"
msgid "Very High"
msgstr "Molto alta"
msgid "Normal"
msgstr "Normale"
msgid "Notify Administrators"
msgstr "Notifica amministratori"
msgid "Add to Vulnerability Report"
msgstr "Aggiungi al rapporto vulnerabilità"
msgid "Active Rule"
msgstr "Regola attiva"
msgid "CVE Ticket"
msgid_plural "CVE Tickets"
msgstr[0] "Ticket CVE"
msgstr[1] "Ticket CVE"
msgid "Add a ticket"
msgstr "Aggiungi un ticket"
msgid "Ticket"
msgstr "Ticket"
msgid "Opening date"
msgstr "Data di apertura"
msgid "Creation type"
msgstr "Tipo di creazione"
msgid "Automatic"
msgstr "Automatica"
msgid "Manual"
msgstr "Manuale"
msgid "Add a CVE"
msgstr "Aggiungi una CVE"
msgid "CVE linked to ticket"
msgstr "CVE collegata al ticket"
msgid "Software Vulnerability Analysis"
msgid_plural "Software Vulnerability Analyses"
msgstr[0] "Analisi vulnerabilità software"
msgstr[1] "Analisi vulnerabilità software"
msgid "Software Vulnerability Alert"
msgid_plural "Software Vulnerability Alerts"
msgstr[0] "Avviso vulnerabilità software"
msgstr[1] "Avvisi vulnerabilità software"
msgid "Software"
msgstr "Software"
msgid "Version"
msgstr "Versione"
msgid "N/A"
msgstr "N/D"
msgid "Unknown software"
msgstr "Software sconosciuto"
msgid "Unknown version"
msgstr "Versione sconosciuta"
msgid "Unknown CVE"
msgstr "CVE sconosciuta"
msgid "Unknown ticket"
msgstr "Ticket sconosciuto"
msgid "No ticket associated"
msgstr "Nessun ticket associato"
msgid "Creation Date"
msgstr "Data di creazione"
msgid "ID"
msgstr "ID"
msgid "Processed"
msgstr "Elaborata"
msgid "Ignored"
msgstr "Ignorata"
msgid "CVE Statistics"
msgstr "Statistiche CVE"
msgid "CVE Severity Distribution"
msgstr "Distribuzione gravità CVE"
msgid "Recent CVEs"
msgstr "CVE recenti"
msgid "Software Vulnerability Alerts"
msgstr "Avvisi vulnerabilità software"
msgid "Scan Software Inventory Now"
msgstr "Scansiona inventario software ora"
msgid "Total Vulnerability Alerts"
msgstr "Totale avvisi vulnerabilità"
msgid "New Alerts"
msgstr "Nuovi avvisi"
msgid "Critical Vulnerabilities"
msgstr "Vulnerabilità critiche"
msgid "High Vulnerabilities"
msgstr "Vulnerabilità alte"
msgid "Recent Vulnerability Alerts"
msgstr "Avvisi vulnerabilità recenti"
msgid "No alerts found"
msgstr "Nessun avviso trovato"
msgid "View all vulnerability alerts"
msgstr "Visualizza tutti gli avvisi di vulnerabilità"
msgid "Software vulnerability analysis completed successfully."
msgstr "Analisi vulnerabilità software completata con successo."
msgid "Software vulnerability analysis completed with no new alerts."
msgstr "Analisi vulnerabilità software completata senza nuovi avvisi."
msgid "Software vulnerability analysis task not found."
msgstr "Attività di analisi vulnerabilità software non trovata."
msgid "A vulnerability has been detected in your software inventory"
msgstr "È stata rilevata una vulnerabilità nel tuo inventario software"
msgid "Vulnerability"
msgstr "Vulnerabilità"
msgid "No vulnerabilities found for this software"
msgstr "Nessuna vulnerabilità trovata per questo software"
msgid "Scan for vulnerabilities now"
msgstr "Scansiona per vulnerabilità ora"
msgid "Vulnerabilities"
msgstr "Vulnerabilità"
msgid "Plugin activated successfully!"
msgstr "Plugin attivato con successo!"
msgid "CVE Management plugin has been activated."
msgstr "Il plugin di gestione CVE è stato attivato."
msgid "Initial CVE data is being synchronized in the background."
msgstr "I dati CVE iniziali sono in fase di sincronizzazione in background."
msgid "Software inventory will be analyzed for vulnerabilities automatically."
msgstr "L'inventario software verrà analizzato automaticamente per individuare vulnerabilità."
msgid "You can now configure the data sources and rules."
msgstr "Ora puoi configurare le fonti di dati e le regole."

336
locales/pl_PL.po Normal file
View File

@ -0,0 +1,336 @@
msgid ""
msgstr ""
"Project-Id-Version: GLPI CVE Plugin 1.0.0\n"
"POT-Creation-Date: 2024-01-15 12:00+0100\n"
"PO-Revision-Date: 2024-01-15 12:00+0100\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: pl_PL\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
msgid "Vulnérabilité"
msgstr "Podatność"
msgid "CVE"
msgid_plural "CVEs"
msgstr[0] "CVE"
msgstr[1] "CVE"
msgstr[2] "CVE"
msgid "CVE ID"
msgstr "ID CVE"
msgid "Severity"
msgstr "Dotkliwość"
msgid "Critical"
msgstr "Krytyczna"
msgid "High"
msgstr "Wysoka"
msgid "Medium"
msgstr "Średnia"
msgid "Low"
msgstr "Niska"
msgid "CVSS Score"
msgstr "Wynik CVSS"
msgid "CVSS Vector"
msgstr "Wektor CVSS"
msgid "Published Date"
msgstr "Data publikacji"
msgid "Modified Date"
msgstr "Data modyfikacji"
msgid "Status"
msgstr "Status"
msgid "New"
msgstr "Nowa"
msgid "Analyzed"
msgstr "Przeanalizowana"
msgid "Assigned"
msgstr "Przypisana"
msgid "Resolved"
msgstr "Rozwiązana"
msgid "Entity"
msgstr "Jednostka"
msgid "Description"
msgstr "Opis"
msgid "References"
msgstr "Odniesienia"
msgid "Enter one URL per line"
msgstr "Wprowadź jeden adres URL w każdej linii"
msgid "Affected Products"
msgstr "Dotknięte produkty"
msgid "Enter one product per line"
msgstr "Wprowadź jeden produkt w każdej linii"
msgid "Creation date"
msgstr "Data utworzenia"
msgid "Last update"
msgstr "Ostatnia aktualizacja"
msgid "Common Vulnerabilities and Exposures"
msgstr "Powszechne podatności i narażenia"
msgid "Dashboard"
msgstr "Panel"
msgid "CVE Rule"
msgid_plural "CVE Rules"
msgstr[0] "Reguła CVE"
msgstr[1] "Reguły CVE"
msgstr[2] "Reguł CVE"
msgid "CVE Source"
msgid_plural "CVE Sources"
msgstr[0] "Źródło CVE"
msgstr[1] "Źródła CVE"
msgstr[2] "Źródeł CVE"
msgid "Source Name"
msgstr "Nazwa źródła"
msgid "Active"
msgstr "Aktywne"
msgid "API URL"
msgstr "URL API"
msgid "Sync Frequency (hours)"
msgstr "Częstotliwość synchronizacji (godziny)"
msgid "API Key"
msgstr "Klucz API"
msgid "Last Sync"
msgstr "Ostatnia synchronizacja"
msgid "Never"
msgstr "Nigdy"
msgid "Sync Status"
msgstr "Status synchronizacji"
msgid "Success"
msgstr "Sukces"
msgid "Failed"
msgstr "Niepowodzenie"
msgid "In Progress"
msgstr "W trakcie"
msgid "Pending"
msgstr "Oczekujące"
msgid "Unknown"
msgstr "Nieznany"
msgid "Sync Now"
msgstr "Synchronizuj teraz"
msgid "Synchronization completed successfully"
msgstr "Synchronizacja zakończona powodzeniem"
msgid "Rule Name"
msgstr "Nazwa reguły"
msgid "Priority"
msgstr "Priorytet"
msgid "Lower numbers are processed first"
msgstr "Niższe liczby są przetwarzane jako pierwsze"
msgid "Create Ticket"
msgstr "Utwórz zgłoszenie"
msgid "Ticket Priority"
msgstr "Priorytet zgłoszenia"
msgid "Very High"
msgstr "Bardzo wysoki"
msgid "Normal"
msgstr "Normalny"
msgid "Notify Administrators"
msgstr "Powiadom administratorów"
msgid "Add to Vulnerability Report"
msgstr "Dodaj do raportu o podatnościach"
msgid "Active Rule"
msgstr "Aktywna reguła"
msgid "CVE Ticket"
msgid_plural "CVE Tickets"
msgstr[0] "Zgłoszenie CVE"
msgstr[1] "Zgłoszenia CVE"
msgstr[2] "Zgłoszeń CVE"
msgid "Add a ticket"
msgstr "Dodaj zgłoszenie"
msgid "Ticket"
msgstr "Zgłoszenie"
msgid "Opening date"
msgstr "Data otwarcia"
msgid "Creation type"
msgstr "Typ utworzenia"
msgid "Automatic"
msgstr "Automatyczny"
msgid "Manual"
msgstr "Ręczny"
msgid "Add a CVE"
msgstr "Dodaj CVE"
msgid "CVE linked to ticket"
msgstr "CVE powiązana ze zgłoszeniem"
msgid "Software Vulnerability Analysis"
msgid_plural "Software Vulnerability Analyses"
msgstr[0] "Analiza podatności oprogramowania"
msgstr[1] "Analizy podatności oprogramowania"
msgstr[2] "Analiz podatności oprogramowania"
msgid "Software Vulnerability Alert"
msgid_plural "Software Vulnerability Alerts"
msgstr[0] "Alert o podatności oprogramowania"
msgstr[1] "Alerty o podatności oprogramowania"
msgstr[2] "Alertów o podatności oprogramowania"
msgid "Software"
msgstr "Oprogramowanie"
msgid "Version"
msgstr "Wersja"
msgid "N/A"
msgstr "N/D"
msgid "Unknown software"
msgstr "Nieznane oprogramowanie"
msgid "Unknown version"
msgstr "Nieznana wersja"
msgid "Unknown CVE"
msgstr "Nieznana CVE"
msgid "Unknown ticket"
msgstr "Nieznane zgłoszenie"
msgid "No ticket associated"
msgstr "Brak powiązanego zgłoszenia"
msgid "Creation Date"
msgstr "Data utworzenia"
msgid "ID"
msgstr "ID"
msgid "Processed"
msgstr "Przetworzona"
msgid "Ignored"
msgstr "Ignorowana"
msgid "CVE Statistics"
msgstr "Statystyki CVE"
msgid "CVE Severity Distribution"
msgstr "Rozkład dotkliwości CVE"
msgid "Recent CVEs"
msgstr "Ostatnie CVE"
msgid "Software Vulnerability Alerts"
msgstr "Alerty o podatności oprogramowania"
msgid "Scan Software Inventory Now"
msgstr "Skanuj inwentarz oprogramowania teraz"
msgid "Total Vulnerability Alerts"
msgstr "Całkowita liczba alertów o podatności"
msgid "New Alerts"
msgstr "Nowe alerty"
msgid "Critical Vulnerabilities"
msgstr "Krytyczne podatności"
msgid "High Vulnerabilities"
msgstr "Wysokie podatności"
msgid "Recent Vulnerability Alerts"
msgstr "Ostatnie alerty o podatności"
msgid "No alerts found"
msgstr "Nie znaleziono alertów"
msgid "View all vulnerability alerts"
msgstr "Wyświetl wszystkie alerty o podatności"
msgid "Software vulnerability analysis completed successfully."
msgstr "Analiza podatności oprogramowania zakończona powodzeniem."
msgid "Software vulnerability analysis completed with no new alerts."
msgstr "Analiza podatności oprogramowania zakończona bez nowych alertów."
msgid "Software vulnerability analysis task not found."
msgstr "Nie znaleziono zadania analizy podatności oprogramowania."
msgid "A vulnerability has been detected in your software inventory"
msgstr "W inwentarzu oprogramowania wykryto podatność"
msgid "Vulnerability"
msgstr "Podatność"
msgid "No vulnerabilities found for this software"
msgstr "Nie znaleziono podatności dla tego oprogramowania"
msgid "Scan for vulnerabilities now"
msgstr "Skanuj w poszukiwaniu podatności teraz"
msgid "Vulnerabilities"
msgstr "Podatności"
msgid "Plugin activated successfully!"
msgstr "Wtyczka aktywowana pomyślnie!"
msgid "CVE Management plugin has been activated."
msgstr "Wtyczka zarządzania CVE została aktywowana."
msgid "Initial CVE data is being synchronized in the background."
msgstr "Początkowe dane CVE są synchronizowane w tle."
msgid "Software inventory will be analyzed for vulnerabilities automatically."
msgstr "Inwentarz oprogramowania zostanie automatycznie przeanalizowany pod kątem podatności."
msgid "You can now configure the data sources and rules."
msgstr "Możesz teraz skonfigurować źródła danych i reguły."

330
locales/pt_PT.po Normal file
View File

@ -0,0 +1,330 @@
msgid ""
msgstr ""
"Project-Id-Version: GLPI CVE Plugin 1.0.0\n"
"POT-Creation-Date: 2024-01-15 12:00+0100\n"
"PO-Revision-Date: 2024-01-15 12:00+0100\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: pt_PT\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
msgid "Vulnérabilité"
msgstr "Vulnerabilidade"
msgid "CVE"
msgid_plural "CVEs"
msgstr[0] "CVE"
msgstr[1] "CVEs"
msgid "CVE ID"
msgstr "ID CVE"
msgid "Severity"
msgstr "Severidade"
msgid "Critical"
msgstr "Crítica"
msgid "High"
msgstr "Alta"
msgid "Medium"
msgstr "Média"
msgid "Low"
msgstr "Baixa"
msgid "CVSS Score"
msgstr "Pontuação CVSS"
msgid "CVSS Vector"
msgstr "Vetor CVSS"
msgid "Published Date"
msgstr "Data de publicação"
msgid "Modified Date"
msgstr "Data de modificação"
msgid "Status"
msgstr "Estado"
msgid "New"
msgstr "Nova"
msgid "Analyzed"
msgstr "Analisada"
msgid "Assigned"
msgstr "Atribuída"
msgid "Resolved"
msgstr "Resolvida"
msgid "Entity"
msgstr "Entidade"
msgid "Description"
msgstr "Descrição"
msgid "References"
msgstr "Referências"
msgid "Enter one URL per line"
msgstr "Insira um URL por linha"
msgid "Affected Products"
msgstr "Produtos afetados"
msgid "Enter one product per line"
msgstr "Insira um produto por linha"
msgid "Creation date"
msgstr "Data de criação"
msgid "Last update"
msgstr "Última atualização"
msgid "Common Vulnerabilities and Exposures"
msgstr "Vulnerabilidades e exposições comuns"
msgid "Dashboard"
msgstr "Painel"
msgid "CVE Rule"
msgid_plural "CVE Rules"
msgstr[0] "Regra CVE"
msgstr[1] "Regras CVE"
msgid "CVE Source"
msgid_plural "CVE Sources"
msgstr[0] "Fonte CVE"
msgstr[1] "Fontes CVE"
msgid "Source Name"
msgstr "Nome da fonte"
msgid "Active"
msgstr "Ativa"
msgid "API URL"
msgstr "URL da API"
msgid "Sync Frequency (hours)"
msgstr "Frequência de sincronização (horas)"
msgid "API Key"
msgstr "Chave API"
msgid "Last Sync"
msgstr "Última sincronização"
msgid "Never"
msgstr "Nunca"
msgid "Sync Status"
msgstr "Estado de sincronização"
msgid "Success"
msgstr "Sucesso"
msgid "Failed"
msgstr "Falha"
msgid "In Progress"
msgstr "Em progresso"
msgid "Pending"
msgstr "Pendente"
msgid "Unknown"
msgstr "Desconhecido"
msgid "Sync Now"
msgstr "Sincronizar agora"
msgid "Synchronization completed successfully"
msgstr "Sincronização concluída com sucesso"
msgid "Rule Name"
msgstr "Nome da regra"
msgid "Priority"
msgstr "Prioridade"
msgid "Lower numbers are processed first"
msgstr "Números mais baixos são processados primeiro"
msgid "Create Ticket"
msgstr "Criar ticket"
msgid "Ticket Priority"
msgstr "Prioridade do ticket"
msgid "Very High"
msgstr "Muito alta"
msgid "Normal"
msgstr "Normal"
msgid "Notify Administrators"
msgstr "Notificar administradores"
msgid "Add to Vulnerability Report"
msgstr "Adicionar ao relatório de vulnerabilidades"
msgid "Active Rule"
msgstr "Regra ativa"
msgid "CVE Ticket"
msgid_plural "CVE Tickets"
msgstr[0] "Ticket CVE"
msgstr[1] "Tickets CVE"
msgid "Add a ticket"
msgstr "Adicionar um ticket"
msgid "Ticket"
msgstr "Ticket"
msgid "Opening date"
msgstr "Data de abertura"
msgid "Creation type"
msgstr "Tipo de criação"
msgid "Automatic"
msgstr "Automática"
msgid "Manual"
msgstr "Manual"
msgid "Add a CVE"
msgstr "Adicionar uma CVE"
msgid "CVE linked to ticket"
msgstr "CVE ligada ao ticket"
msgid "Software Vulnerability Analysis"
msgid_plural "Software Vulnerability Analyses"
msgstr[0] "Análise de vulnerabilidade de software"
msgstr[1] "Análises de vulnerabilidade de software"
msgid "Software Vulnerability Alert"
msgid_plural "Software Vulnerability Alerts"
msgstr[0] "Alerta de vulnerabilidade de software"
msgstr[1] "Alertas de vulnerabilidade de software"
msgid "Software"
msgstr "Software"
msgid "Version"
msgstr "Versão"
msgid "N/A"
msgstr "N/D"
msgid "Unknown software"
msgstr "Software desconhecido"
msgid "Unknown version"
msgstr "Versão desconhecida"
msgid "Unknown CVE"
msgstr "CVE desconhecida"
msgid "Unknown ticket"
msgstr "Ticket desconhecido"
msgid "No ticket associated"
msgstr "Nenhum ticket associado"
msgid "Creation Date"
msgstr "Data de criação"
msgid "ID"
msgstr "ID"
msgid "Processed"
msgstr "Processada"
msgid "Ignored"
msgstr "Ignorada"
msgid "CVE Statistics"
msgstr "Estatísticas CVE"
msgid "CVE Severity Distribution"
msgstr "Distribuição de severidade CVE"
msgid "Recent CVEs"
msgstr "CVEs recentes"
msgid "Software Vulnerability Alerts"
msgstr "Alertas de vulnerabilidade de software"
msgid "Scan Software Inventory Now"
msgstr "Analisar inventário de software agora"
msgid "Total Vulnerability Alerts"
msgstr "Total de alertas de vulnerabilidade"
msgid "New Alerts"
msgstr "Novos alertas"
msgid "Critical Vulnerabilities"
msgstr "Vulnerabilidades críticas"
msgid "High Vulnerabilities"
msgstr "Vulnerabilidades altas"
msgid "Recent Vulnerability Alerts"
msgstr "Alertas de vulnerabilidade recentes"
msgid "No alerts found"
msgstr "Nenhum alerta encontrado"
msgid "View all vulnerability alerts"
msgstr "Ver todos os alertas de vulnerabilidade"
msgid "Software vulnerability analysis completed successfully."
msgstr "Análise de vulnerabilidade de software concluída com sucesso."
msgid "Software vulnerability analysis completed with no new alerts."
msgstr "Análise de vulnerabilidade de software concluída sem novos alertas."
msgid "Software vulnerability analysis task not found."
msgstr "Tarefa de análise de vulnerabilidade de software não encontrada."
msgid "A vulnerability has been detected in your software inventory"
msgstr "Foi detectada uma vulnerabilidade no seu inventário de software"
msgid "Vulnerability"
msgstr "Vulnerabilidade"
msgid "No vulnerabilities found for this software"
msgstr "Nenhuma vulnerabilidade encontrada para este software"
msgid "Scan for vulnerabilities now"
msgstr "Analisar vulnerabilidades agora"
msgid "Vulnerabilities"
msgstr "Vulnerabilidades"
msgid "Plugin activated successfully!"
msgstr "Plugin ativado com sucesso!"
msgid "CVE Management plugin has been activated."
msgstr "O plugin de gestão de CVE foi ativado."
msgid "Initial CVE data is being synchronized in the background."
msgstr "Os dados iniciais de CVE estão a ser sincronizados em segundo plano."
msgid "Software inventory will be analyzed for vulnerabilities automatically."
msgstr "O inventário de software será analisado automaticamente para vulnerabilidades."
msgid "You can now configure the data sources and rules."
msgstr "Agora pode configurar as fontes de dados e as regras."

4076
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

35
package.json Normal file
View File

@ -0,0 +1,35 @@
{
"name": "glpi-cve-plugin-prototype",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"jquery": "^3.7.1",
"lucide-react": "^0.344.0",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"devDependencies": {
"@eslint/js": "^9.9.1",
"@types/jquery": "^3.5.29",
"@types/react": "^18.3.5",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.1",
"autoprefixer": "^10.4.18",
"eslint": "^9.9.1",
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
"eslint-plugin-react-refresh": "^0.4.11",
"globals": "^15.9.0",
"postcss": "^8.4.35",
"tailwindcss": "^3.4.1",
"typescript": "^5.5.3",
"typescript-eslint": "^8.3.0",
"vite": "^5.4.2"
}
}

6
postcss.config.js Normal file
View File

@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

488
public/cve_dashboard.html Normal file
View File

@ -0,0 +1,488 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GLPI CVE Plugin</title>
<!-- Include GLPI's CSS -->
<link rel="stylesheet" href="../css/styles.css">
<!-- Plugin specific CSS -->
<link rel="stylesheet" href="../css/cve.css">
<!-- Font Awesome for icons -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
</head>
<body class="glpi-standard-layout">
<div class="cve-container">
<!-- Plugin Header -->
<div class="cve-card mb-20">
<div class="cve-card-body">
<h1><i class="fas fa-shield-alt text-blue-600 mr-2"></i> CVE Management Plugin</h1>
<p class="text-gray-600">Manage and track Common Vulnerabilities and Exposures</p>
</div>
</div>
<!-- Navigation -->
<div class="cve-nav">
<a class="cve-nav-item active" data-target="cve-dashboard">
<i class="fas fa-tachometer-alt cve-nav-icon"></i>Dashboard
</a>
<a class="cve-nav-item" data-target="cve-list">
<i class="fas fa-shield-alt cve-nav-icon"></i>CVE Management
</a>
<a class="cve-nav-item" data-target="cve-sources">
<i class="fas fa-database cve-nav-icon"></i>Data Sources
</a>
<a class="cve-nav-item" data-target="cve-rules">
<i class="fas fa-cogs cve-nav-icon"></i>Rules
</a>
</div>
<!-- Dashboard Content -->
<div id="cve-dashboard" class="cve-content">
<!-- Statistics Cards -->
<div class="cve-stats-container">
<div class="cve-stat-card cve-critical">
<div class="cve-stat-icon">
<i class="fas fa-exclamation-triangle"></i>
</div>
<div class="cve-stat-content">
<div class="cve-stat-label">Critical Vulnerabilities</div>
<div class="cve-stat-value" id="critical-count">0</div>
</div>
<div class="cve-progress-bar">
<div class="cve-progress-bar-fill" id="critical-progress" style="width: 0%"></div>
</div>
</div>
<div class="cve-stat-card cve-high">
<div class="cve-stat-icon">
<i class="fas fa-exclamation-circle"></i>
</div>
<div class="cve-stat-content">
<div class="cve-stat-label">High Risk Vulnerabilities</div>
<div class="cve-stat-value" id="high-count">0</div>
</div>
<div class="cve-progress-bar">
<div class="cve-progress-bar-fill" id="high-progress" style="width: 0%"></div>
</div>
</div>
<div class="cve-stat-card cve-pending">
<div class="cve-stat-icon">
<i class="fas fa-clock"></i>
</div>
<div class="cve-stat-content">
<div class="cve-stat-label">Pending Analysis</div>
<div class="cve-stat-value" id="pending-count">0</div>
</div>
<div class="cve-progress-bar">
<div class="cve-progress-bar-fill" id="pending-progress" style="width: 0%"></div>
</div>
</div>
<div class="cve-stat-card cve-resolved">
<div class="cve-stat-icon">
<i class="fas fa-check-circle"></i>
</div>
<div class="cve-stat-content">
<div class="cve-stat-label">Resolved Issues</div>
<div class="cve-stat-value" id="resolved-count">0</div>
</div>
<div class="cve-progress-bar">
<div class="cve-progress-bar-fill" id="resolved-progress" style="width: 0%"></div>
</div>
</div>
</div>
<!-- Recent Vulnerabilities -->
<div class="cve-card">
<div class="cve-card-header">
<i class="fas fa-history mr-2"></i> Recent Vulnerabilities
</div>
<div class="cve-card-body">
<div class="table-responsive">
<table class="cve-table" id="recent-cve-table">
<thead>
<tr>
<th>CVE ID</th>
<th>Severity</th>
<th>CVSS</th>
<th>Published</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<!-- Populated by JavaScript -->
</tbody>
</table>
</div>
</div>
</div>
<!-- Severity Distribution Chart -->
<div class="cve-card mt-20">
<div class="cve-card-header">
<i class="fas fa-chart-bar mr-2"></i> Severity Distribution
</div>
<div class="cve-card-body">
<div class="cve-chart-container">
<div>
<div class="cve-chart-bar cve-chart-critical" style="height: 60px"></div>
<div class="cve-chart-label">CRITICAL</div>
<div class="cve-chart-value" id="chart-critical-count">0</div>
</div>
<div>
<div class="cve-chart-bar cve-chart-high" style="height: 120px"></div>
<div class="cve-chart-label">HIGH</div>
<div class="cve-chart-value" id="chart-high-count">0</div>
</div>
<div>
<div class="cve-chart-bar cve-chart-medium" style="height: 80px"></div>
<div class="cve-chart-label">MEDIUM</div>
<div class="cve-chart-value" id="chart-medium-count">0</div>
</div>
<div>
<div class="cve-chart-bar cve-chart-low" style="height: 40px"></div>
<div class="cve-chart-label">LOW</div>
<div class="cve-chart-value" id="chart-low-count">0</div>
</div>
</div>
<div class="text-center mt-10">
<p class="text-gray-500">Total vulnerabilities: <span id="total-cve-count">0</span></p>
</div>
</div>
</div>
</div>
<!-- CVE Management Content -->
<div id="cve-list" class="cve-content" style="display: none;">
<div class="cve-card">
<div class="cve-card-header">
<i class="fas fa-shield-alt mr-2"></i> CVE Management
</div>
<div class="cve-card-body">
<!-- Filters -->
<div class="cve-filters">
<div class="cve-filter-group">
<i class="fas fa-search cve-filter-icon"></i>
<input type="text" id="cve-search" class="cve-filter-input" placeholder="Search CVE ID or description...">
</div>
<div class="cve-filter-group">
<i class="fas fa-filter cve-filter-icon"></i>
<select id="severity-filter" class="cve-filter-select">
<option value="">All Severities</option>
<option value="CRITICAL">Critical</option>
<option value="HIGH">High</option>
<option value="MEDIUM">Medium</option>
<option value="LOW">Low</option>
</select>
</div>
<div class="cve-filter-group">
<i class="fas fa-filter cve-filter-icon"></i>
<select id="status-filter" class="cve-filter-select">
<option value="">All Statuses</option>
<option value="NEW">New</option>
<option value="ANALYZED">Analyzed</option>
<option value="ASSIGNED">Assigned</option>
<option value="RESOLVED">Resolved</option>
</select>
</div>
</div>
<!-- CVE Table -->
<div class="table-responsive">
<table class="cve-table" id="cve-table">
<thead>
<tr>
<th class="cve-sortable" data-column="cve_id">CVE ID</th>
<th>Description</th>
<th class="cve-sortable" data-column="severity">Severity</th>
<th class="cve-sortable" data-column="cvss">CVSS</th>
<th class="cve-sortable" data-column="published">Published</th>
<th class="cve-sortable" data-column="status">Status</th>
<th class="text-right">Actions</th>
</tr>
</thead>
<tbody>
<!-- Populated by JavaScript -->
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Data Sources Content -->
<div id="cve-sources" class="cve-content" style="display: none;">
<div class="cve-card">
<div class="cve-card-header">
<i class="fas fa-database mr-2"></i> CVE Data Sources
</div>
<div class="cve-card-body">
<div class="cve-btn-container">
<button id="add-source-btn" class="btn btn-primary">
<i class="fas fa-plus mr-1"></i> Add Source
</button>
</div>
<!-- Add Source Form -->
<div id="add-source-section" class="cve-form-section" style="display: none;">
<h3 class="cve-form-title">Add New Data Source</h3>
<form id="add-source-form">
<div class="cve-form-grid">
<div class="cve-form-group">
<label class="cve-form-label">Source Name*</label>
<input type="text" id="source-name" class="cve-form-input" required>
</div>
<div class="cve-form-group">
<label class="cve-form-label">API URL*</label>
<input type="text" id="source-url" class="cve-form-input" required>
</div>
<div class="cve-form-group">
<label class="cve-form-label">API Key (if required)</label>
<input type="password" id="source-api-key" class="cve-form-input">
</div>
<div class="cve-form-group">
<label class="cve-form-label">Sync Frequency (hours)</label>
<input type="number" id="source-frequency" class="cve-form-input" value="24" min="1" max="168">
</div>
<div class="cve-form-group">
<label class="cve-form-label">
<input type="checkbox" id="source-active" class="cve-form-checkbox" checked>
Active (enable synchronization)
</label>
</div>
</div>
<div class="cve-form-actions">
<button type="button" class="btn btn-secondary" onclick="$('#add-source-section').hide()">Cancel</button>
<button type="submit" class="btn btn-success">
<i class="fas fa-save mr-1"></i> Save Source
</button>
</div>
</form>
</div>
<!-- Sources Table -->
<table class="cve-table" id="sources-table">
<thead>
<tr>
<th>Source Name</th>
<th>URL</th>
<th>Frequency</th>
<th>Last Sync</th>
<th>Status</th>
<th>Active</th>
<th class="text-right">Actions</th>
</tr>
</thead>
<tbody>
<tr>
<td>National Vulnerability Database (NVD)</td>
<td>https://services.nvd.nist.gov/rest/json/cves/2.0</td>
<td>Every 4 hours</td>
<td>2024-05-15 08:00:00</td>
<td><span class="badge badge-success">SUCCESS</span></td>
<td><span class="badge badge-success">Active</span></td>
<td class="text-right">
<button class="btn btn-sm btn-primary"><i class="fas fa-sync"></i></button>
<button class="btn btn-sm btn-danger"><i class="fas fa-trash"></i></button>
</td>
</tr>
<tr>
<td>MITRE CVE Database</td>
<td>https://cveawg.mitre.org/api/</td>
<td>Every 24 hours</td>
<td>2024-05-14 22:00:00</td>
<td><span class="badge badge-success">SUCCESS</span></td>
<td><span class="badge badge-success">Active</span></td>
<td class="text-right">
<button class="btn btn-sm btn-primary"><i class="fas fa-sync"></i></button>
<button class="btn btn-sm btn-danger"><i class="fas fa-trash"></i></button>
</td>
</tr>
<tr>
<td>CISA KEV Catalog</td>
<td>https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json</td>
<td>Every 12 hours</td>
<td>2024-05-15 04:00:00</td>
<td><span class="badge badge-success">SUCCESS</span></td>
<td><span class="badge badge-success">Active</span></td>
<td class="text-right">
<button class="btn btn-sm btn-primary"><i class="fas fa-sync"></i></button>
<button class="btn btn-sm btn-danger"><i class="fas fa-trash"></i></button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- Rules Content -->
<div id="cve-rules" class="cve-content" style="display: none;">
<div class="cve-card">
<div class="cve-card-header">
<i class="fas fa-cogs mr-2"></i> CVE Processing Rules
</div>
<div class="cve-card-body">
<div class="cve-btn-container">
<button id="add-rule-btn" class="btn btn-primary">
<i class="fas fa-plus mr-1"></i> Add Rule
</button>
</div>
<!-- Add Rule Form -->
<div id="add-rule-section" class="cve-form-section" style="display: none;">
<h3 class="cve-form-title">Add New Rule</h3>
<form id="add-rule-form">
<div class="cve-form-grid">
<div class="cve-form-group" style="grid-column: span 2;">
<label class="cve-form-label">Rule Name*</label>
<input type="text" id="rule-name" class="cve-form-input" required>
</div>
<div>
<h4 class="mb-10">Criteria</h4>
<div class="cve-form-group">
<label class="cve-form-label">Severity</label>
<select id="rule-severity" class="cve-form-select">
<option value="CRITICAL">Critical</option>
<option value="HIGH">High</option>
<option value="MEDIUM">Medium</option>
<option value="LOW">Low</option>
</select>
</div>
<div class="cve-form-group">
<label class="cve-form-label">Priority</label>
<input type="number" id="rule-priority" class="cve-form-input" value="1" min="1">
<p class="mt-5 text-xs text-gray-500">Lower numbers are processed first</p>
</div>
</div>
<div>
<h4 class="mb-10">Actions</h4>
<div class="cve-form-group">
<label class="cve-form-label">
<input type="checkbox" id="create-ticket" class="cve-form-checkbox" checked>
Create ticket automatically
</label>
</div>
<div class="cve-form-group ml-20">
<label class="cve-form-label">Ticket Priority</label>
<select id="ticket-priority" class="cve-form-select">
<option value="VERY HIGH">Very High</option>
<option value="HIGH">High</option>
<option value="NORMAL" selected>Normal</option>
<option value="LOW">Low</option>
</select>
</div>
<div class="cve-form-group">
<label class="cve-form-label">
<input type="checkbox" id="notify-admins" class="cve-form-checkbox">
Send email notification
</label>
</div>
<div class="cve-form-group">
<label class="cve-form-label">
<input type="checkbox" id="add-to-report" class="cve-form-checkbox">
Add to vulnerability report
</label>
</div>
</div>
<div class="cve-form-group" style="grid-column: span 2;">
<label class="cve-form-label">
<input type="checkbox" id="rule-active" class="cve-form-checkbox" checked>
Active (enable this rule)
</label>
</div>
</div>
<div class="cve-form-actions">
<button type="button" class="btn btn-secondary" onclick="$('#add-rule-section').hide()">Cancel</button>
<button type="submit" class="btn btn-success">
<i class="fas fa-save mr-1"></i> Save Rule
</button>
</div>
</form>
</div>
<!-- Rules Table -->
<table class="cve-table" id="rules-table">
<thead>
<tr>
<th>Priority</th>
<th>Rule Name</th>
<th>Criteria</th>
<th>Actions</th>
<th>Status</th>
<th class="text-right">Actions</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Critical Vulnerabilities - Immediate Ticket</td>
<td><span class="badge badge-danger">CRITICAL</span></td>
<td>
<span class="badge badge-purple">Create Ticket</span>
<span class="badge badge-info">Notify</span>
</td>
<td><span class="badge badge-success">Active</span></td>
<td class="text-right">
<button class="btn btn-sm btn-primary">Edit</button>
<button class="btn btn-sm btn-danger"><i class="fas fa-trash"></i></button>
</td>
</tr>
<tr>
<td>2</td>
<td>High Risk - Production Systems</td>
<td><span class="badge badge-warning">HIGH</span></td>
<td>
<span class="badge badge-purple">Create Ticket</span>
</td>
<td><span class="badge badge-success">Active</span></td>
<td class="text-right">
<button class="btn btn-sm btn-primary">Edit</button>
<button class="btn btn-sm btn-danger"><i class="fas fa-trash"></i></button>
</td>
</tr>
<tr>
<td>3</td>
<td>Medium Risk - Batch Report</td>
<td><span class="badge badge-info">MEDIUM</span></td>
<td>
<span class="badge badge-success">Report</span>
</td>
<td><span class="badge badge-success">Active</span></td>
<td class="text-right">
<button class="btn btn-sm btn-primary">Edit</button>
<button class="btn btn-sm btn-danger"><i class="fas fa-trash"></i></button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- jQuery -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<!-- Plugin JS -->
<script src="../js/cve.js"></script>
</body>
</html>

151
public/cve_list.html Normal file
View File

@ -0,0 +1,151 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GLPI CVE Plugin - CVE List</title>
<!-- Include GLPI's CSS -->
<link rel="stylesheet" href="../css/styles.css">
<!-- Plugin specific CSS -->
<link rel="stylesheet" href="../css/cve.css">
<!-- Font Awesome for icons -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
</head>
<body class="glpi-standard-layout">
<div class="cve-container">
<!-- Plugin Header -->
<div class="cve-card mb-20">
<div class="cve-card-body">
<h1><i class="fas fa-shield-alt text-blue-600 mr-2"></i> CVE Management</h1>
<p class="text-gray-600">Browse and manage Common Vulnerabilities and Exposures</p>
</div>
</div>
<!-- Filters -->
<div class="cve-filters">
<div class="cve-filter-group">
<i class="fas fa-search cve-filter-icon"></i>
<input type="text" id="cve-search" class="cve-filter-input" placeholder="Search CVE ID or description...">
</div>
<div class="cve-filter-group">
<i class="fas fa-filter cve-filter-icon"></i>
<select id="severity-filter" class="cve-filter-select">
<option value="">All Severities</option>
<option value="CRITICAL">Critical</option>
<option value="HIGH">High</option>
<option value="MEDIUM">Medium</option>
<option value="LOW">Low</option>
</select>
</div>
<div class="cve-filter-group">
<i class="fas fa-filter cve-filter-icon"></i>
<select id="status-filter" class="cve-filter-select">
<option value="">All Statuses</option>
<option value="NEW">New</option>
<option value="ANALYZED">Analyzed</option>
<option value="ASSIGNED">Assigned</option>
<option value="RESOLVED">Resolved</option>
</select>
</div>
</div>
<!-- CVE Table -->
<div class="cve-card">
<div class="cve-card-header">
<i class="fas fa-shield-alt mr-2"></i> CVE List
</div>
<div class="cve-card-body">
<div class="table-responsive">
<table class="cve-table" id="cve-table">
<thead>
<tr>
<th class="cve-sortable" data-column="cve_id">CVE ID</th>
<th>Description</th>
<th class="cve-sortable" data-column="severity">Severity</th>
<th class="cve-sortable" data-column="cvss">CVSS</th>
<th class="cve-sortable" data-column="published">Published</th>
<th class="cve-sortable" data-column="status">Status</th>
<th class="text-right">Actions</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="#" class="cve-link">CVE-2024-1234</a></td>
<td>A remote code execution vulnerability in Apache Log4j library affecting versions 2.0-beta9 to 2.14.1</td>
<td><span class="badge badge-danger">CRITICAL</span></td>
<td>10.0</td>
<td>2024-05-12</td>
<td><span class="badge badge-info">NEW</span></td>
<td class="text-right">
<button class="btn btn-sm btn-primary">View</button>
<button class="btn btn-sm btn-purple">Create Ticket</button>
<a href="https://nvd.nist.gov/vuln/detail/CVE-2024-1234" target="_blank" class="btn btn-sm btn-secondary"><i class="fas fa-external-link-alt"></i></a>
</td>
</tr>
<tr>
<td><a href="#" class="cve-link">CVE-2024-5678</a></td>
<td>SQL Injection vulnerability in WordPress plugin Contact Form 7 versions prior to 5.7.2</td>
<td><span class="badge badge-warning">HIGH</span></td>
<td>8.8</td>
<td>2024-05-10</td>
<td><span class="badge badge-warning">ANALYZED</span></td>
<td class="text-right">
<button class="btn btn-sm btn-primary">View</button>
<button class="btn btn-sm btn-purple">Create Ticket</button>
<a href="https://nvd.nist.gov/vuln/detail/CVE-2024-5678" target="_blank" class="btn btn-sm btn-secondary"><i class="fas fa-external-link-alt"></i></a>
</td>
</tr>
<tr>
<td><a href="#" class="cve-link">CVE-2024-9012</a></td>
<td>Privilege escalation vulnerability in Microsoft Windows 11 affecting kernel-mode drivers</td>
<td><span class="badge badge-warning">HIGH</span></td>
<td>7.8</td>
<td>2024-05-08</td>
<td><span class="badge badge-primary">ASSIGNED</span></td>
<td class="text-right">
<button class="btn btn-sm btn-primary">View</button>
<button class="btn btn-sm btn-purple">Create Ticket</button>
<a href="https://nvd.nist.gov/vuln/detail/CVE-2024-9012" target="_blank" class="btn btn-sm btn-secondary"><i class="fas fa-external-link-alt"></i></a>
</td>
</tr>
<tr>
<td><a href="#" class="cve-link">CVE-2024-3456</a></td>
<td>Cross-site scripting (XSS) vulnerability in jQuery UI Dialog component prior to version 1.13.2</td>
<td><span class="badge badge-info">MEDIUM</span></td>
<td>5.4</td>
<td>2024-05-05</td>
<td><span class="badge badge-success">RESOLVED</span></td>
<td class="text-right">
<button class="btn btn-sm btn-primary">View</button>
<button class="btn btn-sm btn-purple">Create Ticket</button>
<a href="https://nvd.nist.gov/vuln/detail/CVE-2024-3456" target="_blank" class="btn btn-sm btn-secondary"><i class="fas fa-external-link-alt"></i></a>
</td>
</tr>
<tr>
<td><a href="#" class="cve-link">CVE-2024-7890</a></td>
<td>Information disclosure vulnerability in OpenSSL 3.0.0 through 3.1.1</td>
<td><span class="badge badge-primary">LOW</span></td>
<td>3.7</td>
<td>2024-05-02</td>
<td><span class="badge badge-warning">ANALYZED</span></td>
<td class="text-right">
<button class="btn btn-sm btn-primary">View</button>
<button class="btn btn-sm btn-purple">Create Ticket</button>
<a href="https://nvd.nist.gov/vuln/detail/CVE-2024-7890" target="_blank" class="btn btn-sm btn-secondary"><i class="fas fa-external-link-alt"></i></a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- jQuery -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<!-- Plugin JS -->
<script src="../js/cve.js"></script>
</body>
</html>

View File

@ -0,0 +1,57 @@
#!/usr/bin/env php
<?php
/**
* GLPI CVE Plugin - CLI Inventory Analysis Script
* This script can be called from cron to analyze software inventory
*/
// Define GLPI's constants
define('GLPI_ROOT', dirname(__FILE__, 4));
// Include necessary files
include(GLPI_ROOT . "/inc/autoload.function.php");
include(GLPI_ROOT . "/inc/db.function.php");
include_once(GLPI_ROOT . "/inc/includes.php");
// No time limit for potentially long-running operation
set_time_limit(0);
// Ensure CLI mode is used
if (PHP_SAPI != 'cli') {
echo "This script must be run from command line";
exit(1);
}
// Initialize GLPI
$DB = new DB();
Session::setPath();
Session::start();
Session::loadLanguage();
// Use GLPI's CLI mode
if (!Session::getLoginUserID()) {
Session::setGlpiSessionPath();
Session::setGlpiSessionID('cli_' . getmypid());
$_SESSION['glpi_use_mode'] = Session::NORMAL_MODE;
$_SESSION['glpiname'] = 'cli_cve_inventory';
Session::loadGroups();
}
// Run the inventory analysis
$task = new CronTask();
if ($task->getFromDBbyName('PluginCveCveInventory', 'AnalyzeInventory')) {
echo "Starting software vulnerability analysis...\n";
$result = PluginCveCveInventory::cronAnalyzeInventory($task);
if ($result) {
echo "Software vulnerability analysis completed successfully.\n";
exit(0);
} else {
echo "Software vulnerability analysis completed with no new alerts.\n";
exit(0);
}
} else {
echo "Software vulnerability analysis task not found.\n";
exit(1);
}

57
scripts/cleanup.php Normal file
View File

@ -0,0 +1,57 @@
#!/usr/bin/env php
<?php
/**
* GLPI CVE Plugin - CLI Cleanup Script
* This script removes old CVE data no longer needed
*/
// Define GLPI's constants
define('GLPI_ROOT', dirname(__FILE__, 4));
// Include necessary files
include(GLPI_ROOT . "/inc/autoload.function.php");
include(GLPI_ROOT . "/inc/db.function.php");
include_once(GLPI_ROOT . "/inc/includes.php");
// No time limit for potentially long-running operation
set_time_limit(0);
// Ensure CLI mode is used
if (PHP_SAPI != 'cli') {
echo "This script must be run from command line";
exit(1);
}
// Initialize GLPI
$DB = new DB();
Session::setPath();
Session::start();
Session::loadLanguage();
// Use GLPI's CLI mode
if (!Session::getLoginUserID()) {
Session::setGlpiSessionPath();
Session::setGlpiSessionID('cli_' . getmypid());
$_SESSION['glpi_use_mode'] = Session::NORMAL_MODE;
$_SESSION['glpiname'] = 'cli_cve_cleanup';
Session::loadGroups();
}
// Run the CVE cleanup task
$task = new CronTask();
if ($task->getFromDBbyName('PluginCveCve', 'CleanOldCVEs')) {
echo "Starting CVE cleanup...\n";
$result = PluginCveCve::cronCleanOldCVEs($task);
if ($result) {
echo "CVE cleanup completed successfully.\n";
exit(0);
} else {
echo "CVE cleanup failed or no data to clean.\n";
exit(1);
}
} else {
echo "CVE cleanup task not found.\n";
exit(1);
}

57
scripts/sync_cve.php Normal file
View File

@ -0,0 +1,57 @@
#!/usr/bin/env php
<?php
/**
* GLPI CVE Plugin - CLI Sync Script
* This script can be called from cron to sync CVE data
*/
// Define GLPI's constants
define('GLPI_ROOT', dirname(__FILE__, 4));
// Include necessary files
include(GLPI_ROOT . "/inc/autoload.function.php");
include(GLPI_ROOT . "/inc/db.function.php");
include_once(GLPI_ROOT . "/inc/includes.php");
// No time limit for potentially long-running operation
set_time_limit(0);
// Ensure CLI mode is used
if (PHP_SAPI != 'cli') {
echo "This script must be run from command line";
exit(1);
}
// Initialize GLPI
$DB = new DB();
Session::setPath();
Session::start();
Session::loadLanguage();
// Use GLPI's CLI mode
if (!Session::getLoginUserID()) {
Session::setGlpiSessionPath();
Session::setGlpiSessionID('cli_' . getmypid());
$_SESSION['glpi_use_mode'] = Session::NORMAL_MODE;
$_SESSION['glpiname'] = 'cli_cve_sync';
Session::loadGroups();
}
// Run the CVE synchronization
$task = new CronTask();
if ($task->getFromDBbyName('PluginCveCveSource', 'SyncCVESources')) {
echo "Starting CVE synchronization...\n";
$result = PluginCveCveSource::cronSyncSources($task);
if ($result) {
echo "CVE synchronization completed successfully.\n";
exit(0);
} else {
echo "CVE synchronization failed or no sources to sync.\n";
exit(1);
}
} else {
echo "CVE synchronization task not found.\n";
exit(1);
}

403
setup.php Normal file
View File

@ -0,0 +1,403 @@
<?php
/**
* GLPI CVE Plugin - Main setup file
* Handles plugin initialization, installation, and hooks
*/
define('PLUGIN_CVE_VERSION', '1.0.0');
define('PLUGIN_CVE_MIN_GLPI', '10.0.0');
define('PLUGIN_CVE_MAX_GLPI', '10.99.99');
define('PLUGIN_CVE_DIR', __DIR__);
// Load plugin language definition
include_once(PLUGIN_CVE_DIR . '/inc/define.php');
/**
* Plugin initialization
*
* @return boolean
*/
function plugin_init_cve() {
global $PLUGIN_HOOKS;
$PLUGIN_HOOKS['csrf_compliant']['cve'] = true;
// Add CSS and JS files
$PLUGIN_HOOKS['add_css']['cve'] = ['css/cve.css'];
$PLUGIN_HOOKS['add_javascript']['cve'] = ['js/cve.js'];
// Register classes for itemtype
Plugin::registerClass('PluginCveCve');
Plugin::registerClass('PluginCveCveSource');
Plugin::registerClass('PluginCveCveRule');
Plugin::registerClass('PluginCveCveTicket');
Plugin::registerClass('PluginCveCveInventory');
Plugin::registerClass('PluginCveCveAlert');
// Initialize localization
$PLUGIN_HOOKS['init_session']['cve'] = 'plugin_cve_load_language';
$PLUGIN_HOOKS['change_profile']['cve'] = 'plugin_cve_load_language';
// Add in objects
$PLUGIN_HOOKS['item_get_events']['cve'] = ['NotificationTargetTicket' =>
['PluginCveCveTicket', 'addEvents']];
// Add in database synchronization
$PLUGIN_HOOKS['plugin_datainjection_populate']['cve'] = ['PluginCveCve' =>
'plugin_datainjection_populate_cve'];
// Menu entry - Add to "Tools" menu only
$PLUGIN_HOOKS['menu_toadd']['cve'] = [
'tools' => 'PluginCveCveMenu'
];
// Dashboard widgets
$PLUGIN_HOOKS['dashboard_cards']['cve'] = 'plugin_cve_dashboard_cards';
// Cron tasks registration
CronTask::register('PluginCveCveSource', 'SyncCVESources', 6 * HOUR_TIMESTAMP,
['mode' => CronTask::MODE_EXTERNAL]);
CronTask::register('PluginCveCve', 'CleanOldCVEs', 7 * DAY_TIMESTAMP,
['mode' => CronTask::MODE_EXTERNAL]);
CronTask::register('PluginCveCveInventory', 'AnalyzeInventory', 12 * HOUR_TIMESTAMP,
['mode' => CronTask::MODE_EXTERNAL]);
// Item events (hooks)
$PLUGIN_HOOKS['item_add']['cve'] = ['Ticket' => 'plugin_cve_item_add_ticket'];
$PLUGIN_HOOKS['item_update']['cve'] = ['Ticket' => 'plugin_cve_item_update_ticket'];
$PLUGIN_HOOKS['pre_item_purge']['cve'] = ['Ticket' => 'plugin_cve_pre_item_purge_ticket'];
// Software inventory hook
$PLUGIN_HOOKS['item_add']['cve']['Software'] = 'plugin_cve_item_add_software';
$PLUGIN_HOOKS['item_add']['cve']['SoftwareVersion'] = 'plugin_cve_item_add_softwareversion';
return true;
}
/**
* Handler for software addition
*
* @param Software $software Software that was added
*/
function plugin_cve_item_add_software(Software $software) {
// Schedule a vulnerability analysis when new software is added
$task = new CronTask();
if ($task->getFromDBbyName('PluginCveCveInventory', 'AnalyzeInventory')) {
$task->update([
'id' => $task->fields['id'],
'state' => CronTask::STATE_WAITING
]);
}
}
/**
* Handler for software version addition
*
* @param SoftwareVersion $version Software version that was added
*/
function plugin_cve_item_add_softwareversion(SoftwareVersion $version) {
// Schedule a vulnerability analysis when new software version is added
$task = new CronTask();
if ($task->getFromDBbyName('PluginCveCveInventory', 'AnalyzeInventory')) {
$task->update([
'id' => $task->fields['id'],
'state' => CronTask::STATE_WAITING
]);
}
}
/**
* Plugin version information
*
* @return array Plugin version info
*/
function plugin_version_cve() {
return [
'name' => __('Vulnérabilité', 'cve'),
'version' => PLUGIN_CVE_VERSION,
'author' => 'Your Organization',
'license' => 'GPL v2+',
'homepage' => 'https://github.com/your-org/glpi-cve-plugin',
'requirements' => [
'glpi' => [
'min' => PLUGIN_CVE_MIN_GLPI,
'max' => PLUGIN_CVE_MAX_GLPI,
'dev' => false
],
'php' => [
'min' => '7.4',
'max' => '8.2',
'required' => true
]
]
];
}
/**
* Plugin initialization check
*
* @return boolean
*/
function plugin_cve_check_prerequisites() {
// Check GLPI version
if (version_compare(GLPI_VERSION, PLUGIN_CVE_MIN_GLPI, 'lt') ||
version_compare(GLPI_VERSION, PLUGIN_CVE_MAX_GLPI, 'gt')) {
echo "This plugin requires GLPI >= " . PLUGIN_CVE_MIN_GLPI . " and GLPI <= " . PLUGIN_CVE_MAX_GLPI;
return false;
}
// Check curl extension
if (!extension_loaded('curl')) {
echo "This plugin requires the PHP cURL extension";
return false;
}
return true;
}
/**
* Plugin check config
*
* @return boolean
*/
function plugin_cve_check_config() {
return true;
}
/**
* Plugin installation process
*
* @return boolean
*/
function plugin_cve_install() {
global $DB;
$migration = new Migration(PLUGIN_CVE_VERSION);
// Install core tables
PluginCveCve::install($migration);
PluginCveCveSource::install($migration);
PluginCveCveRule::install($migration);
PluginCveCveTicket::install($migration);
PluginCveCveInventory::install($migration);
PluginCveCveAlert::install($migration);
// Create default notification templates
// This would be implemented here...
$migration->executeMigration();
// Schedule the initial CVE data download as a background task
$task = new CronTask();
// Check if the synchronization task is already registered
$taskId = $task->find(['itemtype' => 'PluginCveCveSource', 'name' => 'SyncCVESources']);
if (count($taskId) == 0) {
// Register the task if it doesn't exist
$taskId = $task->add([
'itemtype' => 'PluginCveCveSource',
'name' => 'SyncCVESources',
'frequency' => 6 * HOUR_TIMESTAMP,
'param' => null,
'state' => CronTask::STATE_WAITING,
'mode' => CronTask::MODE_EXTERNAL,
'allowmode' => CronTask::MODE_EXTERNAL,
'hourmin' => 0,
'hourmax' => 24,
'logs_lifetime' => 30,
'date_creation' => $_SESSION['glpi_currenttime'],
'comment' => 'Synchronize CVE data from external sources'
]);
} else {
$taskData = array_shift($taskId);
$taskId = $taskData['id'];
}
// Register the inventory analysis task
$inventoryTaskId = $task->find(['itemtype' => 'PluginCveCveInventory', 'name' => 'AnalyzeInventory']);
if (count($inventoryTaskId) == 0) {
// Register the task if it doesn't exist
$inventoryTaskId = $task->add([
'itemtype' => 'PluginCveCveInventory',
'name' => 'AnalyzeInventory',
'frequency' => 12 * HOUR_TIMESTAMP,
'param' => null,
'state' => CronTask::STATE_WAITING,
'mode' => CronTask::MODE_EXTERNAL,
'allowmode' => CronTask::MODE_EXTERNAL,
'hourmin' => 0,
'hourmax' => 24,
'logs_lifetime' => 30,
'date_creation' => $_SESSION['glpi_currenttime'],
'comment' => 'Analyze software inventory for vulnerabilities'
]);
} else {
$taskData = array_shift($inventoryTaskId);
$inventoryTaskId = $taskData['id'];
}
// Force run the task immediately to download initial CVE data
if ($taskId > 0) {
// Log the action
Toolbox::logInFile('cve_plugin', 'Initial CVE data synchronization scheduled');
// Queue the task for immediate execution
$task->getFromDB($taskId);
$task->update([
'id' => $taskId,
'state' => CronTask::STATE_WAITING
]);
// Start background sync process via AJAX or CLI
$syncUrl = Plugin::getWebDir('cve') . '/ajax/sync_now.php';
// Use asynchronous curl request to start the sync process
plugin_cve_async_request($syncUrl);
}
// Schedule initial software inventory analysis
if ($inventoryTaskId > 0) {
// Log the action
Toolbox::logInFile('cve_plugin', 'Initial software vulnerability analysis scheduled');
// Queue the task for immediate execution after CVE data is synchronized
$task->getFromDB($inventoryTaskId);
$task->update([
'id' => $inventoryTaskId,
'state' => CronTask::STATE_WAITING
]);
}
return true;
}
/**
* Make an asynchronous HTTP request
*
* @param string $url URL to request
* @return boolean Success
*/
function plugin_cve_async_request($url) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 1);
curl_setopt($ch, CURLOPT_NOSIGNAL, 1);
// Add security token to the request
$params = [
'_glpi_csrf_token' => Session::getNewCSRFToken()
];
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
curl_exec($ch);
curl_close($ch);
return true;
}
/**
* Plugin uninstallation process
*
* @return boolean
*/
function plugin_cve_uninstall() {
global $DB;
$migration = new Migration(PLUGIN_CVE_VERSION);
// Remove cron tasks
$crontask = new CronTask();
$crontask->deleteByCriteria(['itemtype' => 'PluginCveCveSource', 'name' => 'SyncCVESources']);
$crontask->deleteByCriteria(['itemtype' => 'PluginCveCve', 'name' => 'CleanOldCVEs']);
$crontask->deleteByCriteria(['itemtype' => 'PluginCveCveInventory', 'name' => 'AnalyzeInventory']);
// Uninstall core tables
PluginCveCve::uninstall($migration);
PluginCveCveSource::uninstall($migration);
PluginCveCveRule::uninstall($migration);
PluginCveCveTicket::uninstall($migration);
PluginCveCveInventory::uninstall($migration);
PluginCveCveAlert::uninstall($migration);
// Remove user preferences
$query = "DELETE FROM `glpi_configs` WHERE `context` = 'plugin:cve'";
$DB->query($query);
$migration->executeMigration();
return true;
}
/**
* Define dropdown relations
*
* @return array
*/
function plugin_cve_getDatabaseRelations() {
$plugin = new Plugin();
if ($plugin->isActivated('cve')) {
return [
"glpi_entities" => [
"glpi_plugin_cve_cves" => "entities_id",
"glpi_plugin_cve_alerts" => "entities_id"
],
"glpi_tickets" => [
"glpi_plugin_cve_tickets" => "tickets_id",
"glpi_plugin_cve_alerts" => "tickets_id"
],
"glpi_softwares" => [
"glpi_plugin_cve_alerts" => "softwares_id"
],
"glpi_softwareversions" => [
"glpi_plugin_cve_alerts" => "softwareversions_id"
]
];
}
return [];
}
/**
* Define database table relations
*
* @return array
*/
function plugin_cve_getTablesNames() {
$plugin = new Plugin();
$tables = [];
if ($plugin->isActivated('cve')) {
$tables = [
'glpi_plugin_cve_cves',
'glpi_plugin_cve_sources',
'glpi_plugin_cve_rules',
'glpi_plugin_cve_tickets',
'glpi_plugin_cve_alerts'
];
}
return $tables;
}
/**
* Display a nice message when activated
*/
function plugin_cve_postinit() {
global $PLUGIN_HOOKS;
// Add nice message when activated for the first time
if (isset($_GET['activate']) && $_GET['activate'] === 'cve') {
Html::displayTitle(__('Plugin activated successfully!', 'cve'),
__('CVE Management plugin has been activated.', 'cve') . '<br>' .
__('Initial CVE data is being synchronized in the background.', 'cve') . '<br>' .
__('Software inventory will be analyzed for vulnerabilities automatically.', 'cve') . '<br>' .
__('You can now configure the data sources and rules.', 'cve'));
}
}

79
src/App.tsx Normal file
View File

@ -0,0 +1,79 @@
import React, { useState } from 'react';
import { LayoutDashboard, Shield, Settings, Database, AlertCircle } from 'lucide-react';
import Dashboard from './components/Dashboard';
import CVEList from './components/CVEList';
import SourcesConfig from './components/SourcesConfig';
import RulesConfig from './components/RulesConfig';
function App() {
const [activeTab, setActiveTab] = useState<string>('dashboard');
const navigation = [
{ id: 'dashboard', name: 'Dashboard', icon: <LayoutDashboard className="h-5 w-5" /> },
{ id: 'cve', name: 'CVE Management', icon: <Shield className="h-5 w-5" /> },
{ id: 'sources', name: 'Data Sources', icon: <Database className="h-5 w-5" /> },
{ id: 'rules', name: 'Rules', icon: <Settings className="h-5 w-5" /> },
];
return (
<div className="min-h-screen bg-gray-100 flex">
{/* Sidebar */}
<div className="w-64 bg-white shadow-md hidden md:block">
<div className="h-16 flex items-center px-6 border-b border-gray-200">
<AlertCircle className="h-6 w-6 mr-2 text-blue-600" />
<span className="font-bold text-lg">GLPI CVE Plugin</span>
</div>
<nav className="mt-6 px-3">
{navigation.map((item) => (
<button
key={item.id}
onClick={() => setActiveTab(item.id)}
className={`flex items-center px-3 py-2 mt-1 rounded-md w-full text-left ${
activeTab === item.id
? 'bg-blue-50 text-blue-700'
: 'text-gray-700 hover:bg-gray-100'
}`}
>
<span className="mr-3">{item.icon}</span>
{item.name}
</button>
))}
</nav>
</div>
{/* Mobile navbar */}
<div className="md:hidden w-full bg-white p-4 flex border-b border-gray-200">
<div className="flex items-center">
<AlertCircle className="h-6 w-6 mr-2 text-blue-600" />
<span className="font-bold text-lg">GLPI CVE Plugin</span>
</div>
</div>
{/* Mobile navigation */}
<div className="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-200 flex justify-around py-2 md:hidden">
{navigation.map((item) => (
<button
key={item.id}
onClick={() => setActiveTab(item.id)}
className={`flex flex-col items-center px-3 py-2 text-sm ${
activeTab === item.id ? 'text-blue-700' : 'text-gray-600'
}`}
>
{item.icon}
<span className="mt-1">{item.name}</span>
</button>
))}
</div>
{/* Main content */}
<div className="flex-1 min-h-screen md:ml-64">
{activeTab === 'dashboard' && <Dashboard />}
{activeTab === 'cve' && <CVEList />}
{activeTab === 'sources' && <SourcesConfig />}
{activeTab === 'rules' && <RulesConfig />}
</div>
</div>
);
}
export default App;

208
src/components/CVEList.tsx Normal file
View File

@ -0,0 +1,208 @@
import React, { useState } from 'react';
import { Search, Filter, ChevronDown, ChevronUp, ExternalLink } from 'lucide-react';
import { mockCVEs } from '../data/mockData';
import { CVE } from '../types/cve';
const CVEList: React.FC = () => {
const [searchTerm, setSearchTerm] = useState('');
const [filterSeverity, setFilterSeverity] = useState<string>('');
const [filterStatus, setFilterStatus] = useState<string>('');
const [sortField, setSortField] = useState<keyof CVE>('published_date');
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc');
const handleSort = (field: keyof CVE) => {
if (sortField === field) {
setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc');
} else {
setSortField(field);
setSortDirection('desc');
}
};
const filteredCVEs = mockCVEs.filter(cve => {
return (
(searchTerm === '' ||
cve.cve_id.toLowerCase().includes(searchTerm.toLowerCase()) ||
cve.description.toLowerCase().includes(searchTerm.toLowerCase())) &&
(filterSeverity === '' || cve.severity === filterSeverity) &&
(filterStatus === '' || cve.status === filterStatus)
);
}).sort((a, b) => {
if (a[sortField] < b[sortField]) return sortDirection === 'asc' ? -1 : 1;
if (a[sortField] > b[sortField]) return sortDirection === 'asc' ? 1 : -1;
return 0;
});
const getSortIcon = (field: keyof CVE) => {
if (sortField !== field) return null;
return sortDirection === 'asc' ? <ChevronUp className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />;
};
return (
<div className="p-6">
<div className="mb-6">
<h1 className="text-2xl font-bold mb-1">CVE Management</h1>
<p className="text-gray-600">Browse and manage Common Vulnerabilities and Exposures</p>
</div>
<div className="mb-6 flex flex-col md:flex-row gap-4">
<div className="flex-1 relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<Search className="h-5 w-5 text-gray-400" />
</div>
<input
type="text"
className="block w-full pl-10 pr-3 py-2 border border-gray-300 rounded-md leading-5 bg-white placeholder-gray-500 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
placeholder="Search CVE ID or description..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</div>
<div className="flex space-x-4">
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<Filter className="h-5 w-5 text-gray-400" />
</div>
<select
className="block w-full pl-10 pr-3 py-2 border border-gray-300 rounded-md leading-5 bg-white focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
value={filterSeverity}
onChange={(e) => setFilterSeverity(e.target.value)}
>
<option value="">All Severities</option>
<option value="CRITICAL">Critical</option>
<option value="HIGH">High</option>
<option value="MEDIUM">Medium</option>
<option value="LOW">Low</option>
</select>
</div>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<Filter className="h-5 w-5 text-gray-400" />
</div>
<select
className="block w-full pl-10 pr-3 py-2 border border-gray-300 rounded-md leading-5 bg-white focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
value={filterStatus}
onChange={(e) => setFilterStatus(e.target.value)}
>
<option value="">All Statuses</option>
<option value="NEW">New</option>
<option value="ANALYZED">Analyzed</option>
<option value="ASSIGNED">Assigned</option>
<option value="RESOLVED">Resolved</option>
</select>
</div>
</div>
</div>
<div className="bg-white shadow-md rounded-lg overflow-hidden">
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer"
onClick={() => handleSort('cve_id')}
>
<div className="flex items-center">
CVE ID
{getSortIcon('cve_id')}
</div>
</th>
<th
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer"
onClick={() => handleSort('severity')}
>
<div className="flex items-center">
Severity
{getSortIcon('severity')}
</div>
</th>
<th
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer"
onClick={() => handleSort('cvss_score')}
>
<div className="flex items-center">
CVSS
{getSortIcon('cvss_score')}
</div>
</th>
<th
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer"
onClick={() => handleSort('published_date')}
>
<div className="flex items-center">
Published
{getSortIcon('published_date')}
</div>
</th>
<th
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer"
onClick={() => handleSort('status')}
>
<div className="flex items-center">
Status
{getSortIcon('status')}
</div>
</th>
<th className="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
Actions
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{filteredCVEs.map((cve) => (
<tr key={cve.id} className="hover:bg-gray-50">
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-blue-600">
{cve.cve_id}
</td>
<td className="px-6 py-4 whitespace-nowrap">
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
cve.severity === 'CRITICAL' ? 'bg-red-100 text-red-800' :
cve.severity === 'HIGH' ? 'bg-orange-100 text-orange-800' :
cve.severity === 'MEDIUM' ? 'bg-yellow-100 text-yellow-800' :
'bg-blue-100 text-blue-800'
}`}>
{cve.severity}
</span>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{cve.cvss_score.toFixed(1)}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{new Date(cve.published_date).toLocaleDateString()}
</td>
<td className="px-6 py-4 whitespace-nowrap">
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
cve.status === 'NEW' ? 'bg-blue-100 text-blue-800' :
cve.status === 'ANALYZED' ? 'bg-yellow-100 text-yellow-800' :
cve.status === 'ASSIGNED' ? 'bg-purple-100 text-purple-800' :
'bg-green-100 text-green-800'
}`}>
{cve.status}
</span>
</td>
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button className="text-blue-600 hover:text-blue-900 mr-3">View</button>
<button className="text-purple-600 hover:text-purple-900 mr-3">Create Ticket</button>
<a
href={cve.references[0]}
target="_blank"
rel="noopener noreferrer"
className="text-gray-600 hover:text-gray-900 inline-flex items-center"
>
<ExternalLink className="h-4 w-4" />
</a>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
);
};
export default CVEList;

View File

@ -0,0 +1,176 @@
import React from 'react';
import { PieChart as ChartPie, ShieldAlert, Clock, FileText } from 'lucide-react';
import { mockCVEs } from '../data/mockData';
const severityColors = {
CRITICAL: 'bg-red-500',
HIGH: 'bg-orange-500',
MEDIUM: 'bg-yellow-500',
LOW: 'bg-blue-500'
};
const statusCounts = {
NEW: mockCVEs.filter(cve => cve.status === 'NEW').length,
ANALYZED: mockCVEs.filter(cve => cve.status === 'ANALYZED').length,
ASSIGNED: mockCVEs.filter(cve => cve.status === 'ASSIGNED').length,
RESOLVED: mockCVEs.filter(cve => cve.status === 'RESOLVED').length,
};
const severityCounts = {
CRITICAL: mockCVEs.filter(cve => cve.severity === 'CRITICAL').length,
HIGH: mockCVEs.filter(cve => cve.severity === 'HIGH').length,
MEDIUM: mockCVEs.filter(cve => cve.severity === 'MEDIUM').length,
LOW: mockCVEs.filter(cve => cve.severity === 'LOW').length,
};
const Dashboard = () => {
return (
<div className="p-6">
<div className="mb-8">
<h1 className="text-2xl font-bold mb-1">CVE Dashboard</h1>
<p className="text-gray-600">Overview of vulnerability management status</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
<div className="bg-white rounded-lg shadow p-6">
<div className="flex items-center mb-4">
<div className="rounded-full bg-red-100 p-3 mr-4">
<ShieldAlert className="h-6 w-6 text-red-600" />
</div>
<div>
<p className="text-gray-500 text-sm">Critical Vulnerabilities</p>
<p className="text-2xl font-bold">{severityCounts.CRITICAL}</p>
</div>
</div>
<div className="h-2 bg-gray-200 rounded-full overflow-hidden">
<div className="bg-red-500 h-full" style={{ width: `${(severityCounts.CRITICAL / mockCVEs.length) * 100}%` }}></div>
</div>
</div>
<div className="bg-white rounded-lg shadow p-6">
<div className="flex items-center mb-4">
<div className="rounded-full bg-orange-100 p-3 mr-4">
<ShieldAlert className="h-6 w-6 text-orange-600" />
</div>
<div>
<p className="text-gray-500 text-sm">High Risk Vulnerabilities</p>
<p className="text-2xl font-bold">{severityCounts.HIGH}</p>
</div>
</div>
<div className="h-2 bg-gray-200 rounded-full overflow-hidden">
<div className="bg-orange-500 h-full" style={{ width: `${(severityCounts.HIGH / mockCVEs.length) * 100}%` }}></div>
</div>
</div>
<div className="bg-white rounded-lg shadow p-6">
<div className="flex items-center mb-4">
<div className="rounded-full bg-blue-100 p-3 mr-4">
<Clock className="h-6 w-6 text-blue-600" />
</div>
<div>
<p className="text-gray-500 text-sm">Pending Analysis</p>
<p className="text-2xl font-bold">{statusCounts.NEW}</p>
</div>
</div>
<div className="h-2 bg-gray-200 rounded-full overflow-hidden">
<div className="bg-blue-500 h-full" style={{ width: `${(statusCounts.NEW / mockCVEs.length) * 100}%` }}></div>
</div>
</div>
<div className="bg-white rounded-lg shadow p-6">
<div className="flex items-center mb-4">
<div className="rounded-full bg-green-100 p-3 mr-4">
<FileText className="h-6 w-6 text-green-600" />
</div>
<div>
<p className="text-gray-500 text-sm">Resolved Issues</p>
<p className="text-2xl font-bold">{statusCounts.RESOLVED}</p>
</div>
</div>
<div className="h-2 bg-gray-200 rounded-full overflow-hidden">
<div className="bg-green-500 h-full" style={{ width: `${(statusCounts.RESOLVED / mockCVEs.length) * 100}%` }}></div>
</div>
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div className="col-span-2 bg-white rounded-lg shadow">
<div className="p-6 border-b border-gray-200">
<h2 className="text-xl font-semibold">Recent Vulnerabilities</h2>
</div>
<div className="p-6">
<div className="overflow-x-auto">
<table className="min-w-full">
<thead>
<tr className="bg-gray-50">
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">CVE ID</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Severity</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">CVSS</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Published</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{mockCVEs.slice(0, 5).map((cve) => (
<tr key={cve.id} className="hover:bg-gray-50">
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-blue-600">{cve.cve_id}</td>
<td className="px-6 py-4 whitespace-nowrap">
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
cve.severity === 'CRITICAL' ? 'bg-red-100 text-red-800' :
cve.severity === 'HIGH' ? 'bg-orange-100 text-orange-800' :
cve.severity === 'MEDIUM' ? 'bg-yellow-100 text-yellow-800' :
'bg-blue-100 text-blue-800'
}`}>
{cve.severity}
</span>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{cve.cvss_score.toFixed(1)}</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{new Date(cve.published_date).toLocaleDateString()}
</td>
<td className="px-6 py-4 whitespace-nowrap">
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
cve.status === 'NEW' ? 'bg-blue-100 text-blue-800' :
cve.status === 'ANALYZED' ? 'bg-yellow-100 text-yellow-800' :
cve.status === 'ASSIGNED' ? 'bg-purple-100 text-purple-800' :
'bg-green-100 text-green-800'
}`}>
{cve.status}
</span>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
<div className="bg-white rounded-lg shadow">
<div className="p-6 border-b border-gray-200">
<h2 className="text-xl font-semibold">Severity Distribution</h2>
</div>
<div className="p-6 flex flex-col items-center">
<div className="w-full h-48 mb-4 flex items-end justify-around">
{Object.entries(severityCounts).map(([severity, count]) => (
<div key={severity} className="flex flex-col items-center">
<div
className={`${severityColors[severity as keyof typeof severityColors]} rounded-t-lg w-16`}
style={{ height: `${Math.max((count / mockCVEs.length) * 150, 20)}px` }}
></div>
<div className="mt-2 text-xs font-medium">{severity}</div>
<div className="text-gray-600 font-semibold">{count}</div>
</div>
))}
</div>
<div className="mt-4 text-gray-500 text-sm text-center">
Total vulnerabilities: {mockCVEs.length}
</div>
</div>
</div>
</div>
</div>
);
};
export default Dashboard;

View File

@ -0,0 +1,362 @@
import React, { useState } from 'react';
import { Save, Plus, Trash2 } from 'lucide-react';
import { mockRules } from '../data/mockData';
import { CVERule } from '../types/cve';
const RulesConfig: React.FC = () => {
const [rules, setRules] = useState<CVERule[]>(mockRules);
const [editingRule, setEditingRule] = useState<Partial<CVERule> | null>(null);
const [isEditing, setIsEditing] = useState<boolean>(false);
const handleEditRule = (rule: CVERule) => {
setEditingRule({ ...rule });
setIsEditing(true);
};
const handleNewRule = () => {
setEditingRule({
id: rules.length + 1,
name: '',
criteria: { severity: 'HIGH' },
actions: { create_ticket: true, ticket_priority: 'NORMAL' },
priority: rules.length + 1,
is_active: true
});
setIsEditing(true);
};
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => {
const { name, value, type } = e.target as HTMLInputElement;
if (name === 'severity' || name === 'ticket_priority') {
setEditingRule({
...editingRule,
criteria: name === 'severity' ? { ...editingRule?.criteria, severity: value } : editingRule?.criteria,
actions: name === 'ticket_priority' ? { ...editingRule?.actions, ticket_priority: value } : editingRule?.actions
});
return;
}
setEditingRule({
...editingRule,
[name]: type === 'checkbox'
? (e.target as HTMLInputElement).checked
: type === 'number'
? parseInt(value)
: value
});
};
const handleActionChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, checked } = e.target;
if (editingRule) {
setEditingRule({
...editingRule,
actions: {
...editingRule.actions,
[name]: checked
}
});
}
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (editingRule && editingRule.name) {
if (editingRule.id && rules.some(r => r.id === editingRule.id)) {
// Update existing rule
setRules(rules.map(rule => rule.id === editingRule.id ? editingRule as CVERule : rule));
} else {
// Add new rule
setRules([...rules, editingRule as CVERule]);
}
setIsEditing(false);
setEditingRule(null);
}
};
const handleDelete = (ruleId: number) => {
setRules(rules.filter(rule => rule.id !== ruleId));
};
const handleCancel = () => {
setIsEditing(false);
setEditingRule(null);
};
return (
<div className="p-6">
<div className="mb-6">
<h1 className="text-2xl font-bold mb-1">CVE Processing Rules</h1>
<p className="text-gray-600">Configure automated actions for vulnerability management</p>
</div>
<div className="mb-6 flex justify-between">
<p className="text-sm text-gray-500">
Define rules that automatically process CVEs based on specific criteria. Rules are evaluated in priority order.
</p>
<button
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 flex items-center"
onClick={handleNewRule}
>
<Plus className="h-4 w-4 mr-2" /> Add Rule
</button>
</div>
{isEditing && editingRule && (
<div className="bg-white rounded-lg shadow-md p-6 mb-6">
<h2 className="text-lg font-semibold mb-4">
{editingRule.id && rules.some(r => r.id === editingRule.id) ? 'Edit Rule' : 'Add New Rule'}
</h2>
<form onSubmit={handleSubmit}>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="col-span-2">
<label className="block text-sm font-medium text-gray-700 mb-1">
Rule Name*
</label>
<input
type="text"
name="name"
value={editingRule.name}
onChange={handleChange}
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
required
/>
</div>
<div>
<h3 className="text-md font-medium mb-3">Criteria</h3>
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-1">
Severity
</label>
<select
name="severity"
value={editingRule.criteria?.severity}
onChange={handleChange}
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
>
<option value="CRITICAL">Critical</option>
<option value="HIGH">High</option>
<option value="MEDIUM">Medium</option>
<option value="LOW">Low</option>
</select>
</div>
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-1">
Priority
</label>
<input
type="number"
name="priority"
value={editingRule.priority}
onChange={handleChange}
min="1"
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
/>
<p className="mt-1 text-xs text-gray-500">Lower numbers are processed first</p>
</div>
</div>
<div>
<h3 className="text-md font-medium mb-3">Actions</h3>
<div className="mb-4 flex items-center">
<input
type="checkbox"
id="create_ticket"
name="create_ticket"
checked={!!editingRule.actions?.create_ticket}
onChange={handleActionChange}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
/>
<label htmlFor="create_ticket" className="ml-2 block text-sm text-gray-900">
Create ticket automatically
</label>
</div>
{editingRule.actions?.create_ticket && (
<div className="mb-4 ml-6">
<label className="block text-sm font-medium text-gray-700 mb-1">
Ticket Priority
</label>
<select
name="ticket_priority"
value={editingRule.actions?.ticket_priority}
onChange={handleChange}
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
>
<option value="VERY HIGH">Very High</option>
<option value="HIGH">High</option>
<option value="NORMAL">Normal</option>
<option value="LOW">Low</option>
</select>
</div>
)}
<div className="mb-4 flex items-center">
<input
type="checkbox"
id="notify_admins"
name="notify_admins"
checked={!!editingRule.actions?.notify_admins}
onChange={handleActionChange}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
/>
<label htmlFor="notify_admins" className="ml-2 block text-sm text-gray-900">
Send email notification
</label>
</div>
<div className="mb-4 flex items-center">
<input
type="checkbox"
id="add_to_report"
name="add_to_report"
checked={!!editingRule.actions?.add_to_report}
onChange={handleActionChange}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
/>
<label htmlFor="add_to_report" className="ml-2 block text-sm text-gray-900">
Add to vulnerability report
</label>
</div>
</div>
<div className="col-span-2 flex items-center">
<input
type="checkbox"
id="is_active"
name="is_active"
checked={!!editingRule.is_active}
onChange={handleChange}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
/>
<label htmlFor="is_active" className="ml-2 block text-sm text-gray-900">
Active (enable this rule)
</label>
</div>
</div>
<div className="mt-6 flex justify-end space-x-3">
<button
type="button"
className="px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
onClick={handleCancel}
>
Cancel
</button>
<button
type="submit"
className="px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 flex items-center"
>
<Save className="h-4 w-4 mr-2" /> Save Rule
</button>
</div>
</form>
</div>
)}
<div className="bg-white shadow-md rounded-lg overflow-hidden">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Priority
</th>
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Rule Name
</th>
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Criteria
</th>
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Actions
</th>
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Status
</th>
<th scope="col" className="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
Actions
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{rules.map((rule) => (
<tr key={rule.id} className="hover:bg-gray-50">
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
{rule.priority}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
{rule.name}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{rule.criteria.severity && (
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
rule.criteria.severity === 'CRITICAL' ? 'bg-red-100 text-red-800' :
rule.criteria.severity === 'HIGH' ? 'bg-orange-100 text-orange-800' :
rule.criteria.severity === 'MEDIUM' ? 'bg-yellow-100 text-yellow-800' :
'bg-blue-100 text-blue-800'
}`}>
{rule.criteria.severity}
</span>
)}
{rule.criteria.system_tags && (
<span className="ml-2 inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800">
System Tags
</span>
)}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{rule.actions.create_ticket && (
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-100 text-purple-800 mr-1">
Create Ticket
</span>
)}
{rule.actions.notify_admins && (
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800 mr-1">
Notify
</span>
)}
{rule.actions.add_to_report && (
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
Report
</span>
)}
</td>
<td className="px-6 py-4 whitespace-nowrap">
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
rule.is_active ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800'
}`}>
{rule.is_active ? 'Active' : 'Inactive'}
</span>
</td>
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button
className="text-indigo-600 hover:text-indigo-900 mr-3"
onClick={() => handleEditRule(rule)}
>
Edit
</button>
<button
className="text-red-600 hover:text-red-900"
onClick={() => handleDelete(rule.id)}
>
<Trash2 className="h-4 w-4" />
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
);
};
export default RulesConfig;

View File

@ -0,0 +1,286 @@
import React, { useState } from 'react';
import { Save, Plus, Trash2, RefreshCw } from 'lucide-react';
import { mockSources } from '../data/mockData';
import { CVESource } from '../types/cve';
const SourcesConfig: React.FC = () => {
const [sources, setSources] = useState<CVESource[]>(mockSources);
const [newSource, setNewSource] = useState<boolean>(false);
const [formData, setFormData] = useState<Partial<CVESource>>({
name: '',
url: '',
api_key: '',
is_active: true,
sync_frequency: 24
});
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
const { name, value, type } = e.target as HTMLInputElement;
setFormData({
...formData,
[name]: type === 'checkbox' ? (e.target as HTMLInputElement).checked :
type === 'number' ? parseInt(value) : value
});
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (formData.name && formData.url) {
const newSourceObj: CVESource = {
id: sources.length + 1,
name: formData.name || '',
url: formData.url || '',
api_key: formData.api_key || '',
is_active: formData.is_active || false,
sync_frequency: formData.sync_frequency || 24,
last_sync: 'Never',
sync_status: 'PENDING'
};
setSources([...sources, newSourceObj]);
setNewSource(false);
setFormData({
name: '',
url: '',
api_key: '',
is_active: true,
sync_frequency: 24
});
}
};
const handleSync = (sourceId: number) => {
// In a real implementation, this would trigger the sync process
// For this prototype, we'll just update the last_sync time
setSources(
sources.map(source =>
source.id === sourceId
? {
...source,
last_sync: new Date().toISOString(),
sync_status: 'SUCCESS' as const
}
: source
)
);
};
const handleDelete = (sourceId: number) => {
setSources(sources.filter(source => source.id !== sourceId));
};
return (
<div className="p-6">
<div className="mb-6">
<h1 className="text-2xl font-bold mb-1">CVE Data Sources</h1>
<p className="text-gray-600">Configure and manage vulnerability data sources</p>
</div>
<div className="mb-6 flex justify-between">
<p className="text-sm text-gray-500">
Configure external data sources for CVE information. The plugin will automatically sync with these sources based on the configured frequency.
</p>
<button
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 flex items-center"
onClick={() => setNewSource(true)}
>
<Plus className="h-4 w-4 mr-2" /> Add Source
</button>
</div>
{newSource && (
<div className="bg-white rounded-lg shadow-md p-6 mb-6">
<h2 className="text-lg font-semibold mb-4">Add New Data Source</h2>
<form onSubmit={handleSubmit}>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Source Name*
</label>
<input
type="text"
name="name"
value={formData.name}
onChange={handleChange}
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
API URL*
</label>
<input
type="text"
name="url"
value={formData.url}
onChange={handleChange}
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
API Key (if required)
</label>
<input
type="password"
name="api_key"
value={formData.api_key}
onChange={handleChange}
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Sync Frequency (hours)
</label>
<input
type="number"
name="sync_frequency"
value={formData.sync_frequency}
onChange={handleChange}
min="1"
max="168"
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
/>
</div>
<div className="flex items-center">
<input
type="checkbox"
id="is_active"
name="is_active"
checked={formData.is_active}
onChange={handleChange}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
/>
<label htmlFor="is_active" className="ml-2 block text-sm text-gray-900">
Active (enable synchronization)
</label>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Data Format
</label>
<select
name="data_format"
value={formData.data_format || 'JSON'}
onChange={handleChange}
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
>
<option value="JSON">JSON</option>
<option value="XML">XML</option>
<option value="CSV">CSV</option>
</select>
</div>
</div>
<div className="mt-6 flex justify-end space-x-3">
<button
type="button"
className="px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
onClick={() => setNewSource(false)}
>
Cancel
</button>
<button
type="submit"
className="px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 flex items-center"
>
<Save className="h-4 w-4 mr-2" /> Save Source
</button>
</div>
</form>
</div>
)}
<div className="bg-white shadow-md rounded-lg overflow-hidden">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Source Name
</th>
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
URL
</th>
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Frequency
</th>
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Last Sync
</th>
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Status
</th>
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Active
</th>
<th scope="col" className="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
Actions
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{sources.map((source) => (
<tr key={source.id}>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
{source.name}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{source.url}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
Every {source.sync_frequency} hours
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{source.last_sync === 'Never'
? 'Never'
: new Date(source.last_sync).toLocaleString()}
</td>
<td className="px-6 py-4 whitespace-nowrap">
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
source.sync_status === 'SUCCESS' ? 'bg-green-100 text-green-800' :
source.sync_status === 'FAILED' ? 'bg-red-100 text-red-800' :
source.sync_status === 'IN_PROGRESS' ? 'bg-yellow-100 text-yellow-800' :
'bg-gray-100 text-gray-800'
}`}>
{source.sync_status}
</span>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
source.is_active ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800'
}`}>
{source.is_active ? 'Active' : 'Inactive'}
</span>
</td>
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button
className="text-blue-600 hover:text-blue-900 mr-3"
onClick={() => handleSync(source.id)}
>
<RefreshCw className="h-4 w-4" />
</button>
<button
className="text-red-600 hover:text-red-900"
onClick={() => handleDelete(source.id)}
>
<Trash2 className="h-4 w-4" />
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
);
};
export default SourcesConfig;

502
src/css/cve.css Normal file
View File

@ -0,0 +1,502 @@
/**
* GLPI CVE Plugin - Main CSS
* This file contains styles specific to the CVE plugin
*/
/* General Layout */
.cve-container {
margin: 0;
padding: 15px;
background-color: #f5f5f5;
}
.cve-card {
background: #fff;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
margin-bottom: 20px;
overflow: hidden;
}
.cve-card-header {
border-bottom: 1px solid #e5e5e5;
padding: 15px;
font-weight: bold;
background-color: #f9f9f9;
}
.cve-card-body {
padding: 15px;
}
/* Dashboard Components */
.cve-stats-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
gap: 20px;
margin-bottom: 20px;
}
.cve-stat-card {
position: relative;
padding: 15px;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
background-color: #fff;
}
.cve-stat-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 48px;
height: 48px;
border-radius: 50%;
margin-right: 15px;
}
.cve-stat-content {
display: inline-block;
vertical-align: top;
}
.cve-stat-label {
font-size: 14px;
color: #666;
margin-bottom: 5px;
}
.cve-stat-value {
font-size: 24px;
font-weight: bold;
margin-top: 0;
margin-bottom: 10px;
}
.cve-progress-bar {
height: 8px;
background-color: #e9ecef;
border-radius: 4px;
overflow: hidden;
margin-top: 10px;
}
.cve-progress-bar-fill {
height: 100%;
border-radius: 4px;
}
/* Severity colors */
.cve-critical {
background-color: #ffebee;
}
.cve-critical .cve-stat-icon {
background-color: #ffcdd2;
color: #c62828;
}
.cve-critical .cve-progress-bar-fill {
background-color: #f44336;
}
.cve-high {
background-color: #fff3e0;
}
.cve-high .cve-stat-icon {
background-color: #ffe0b2;
color: #ef6c00;
}
.cve-high .cve-progress-bar-fill {
background-color: #ff9800;
}
.cve-pending {
background-color: #e3f2fd;
}
.cve-pending .cve-stat-icon {
background-color: #bbdefb;
color: #1565c0;
}
.cve-pending .cve-progress-bar-fill {
background-color: #2196f3;
}
.cve-resolved {
background-color: #e8f5e9;
}
.cve-resolved .cve-stat-icon {
background-color: #c8e6c9;
color: #2e7d32;
}
.cve-resolved .cve-progress-bar-fill {
background-color: #4caf50;
}
/* Tables */
.cve-table {
width: 100%;
border-collapse: collapse;
}
.cve-table th {
background-color: #f5f5f5;
text-align: left;
padding: 10px;
font-weight: bold;
border-bottom: 2px solid #ddd;
}
.cve-table td {
padding: 10px;
border-bottom: 1px solid #ddd;
}
.cve-table tr:hover {
background-color: #f9f9f9;
}
.cve-sortable {
cursor: pointer;
position: relative;
}
.cve-sortable:after {
content: '⇕';
margin-left: 5px;
color: #999;
font-size: 12px;
}
.cve-sortable.sort-asc:after {
content: '↑';
color: #333;
}
.cve-sortable.sort-desc:after {
content: '↓';
color: #333;
}
/* Badges */
.badge {
display: inline-block;
padding: 3px 7px;
font-size: 12px;
font-weight: bold;
line-height: 1;
text-align: center;
white-space: nowrap;
vertical-align: middle;
border-radius: 10px;
}
.badge-danger {
background-color: #ffcdd2;
color: #c62828;
}
.badge-warning {
background-color: #ffe0b2;
color: #ef6c00;
}
.badge-info {
background-color: #bbdefb;
color: #1565c0;
}
.badge-primary {
background-color: #c5cae9;
color: #283593;
}
.badge-success {
background-color: #c8e6c9;
color: #2e7d32;
}
.badge-purple {
background-color: #e1bee7;
color: #6a1b9a;
}
.badge-inactive {
background-color: #e0e0e0;
color: #616161;
}
.badge-pending {
background-color: #fffde7;
color: #f57f17;
}
/* Filters */
.cve-filters {
display: flex;
flex-wrap: wrap;
gap: 15px;
margin-bottom: 20px;
}
.cve-filter-group {
position: relative;
flex: 1;
min-width: 200px;
}
.cve-filter-icon {
position: absolute;
left: 10px;
top: 10px;
color: #757575;
}
.cve-filter-input,
.cve-filter-select {
width: 100%;
padding: 8px 8px 8px 35px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 14px;
}
/* Buttons */
.cve-btn-container {
display: flex;
justify-content: flex-end;
margin-bottom: 20px;
}
.btn {
display: inline-block;
font-weight: 400;
text-align: center;
white-space: nowrap;
vertical-align: middle;
user-select: none;
border: 1px solid transparent;
padding: 6px 12px;
font-size: 14px;
line-height: 1.5;
border-radius: 4px;
transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out;
cursor: pointer;
}
.btn:focus, .btn:hover {
text-decoration: none;
}
.btn-primary {
color: #fff;
background-color: #2196f3;
border-color: #2196f3;
}
.btn-primary:hover {
background-color: #0d8bf2;
border-color: #0c83e9;
}
.btn-success {
color: #fff;
background-color: #4caf50;
border-color: #4caf50;
}
.btn-success:hover {
background-color: #43a047;
border-color: #3d9142;
}
.btn-danger {
color: #fff;
background-color: #f44336;
border-color: #f44336;
}
.btn-danger:hover {
background-color: #e53935;
border-color: #d32f2f;
}
.btn-purple {
color: #fff;
background-color: #9c27b0;
border-color: #9c27b0;
}
.btn-purple:hover {
background-color: #8e24aa;
border-color: #7b1fa2;
}
.btn-secondary {
color: #fff;
background-color: #757575;
border-color: #757575;
}
.btn-secondary:hover {
background-color: #616161;
border-color: #616161;
}
.btn-sm {
padding: 4px 8px;
font-size: 12px;
line-height: 1.5;
border-radius: 3px;
}
/* Forms */
.cve-form-section {
background-color: #fff;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
padding: 20px;
margin-bottom: 20px;
}
.cve-form-title {
font-size: 18px;
margin-bottom: 15px;
font-weight: bold;
}
.cve-form-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
margin-bottom: 20px;
}
.cve-form-group {
margin-bottom: 15px;
}
.cve-form-label {
display: block;
margin-bottom: 5px;
font-weight: 500;
font-size: 14px;
}
.cve-form-input,
.cve-form-select {
width: 100%;
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 14px;
}
.cve-form-checkbox {
margin-right: 8px;
}
.cve-form-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 20px;
}
/* Navigation */
.cve-nav {
margin-bottom: 20px;
border-bottom: 1px solid #ddd;
}
.cve-nav-item {
display: inline-block;
padding: 10px 15px;
margin-right: 5px;
margin-bottom: -1px;
cursor: pointer;
border: 1px solid transparent;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
.cve-nav-item:hover {
background-color: #f5f5f5;
}
.cve-nav-item.active {
background-color: #fff;
border: 1px solid #ddd;
border-bottom-color: #fff;
font-weight: bold;
}
.cve-nav-icon {
margin-right: 5px;
}
/* Mobile responsiveness */
@media (max-width: 768px) {
.cve-stats-container {
grid-template-columns: 1fr;
}
.cve-filters {
flex-direction: column;
}
.cve-table {
display: block;
overflow-x: auto;
white-space: nowrap;
}
.cve-form-grid {
grid-template-columns: 1fr;
}
}
/* Chart styles */
.cve-chart-container {
height: 300px;
display: flex;
align-items: flex-end;
justify-content: space-around;
padding: 20px;
}
.cve-chart-bar {
width: 60px;
border-radius: 4px 4px 0 0;
display: flex;
flex-direction: column;
align-items: center;
transition: height 0.3s ease;
}
.cve-chart-label {
margin-top: 8px;
font-size: 12px;
text-align: center;
font-weight: bold;
}
.cve-chart-value {
margin-top: 4px;
font-size: 14px;
font-weight: 500;
}
.cve-chart-critical {
background-color: #f44336;
}
.cve-chart-high {
background-color: #ff9800;
}
.cve-chart-medium {
background-color: #ffeb3b;
}
.cve-chart-low {
background-color: #2196f3;
}

181
src/data/mockData.ts Normal file
View File

@ -0,0 +1,181 @@
import { CVE, CVESource, CVERule } from '../types/cve';
export const mockCVEs: CVE[] = [
{
id: 1,
cve_id: 'CVE-2024-1234',
description: 'A remote code execution vulnerability in Apache Log4j library affecting versions 2.0-beta9 to 2.14.1',
cvss_score: 10.0,
cvss_vector: 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H',
severity: 'CRITICAL',
published_date: '2024-05-12T00:00:00Z',
modified_date: '2024-05-14T00:00:00Z',
status: 'NEW',
references: [
'https://nvd.nist.gov/vuln/detail/CVE-2024-1234',
'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-1234'
],
affected_products: ['Apache Log4j 2.0-2.14.1', 'Applications using affected Log4j versions'],
date_creation: '2024-05-15T10:30:00Z',
date_mod: '2024-05-15T10:30:00Z'
},
{
id: 2,
cve_id: 'CVE-2024-5678',
description: 'SQL Injection vulnerability in WordPress plugin Contact Form 7 versions prior to 5.7.2',
cvss_score: 8.8,
cvss_vector: 'CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H',
severity: 'HIGH',
published_date: '2024-05-10T00:00:00Z',
modified_date: '2024-05-11T00:00:00Z',
status: 'ANALYZED',
references: [
'https://nvd.nist.gov/vuln/detail/CVE-2024-5678',
'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-5678'
],
affected_products: ['WordPress Contact Form 7 < 5.7.2'],
date_creation: '2024-05-12T08:15:00Z',
date_mod: '2024-05-12T14:25:00Z'
},
{
id: 3,
cve_id: 'CVE-2024-9012',
description: 'Privilege escalation vulnerability in Microsoft Windows 11 affecting kernel-mode drivers',
cvss_score: 7.8,
cvss_vector: 'CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H',
severity: 'HIGH',
published_date: '2024-05-08T00:00:00Z',
modified_date: '2024-05-09T00:00:00Z',
status: 'ASSIGNED',
references: [
'https://nvd.nist.gov/vuln/detail/CVE-2024-9012',
'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-9012'
],
affected_products: ['Microsoft Windows 11 21H2', 'Microsoft Windows 11 22H2'],
date_creation: '2024-05-10T09:45:00Z',
date_mod: '2024-05-13T11:20:00Z'
},
{
id: 4,
cve_id: 'CVE-2024-3456',
description: 'Cross-site scripting (XSS) vulnerability in jQuery UI Dialog component prior to version 1.13.2',
cvss_score: 5.4,
cvss_vector: 'CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:N',
severity: 'MEDIUM',
published_date: '2024-05-05T00:00:00Z',
modified_date: '2024-05-06T00:00:00Z',
status: 'RESOLVED',
references: [
'https://nvd.nist.gov/vuln/detail/CVE-2024-3456',
'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-3456'
],
affected_products: ['jQuery UI < 1.13.2'],
date_creation: '2024-05-07T14:30:00Z',
date_mod: '2024-05-15T09:10:00Z'
},
{
id: 5,
cve_id: 'CVE-2024-7890',
description: 'Information disclosure vulnerability in OpenSSL 3.0.0 through 3.1.1',
cvss_score: 3.7,
cvss_vector: 'CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N',
severity: 'LOW',
published_date: '2024-05-02T00:00:00Z',
modified_date: '2024-05-03T00:00:00Z',
status: 'ANALYZED',
references: [
'https://nvd.nist.gov/vuln/detail/CVE-2024-7890',
'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-7890'
],
affected_products: ['OpenSSL 3.0.0-3.1.1'],
date_creation: '2024-05-04T16:45:00Z',
date_mod: '2024-05-10T13:25:00Z'
}
];
export const mockSources: CVESource[] = [
{
id: 1,
name: 'National Vulnerability Database (NVD)',
url: 'https://services.nvd.nist.gov/rest/json/cves/2.0',
api_key: '********-****-****-****-************',
source_type: 'NVD',
data_format: 'JSON',
is_active: true,
sync_frequency: 4,
last_sync: '2024-05-15T08:00:00Z',
sync_status: 'SUCCESS'
},
{
id: 2,
name: 'MITRE CVE Database',
url: 'https://cveawg.mitre.org/api/',
api_key: '',
source_type: 'MITRE',
data_format: 'JSON',
is_active: true,
sync_frequency: 24,
last_sync: '2024-05-14T22:00:00Z',
sync_status: 'SUCCESS'
},
{
id: 3,
name: 'CISA KEV Catalog',
url: 'https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json',
api_key: '',
source_type: 'CISA',
data_format: 'JSON',
is_active: true,
sync_frequency: 12,
last_sync: '2024-05-15T04:00:00Z',
sync_status: 'SUCCESS'
}
];
export const mockRules: CVERule[] = [
{
id: 1,
name: 'Critical Vulnerabilities - Immediate Ticket',
criteria: {
severity: 'CRITICAL',
affected_systems: { min: 1 }
},
actions: {
create_ticket: true,
ticket_priority: 'VERY HIGH',
notify_admins: true
},
priority: 1,
is_active: true
},
{
id: 2,
name: 'High Risk - Production Systems',
criteria: {
severity: 'HIGH',
system_tags: ['production', 'customer-facing']
},
actions: {
create_ticket: true,
ticket_priority: 'HIGH',
notify_admins: false
},
priority: 2,
is_active: true
},
{
id: 3,
name: 'Medium Risk - Batch Report',
criteria: {
severity: 'MEDIUM',
system_count: { min: 1 }
},
actions: {
create_ticket: false,
add_to_report: true,
schedule_report: 'WEEKLY'
},
priority: 3,
is_active: true
}
];

3
src/index.css Normal file
View File

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

417
src/js/cve.js Normal file
View File

@ -0,0 +1,417 @@
/**
* GLPI CVE Plugin - Main JavaScript
* This file contains jQuery code for the CVE plugin functionality
*/
$(document).ready(function() {
// Navigation handling
$('.cve-nav-item').on('click', function(e) {
e.preventDefault();
const target = $(this).data('target');
// Update active state
$('.cve-nav-item').removeClass('active');
$(this).addClass('active');
// Show relevant content
$('.cve-content').hide();
$(`#${target}`).show();
// Update URL hash for bookmarking
window.location.hash = target;
});
// Initialize based on hash or default to dashboard
const initPage = () => {
let target = window.location.hash.substring(1) || 'cve-dashboard';
$(`.cve-nav-item[data-target="${target}"]`).trigger('click');
};
initPage();
// CVE Search functionality
$('#cve-search').on('keyup', function() {
const searchTerm = $(this).val().toLowerCase();
filterCVEs(searchTerm, $('#severity-filter').val(), $('#status-filter').val());
});
// Severity and Status filters
$('#severity-filter, #status-filter').on('change', function() {
filterCVEs(
$('#cve-search').val().toLowerCase(),
$('#severity-filter').val(),
$('#status-filter').val()
);
});
// Table sorting
$('.cve-sortable').on('click', function() {
const column = $(this).data('column');
const currentDir = $(this).hasClass('sort-asc') ? 'asc' : 'desc';
const newDir = currentDir === 'asc' ? 'desc' : 'asc';
// Update visual indicators
$('.cve-sortable').removeClass('sort-asc sort-desc');
$(this).addClass(`sort-${newDir}`);
sortCVETable(column, newDir);
});
// Sources form handling
$('#add-source-form').on('submit', function(e) {
e.preventDefault();
const newSource = {
name: $('#source-name').val(),
url: $('#source-url').val(),
api_key: $('#source-api-key').val(),
sync_frequency: parseInt($('#source-frequency').val()),
is_active: $('#source-active').prop('checked')
};
// In a real implementation, this would be an AJAX POST
console.log('Adding new source:', newSource);
// For the prototype, just add to the table
addSourceToTable(newSource);
// Reset form
$(this).trigger('reset');
$('#add-source-section').hide();
});
// New source button
$('#add-source-btn').on('click', function() {
$('#add-source-section').toggle();
});
// Rule form handling
$('#add-rule-form').on('submit', function(e) {
e.preventDefault();
const newRule = {
name: $('#rule-name').val(),
criteria: {
severity: $('#rule-severity').val()
},
actions: {
create_ticket: $('#create-ticket').prop('checked'),
ticket_priority: $('#ticket-priority').val(),
notify_admins: $('#notify-admins').prop('checked'),
add_to_report: $('#add-to-report').prop('checked')
},
priority: parseInt($('#rule-priority').val()),
is_active: $('#rule-active').prop('checked')
};
// In a real implementation, this would be an AJAX POST
console.log('Adding new rule:', newRule);
// For the prototype, just add to the table
addRuleToTable(newRule);
// Reset form
$(this).trigger('reset');
$('#add-rule-section').hide();
});
// New rule button
$('#add-rule-btn').on('click', function() {
$('#add-rule-section').toggle();
});
// Helper functions
function filterCVEs(searchTerm, severity, status) {
$('#cve-table tbody tr').each(function() {
const cveId = $(this).find('td:nth-child(1)').text().toLowerCase();
const description = $(this).find('td:nth-child(2)').text().toLowerCase();
const rowSeverity = $(this).find('td:nth-child(3)').text();
const rowStatus = $(this).find('td:nth-child(6)').text();
const matchesSearch = !searchTerm || cveId.includes(searchTerm) || description.includes(searchTerm);
const matchesSeverity = !severity || rowSeverity === severity;
const matchesStatus = !status || rowStatus === status;
if (matchesSearch && matchesSeverity && matchesStatus) {
$(this).show();
} else {
$(this).hide();
}
});
}
function sortCVETable(column, direction) {
const rows = $('#cve-table tbody tr').toArray();
rows.sort(function(a, b) {
let aValue = $(a).find(`td:nth-child(${getColumnIndex(column)})`).text();
let bValue = $(b).find(`td:nth-child(${getColumnIndex(column)})`).text();
// Handle numeric values
if (column === 'cvss') {
aValue = parseFloat(aValue);
bValue = parseFloat(bValue);
}
// Handle dates
if (column === 'published') {
aValue = new Date(aValue);
bValue = new Date(bValue);
}
if (direction === 'asc') {
return aValue > bValue ? 1 : -1;
} else {
return aValue < bValue ? 1 : -1;
}
});
// Reappend sorted rows
$('#cve-table tbody').empty();
$.each(rows, function(index, row) {
$('#cve-table tbody').append(row);
});
}
function getColumnIndex(columnName) {
const indices = {
'cve_id': 1,
'severity': 3,
'cvss': 4,
'published': 5,
'status': 6
};
return indices[columnName] || 1;
}
function addSourceToTable(source) {
const now = new Date().toLocaleString();
const newRow = `
<tr>
<td>${source.name}</td>
<td>${source.url}</td>
<td>Every ${source.sync_frequency} hours</td>
<td>Never</td>
<td><span class="badge badge-pending">PENDING</span></td>
<td><span class="badge badge-${source.is_active ? 'success' : 'inactive'}">${source.is_active ? 'Active' : 'Inactive'}</span></td>
<td class="text-right">
<button class="btn btn-sm btn-primary sync-source-btn"><i class="fas fa-sync"></i></button>
<button class="btn btn-sm btn-danger delete-source-btn"><i class="fas fa-trash"></i></button>
</td>
</tr>
`;
$('#sources-table tbody').append(newRow);
}
function addRuleToTable(rule) {
const severityClass = {
'CRITICAL': 'danger',
'HIGH': 'warning',
'MEDIUM': 'info',
'LOW': 'primary'
}[rule.criteria.severity] || 'primary';
const actionLabels = [];
if (rule.actions.create_ticket) actionLabels.push('<span class="badge badge-purple">Create Ticket</span>');
if (rule.actions.notify_admins) actionLabels.push('<span class="badge badge-info">Notify</span>');
if (rule.actions.add_to_report) actionLabels.push('<span class="badge badge-success">Report</span>');
const newRow = `
<tr>
<td>${rule.priority}</td>
<td>${rule.name}</td>
<td><span class="badge badge-${severityClass}">${rule.criteria.severity}</span></td>
<td>${actionLabels.join(' ')}</td>
<td><span class="badge badge-${rule.is_active ? 'success' : 'inactive'}">${rule.is_active ? 'Active' : 'Inactive'}</span></td>
<td class="text-right">
<button class="btn btn-sm btn-primary edit-rule-btn">Edit</button>
<button class="btn btn-sm btn-danger delete-rule-btn"><i class="fas fa-trash"></i></button>
</td>
</tr>
`;
$('#rules-table tbody').append(newRow);
}
// Load mock data for the prototype
loadMockData();
function loadMockData() {
// This would be replaced by AJAX calls to the GLPI API in a real implementation
const mockCVEs = [
{
id: 1,
cve_id: 'CVE-2024-1234',
description: 'A remote code execution vulnerability in Apache Log4j library affecting versions 2.0-beta9 to 2.14.1',
cvss_score: 10.0,
cvss_vector: 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H',
severity: 'CRITICAL',
published_date: '2024-05-12',
modified_date: '2024-05-14',
status: 'NEW',
references: [
'https://nvd.nist.gov/vuln/detail/CVE-2024-1234',
'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-1234'
],
affected_products: ['Apache Log4j 2.0-2.14.1', 'Applications using affected Log4j versions'],
date_creation: '2024-05-15',
date_mod: '2024-05-15'
},
{
id: 2,
cve_id: 'CVE-2024-5678',
description: 'SQL Injection vulnerability in WordPress plugin Contact Form 7 versions prior to 5.7.2',
cvss_score: 8.8,
cvss_vector: 'CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H',
severity: 'HIGH',
published_date: '2024-05-10',
modified_date: '2024-05-11',
status: 'ANALYZED',
references: [
'https://nvd.nist.gov/vuln/detail/CVE-2024-5678',
'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-5678'
],
affected_products: ['WordPress Contact Form 7 < 5.7.2'],
date_creation: '2024-05-12',
date_mod: '2024-05-12'
},
{
id: 3,
cve_id: 'CVE-2024-9012',
description: 'Privilege escalation vulnerability in Microsoft Windows 11 affecting kernel-mode drivers',
cvss_score: 7.8,
cvss_vector: 'CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H',
severity: 'HIGH',
published_date: '2024-05-08',
modified_date: '2024-05-09',
status: 'ASSIGNED',
references: [
'https://nvd.nist.gov/vuln/detail/CVE-2024-9012',
'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-9012'
],
affected_products: ['Microsoft Windows 11 21H2', 'Microsoft Windows 11 22H2'],
date_creation: '2024-05-10',
date_mod: '2024-05-13'
},
{
id: 4,
cve_id: 'CVE-2024-3456',
description: 'Cross-site scripting (XSS) vulnerability in jQuery UI Dialog component prior to version 1.13.2',
cvss_score: 5.4,
cvss_vector: 'CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:N',
severity: 'MEDIUM',
published_date: '2024-05-05',
modified_date: '2024-05-06',
status: 'RESOLVED',
references: [
'https://nvd.nist.gov/vuln/detail/CVE-2024-3456',
'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-3456'
],
affected_products: ['jQuery UI < 1.13.2'],
date_creation: '2024-05-07',
date_mod: '2024-05-15'
},
{
id: 5,
cve_id: 'CVE-2024-7890',
description: 'Information disclosure vulnerability in OpenSSL 3.0.0 through 3.1.1',
cvss_score: 3.7,
cvss_vector: 'CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N',
severity: 'LOW',
published_date: '2024-05-02',
modified_date: '2024-05-03',
status: 'ANALYZED',
references: [
'https://nvd.nist.gov/vuln/detail/CVE-2024-7890',
'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-7890'
],
affected_products: ['OpenSSL 3.0.0-3.1.1'],
date_creation: '2024-05-04',
date_mod: '2024-05-10'
}
];
// Populate CVE table
mockCVEs.forEach(cve => {
const severityClass = {
'CRITICAL': 'danger',
'HIGH': 'warning',
'MEDIUM': 'info',
'LOW': 'primary'
}[cve.severity];
const statusClass = {
'NEW': 'info',
'ANALYZED': 'warning',
'ASSIGNED': 'primary',
'RESOLVED': 'success'
}[cve.status];
const row = `
<tr>
<td><a href="#" class="cve-link">${cve.cve_id}</a></td>
<td>${cve.description.substring(0, 50)}...</td>
<td><span class="badge badge-${severityClass}">${cve.severity}</span></td>
<td>${cve.cvss_score.toFixed(1)}</td>
<td>${cve.published_date}</td>
<td><span class="badge badge-${statusClass}">${cve.status}</span></td>
<td class="text-right">
<button class="btn btn-sm btn-primary view-cve-btn">View</button>
<button class="btn btn-sm btn-purple create-ticket-btn">Create Ticket</button>
<a href="${cve.references[0]}" target="_blank" class="btn btn-sm btn-secondary"><i class="fas fa-external-link-alt"></i></a>
</td>
</tr>
`;
$('#cve-table tbody').append(row);
});
// Update dashboard counters
const criticalCount = mockCVEs.filter(cve => cve.severity === 'CRITICAL').length;
const highCount = mockCVEs.filter(cve => cve.severity === 'HIGH').length;
const pendingCount = mockCVEs.filter(cve => cve.status === 'NEW').length;
const resolvedCount = mockCVEs.filter(cve => cve.status === 'RESOLVED').length;
$('#critical-count').text(criticalCount);
$('#high-count').text(highCount);
$('#pending-count').text(pendingCount);
$('#resolved-count').text(resolvedCount);
// Set progress bars
$('#critical-progress').css('width', `${(criticalCount / mockCVEs.length) * 100}%`);
$('#high-progress').css('width', `${(highCount / mockCVEs.length) * 100}%`);
$('#pending-progress').css('width', `${(pendingCount / mockCVEs.length) * 100}%`);
$('#resolved-progress').css('width', `${(resolvedCount / mockCVEs.length) * 100}%`);
// Populate recent vulnerabilities table
mockCVEs.slice(0, 5).forEach(cve => {
const severityClass = {
'CRITICAL': 'danger',
'HIGH': 'warning',
'MEDIUM': 'info',
'LOW': 'primary'
}[cve.severity];
const statusClass = {
'NEW': 'info',
'ANALYZED': 'warning',
'ASSIGNED': 'primary',
'RESOLVED': 'success'
}[cve.status];
const row = `
<tr>
<td><a href="#" class="cve-link">${cve.cve_id}</a></td>
<td><span class="badge badge-${severityClass}">${cve.severity}</span></td>
<td>${cve.cvss_score.toFixed(1)}</td>
<td>${cve.published_date}</td>
<td><span class="badge badge-${statusClass}">${cve.status}</span></td>
</tr>
`;
$('#recent-cve-table tbody').append(row);
});
}
});

10
src/main.tsx Normal file
View File

@ -0,0 +1,10 @@
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App.tsx';
import './index.css';
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>
);

45
src/types/cve.ts Normal file
View File

@ -0,0 +1,45 @@
export interface CVE {
id: number;
cve_id: string;
description: string;
cvss_score: number;
cvss_vector: string;
severity: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
published_date: string;
modified_date: string;
status: 'NEW' | 'ANALYZED' | 'ASSIGNED' | 'RESOLVED';
references: string[];
affected_products: string[];
date_creation: string;
date_mod: string;
}
export interface CVESource {
id: number;
name: string;
url: string;
api_key: string;
source_type?: string;
data_format?: string;
is_active: boolean;
sync_frequency: number;
last_sync: string;
sync_status: 'SUCCESS' | 'FAILED' | 'IN_PROGRESS' | 'PENDING';
}
export interface CVERule {
id: number;
name: string;
criteria: any;
actions: any;
priority: number;
is_active: boolean;
}
export interface CVETicket {
id: number;
cves_id: number;
tickets_id: number;
creation_type: 'AUTO' | 'MANUAL';
date_creation: string;
}

1
src/vite-env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="vite/client" />

8
tailwind.config.js Normal file
View File

@ -0,0 +1,8 @@
/** @type {import('tailwindcss').Config} */
export default {
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
theme: {
extend: {},
},
plugins: [],
};

22
tools/extract_template.sh Normal file
View File

@ -0,0 +1,22 @@
#!/bin/bash
# Extract localization template from GLPI CVE Plugin
# Define variables
NAME="GLPI CVE Plugin"
POTFILE=plugin_cve.pot
BASEDIR=..
# Extract strings
cd ${BASEDIR}/inc/
xgettext *.php *.class.php -o ../locales/${POTFILE} -L PHP --add-comments=TRANS --from-code=UTF-8 --force-po --copyright-holder="${NAME}" --package-name="${NAME}" --package-version="1.0.0" --msgid-bugs-address="development@yourdomain.com" --keyword=__:1,2t --keyword=_e:1,2t --keyword=_n:1,2,4t --keyword=_s:1,2t --keyword=_sx:1,2,3t --keyword=_nx:1,2,4,5t
cd ${BASEDIR}/front/
xgettext *.php -o ../locales/${POTFILE} -L PHP --add-comments=TRANS --from-code=UTF-8 --force-po --join-existing --copyright-holder="${NAME}" --package-name="${NAME}" --package-version="1.0.0" --msgid-bugs-address="development@yourdomain.com" --keyword=__:1,2t --keyword=_e:1,2t --keyword=_n:1,2,4t --keyword=_s:1,2t --keyword=_sx:1,2,3t --keyword=_nx:1,2,4,5t
cd ${BASEDIR}/ajax/
xgettext *.php -o ../locales/${POTFILE} -L PHP --add-comments=TRANS --from-code=UTF-8 --force-po --join-existing --copyright-holder="${NAME}" --package-name="${NAME}" --package-version="1.0.0" --msgid-bugs-address="development@yourdomain.com" --keyword=__:1,2t --keyword=_e:1,2t --keyword=_n:1,2,4t --keyword=_s:1,2t --keyword=_sx:1,2,3t --keyword=_nx:1,2,4,5t
cd ${BASEDIR}/
xgettext *.php -o locales/${POTFILE} -L PHP --add-comments=TRANS --from-code=UTF-8 --force-po --join-existing --copyright-holder="${NAME}" --package-name="${NAME}" --package-version="1.0.0" --msgid-bugs-address="development@yourdomain.com" --keyword=__:1,2t --keyword=_e:1,2t --keyword=_n:1,2,4t --keyword=_s:1,2t --keyword=_sx:1,2,3t --keyword=_nx:1,2,4,5t
echo "Localization template generated at locales/${POTFILE}"

17
tools/generate_mo.sh Normal file
View File

@ -0,0 +1,17 @@
#!/bin/bash
# Generate MO files from PO files for GLPI CVE Plugin
# Define base directory
BASEDIR=../locales
# For each PO file
for pofile in ${BASEDIR}/*.po; do
# Get language code
lang=$(basename "$pofile" .po)
echo "Processing $lang..."
# Generate MO file
msgfmt -v -o ${BASEDIR}/${lang}.mo ${pofile}
done
echo "MO files generation completed"

18
tools/update_po.sh Normal file
View File

@ -0,0 +1,18 @@
#!/bin/bash
# Update PO files from POT template for GLPI CVE Plugin
# Define variables
POTFILE=plugin_cve.pot
BASEDIR=../locales
# For each PO file
for pofile in ${BASEDIR}/*.po; do
# Get language code
lang=$(basename "$pofile" .po)
echo "Updating $lang..."
# Update PO file from template
msgmerge --update --no-fuzzy-matching --backup=off ${pofile} ${BASEDIR}/${POTFILE}
done
echo "PO files update completed"

24
tsconfig.app.json Normal file
View File

@ -0,0 +1,24 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"]
}

7
tsconfig.json Normal file
View File

@ -0,0 +1,7 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}

22
tsconfig.node.json Normal file
View File

@ -0,0 +1,22 @@
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2023"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["vite.config.ts"]
}

10
vite.config.ts Normal file
View File

@ -0,0 +1,10 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
optimizeDeps: {
exclude: ['lucide-react'],
},
});