218 lines
6.2 KiB
PHP
Executable File
218 lines
6.2 KiB
PHP
Executable File
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace ServerManager\Services;
|
|
|
|
use ServerManager\Core\Database;
|
|
use ServerManager\Core\Session;
|
|
use ServerManager\Core\Logger;
|
|
|
|
class AuditService
|
|
{
|
|
private Database $db;
|
|
private Logger $logger;
|
|
|
|
public function __construct()
|
|
{
|
|
$this->db = Database::getInstance();
|
|
$this->logger = Logger::getInstance();
|
|
}
|
|
|
|
public function log(
|
|
string $action,
|
|
string $entityType,
|
|
?int $entityId,
|
|
?array $details = null,
|
|
?array $metadata = null
|
|
): void {
|
|
$userId = Session::get('user_id');
|
|
$username = Session::get('username');
|
|
$ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
|
|
$userAgent = $_SERVER['HTTP_USER_AGENT'] ?? 'unknown';
|
|
$now = date('Y-m-d H:i:s');
|
|
|
|
$this->db->insert('audit_logs', [
|
|
'user_id' => $userId,
|
|
'username' => $username ?? 'system',
|
|
'action' => $action,
|
|
'entity_type' => $entityType,
|
|
'entity_id' => $entityId,
|
|
'details' => $details ? json_encode($details, JSON_UNESCAPED_SLASHES) : null,
|
|
'ip_address' => $ip,
|
|
'user_agent' => $userAgent,
|
|
'created_at' => $now,
|
|
]);
|
|
|
|
$logMessage = sprintf(
|
|
'[AUDIT] %s | %s | %s:%s | User: %s (%d)',
|
|
$action,
|
|
$entityType,
|
|
$entityId ?? 'N/A',
|
|
$ip,
|
|
$username ?? 'system',
|
|
$userId ?? 0
|
|
);
|
|
|
|
$this->logger->info($logMessage);
|
|
|
|
if (in_array($action, ['credential_access', 'credential_decrypt', 'credential_use'])) {
|
|
$this->logger->warning("[CREDENTIAL_ACCESS] {$logMessage}");
|
|
}
|
|
}
|
|
|
|
public function logCredentialAccess(int $serverId, string $reason): void
|
|
{
|
|
$server = $this->db->fetch('SELECT name, ip_address FROM servers WHERE id = ?', [$serverId]);
|
|
$serverName = $server['name'] ?? 'Unknown';
|
|
|
|
$this->log(
|
|
'credential_access',
|
|
'server_credentials',
|
|
$serverId,
|
|
[
|
|
'server_name' => $serverName,
|
|
'ip_address' => $server['ip_address'] ?? 'unknown',
|
|
'reason' => $reason,
|
|
'timestamp' => date('c'),
|
|
]
|
|
);
|
|
}
|
|
|
|
public function logLogin(string $username, bool $success, ?string $reason = null): void
|
|
{
|
|
$this->log(
|
|
$success ? 'login_success' : 'login_failed',
|
|
'user',
|
|
null,
|
|
[
|
|
'username' => $username,
|
|
'success' => $success,
|
|
'reason' => $reason,
|
|
]
|
|
);
|
|
|
|
if (!$success) {
|
|
$this->logger->warning("[SECURITY] Failed login attempt for username: {$username}", [
|
|
'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
|
|
]);
|
|
}
|
|
}
|
|
|
|
public function logServerAction(int $serverId, string $action, string $command): void
|
|
{
|
|
$this->log(
|
|
'server_action',
|
|
'server',
|
|
$serverId,
|
|
[
|
|
'action' => $action,
|
|
'command' => $command,
|
|
]
|
|
);
|
|
}
|
|
|
|
public function logUserManagement(int $userId, string $action): void
|
|
{
|
|
$this->log(
|
|
$action,
|
|
'user',
|
|
$userId,
|
|
[
|
|
'performed_by' => Session::get('username'),
|
|
]
|
|
);
|
|
}
|
|
|
|
public function getRecentActivity(int $limit = 20, ?int $userId = null): array
|
|
{
|
|
$sql = 'SELECT al.*, u.username as actor_name
|
|
FROM audit_logs al
|
|
LEFT JOIN users u ON al.user_id = u.id';
|
|
$params = [];
|
|
|
|
if ($userId !== null) {
|
|
$sql .= ' WHERE al.user_id = ?';
|
|
$params[] = $userId;
|
|
}
|
|
|
|
$sql .= ' ORDER BY al.created_at DESC LIMIT ?';
|
|
$params[] = $limit;
|
|
|
|
return $this->db->fetchAll($sql, $params);
|
|
}
|
|
|
|
public function getAuditLogs(int $page = 1, int $perPage = 50, array $filters = []): array
|
|
{
|
|
$where = [];
|
|
$params = [];
|
|
|
|
if (!empty($filters['action'])) {
|
|
$where[] = 'al.action = ?';
|
|
$params[] = $filters['action'];
|
|
}
|
|
|
|
if (!empty($filters['user_id'])) {
|
|
$where[] = 'al.user_id = ?';
|
|
$params[] = $filters['user_id'];
|
|
}
|
|
|
|
if (!empty($filters['username'])) {
|
|
$where[] = '(al.username LIKE ? OR u.username LIKE ?)';
|
|
$params[] = '%' . $filters['username'] . '%';
|
|
$params[] = '%' . $filters['username'] . '%';
|
|
}
|
|
|
|
if (!empty($filters['entity_type'])) {
|
|
$where[] = 'al.entity_type = ?';
|
|
$params[] = $filters['entity_type'];
|
|
}
|
|
|
|
if (!empty($filters['ip_address'])) {
|
|
$where[] = 'al.ip_address LIKE ?';
|
|
$params[] = '%' . $filters['ip_address'] . '%';
|
|
}
|
|
|
|
if (!empty($filters['details'])) {
|
|
$where[] = 'al.details LIKE ?';
|
|
$params[] = '%' . $filters['details'] . '%';
|
|
}
|
|
|
|
if (!empty($filters['date_from'])) {
|
|
$where[] = 'al.created_at >= ?';
|
|
$params[] = $filters['date_from'];
|
|
}
|
|
|
|
if (!empty($filters['date_to'])) {
|
|
$where[] = 'al.created_at <= ?';
|
|
$params[] = $filters['date_to'];
|
|
}
|
|
|
|
$whereClause = !empty($where) ? 'WHERE ' . implode(' AND ', $where) : '';
|
|
$offset = ($page - 1) * $perPage;
|
|
|
|
$total = $this->db->fetch(
|
|
"SELECT COUNT(*) as total FROM audit_logs al {$whereClause}",
|
|
$params
|
|
);
|
|
|
|
$logs = $this->db->fetchAll(
|
|
"SELECT al.*, u.username as actor_name
|
|
FROM audit_logs al
|
|
LEFT JOIN users u ON al.user_id = u.id
|
|
{$whereClause}
|
|
ORDER BY al.created_at DESC
|
|
LIMIT ? OFFSET ?",
|
|
array_merge($params, [$perPage, $offset])
|
|
);
|
|
|
|
return [
|
|
'data' => $logs,
|
|
'total' => (int) ($total['total'] ?? 0),
|
|
'page' => $page,
|
|
'per_page' => $perPage,
|
|
'total_pages' => ceil((int) ($total['total'] ?? 0) / $perPage),
|
|
];
|
|
}
|
|
}
|