- Change query_json column from TEXT to JSON, ip/user_agent to CHAR(64) for PII redaction compliance - Update repository, service, and views for hardened schema - Add architecture contract test for security logging redaction - Add search resource provider test coverage - Include DB migration script for existing installs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
120 lines
4.9 KiB
PHP
120 lines
4.9 KiB
PHP
<?php
|
|
|
|
namespace MintyPHP\Module\Audit\Providers;
|
|
|
|
use MintyPHP\App\Module\Contracts\SearchResourceProvider;
|
|
|
|
/**
|
|
* Provides audit search resources (API audit, system audit) for the global search.
|
|
*/
|
|
final class AuditSearchResourceProvider implements SearchResourceProvider
|
|
{
|
|
public function resources(): array
|
|
{
|
|
return [
|
|
'api-audit' => [
|
|
'label' => t('API audit'),
|
|
'permission' => 'audit.api.view',
|
|
'count_sql' => "select count(*) from api_audit_log where (request_id like ? escape '\\\\' or path like ? escape '\\\\' or error_code like ? escape '\\\\' or ip like ? escape '\\\\' or user_agent like ? escape '\\\\')",
|
|
'preview_sql' => "select id, request_id, method, path, status_code, created_at from api_audit_log where (request_id like ? escape '\\\\' or path like ? escape '\\\\' or error_code like ? escape '\\\\' or ip like ? escape '\\\\' or user_agent like ? escape '\\\\') order by created_at desc limit ?",
|
|
'result_sql' => "select id, request_id, method, path, status_code, created_at from api_audit_log where (request_id like ? escape '\\\\' or path like ? escape '\\\\' or error_code like ? escape '\\\\' or ip like ? escape '\\\\' or user_agent like ? escape '\\\\') order by created_at desc",
|
|
],
|
|
'system-audit' => [
|
|
'label' => t('System audit'),
|
|
'permission' => 'audit.system.view',
|
|
'count_sql' => "select count(*) from system_audit_log where (request_id like ? escape '\\\\' or event_type like ? escape '\\\\' or error_code like ? escape '\\\\' or path like ? escape '\\\\')",
|
|
'preview_sql' => "select id, request_id, event_type, outcome, created_at from system_audit_log where (request_id like ? escape '\\\\' or event_type like ? escape '\\\\' or error_code like ? escape '\\\\' or path like ? escape '\\\\') order by created_at desc limit ?",
|
|
'result_sql' => "select id, request_id, event_type, outcome, created_at from system_audit_log where (request_id like ? escape '\\\\' or event_type like ? escape '\\\\' or error_code like ? escape '\\\\' or path like ? escape '\\\\') order by created_at desc",
|
|
],
|
|
];
|
|
}
|
|
|
|
public function mapResultItem(string $resourceKey, array $row): ?array
|
|
{
|
|
if ($resourceKey === 'api-audit') {
|
|
return $this->mapApiAuditItem($row);
|
|
}
|
|
|
|
if ($resourceKey === 'system-audit') {
|
|
return $this->mapSystemAuditItem($row);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public function listUrl(string $resourceKey, string $encodedSearch): string
|
|
{
|
|
return match ($resourceKey) {
|
|
'api-audit' => lurl('admin/api-audit') . '?search=' . $encodedSearch,
|
|
'system-audit' => lurl('admin/system-audit') . '?search=' . $encodedSearch,
|
|
default => '',
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $row
|
|
* @return array{title: string, subtitle: string, url: string, icon: string}|null
|
|
*/
|
|
private function mapApiAuditItem(array $row): ?array
|
|
{
|
|
$method = strtoupper(trim((string) ($row['method'] ?? '')));
|
|
$path = trim((string) ($row['path'] ?? ''));
|
|
$statusCode = (int) ($row['status_code'] ?? 0);
|
|
$createdAt = trim((string) ($row['created_at'] ?? ''));
|
|
|
|
$title = trim($method . ' ' . $path);
|
|
if ($title === '') {
|
|
$title = trim((string) ($row['request_id'] ?? ''));
|
|
}
|
|
if ($title === '') {
|
|
return null;
|
|
}
|
|
|
|
$subtitleParts = [];
|
|
if ($statusCode > 0) {
|
|
$subtitleParts[] = (string) $statusCode;
|
|
}
|
|
if ($createdAt !== '') {
|
|
$subtitleParts[] = $createdAt;
|
|
}
|
|
|
|
return [
|
|
'title' => $title,
|
|
'subtitle' => implode(' — ', $subtitleParts),
|
|
'url' => lurl('admin/api-audit/view/' . ($row['id'] ?? '')),
|
|
'icon' => 'bi-shield-check',
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $row
|
|
* @return array{title: string, subtitle: string, url: string, icon: string}|null
|
|
*/
|
|
private function mapSystemAuditItem(array $row): ?array
|
|
{
|
|
$eventType = trim((string) ($row['event_type'] ?? ''));
|
|
$outcome = strtolower(trim((string) ($row['outcome'] ?? '')));
|
|
$createdAt = trim((string) ($row['created_at'] ?? ''));
|
|
|
|
$title = $eventType !== '' ? $eventType : trim((string) ($row['request_id'] ?? ''));
|
|
if ($title === '') {
|
|
return null;
|
|
}
|
|
|
|
$subtitleParts = [];
|
|
if ($outcome !== '') {
|
|
$subtitleParts[] = strtoupper($outcome);
|
|
}
|
|
if ($createdAt !== '') {
|
|
$subtitleParts[] = $createdAt;
|
|
}
|
|
|
|
return [
|
|
'title' => $title,
|
|
'subtitle' => implode(' — ', $subtitleParts),
|
|
'url' => lurl('admin/system-audit/view/' . ($row['id'] ?? '')),
|
|
'icon' => 'bi-journal-lock',
|
|
];
|
|
}
|
|
}
|