mirror of
https://github.com/tips-of-mine/GLPI-Plugin-CVE-Prototype.git
synced 2025-06-27 14:48:45 +02:00
Start repository
This commit is contained in:
3
.bolt/config.json
Normal file
3
.bolt/config.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"template": "bolt-vite-react-ts"
|
||||
}
|
5
.bolt/prompt
Normal file
5
.bolt/prompt
Normal 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
25
.gitignore
vendored
Normal 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
|
66
ajax/sync_now.php
Normal file
66
ajax/sync_now.php
Normal 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
28
eslint.config.js
Normal 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
95
front/alert.form.php
Normal 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
20
front/alert.php
Normal 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
88
front/cvesource.form.php
Normal 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
20
front/cvesource.php
Normal 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
162
front/inventory.php
Normal 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
171
hook.php
Normal 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
752
inc/cve.class.php
Normal 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
414
inc/cvealert.class.php
Normal 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
358
inc/cveinventory.class.php
Normal 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
497
inc/cverule.class.php
Normal 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
1095
inc/cvesource.class.php
Normal file
File diff suppressed because it is too large
Load Diff
494
inc/cveticket.class.php
Normal file
494
inc/cveticket.class.php
Normal 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
78
inc/define.php
Normal 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
273
inc/menu.class.php
Normal 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
13
index.html
Normal 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
330
locales/de_DE.po
Normal 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
351
locales/en_GB.po
Normal 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
330
locales/es_ES.po
Normal 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
330
locales/fr_FR.po
Normal 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
330
locales/it_IT.po
Normal 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
336
locales/pl_PL.po
Normal 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
330
locales/pt_PT.po
Normal 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
4076
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
35
package.json
Normal file
35
package.json
Normal 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
6
postcss.config.js
Normal file
@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
488
public/cve_dashboard.html
Normal file
488
public/cve_dashboard.html
Normal 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
151
public/cve_list.html
Normal 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>
|
57
scripts/analyze_inventory.php
Normal file
57
scripts/analyze_inventory.php
Normal 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
57
scripts/cleanup.php
Normal 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
57
scripts/sync_cve.php
Normal 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
403
setup.php
Normal 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
79
src/App.tsx
Normal 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
208
src/components/CVEList.tsx
Normal 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;
|
176
src/components/Dashboard.tsx
Normal file
176
src/components/Dashboard.tsx
Normal 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;
|
362
src/components/RulesConfig.tsx
Normal file
362
src/components/RulesConfig.tsx
Normal 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;
|
286
src/components/SourcesConfig.tsx
Normal file
286
src/components/SourcesConfig.tsx
Normal 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
502
src/css/cve.css
Normal 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
181
src/data/mockData.ts
Normal 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
3
src/index.css
Normal file
@ -0,0 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
417
src/js/cve.js
Normal file
417
src/js/cve.js
Normal 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
10
src/main.tsx
Normal 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
45
src/types/cve.ts
Normal 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
1
src/vite-env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
8
tailwind.config.js
Normal file
8
tailwind.config.js
Normal 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
22
tools/extract_template.sh
Normal 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
17
tools/generate_mo.sh
Normal 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
18
tools/update_po.sh
Normal 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
24
tsconfig.app.json
Normal 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
7
tsconfig.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
]
|
||||
}
|
22
tsconfig.node.json
Normal file
22
tsconfig.node.json
Normal 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
10
vite.config.ts
Normal 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'],
|
||||
},
|
||||
});
|
Reference in New Issue
Block a user