refactor(audit): extract audit domain into self-contained module
Move the entire audit subsystem (system audit, API audit, import audit, user lifecycle audit, frontend telemetry) from core into modules/audit/. Core decoupling via interface-based injection: - AuditRecorderInterface replaces SystemAuditService in 10+ core services - UserLifecycleAuditInterface / ImportAuditInterface for specialized flows - NullAuditRecorder fallback when audit module is disabled - ApiBootstrap/ApiResponse use null-safe callable resolvers Module structure (modules/audit/): - Manifest with routes, permissions, scheduler jobs, authorization policy - 9 services, 8 repositories, 6 domain enums, 4 job handlers - 33 page files, 4 JS files, 8 test files, migration scripts, i18n Core cleanup: - OperationsAuthorizationPolicy, UiCapabilityMap, PermissionService surgically cleaned of audit-specific constants - Sidebar template cleared of hardcoded audit navigation - AuditModuleIsolationContractTest ensures no future core→module coupling All quality gates pass: 1346 tests (19,276 assertions), PHPStan level 5 clean, architecture contracts verified. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -27,12 +27,14 @@
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"MintyPHP\\Tests\\": "tests/"
|
||||
"MintyPHP\\Tests\\": "tests/",
|
||||
"MintyPHP\\Tests\\Module\\Audit\\": "modules/audit/tests/Module/Audit/"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"MintyPHP\\": "lib/"
|
||||
"MintyPHP\\": "lib/",
|
||||
"MintyPHP\\Module\\Audit\\": "modules/audit/lib/Module/Audit/"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -12,5 +12,5 @@
|
||||
* Each entry must match a directory name under modules/ containing a module.php manifest.
|
||||
*/
|
||||
return [
|
||||
'enabled_modules' => ['addressbook', 'bookmarks', 'notifications'],
|
||||
'enabled_modules' => ['audit', 'addressbook', 'bookmarks', 'notifications'],
|
||||
];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Notifications Modul (V1)
|
||||
|
||||
Letzte Aktualisierung: 2026-03-19
|
||||
Letzte Aktualisierung: 2026-03-25
|
||||
|
||||
## Ziel
|
||||
|
||||
@@ -45,6 +45,41 @@ Kanonische Quelle: `modules/notifications/module.php`
|
||||
5. `NotificationRepository` persistiert in `user_notifications`.
|
||||
6. Bell-Endpunkte lesen tenant-scoped Daten und aktualisieren den Session-`unread_count`.
|
||||
|
||||
## Producer-Contract (fuer Core + andere Module)
|
||||
|
||||
Stabile Erzeugung erfolgt ueber:
|
||||
|
||||
- `MintyPHP\Module\Notifications\Service\NotificationMessage`
|
||||
- `MintyPHP\Module\Notifications\Service\NotificationService::createFromMessage(...)`
|
||||
- `NotificationService::createForTenantUsersFromMessage(...)`
|
||||
- `NotificationService::createForTenantAdminUsersFromMessage(...)`
|
||||
|
||||
Beispiel (locale-neutral, mit i18n-Key):
|
||||
|
||||
```php
|
||||
use MintyPHP\Module\Notifications\Service\NotificationMessage;
|
||||
use MintyPHP\Module\Notifications\Service\NotificationService;
|
||||
|
||||
$message = NotificationMessage::localized(
|
||||
'user.created',
|
||||
'New user: %s',
|
||||
[$displayName],
|
||||
null,
|
||||
[],
|
||||
'admin/users/edit/' . $uuid,
|
||||
['user_id' => $userId, 'uuid' => $uuid]
|
||||
);
|
||||
|
||||
$notificationService->createForTenantAdminUsersFromMessage(
|
||||
$tenantId,
|
||||
$message,
|
||||
$actorUserId,
|
||||
$allowedAdminSet
|
||||
);
|
||||
```
|
||||
|
||||
Plain/Fallback-Erzeugung bleibt moeglich mit `NotificationMessage::plain(...)`.
|
||||
|
||||
## Datenvertrag Notification
|
||||
|
||||
Persistierter Kernvertrag:
|
||||
@@ -55,6 +90,16 @@ Persistierter Kernvertrag:
|
||||
- `link` (nullable string)
|
||||
- `data` (nullable JSON)
|
||||
|
||||
Erweiterung fuer locale-neutrale Texte:
|
||||
|
||||
- `data.__message.title_key` (string)
|
||||
- `data.__message.title_params` (array)
|
||||
- `data.__message.body_key` (string, optional)
|
||||
- `data.__message.body_params` (array, optional)
|
||||
|
||||
Wenn `__message` vorhanden ist, wird der Anzeigetext beim Lesen in der aktuellen
|
||||
Empfaenger-Locale gerendert (Fallback auf persistiertes `title`/`body` bleibt erhalten).
|
||||
|
||||
Dedupe-Metadaten (intern):
|
||||
|
||||
- `dedupe_fingerprint`
|
||||
@@ -112,4 +157,3 @@ Dedupe-Metadaten (intern):
|
||||
2. Actor fuehrt User-Aktion aus.
|
||||
3. Zweiter Admin sieht Bell-Badge + Listeneintrag.
|
||||
4. Wiederholung innerhalb 30 Minuten erzeugt kein Duplikat.
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ use MintyPHP\Service\Access\AuthorizationService;
|
||||
use MintyPHP\Service\Access\PermissionService;
|
||||
use MintyPHP\Service\Access\RoleService;
|
||||
use MintyPHP\Service\Access\UiAccessService;
|
||||
use MintyPHP\Service\Audit\SystemAuditService;
|
||||
use MintyPHP\Service\Audit\AuditRecorderInterface;
|
||||
use MintyPHP\Service\Directory\DirectoryServicesFactory;
|
||||
|
||||
final class AccessRegistrar implements ContainerRegistrar
|
||||
@@ -40,7 +40,7 @@ final class AccessRegistrar implements ContainerRegistrar
|
||||
$c->get(AccessServicesFactory::class)->createUserRoleRepository(),
|
||||
$c->get(PermissionService::class),
|
||||
$c->get(RoleRepository::class),
|
||||
$c->get(SystemAuditService::class)
|
||||
$c->get(AuditRecorderInterface::class)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ namespace MintyPHP\App\Container\Registrars;
|
||||
use MintyPHP\App\AppContainer;
|
||||
use MintyPHP\App\Container\ContainerRegistrar;
|
||||
use MintyPHP\App\Module\ModuleRegistry;
|
||||
use MintyPHP\Http\ApiSystemAuditReporter;
|
||||
use MintyPHP\Http\CookieStore;
|
||||
use MintyPHP\Http\CookieStoreInterface;
|
||||
use MintyPHP\Http\Input\RequestInputFactory;
|
||||
@@ -18,15 +17,7 @@ use MintyPHP\Repository\Search\SearchQueryRepository;
|
||||
use MintyPHP\Repository\Stats\AdminStatsRepository;
|
||||
use MintyPHP\Repository\System\SystemHealthRepository;
|
||||
use MintyPHP\Repository\Tenant\UserTenantRepository;
|
||||
use MintyPHP\Repository\User\UserReadRepository;
|
||||
use MintyPHP\Service\Access\PermissionService;
|
||||
use MintyPHP\Service\Audit\ApiAuditService;
|
||||
use MintyPHP\Service\Audit\AuditMetadataEnricher;
|
||||
use MintyPHP\Service\Audit\AuditServicesFactory;
|
||||
use MintyPHP\Service\Audit\FrontendTelemetryIngestService;
|
||||
use MintyPHP\Service\Audit\ImportAuditService;
|
||||
use MintyPHP\Service\Audit\SystemAuditService;
|
||||
use MintyPHP\Service\Audit\UserLifecycleAuditService;
|
||||
use MintyPHP\Service\CustomField\CustomFieldServicesFactory;
|
||||
use MintyPHP\Service\CustomField\TenantCustomFieldService;
|
||||
use MintyPHP\Service\CustomField\UserCustomFieldValueService;
|
||||
@@ -42,7 +33,6 @@ use MintyPHP\Service\Scheduler\SchedulerServicesFactory;
|
||||
use MintyPHP\Service\Search\SearchDataService;
|
||||
use MintyPHP\Service\Security\RateLimiterService;
|
||||
use MintyPHP\Service\Security\SecurityServicesFactory;
|
||||
use MintyPHP\Service\Settings\SettingsFrontendTelemetryGateway;
|
||||
use MintyPHP\Service\Stats\AdminStatsViewDataService;
|
||||
use MintyPHP\Service\System\SystemHealthService;
|
||||
use MintyPHP\Service\System\SystemInfoService;
|
||||
@@ -71,27 +61,13 @@ final class AppServicesRegistrar implements ContainerRegistrar
|
||||
$container->set(RateLimiterService::class, static fn (AppContainer $c): RateLimiterService => $c->get(SecurityServicesFactory::class)->createRateLimiterService());
|
||||
$container->set(MailService::class, static fn (AppContainer $c): MailService => $c->get(MailServicesFactory::class)->createMailService());
|
||||
$container->set(MailLogService::class, static fn (AppContainer $c): MailLogService => $c->get(MailServicesFactory::class)->createMailLogService());
|
||||
$container->set(ApiAuditService::class, static fn (AppContainer $c): ApiAuditService => $c->get(AuditServicesFactory::class)->createApiAuditService());
|
||||
$container->set(UserLifecycleAuditService::class, static fn (AppContainer $c): UserLifecycleAuditService => $c->get(AuditServicesFactory::class)->createUserLifecycleAuditService());
|
||||
$container->set(SystemAuditService::class, static fn (AppContainer $c): SystemAuditService => $c->get(AuditServicesFactory::class)->createSystemAuditService());
|
||||
$container->set(FrontendTelemetryIngestService::class, static fn (AppContainer $c): FrontendTelemetryIngestService => new FrontendTelemetryIngestService(
|
||||
$c->get(SystemAuditService::class),
|
||||
$c->get(SettingsFrontendTelemetryGateway::class),
|
||||
$c->get(RateLimiterService::class),
|
||||
$c->get(SessionStoreInterface::class)
|
||||
));
|
||||
$container->set(ApiSystemAuditReporter::class, static fn (AppContainer $c): ApiSystemAuditReporter => new ApiSystemAuditReporter(
|
||||
$c->get(SystemAuditService::class)
|
||||
));
|
||||
// Audit services are registered by the audit module's AuditContainerRegistrar.
|
||||
$container->set(ImportService::class, static fn (AppContainer $c): ImportService => $c->get(ImportServicesFactory::class)->createImportService());
|
||||
$container->set(ImportAuditService::class, static fn (AppContainer $c): ImportAuditService => $c->get(ImportServicesFactory::class)->createImportAuditService());
|
||||
$container->set(ScheduledJobService::class, static fn (AppContainer $c): ScheduledJobService => $c->get(SchedulerServicesFactory::class)->createScheduledJobService());
|
||||
$container->set(SchedulerRunService::class, static fn (AppContainer $c): SchedulerRunService => $c->get(SchedulerServicesFactory::class)->createSchedulerRunService());
|
||||
$container->set(TenantCustomFieldService::class, static fn (AppContainer $c): TenantCustomFieldService => $c->get(CustomFieldServicesFactory::class)->createTenantCustomFieldService());
|
||||
$container->set(UserCustomFieldValueService::class, static fn (AppContainer $c): UserCustomFieldValueService => $c->get(CustomFieldServicesFactory::class)->createUserCustomFieldValueService());
|
||||
$container->set(AuditMetadataEnricher::class, static fn (AppContainer $c): AuditMetadataEnricher => new AuditMetadataEnricher(
|
||||
$c->get(UserReadRepository::class)
|
||||
));
|
||||
// AuditMetadataEnricher is registered by the audit module's AuditContainerRegistrar.
|
||||
$container->set(GridUserCountEnricher::class, static fn (): GridUserCountEnricher => new GridUserCountEnricher());
|
||||
$container->set(IntendedUrlService::class, static fn (): IntendedUrlService => new IntendedUrlService());
|
||||
$container->set(RequestInputFactory::class, static fn (): RequestInputFactory => new RequestInputFactory());
|
||||
|
||||
@@ -8,7 +8,6 @@ use MintyPHP\Repository\Search\SearchQueryRepository;
|
||||
use MintyPHP\Repository\Stats\AdminStatsRepository;
|
||||
use MintyPHP\Repository\Support\DatabaseSessionRepository;
|
||||
use MintyPHP\Service\Access\AccessRepositoryFactory;
|
||||
use MintyPHP\Service\Audit\AuditRepositoryFactory;
|
||||
use MintyPHP\Service\Auth\AuthRepositoryFactory;
|
||||
use MintyPHP\Service\Directory\DirectoryRepositoryFactory;
|
||||
use MintyPHP\Service\Import\ImportRepositoryFactory;
|
||||
@@ -24,7 +23,6 @@ final class RepositoryFactoryRegistrar implements ContainerRegistrar
|
||||
public function register(AppContainer $container): void
|
||||
{
|
||||
$container->set(SettingRepositoryFactory::class, static fn (): SettingRepositoryFactory => new SettingRepositoryFactory());
|
||||
$container->set(AuditRepositoryFactory::class, static fn (): AuditRepositoryFactory => new AuditRepositoryFactory());
|
||||
$container->set(SecurityRepositoryFactory::class, static fn (): SecurityRepositoryFactory => new SecurityRepositoryFactory());
|
||||
$container->set(MailRepositoryFactory::class, static fn (): MailRepositoryFactory => new MailRepositoryFactory());
|
||||
$container->set(ImportRepositoryFactory::class, static fn (): ImportRepositoryFactory => new ImportRepositoryFactory());
|
||||
|
||||
@@ -18,8 +18,12 @@ use MintyPHP\Service\Access\AccessRepositoryFactory;
|
||||
use MintyPHP\Service\Access\AccessServicesFactory;
|
||||
use MintyPHP\Service\Access\AssignableRoleService;
|
||||
use MintyPHP\Service\Access\PermissionService;
|
||||
use MintyPHP\Service\Audit\AuditRepositoryFactory;
|
||||
use MintyPHP\Service\Audit\AuditServicesFactory;
|
||||
use MintyPHP\Service\Audit\AuditRecorderInterface;
|
||||
use MintyPHP\Service\Audit\ImportAuditInterface;
|
||||
use MintyPHP\Service\Audit\NullAuditRecorder;
|
||||
use MintyPHP\Service\Audit\NullImportAudit;
|
||||
use MintyPHP\Service\Audit\NullUserLifecycleAudit;
|
||||
use MintyPHP\Service\Audit\UserLifecycleAuditInterface;
|
||||
use MintyPHP\Service\Auth\AuthGatewayFactory;
|
||||
use MintyPHP\Service\Auth\AuthRepositoryFactory;
|
||||
use MintyPHP\Service\Auth\AuthServicesFactory;
|
||||
@@ -52,12 +56,9 @@ final class ServiceFactoryRegistrar implements ContainerRegistrar
|
||||
$container->set(SettingServicesFactory::class, static fn (AppContainer $c): SettingServicesFactory => new SettingServicesFactory(
|
||||
$c->get(SettingRepositoryFactory::class)
|
||||
));
|
||||
$container->set(AuditServicesFactory::class, static fn (AppContainer $c): AuditServicesFactory => new AuditServicesFactory(
|
||||
$c->get(AuditRepositoryFactory::class),
|
||||
$c->get(SettingServicesFactory::class)->createSettingsSystemAuditGateway(),
|
||||
$c->get(RequestRuntimeInterface::class),
|
||||
$c->get(SessionStoreInterface::class)
|
||||
));
|
||||
// Audit interfaces are NOT registered here — they are set AFTER module registrars
|
||||
// in registerContainer.php so the audit module can provide real implementations.
|
||||
// If the audit module is disabled, null implementations are used as fallback.
|
||||
$container->set(SecurityServicesFactory::class, static fn (AppContainer $c): SecurityServicesFactory => new SecurityServicesFactory(
|
||||
$c->get(SecurityRepositoryFactory::class)
|
||||
));
|
||||
@@ -81,20 +82,20 @@ final class ServiceFactoryRegistrar implements ContainerRegistrar
|
||||
$c->get(AccessServicesFactory::class),
|
||||
$c->get(SettingServicesFactory::class),
|
||||
$c->get(DirectoryServicesFactory::class),
|
||||
$c->get(ImportRepositoryFactory::class),
|
||||
$c->get(ImportAuditInterface::class),
|
||||
$c->get(TenantScopeService::class),
|
||||
$c->get(SessionStoreInterface::class)
|
||||
));
|
||||
$container->set(SchedulerServicesFactory::class, static fn (AppContainer $c): SchedulerServicesFactory => new SchedulerServicesFactory(
|
||||
$c->get(UserServicesFactory::class),
|
||||
$c->get(AuditServicesFactory::class),
|
||||
$c->get(AuditRecorderInterface::class),
|
||||
$c->get(DatabaseSessionRepository::class),
|
||||
$c->get(SchedulerRepositoryFactory::class),
|
||||
$c
|
||||
));
|
||||
$container->set(DirectoryServicesFactory::class, static fn (AppContainer $c): DirectoryServicesFactory => new DirectoryServicesFactory(
|
||||
$c->get(UserServicesFactory::class),
|
||||
$c->get(AuditServicesFactory::class),
|
||||
$c->get(AuditRecorderInterface::class),
|
||||
$c->get(DirectoryRepositoryFactory::class),
|
||||
$c->get(DirectoryGatewayFactory::class)
|
||||
));
|
||||
@@ -111,7 +112,7 @@ final class ServiceFactoryRegistrar implements ContainerRegistrar
|
||||
));
|
||||
$container->set(AccessGatewayFactory::class, static fn (AppContainer $c): AccessGatewayFactory => new AccessGatewayFactory(
|
||||
$c->get(AccessRepositoryFactory::class),
|
||||
$c->get(AuditServicesFactory::class),
|
||||
$c->get(AuditRecorderInterface::class),
|
||||
$c->get(SessionStoreInterface::class)
|
||||
));
|
||||
$container->set(AccessPolicyFactory::class, static fn (AppContainer $c): AccessPolicyFactory => new AccessPolicyFactory(
|
||||
@@ -127,7 +128,8 @@ final class ServiceFactoryRegistrar implements ContainerRegistrar
|
||||
$c->get(DirectoryRepositoryFactory::class),
|
||||
));
|
||||
$container->set(UserServicesFactory::class, static fn (AppContainer $c): UserServicesFactory => new UserServicesFactory(
|
||||
$c->get(AuditServicesFactory::class),
|
||||
$c->get(AuditRecorderInterface::class),
|
||||
$c->get(UserLifecycleAuditInterface::class),
|
||||
$c->get(UserRepositoryFactory::class),
|
||||
$c->get(UserGatewayFactory::class),
|
||||
$c->get(DatabaseSessionRepository::class),
|
||||
@@ -144,7 +146,7 @@ final class ServiceFactoryRegistrar implements ContainerRegistrar
|
||||
));
|
||||
$container->set(AuthServicesFactory::class, static fn (AppContainer $c): AuthServicesFactory => new AuthServicesFactory(
|
||||
$c->get(UserServicesFactory::class),
|
||||
$c->get(AuditServicesFactory::class),
|
||||
$c->get(AuditRecorderInterface::class),
|
||||
$c->get(MailServicesFactory::class),
|
||||
$c->get(AuthRepositoryFactory::class),
|
||||
$c->get(AuthGatewayFactory::class),
|
||||
|
||||
@@ -7,7 +7,7 @@ use MintyPHP\App\Container\ContainerRegistrar;
|
||||
use MintyPHP\Repository\Auth\ApiTokenRepository;
|
||||
use MintyPHP\Repository\Auth\RememberTokenRepository;
|
||||
use MintyPHP\Service\Access\RoleService;
|
||||
use MintyPHP\Service\Audit\SystemAuditService;
|
||||
use MintyPHP\Service\Audit\AuditRecorderInterface;
|
||||
use MintyPHP\Service\Branding\BrandingFaviconService;
|
||||
use MintyPHP\Service\Branding\BrandingLogoService;
|
||||
use MintyPHP\Service\Branding\BrandingServicesFactory;
|
||||
@@ -63,7 +63,7 @@ final class SettingsRegistrar implements ContainerRegistrar
|
||||
$c->get(DepartmentService::class),
|
||||
$c->get(RememberTokenRepository::class),
|
||||
$c->get(ApiTokenRepository::class),
|
||||
$c->get(SystemAuditService::class)
|
||||
$c->get(AuditRecorderInterface::class)
|
||||
));
|
||||
$container->set(ThemeConfigGateway::class, static fn (AppContainer $c): ThemeConfigGateway => $c->get(SettingServicesFactory::class)->createThemeConfigGateway());
|
||||
// Branding services are registered here because they depend on settings gateways
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace MintyPHP\App\Module;
|
||||
use MintyPHP\App\AppContainer;
|
||||
use MintyPHP\App\Module\Contracts\EventListener;
|
||||
use MintyPHP\Http\RequestContext;
|
||||
use MintyPHP\Service\Audit\SystemAuditService;
|
||||
use MintyPHP\Service\Audit\AuditRecorderInterface;
|
||||
|
||||
/**
|
||||
* Fire-and-forget event dispatcher for module lifecycle events.
|
||||
@@ -22,7 +22,7 @@ final class ModuleEventDispatcher
|
||||
public function __construct(
|
||||
private readonly array $listenerMap,
|
||||
private readonly AppContainer $container,
|
||||
private readonly ?SystemAuditService $systemAuditService = null
|
||||
private readonly ?AuditRecorderInterface $systemAuditService = null
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,12 @@ use MintyPHP\App\Container\Registrars\SettingsRegistrar;
|
||||
use MintyPHP\App\Container\Registrars\UserRegistrar;
|
||||
use MintyPHP\App\Module\ModuleEventDispatcher;
|
||||
use MintyPHP\App\Module\ModuleRegistry;
|
||||
use MintyPHP\Service\Audit\SystemAuditService;
|
||||
use MintyPHP\Service\Audit\AuditRecorderInterface;
|
||||
use MintyPHP\Service\Audit\ImportAuditInterface;
|
||||
use MintyPHP\Service\Audit\NullAuditRecorder;
|
||||
use MintyPHP\Service\Audit\NullImportAudit;
|
||||
use MintyPHP\Service\Audit\NullUserLifecycleAudit;
|
||||
use MintyPHP\Service\Audit\UserLifecycleAuditInterface;
|
||||
|
||||
$container = new AppContainer();
|
||||
|
||||
@@ -48,7 +53,7 @@ $container->set(ModuleRegistry::class, static fn (): ModuleRegistry => $moduleRe
|
||||
$container->set(ModuleEventDispatcher::class, static fn () => new ModuleEventDispatcher(
|
||||
$moduleRegistry->getEventListeners(),
|
||||
$container,
|
||||
$container->has(SystemAuditService::class) ? $container->get(SystemAuditService::class) : null
|
||||
$container->has(AuditRecorderInterface::class) ? $container->get(AuditRecorderInterface::class) : null
|
||||
));
|
||||
|
||||
// Disallow overriding existing core bindings from module registrars.
|
||||
@@ -67,4 +72,15 @@ foreach ($moduleRegistry->getContainerRegistrars() as $registrarClass) {
|
||||
$registrarInstance->register($container);
|
||||
}
|
||||
|
||||
// ── Audit interface fallbacks (null implementations when audit module is disabled) ──
|
||||
if (!$container->has(AuditRecorderInterface::class)) {
|
||||
$container->set(AuditRecorderInterface::class, static fn (): AuditRecorderInterface => new NullAuditRecorder());
|
||||
}
|
||||
if (!$container->has(UserLifecycleAuditInterface::class)) {
|
||||
$container->set(UserLifecycleAuditInterface::class, static fn (): UserLifecycleAuditInterface => new NullUserLifecycleAudit());
|
||||
}
|
||||
if (!$container->has(ImportAuditInterface::class)) {
|
||||
$container->set(ImportAuditInterface::class, static fn (): ImportAuditInterface => new NullImportAudit());
|
||||
}
|
||||
|
||||
return $container;
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace MintyPHP\Console\Commands\Scheduler;
|
||||
|
||||
use MintyPHP\Console\Command;
|
||||
use MintyPHP\DB;
|
||||
use MintyPHP\Service\Audit\SystemAuditService;
|
||||
use MintyPHP\Service\Audit\AuditRecorderInterface;
|
||||
use MintyPHP\Service\Scheduler\SchedulerServicesFactory;
|
||||
|
||||
final class RunCommand extends Command
|
||||
@@ -44,7 +44,7 @@ final class RunCommand extends Command
|
||||
}
|
||||
} catch (\Throwable $exception) {
|
||||
try {
|
||||
app(SystemAuditService::class)->record('scheduler.run', 'failed', [
|
||||
app(AuditRecorderInterface::class)->record('scheduler.run', 'failed', [
|
||||
'error_code' => 'unexpected_error',
|
||||
'metadata' => [
|
||||
'trigger_type' => 'scheduler',
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace MintyPHP\Http;
|
||||
|
||||
use MintyPHP\Service\Audit\ApiAuditService;
|
||||
use MintyPHP\Service\Security\RateLimiterService;
|
||||
use MintyPHP\Service\Settings\SettingsApiPolicyGateway;
|
||||
|
||||
@@ -20,20 +19,20 @@ class ApiBootstrap
|
||||
|
||||
private static bool $initialized = false;
|
||||
private static bool $shutdownRegistered = false;
|
||||
/** @var (callable(): ApiAuditService)|null */
|
||||
/** @var (callable(): object)|null Resolver for API audit service (provided by audit module) */
|
||||
private static $apiAuditServiceResolver = null;
|
||||
/** @var (callable(): SettingsApiPolicyGateway)|null */
|
||||
private static $settingsApiPolicyGatewayResolver = null;
|
||||
/** @var (callable(): RateLimiterService)|null */
|
||||
private static $rateLimiterServiceResolver = null;
|
||||
/** @var (callable(): ApiSystemAuditReporter)|null */
|
||||
/** @var (callable(): object)|null Resolver for API system audit reporter (provided by audit module) */
|
||||
private static $apiSystemAuditReporterResolver = null;
|
||||
|
||||
public static function configure(
|
||||
callable $apiAuditServiceResolver,
|
||||
?callable $apiAuditServiceResolver,
|
||||
callable $settingsApiPolicyGatewayResolver,
|
||||
callable $rateLimiterServiceResolver,
|
||||
callable $apiSystemAuditReporterResolver
|
||||
?callable $apiSystemAuditReporterResolver
|
||||
): void {
|
||||
self::$apiAuditServiceResolver = $apiAuditServiceResolver;
|
||||
self::$settingsApiPolicyGatewayResolver = $settingsApiPolicyGatewayResolver;
|
||||
@@ -66,7 +65,7 @@ class ApiBootstrap
|
||||
header('X-Request-Id: ' . RequestContext::requestHeaderValue());
|
||||
|
||||
self::registerShutdownHandler();
|
||||
self::apiAuditService()->startRequestContext();
|
||||
self::tryStartAudit();
|
||||
self::startSystemAuditReporter();
|
||||
self::setCorsHeaders();
|
||||
|
||||
@@ -123,7 +122,7 @@ class ApiBootstrap
|
||||
if ($statusCode <= 0) {
|
||||
$statusCode = 200;
|
||||
}
|
||||
self::apiAuditService()->finish($statusCode);
|
||||
self::tryFinishAudit($statusCode);
|
||||
self::finishSystemAuditReporter($statusCode);
|
||||
return;
|
||||
}
|
||||
@@ -135,7 +134,7 @@ class ApiBootstrap
|
||||
if ($statusCode <= 0) {
|
||||
$statusCode = 200;
|
||||
}
|
||||
self::apiAuditService()->finish($statusCode);
|
||||
self::tryFinishAudit($statusCode);
|
||||
self::finishSystemAuditReporter($statusCode);
|
||||
return;
|
||||
}
|
||||
@@ -144,7 +143,7 @@ class ApiBootstrap
|
||||
if ($statusCode < 400) {
|
||||
$statusCode = 500;
|
||||
}
|
||||
self::apiAuditService()->finish($statusCode, 'fatal_error');
|
||||
self::tryFinishAudit($statusCode, 'fatal_error');
|
||||
self::finishSystemAuditReporter($statusCode, 'fatal_error');
|
||||
});
|
||||
}
|
||||
@@ -252,20 +251,43 @@ class ApiBootstrap
|
||||
return self::resolveDependency(self::$rateLimiterServiceResolver, RateLimiterService::class, 'ApiBootstrap');
|
||||
}
|
||||
|
||||
private static function apiAuditService(): ApiAuditService
|
||||
private static function tryStartAudit(): void
|
||||
{
|
||||
return self::resolveDependency(self::$apiAuditServiceResolver, ApiAuditService::class, 'ApiBootstrap');
|
||||
try {
|
||||
if (is_callable(self::$apiAuditServiceResolver)) {
|
||||
$service = (self::$apiAuditServiceResolver)();
|
||||
if (is_object($service) && method_exists($service, 'startRequestContext')) {
|
||||
$service->startRequestContext();
|
||||
}
|
||||
}
|
||||
} catch (\Throwable) {
|
||||
// fail-open
|
||||
}
|
||||
}
|
||||
|
||||
private static function systemAuditReporter(): ApiSystemAuditReporter
|
||||
private static function tryFinishAudit(int $statusCode, ?string $errorCode = null): void
|
||||
{
|
||||
return self::resolveDependency(self::$apiSystemAuditReporterResolver, ApiSystemAuditReporter::class, 'ApiBootstrap');
|
||||
try {
|
||||
if (is_callable(self::$apiAuditServiceResolver)) {
|
||||
$service = (self::$apiAuditServiceResolver)();
|
||||
if (is_object($service) && method_exists($service, 'finish')) {
|
||||
$service->finish($statusCode, $errorCode);
|
||||
}
|
||||
}
|
||||
} catch (\Throwable) {
|
||||
// fail-open
|
||||
}
|
||||
}
|
||||
|
||||
private static function startSystemAuditReporter(): void
|
||||
{
|
||||
try {
|
||||
self::systemAuditReporter()->start();
|
||||
if (is_callable(self::$apiSystemAuditReporterResolver)) {
|
||||
$service = (self::$apiSystemAuditReporterResolver)();
|
||||
if (is_object($service) && method_exists($service, 'start')) {
|
||||
$service->start();
|
||||
}
|
||||
}
|
||||
} catch (\Throwable) {
|
||||
// fail-open
|
||||
}
|
||||
@@ -274,7 +296,12 @@ class ApiBootstrap
|
||||
private static function finishSystemAuditReporter(int $statusCode, ?string $errorCode = null): void
|
||||
{
|
||||
try {
|
||||
self::systemAuditReporter()->finish($statusCode, $errorCode);
|
||||
if (is_callable(self::$apiSystemAuditReporterResolver)) {
|
||||
$service = (self::$apiSystemAuditReporterResolver)();
|
||||
if (is_object($service) && method_exists($service, 'finish')) {
|
||||
$service->finish($statusCode, $errorCode);
|
||||
}
|
||||
}
|
||||
} catch (\Throwable) {
|
||||
// fail-open
|
||||
}
|
||||
|
||||
@@ -4,21 +4,20 @@ namespace MintyPHP\Http;
|
||||
|
||||
use MintyPHP\Http\Input\FormErrors;
|
||||
use MintyPHP\Service\Access\AuthorizationService;
|
||||
use MintyPHP\Service\Audit\ApiAuditService;
|
||||
|
||||
class ApiResponse
|
||||
{
|
||||
/** @var (callable(): ApiAuditService)|null */
|
||||
/** @var (callable(): object)|null Resolver for API audit service (provided by audit module) */
|
||||
private static $apiAuditServiceResolver = null;
|
||||
/** @var (callable(): AuthorizationService)|null */
|
||||
private static $authorizationServiceResolver = null;
|
||||
/** @var (callable(): ApiSystemAuditReporter)|null */
|
||||
/** @var (callable(): object)|null Resolver for API system audit reporter (provided by audit module) */
|
||||
private static $apiSystemAuditReporterResolver = null;
|
||||
|
||||
public static function configure(
|
||||
callable $apiAuditServiceResolver,
|
||||
?callable $apiAuditServiceResolver,
|
||||
callable $authorizationServiceResolver,
|
||||
callable $apiSystemAuditReporterResolver
|
||||
?callable $apiSystemAuditReporterResolver
|
||||
): void {
|
||||
self::$apiAuditServiceResolver = $apiAuditServiceResolver;
|
||||
self::$authorizationServiceResolver = $authorizationServiceResolver;
|
||||
@@ -169,7 +168,7 @@ class ApiResponse
|
||||
|
||||
if ($body === null) {
|
||||
self::finishSystemAuditReporter($status, $errorCode);
|
||||
self::apiAuditService()->finish($status, $errorCode);
|
||||
self::tryFinishAudit($status, $errorCode);
|
||||
die();
|
||||
}
|
||||
|
||||
@@ -190,7 +189,7 @@ class ApiResponse
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
self::finishSystemAuditReporter($status, $errorCode);
|
||||
self::apiAuditService()->finish($status, $errorCode);
|
||||
self::tryFinishAudit($status, $errorCode);
|
||||
die($json);
|
||||
}
|
||||
|
||||
@@ -201,7 +200,7 @@ class ApiResponse
|
||||
return trim($requestId);
|
||||
}
|
||||
|
||||
$requestId = self::apiAuditService()->currentRequestId();
|
||||
$requestId = self::tryGetAuditRequestId();
|
||||
if (is_string($requestId) && trim($requestId) !== '') {
|
||||
return trim($requestId);
|
||||
}
|
||||
@@ -239,18 +238,33 @@ class ApiResponse
|
||||
return $details;
|
||||
}
|
||||
|
||||
private static function apiAuditService(): ApiAuditService
|
||||
private static function tryFinishAudit(int $statusCode, ?string $errorCode = null): void
|
||||
{
|
||||
if (!is_callable(self::$apiAuditServiceResolver)) {
|
||||
throw new \RuntimeException('ApiResponse is not configured for dependency: ' . ApiAuditService::class);
|
||||
try {
|
||||
if (is_callable(self::$apiAuditServiceResolver)) {
|
||||
$service = (self::$apiAuditServiceResolver)();
|
||||
if (is_object($service) && method_exists($service, 'finish')) {
|
||||
$service->finish($statusCode, $errorCode);
|
||||
}
|
||||
}
|
||||
} catch (\Throwable) {
|
||||
// fail-open
|
||||
}
|
||||
}
|
||||
|
||||
$service = (self::$apiAuditServiceResolver)();
|
||||
if (!$service instanceof ApiAuditService) {
|
||||
throw new \RuntimeException('ApiResponse resolver returned invalid dependency: ' . ApiAuditService::class);
|
||||
private static function tryGetAuditRequestId(): ?string
|
||||
{
|
||||
try {
|
||||
if (is_callable(self::$apiAuditServiceResolver)) {
|
||||
$service = (self::$apiAuditServiceResolver)();
|
||||
if (is_object($service) && method_exists($service, 'currentRequestId')) {
|
||||
return $service->currentRequestId();
|
||||
}
|
||||
}
|
||||
} catch (\Throwable) {
|
||||
// fail-open
|
||||
}
|
||||
|
||||
return $service;
|
||||
return null;
|
||||
}
|
||||
|
||||
private static function authorizationService(): AuthorizationService
|
||||
@@ -267,24 +281,15 @@ class ApiResponse
|
||||
return $service;
|
||||
}
|
||||
|
||||
private static function systemAuditReporter(): ApiSystemAuditReporter
|
||||
{
|
||||
if (!is_callable(self::$apiSystemAuditReporterResolver)) {
|
||||
throw new \RuntimeException('ApiResponse is not configured for dependency: ' . ApiSystemAuditReporter::class);
|
||||
}
|
||||
|
||||
$service = (self::$apiSystemAuditReporterResolver)();
|
||||
if (!$service instanceof ApiSystemAuditReporter) {
|
||||
throw new \RuntimeException('ApiResponse resolver returned invalid dependency: ' . ApiSystemAuditReporter::class);
|
||||
}
|
||||
|
||||
return $service;
|
||||
}
|
||||
|
||||
private static function finishSystemAuditReporter(int $statusCode, ?string $errorCode = null): void
|
||||
{
|
||||
try {
|
||||
self::systemAuditReporter()->finish($statusCode, $errorCode);
|
||||
if (is_callable(self::$apiSystemAuditReporterResolver)) {
|
||||
$service = (self::$apiSystemAuditReporterResolver)();
|
||||
if (is_object($service) && method_exists($service, 'finish')) {
|
||||
$service->finish($statusCode, $errorCode);
|
||||
}
|
||||
}
|
||||
} catch (\Throwable) {
|
||||
// fail-open
|
||||
}
|
||||
|
||||
@@ -3,14 +3,10 @@
|
||||
namespace MintyPHP\Repository\Stats;
|
||||
|
||||
use MintyPHP\DB;
|
||||
use MintyPHP\Domain\Taxonomy\ImportAuditStatus;
|
||||
use MintyPHP\Domain\Taxonomy\MailLogStatus;
|
||||
use MintyPHP\Domain\Taxonomy\ScheduledJobRunStatus;
|
||||
use MintyPHP\Domain\Taxonomy\ScheduledJobTriggerType;
|
||||
use MintyPHP\Domain\Taxonomy\SystemAuditOutcome;
|
||||
use MintyPHP\Domain\Taxonomy\TenantStatus;
|
||||
use MintyPHP\Domain\Taxonomy\UserLifecycleAction;
|
||||
use MintyPHP\Domain\Taxonomy\UserLifecycleStatus;
|
||||
|
||||
class AdminStatsRepository implements AdminStatsRepositoryInterface
|
||||
{
|
||||
@@ -21,15 +17,15 @@ class AdminStatsRepository implements AdminStatsRepositoryInterface
|
||||
{
|
||||
$tenantActiveStatus = TenantStatus::Active->value;
|
||||
$tenantInactiveStatus = TenantStatus::Inactive->value;
|
||||
$importStatusRunning = ImportAuditStatus::Running->value;
|
||||
$importStatusSuccess = ImportAuditStatus::Success->value;
|
||||
$importStatusPartial = ImportAuditStatus::Partial->value;
|
||||
$importStatusFailed = ImportAuditStatus::Failed->value;
|
||||
$systemAuditOutcomeFailed = SystemAuditOutcome::Failed->value;
|
||||
$systemAuditOutcomeDenied = SystemAuditOutcome::Denied->value;
|
||||
$lifecycleStatusFailed = UserLifecycleStatus::Failed->value;
|
||||
$lifecycleStatusSkipped = UserLifecycleStatus::Skipped->value;
|
||||
$lifecycleActionRestore = UserLifecycleAction::Restore->value;
|
||||
$importStatusRunning = 'running';
|
||||
$importStatusSuccess = 'success';
|
||||
$importStatusPartial = 'partial';
|
||||
$importStatusFailed = 'failed';
|
||||
$systemAuditOutcomeFailed = 'failed';
|
||||
$systemAuditOutcomeDenied = 'denied';
|
||||
$lifecycleStatusFailed = 'failed';
|
||||
$lifecycleStatusSkipped = 'skipped';
|
||||
$lifecycleActionRestore = 'restore';
|
||||
$schedulerTriggerScheduler = ScheduledJobTriggerType::Scheduler->value;
|
||||
$schedulerRunStatusFailed = ScheduledJobRunStatus::Failed->value;
|
||||
$mailStatusFailed = MailLogStatus::Failed->value;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
namespace MintyPHP\Service\Access;
|
||||
|
||||
use MintyPHP\Http\SessionStoreInterface;
|
||||
use MintyPHP\Service\Audit\AuditServicesFactory;
|
||||
use MintyPHP\Service\Audit\AuditRecorderInterface;
|
||||
|
||||
class AccessGatewayFactory
|
||||
{
|
||||
@@ -11,7 +11,7 @@ class AccessGatewayFactory
|
||||
|
||||
public function __construct(
|
||||
private readonly AccessRepositoryFactory $accessRepositoryFactory,
|
||||
private readonly AuditServicesFactory $auditServicesFactory,
|
||||
private readonly AuditRecorderInterface $auditRecorder,
|
||||
private readonly SessionStoreInterface $sessionStore
|
||||
) {
|
||||
}
|
||||
@@ -22,7 +22,7 @@ class AccessGatewayFactory
|
||||
$this->accessRepositoryFactory->createPermissionRepository(),
|
||||
$this->accessRepositoryFactory->createRolePermissionRepository(),
|
||||
$this->accessRepositoryFactory->createUserRoleRepository(),
|
||||
$this->auditServicesFactory->createSystemAuditService(),
|
||||
$this->auditRecorder,
|
||||
$this->sessionStore
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace MintyPHP\Service\Access;
|
||||
use MintyPHP\Repository\Access\RoleAssignableRoleRepositoryInterface;
|
||||
use MintyPHP\Repository\Access\RoleRepositoryInterface;
|
||||
use MintyPHP\Repository\Access\UserRoleRepositoryInterface;
|
||||
use MintyPHP\Service\Audit\SystemAuditService;
|
||||
use MintyPHP\Service\Audit\AuditRecorderInterface;
|
||||
|
||||
class AssignableRoleService
|
||||
{
|
||||
@@ -14,7 +14,7 @@ class AssignableRoleService
|
||||
private readonly UserRoleRepositoryInterface $userRoleRepository,
|
||||
private readonly PermissionService $permissionService,
|
||||
private readonly RoleRepositoryInterface $roleRepository,
|
||||
private readonly SystemAuditService $systemAuditService
|
||||
private readonly AuditRecorderInterface $systemAuditService
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -4,13 +4,7 @@ namespace MintyPHP\Service\Access;
|
||||
|
||||
final class OperationsAuthorizationPolicy implements AuthorizationPolicyInterface
|
||||
{
|
||||
public const ABILITY_ADMIN_USER_LIFECYCLE_AUDIT_VIEW = 'ops.admin.user_lifecycle_audit.view';
|
||||
public const ABILITY_ADMIN_USERS_LIFECYCLE_RESTORE = 'ops.admin.users.lifecycle_restore';
|
||||
public const ABILITY_ADMIN_API_AUDIT_VIEW = 'ops.admin.api_audit.view';
|
||||
public const ABILITY_ADMIN_SYSTEM_AUDIT_VIEW = 'ops.admin.system_audit.view';
|
||||
public const ABILITY_ADMIN_SYSTEM_AUDIT_PURGE = 'ops.admin.system_audit.purge';
|
||||
public const ABILITY_ADMIN_DOCS_VIEW = 'ops.admin.docs.view';
|
||||
public const ABILITY_ADMIN_IMPORTS_AUDIT_VIEW = 'ops.admin.imports_audit.view';
|
||||
public const ABILITY_ADMIN_IMPORTS_VIEW = 'ops.admin.imports.view';
|
||||
public const ABILITY_ADMIN_JOBS_RUN_NOW = 'ops.admin.jobs.run_now';
|
||||
public const ABILITY_ADMIN_JOBS_VIEW = 'ops.admin.jobs.view';
|
||||
@@ -49,13 +43,7 @@ final class OperationsAuthorizationPolicy implements AuthorizationPolicyInterfac
|
||||
public function supports(string $ability): bool
|
||||
{
|
||||
return in_array($ability, [
|
||||
self::ABILITY_ADMIN_USER_LIFECYCLE_AUDIT_VIEW,
|
||||
self::ABILITY_ADMIN_USERS_LIFECYCLE_RESTORE,
|
||||
self::ABILITY_ADMIN_API_AUDIT_VIEW,
|
||||
self::ABILITY_ADMIN_SYSTEM_AUDIT_VIEW,
|
||||
self::ABILITY_ADMIN_SYSTEM_AUDIT_PURGE,
|
||||
self::ABILITY_ADMIN_DOCS_VIEW,
|
||||
self::ABILITY_ADMIN_IMPORTS_AUDIT_VIEW,
|
||||
self::ABILITY_ADMIN_IMPORTS_VIEW,
|
||||
self::ABILITY_ADMIN_JOBS_RUN_NOW,
|
||||
self::ABILITY_ADMIN_JOBS_VIEW,
|
||||
@@ -98,13 +86,7 @@ final class OperationsAuthorizationPolicy implements AuthorizationPolicyInterfac
|
||||
return match ($ability) {
|
||||
self::ABILITY_ADMIN_USERS_CREATE_EDIT_CUSTOM_FIELDS => $this->authorizeUsersCreateCustomFields($actorUserId),
|
||||
self::ABILITY_API_TOKENS_SELF_MANAGE => $this->authorizeApiTokensSelfManage($actorUserId),
|
||||
self::ABILITY_ADMIN_USER_LIFECYCLE_AUDIT_VIEW => $this->allowIfHas($actorUserId, PermissionService::USER_LIFECYCLE_AUDIT_VIEW),
|
||||
self::ABILITY_ADMIN_USERS_LIFECYCLE_RESTORE => $this->allowIfHas($actorUserId, PermissionService::USERS_LIFECYCLE_RESTORE),
|
||||
self::ABILITY_ADMIN_API_AUDIT_VIEW => $this->allowIfHas($actorUserId, PermissionService::API_AUDIT_VIEW),
|
||||
self::ABILITY_ADMIN_SYSTEM_AUDIT_VIEW => $this->allowIfHas($actorUserId, PermissionService::SYSTEM_AUDIT_VIEW),
|
||||
self::ABILITY_ADMIN_SYSTEM_AUDIT_PURGE => $this->allowIfHas($actorUserId, PermissionService::SYSTEM_AUDIT_PURGE),
|
||||
self::ABILITY_ADMIN_DOCS_VIEW => $this->allowIfHas($actorUserId, PermissionService::DOCS_VIEW),
|
||||
self::ABILITY_ADMIN_IMPORTS_AUDIT_VIEW => $this->allowIfHas($actorUserId, PermissionService::IMPORTS_AUDIT_VIEW),
|
||||
self::ABILITY_ADMIN_IMPORTS_VIEW => $this->allowIfHas($actorUserId, PermissionService::IMPORTS_VIEW),
|
||||
self::ABILITY_ADMIN_JOBS_RUN_NOW => $this->allowIfHas($actorUserId, PermissionService::JOBS_RUN_NOW),
|
||||
self::ABILITY_ADMIN_JOBS_VIEW => $this->allowIfHas($actorUserId, PermissionService::JOBS_VIEW),
|
||||
|
||||
@@ -6,7 +6,7 @@ use MintyPHP\Http\SessionStoreInterface;
|
||||
use MintyPHP\Repository\Access\PermissionRepositoryInterface;
|
||||
use MintyPHP\Repository\Access\RolePermissionRepositoryInterface;
|
||||
use MintyPHP\Repository\Access\UserRoleRepositoryInterface;
|
||||
use MintyPHP\Service\Audit\SystemAuditService;
|
||||
use MintyPHP\Service\Audit\AuditRecorderInterface;
|
||||
|
||||
/**
|
||||
* Central RBAC permission service — resolves and caches permission keys for users.
|
||||
@@ -28,7 +28,7 @@ class PermissionService
|
||||
private readonly PermissionRepositoryInterface $permissionRepository,
|
||||
private readonly RolePermissionRepositoryInterface $rolePermissionRepository,
|
||||
private readonly UserRoleRepositoryInterface $userRoleRepository,
|
||||
private readonly SystemAuditService $systemAuditService,
|
||||
private readonly AuditRecorderInterface $systemAuditService,
|
||||
private readonly SessionStoreInterface $sessionStore
|
||||
) {
|
||||
}
|
||||
@@ -65,22 +65,16 @@ class PermissionService
|
||||
public const SETTINGS_UPDATE = 'settings.update';
|
||||
public const TENANT_SCOPE_GLOBAL = 'tenant.scope.global';
|
||||
public const IMPORTS_VIEW = 'imports.view';
|
||||
public const IMPORTS_AUDIT_VIEW = 'imports.audit.view';
|
||||
public const JOBS_VIEW = 'jobs.view';
|
||||
public const JOBS_MANAGE = 'jobs.manage';
|
||||
public const JOBS_RUN_NOW = 'jobs.run_now';
|
||||
public const USER_LIFECYCLE_AUDIT_VIEW = 'user_lifecycle_audit.view';
|
||||
public const USERS_IMPORT = 'users.import';
|
||||
public const USERS_IMPORT_ASSIGNMENTS = 'users.import_assignments';
|
||||
public const USERS_LIFECYCLE_RESTORE = 'users.lifecycle_restore';
|
||||
public const CUSTOM_FIELDS_MANAGE = 'custom_fields.manage';
|
||||
public const CUSTOM_FIELDS_EDIT_VALUES = 'custom_fields.edit_values';
|
||||
public const API_DOCS_VIEW = 'api_docs.view';
|
||||
public const DOCS_VIEW = 'docs.view';
|
||||
public const MAIL_LOG_VIEW = 'mail_log.view';
|
||||
public const API_AUDIT_VIEW = 'api_audit.view';
|
||||
public const SYSTEM_AUDIT_VIEW = 'system_audit.view';
|
||||
public const SYSTEM_AUDIT_PURGE = 'system_audit.purge';
|
||||
public const STATS_VIEW = 'stats.view';
|
||||
public const SYSTEM_INFO_VIEW = 'system_info.view';
|
||||
public const API_TOKENS_MANAGE = 'api_tokens.manage';
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
namespace MintyPHP\Service\Access;
|
||||
|
||||
use MintyPHP\Repository\Access\RoleRepositoryInterface;
|
||||
use MintyPHP\Service\Audit\SystemAuditService;
|
||||
use MintyPHP\Service\Audit\AuditRecorderInterface;
|
||||
use MintyPHP\Service\Directory\DirectorySettingsGateway;
|
||||
|
||||
class RoleService
|
||||
@@ -11,7 +11,7 @@ class RoleService
|
||||
public function __construct(
|
||||
private readonly RoleRepositoryInterface $roleRepository,
|
||||
private readonly DirectorySettingsGateway $settingsGateway,
|
||||
private readonly SystemAuditService $systemAuditService
|
||||
private readonly AuditRecorderInterface $systemAuditService
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -24,10 +24,6 @@ final class UiCapabilityMap
|
||||
'can_view_api_docs' => OperationsAuthorizationPolicy::ABILITY_ADMIN_API_DOCS_VIEW,
|
||||
'can_view_docs' => OperationsAuthorizationPolicy::ABILITY_ADMIN_DOCS_VIEW,
|
||||
'can_view_mail_log' => OperationsAuthorizationPolicy::ABILITY_ADMIN_MAIL_LOG_VIEW,
|
||||
'can_view_api_audit' => OperationsAuthorizationPolicy::ABILITY_ADMIN_API_AUDIT_VIEW,
|
||||
'can_view_system_audit' => OperationsAuthorizationPolicy::ABILITY_ADMIN_SYSTEM_AUDIT_VIEW,
|
||||
'can_view_imports_audit' => OperationsAuthorizationPolicy::ABILITY_ADMIN_IMPORTS_AUDIT_VIEW,
|
||||
'can_view_user_lifecycle_audit' => OperationsAuthorizationPolicy::ABILITY_ADMIN_USER_LIFECYCLE_AUDIT_VIEW,
|
||||
'can_view_stats' => OperationsAuthorizationPolicy::ABILITY_ADMIN_STATS_VIEW,
|
||||
'can_view_system_info' => OperationsAuthorizationPolicy::ABILITY_ADMIN_SYSTEM_INFO_VIEW,
|
||||
];
|
||||
@@ -57,29 +53,13 @@ final class UiCapabilityMap
|
||||
'can_view_mail_log' => OperationsAuthorizationPolicy::ABILITY_ADMIN_MAIL_LOG_VIEW,
|
||||
'can_view_users' => UserAuthorizationPolicy::ABILITY_ADMIN_USERS_VIEW,
|
||||
'can_view_tenants' => TenantAuthorizationPolicy::ABILITY_ADMIN_TENANTS_VIEW,
|
||||
'can_view_imports_audit' => OperationsAuthorizationPolicy::ABILITY_ADMIN_IMPORTS_AUDIT_VIEW,
|
||||
'can_view_system_audit' => OperationsAuthorizationPolicy::ABILITY_ADMIN_SYSTEM_AUDIT_VIEW,
|
||||
'can_view_user_lifecycle_audit' => OperationsAuthorizationPolicy::ABILITY_ADMIN_USER_LIFECYCLE_AUDIT_VIEW,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
public const PAGE_AUDIT_PURGE = [
|
||||
public const PAGE_JOBS_PURGE = [
|
||||
'can_purge' => SettingsAuthorizationPolicy::ABILITY_ADMIN_SETTINGS_UPDATE,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
public const PAGE_SYSTEM_AUDIT = [
|
||||
'can_purge_system_audit' => OperationsAuthorizationPolicy::ABILITY_ADMIN_SYSTEM_AUDIT_PURGE,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
public const PAGE_USER_LIFECYCLE_VIEW = [
|
||||
'can_restore' => OperationsAuthorizationPolicy::ABILITY_ADMIN_USERS_LIFECYCLE_RESTORE,
|
||||
];
|
||||
}
|
||||
|
||||
21
lib/Service/Audit/AuditRecorderInterface.php
Normal file
21
lib/Service/Audit/AuditRecorderInterface.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Service\Audit;
|
||||
|
||||
/**
|
||||
* Core-side contract for recording system audit events.
|
||||
*
|
||||
* Implemented by the audit module's SystemAuditService when the module is active.
|
||||
* Falls back to NullAuditRecorder (no-op) when the audit module is disabled.
|
||||
*/
|
||||
interface AuditRecorderInterface
|
||||
{
|
||||
/**
|
||||
* @param array<string, mixed> $context
|
||||
*/
|
||||
public function record(
|
||||
string $eventType,
|
||||
string $outcome = 'success',
|
||||
array $context = []
|
||||
): ?int;
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Service\Audit;
|
||||
|
||||
use MintyPHP\Repository\Audit\ApiAuditLogRepository;
|
||||
use MintyPHP\Repository\Audit\ApiAuditLogRepositoryInterface;
|
||||
use MintyPHP\Repository\Audit\SystemAuditLogRepository;
|
||||
use MintyPHP\Repository\Audit\SystemAuditLogRepositoryInterface;
|
||||
use MintyPHP\Repository\Audit\UserLifecycleAuditRepository;
|
||||
use MintyPHP\Repository\Audit\UserLifecycleAuditRepositoryInterface;
|
||||
|
||||
class AuditRepositoryFactory
|
||||
{
|
||||
private ?ApiAuditLogRepository $apiAuditLogRepository = null;
|
||||
private ?UserLifecycleAuditRepository $userLifecycleAuditRepository = null;
|
||||
private ?SystemAuditLogRepository $systemAuditLogRepository = null;
|
||||
|
||||
public function createApiAuditLogRepository(): ApiAuditLogRepositoryInterface
|
||||
{
|
||||
return $this->apiAuditLogRepository ??= new ApiAuditLogRepository();
|
||||
}
|
||||
|
||||
public function createUserLifecycleAuditRepository(): UserLifecycleAuditRepositoryInterface
|
||||
{
|
||||
return $this->userLifecycleAuditRepository ??= new UserLifecycleAuditRepository();
|
||||
}
|
||||
|
||||
public function createSystemAuditLogRepository(): SystemAuditLogRepositoryInterface
|
||||
{
|
||||
return $this->systemAuditLogRepository ??= new SystemAuditLogRepository();
|
||||
}
|
||||
}
|
||||
28
lib/Service/Audit/ImportAuditInterface.php
Normal file
28
lib/Service/Audit/ImportAuditInterface.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Service\Audit;
|
||||
|
||||
/**
|
||||
* Core-side contract for recording import audit events.
|
||||
*
|
||||
* Implemented by the audit module's ImportAuditService when active.
|
||||
* Falls back to NullImportAudit (no-op) when the audit module is disabled.
|
||||
*/
|
||||
interface ImportAuditInterface
|
||||
{
|
||||
/**
|
||||
* @param array<int, string> $mappedTargets
|
||||
*/
|
||||
public function startRun(
|
||||
string $profileKey,
|
||||
array $mappedTargets,
|
||||
?string $sourceFilename,
|
||||
int $userId,
|
||||
?int $currentTenantId
|
||||
): ?int;
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $result
|
||||
*/
|
||||
public function finishRun(?int $runId, array $result, ?string $forcedStatus = null): void;
|
||||
}
|
||||
17
lib/Service/Audit/NullAuditRecorder.php
Normal file
17
lib/Service/Audit/NullAuditRecorder.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Service\Audit;
|
||||
|
||||
/**
|
||||
* No-op audit recorder used when the audit module is disabled.
|
||||
*/
|
||||
final class NullAuditRecorder implements AuditRecorderInterface
|
||||
{
|
||||
public function record(
|
||||
string $eventType,
|
||||
string $outcome = 'success',
|
||||
array $context = []
|
||||
): ?int {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
23
lib/Service/Audit/NullImportAudit.php
Normal file
23
lib/Service/Audit/NullImportAudit.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Service\Audit;
|
||||
|
||||
/**
|
||||
* No-op import audit used when the audit module is disabled.
|
||||
*/
|
||||
final class NullImportAudit implements ImportAuditInterface
|
||||
{
|
||||
public function startRun(
|
||||
string $profileKey,
|
||||
array $mappedTargets,
|
||||
?string $sourceFilename,
|
||||
int $userId,
|
||||
?int $currentTenantId
|
||||
): ?int {
|
||||
return null;
|
||||
}
|
||||
|
||||
public function finishRun(?int $runId, array $result, ?string $forcedStatus = null): void
|
||||
{
|
||||
}
|
||||
}
|
||||
74
lib/Service/Audit/NullUserLifecycleAudit.php
Normal file
74
lib/Service/Audit/NullUserLifecycleAudit.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Service\Audit;
|
||||
|
||||
/**
|
||||
* No-op user lifecycle audit used when the audit module is disabled.
|
||||
*/
|
||||
final class NullUserLifecycleAudit implements UserLifecycleAuditInterface
|
||||
{
|
||||
public function logDeactivate(
|
||||
string $runUuid,
|
||||
string $triggerType,
|
||||
array $policy,
|
||||
?int $actorUserId,
|
||||
array $targetUser,
|
||||
string $status = 'success',
|
||||
?string $reasonCode = null
|
||||
): bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function logDeleteWithSnapshot(
|
||||
string $runUuid,
|
||||
string $triggerType,
|
||||
array $policy,
|
||||
?int $actorUserId,
|
||||
array $targetUser
|
||||
): int|false {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function logDeleteFailure(
|
||||
string $runUuid,
|
||||
string $triggerType,
|
||||
array $policy,
|
||||
?int $actorUserId,
|
||||
array $targetUser,
|
||||
string $reasonCode
|
||||
): bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function logRestore(
|
||||
string $runUuid,
|
||||
string $triggerType,
|
||||
array $policy,
|
||||
?int $actorUserId,
|
||||
array $targetUser,
|
||||
string $status = 'success',
|
||||
?string $reasonCode = null
|
||||
): bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function markDeleteEventRestored(int $auditId, int $restoredByUserId, int $restoredUserId): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function updateStatus(int $id, string $status, ?string $reasonCode = null): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function findDeleteEventForRestore(int $id, bool $forUpdate = false): ?array
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function decryptSnapshot(array $event): ?array
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
73
lib/Service/Audit/UserLifecycleAuditInterface.php
Normal file
73
lib/Service/Audit/UserLifecycleAuditInterface.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Service\Audit;
|
||||
|
||||
/**
|
||||
* Core-side contract for recording user lifecycle audit events (deactivation, deletion, restore).
|
||||
*
|
||||
* Implemented by the audit module's UserLifecycleAuditService when active.
|
||||
* Falls back to NullUserLifecycleAudit (no-op) when the audit module is disabled.
|
||||
*/
|
||||
interface UserLifecycleAuditInterface
|
||||
{
|
||||
/**
|
||||
* @param array<string, mixed> $policy
|
||||
* @param array<string, mixed> $targetUser
|
||||
*/
|
||||
public function logDeactivate(
|
||||
string $runUuid,
|
||||
string $triggerType,
|
||||
array $policy,
|
||||
?int $actorUserId,
|
||||
array $targetUser,
|
||||
string $status = 'success',
|
||||
?string $reasonCode = null
|
||||
): bool;
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $policy
|
||||
* @param array<string, mixed> $targetUser
|
||||
*/
|
||||
public function logDeleteWithSnapshot(
|
||||
string $runUuid,
|
||||
string $triggerType,
|
||||
array $policy,
|
||||
?int $actorUserId,
|
||||
array $targetUser
|
||||
): int|false;
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $policy
|
||||
* @param array<string, mixed> $targetUser
|
||||
*/
|
||||
public function logDeleteFailure(
|
||||
string $runUuid,
|
||||
string $triggerType,
|
||||
array $policy,
|
||||
?int $actorUserId,
|
||||
array $targetUser,
|
||||
string $reasonCode
|
||||
): bool;
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $policy
|
||||
* @param array<string, mixed> $targetUser
|
||||
*/
|
||||
public function logRestore(
|
||||
string $runUuid,
|
||||
string $triggerType,
|
||||
array $policy,
|
||||
?int $actorUserId,
|
||||
array $targetUser,
|
||||
string $status = 'success',
|
||||
?string $reasonCode = null
|
||||
): bool;
|
||||
|
||||
public function markDeleteEventRestored(int $auditId, int $restoredByUserId, int $restoredUserId): bool;
|
||||
|
||||
public function updateStatus(int $id, string $status, ?string $reasonCode = null): bool;
|
||||
|
||||
public function findDeleteEventForRestore(int $id, bool $forUpdate = false): ?array;
|
||||
|
||||
public function decryptSnapshot(array $event): ?array;
|
||||
}
|
||||
@@ -9,7 +9,7 @@ use MintyPHP\Repository\User\UserReadRepositoryInterface;
|
||||
use MintyPHP\Repository\User\UserWriteRepositoryInterface;
|
||||
use MintyPHP\Router;
|
||||
use MintyPHP\Service\Access\PermissionService;
|
||||
use MintyPHP\Service\Audit\SystemAuditService;
|
||||
use MintyPHP\Service\Audit\AuditRecorderInterface;
|
||||
use MintyPHP\Service\User\UserAccountService;
|
||||
use MintyPHP\Service\User\UserTenantContextService;
|
||||
use MintyPHP\Session;
|
||||
@@ -42,7 +42,7 @@ class AuthService
|
||||
private readonly PermissionService $permissionService,
|
||||
private readonly TenantSsoService $tenantSsoService,
|
||||
private readonly AuthSessionTenantContextService $authSessionTenantContextService,
|
||||
private readonly SystemAuditService $systemAuditService,
|
||||
private readonly AuditRecorderInterface $systemAuditService,
|
||||
private readonly SessionStoreInterface $sessionStore,
|
||||
private readonly ?ModuleEventDispatcher $eventDispatcher = null
|
||||
) {
|
||||
|
||||
@@ -8,7 +8,7 @@ use MintyPHP\Http\CookieStoreInterface;
|
||||
use MintyPHP\Http\RequestRuntimeInterface;
|
||||
use MintyPHP\Http\SessionStoreInterface;
|
||||
use MintyPHP\Repository\Support\DatabaseSessionRepository;
|
||||
use MintyPHP\Service\Audit\AuditServicesFactory;
|
||||
use MintyPHP\Service\Audit\AuditRecorderInterface;
|
||||
use MintyPHP\Service\Mail\MailServicesFactory;
|
||||
use MintyPHP\Service\User\UserServicesFactory;
|
||||
|
||||
@@ -28,7 +28,7 @@ class AuthServicesFactory
|
||||
|
||||
public function __construct(
|
||||
private readonly UserServicesFactory $userServicesFactory,
|
||||
private readonly AuditServicesFactory $auditServicesFactory,
|
||||
private readonly AuditRecorderInterface $auditRecorder,
|
||||
private readonly MailServicesFactory $mailServicesFactory,
|
||||
private readonly AuthRepositoryFactory $authRepositoryFactory,
|
||||
private readonly AuthGatewayFactory $authGatewayFactory,
|
||||
@@ -101,7 +101,7 @@ class AuthServicesFactory
|
||||
$this->authGatewayFactory->createPermissionService(),
|
||||
$this->createTenantSsoService(),
|
||||
$this->createAuthSessionTenantContextService(),
|
||||
$this->auditServicesFactory->createSystemAuditService(),
|
||||
$this->auditRecorder,
|
||||
$this->sessionStore,
|
||||
$this->resolveEventDispatcher()
|
||||
);
|
||||
|
||||
@@ -6,7 +6,7 @@ use MintyPHP\Repository\Access\RoleRepositoryInterface;
|
||||
use MintyPHP\Repository\Org\DepartmentRepositoryInterface;
|
||||
use MintyPHP\Repository\Tenant\TenantRepositoryInterface;
|
||||
use MintyPHP\Service\Access\RoleService;
|
||||
use MintyPHP\Service\Audit\AuditServicesFactory;
|
||||
use MintyPHP\Service\Audit\AuditRecorderInterface;
|
||||
use MintyPHP\Service\Org\DepartmentService;
|
||||
use MintyPHP\Service\Tenant\TenantScopeService;
|
||||
use MintyPHP\Service\Tenant\TenantService;
|
||||
@@ -20,7 +20,7 @@ class DirectoryServicesFactory
|
||||
|
||||
public function __construct(
|
||||
private readonly UserServicesFactory $userServicesFactory,
|
||||
private readonly AuditServicesFactory $auditServicesFactory,
|
||||
private readonly AuditRecorderInterface $auditRecorder,
|
||||
private readonly DirectoryRepositoryFactory $directoryRepositoryFactory,
|
||||
private readonly DirectoryGatewayFactory $directoryGatewayFactory
|
||||
) {
|
||||
@@ -32,7 +32,7 @@ class DirectoryServicesFactory
|
||||
$this->createTenantRepository(),
|
||||
$this->createDepartmentRepository(),
|
||||
$this->createDirectorySettingsGateway(),
|
||||
$this->auditServicesFactory->createSystemAuditService()
|
||||
$this->auditRecorder
|
||||
);
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ class DirectoryServicesFactory
|
||||
$this->createDepartmentRepository(),
|
||||
$this->createDirectorySettingsGateway(),
|
||||
$this->directoryGatewayFactory->getTenantScopeService(),
|
||||
$this->auditServicesFactory->createSystemAuditService()
|
||||
$this->auditRecorder
|
||||
);
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ class DirectoryServicesFactory
|
||||
return $this->roleService ??= new RoleService(
|
||||
$this->createRoleRepository(),
|
||||
$this->createDirectorySettingsGateway(),
|
||||
$this->auditServicesFactory->createSystemAuditService()
|
||||
$this->auditRecorder
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,15 +2,6 @@
|
||||
|
||||
namespace MintyPHP\Service\Import;
|
||||
|
||||
use MintyPHP\Repository\Audit\ImportAuditRunRepository;
|
||||
use MintyPHP\Repository\Audit\ImportAuditRunRepositoryInterface;
|
||||
|
||||
class ImportRepositoryFactory
|
||||
{
|
||||
private ?ImportAuditRunRepository $importAuditRunRepository = null;
|
||||
|
||||
public function createImportAuditRunRepository(): ImportAuditRunRepositoryInterface
|
||||
{
|
||||
return $this->importAuditRunRepository ??= new ImportAuditRunRepository();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace MintyPHP\Service\Import;
|
||||
|
||||
use MintyPHP\Http\SessionStoreInterface;
|
||||
use MintyPHP\Service\Access\PermissionService;
|
||||
use MintyPHP\Service\Audit\ImportAuditService;
|
||||
use MintyPHP\Service\Audit\ImportAuditInterface;
|
||||
use MintyPHP\Service\I18n\TranslatesServiceText;
|
||||
use MintyPHP\Service\Import\Profile\ImportProfileInterface;
|
||||
use MintyPHP\Service\Settings\SettingsDefaultsGateway;
|
||||
@@ -44,7 +44,7 @@ class ImportService
|
||||
public function __construct(
|
||||
private readonly CsvReaderService $csvReaderService,
|
||||
private readonly ImportTempFileService $importTempFileService,
|
||||
private readonly ImportAuditService $importAuditService,
|
||||
private readonly ImportAuditInterface $importAuditService,
|
||||
private readonly ImportStateStoreService $importStateStoreService,
|
||||
private readonly array $profiles,
|
||||
private readonly PermissionService $permissionService,
|
||||
|
||||
@@ -3,10 +3,9 @@
|
||||
namespace MintyPHP\Service\Import;
|
||||
|
||||
use MintyPHP\Http\SessionStoreInterface;
|
||||
use MintyPHP\Repository\Audit\ImportAuditRunRepositoryInterface;
|
||||
use MintyPHP\Service\Access\AccessServicesFactory;
|
||||
use MintyPHP\Service\Access\PermissionService;
|
||||
use MintyPHP\Service\Audit\ImportAuditService;
|
||||
use MintyPHP\Service\Audit\ImportAuditInterface;
|
||||
use MintyPHP\Service\Directory\DirectoryServicesFactory;
|
||||
use MintyPHP\Service\Import\Profile\DepartmentImportGateway;
|
||||
use MintyPHP\Service\Import\Profile\DepartmentImportProfile;
|
||||
@@ -22,7 +21,6 @@ class ImportServicesFactory
|
||||
private ?CsvReaderService $csvReaderService = null;
|
||||
private ?ImportTempFileService $importTempFileService = null;
|
||||
private ?ImportStateStoreService $importStateStoreService = null;
|
||||
private ?ImportAuditService $importAuditService = null;
|
||||
private ?PermissionService $permissionService = null;
|
||||
private ?SettingsDefaultsGateway $settingsDefaultsGateway = null;
|
||||
private ?ImportService $importService = null;
|
||||
@@ -32,7 +30,7 @@ class ImportServicesFactory
|
||||
private readonly AccessServicesFactory $accessServicesFactory,
|
||||
private readonly SettingServicesFactory $settingServicesFactory,
|
||||
private readonly DirectoryServicesFactory $directoryServicesFactory,
|
||||
private readonly ImportRepositoryFactory $importRepositoryFactory,
|
||||
private readonly ImportAuditInterface $importAudit,
|
||||
private readonly TenantScopeService $tenantScopeService,
|
||||
private readonly SessionStoreInterface $sessionStore
|
||||
) {
|
||||
@@ -66,7 +64,7 @@ class ImportServicesFactory
|
||||
return $this->importService = new ImportService(
|
||||
$this->getCsvReaderService(),
|
||||
$this->getImportTempFileService(),
|
||||
$this->createImportAuditService(),
|
||||
$this->importAudit,
|
||||
$this->createImportStateStoreService(),
|
||||
$profiles,
|
||||
$this->createPermissionService(),
|
||||
@@ -76,11 +74,6 @@ class ImportServicesFactory
|
||||
);
|
||||
}
|
||||
|
||||
public function createImportAuditService(): ImportAuditService
|
||||
{
|
||||
return $this->importAuditService ??= new ImportAuditService($this->getImportAuditRunRepository());
|
||||
}
|
||||
|
||||
public function createImportStateStoreService(): ImportStateStoreService
|
||||
{
|
||||
return $this->importStateStoreService ??= new ImportStateStoreService(
|
||||
@@ -99,11 +92,6 @@ class ImportServicesFactory
|
||||
return $this->importTempFileService ??= new ImportTempFileService();
|
||||
}
|
||||
|
||||
private function getImportAuditRunRepository(): ImportAuditRunRepositoryInterface
|
||||
{
|
||||
return $this->importRepositoryFactory->createImportAuditRunRepository();
|
||||
}
|
||||
|
||||
private function createPermissionService(): PermissionService
|
||||
{
|
||||
return $this->permissionService ??= $this->accessServicesFactory->createPermissionService();
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
namespace MintyPHP\Service\Org;
|
||||
|
||||
use MintyPHP\Repository\Org\DepartmentRepositoryInterface;
|
||||
use MintyPHP\Service\Audit\SystemAuditService;
|
||||
use MintyPHP\Service\Audit\AuditRecorderInterface;
|
||||
use MintyPHP\Service\Directory\DirectorySettingsGateway;
|
||||
use MintyPHP\Service\Tenant\TenantScopeService;
|
||||
use MintyPHP\Service\User\UserServicesFactory;
|
||||
@@ -15,7 +15,7 @@ class DepartmentService
|
||||
private readonly DepartmentRepositoryInterface $departmentRepository,
|
||||
private readonly DirectorySettingsGateway $settingsGateway,
|
||||
private readonly TenantScopeService $scopeGateway,
|
||||
private readonly SystemAuditService $systemAuditService
|
||||
private readonly AuditRecorderInterface $systemAuditService
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -4,9 +4,7 @@ namespace MintyPHP\Service\Scheduler;
|
||||
|
||||
use MintyPHP\App\AppContainer;
|
||||
use MintyPHP\App\Module\ModuleRegistry;
|
||||
use MintyPHP\Service\Audit\SystemAuditService;
|
||||
use MintyPHP\Service\Scheduler\Handler\ScheduledJobHandlerInterface;
|
||||
use MintyPHP\Service\Scheduler\Handler\SystemAuditPurgeJobHandler;
|
||||
use MintyPHP\Service\Scheduler\Handler\UserLifecycleJobHandler;
|
||||
use MintyPHP\Service\User\UserLifecycleService;
|
||||
use RuntimeException;
|
||||
@@ -14,7 +12,6 @@ use RuntimeException;
|
||||
class ScheduledJobRegistry
|
||||
{
|
||||
public const USER_LIFECYCLE_RUN = 'user_lifecycle_run';
|
||||
public const SYSTEM_AUDIT_PURGE = 'system_audit_purge';
|
||||
|
||||
/** @var array<string, ScheduledJobHandlerInterface> */
|
||||
private array $handlers;
|
||||
@@ -38,12 +35,10 @@ class ScheduledJobRegistry
|
||||
|
||||
public function __construct(
|
||||
UserLifecycleService $userLifecycleService,
|
||||
SystemAuditService $systemAuditService,
|
||||
private readonly AppContainer $container
|
||||
) {
|
||||
$this->handlers = [
|
||||
self::USER_LIFECYCLE_RUN => new UserLifecycleJobHandler($userLifecycleService),
|
||||
self::SYSTEM_AUDIT_PURGE => new SystemAuditPurgeJobHandler($systemAuditService),
|
||||
];
|
||||
|
||||
$this->loadModuleJobs();
|
||||
|
||||
@@ -5,13 +5,12 @@ namespace MintyPHP\Service\Scheduler;
|
||||
use MintyPHP\Domain\Taxonomy\ScheduledJobRunStatus;
|
||||
use MintyPHP\Domain\Taxonomy\ScheduledJobTriggerType;
|
||||
use MintyPHP\Domain\Taxonomy\SchedulerRuntimeResult;
|
||||
use MintyPHP\Domain\Taxonomy\SystemAuditOutcome;
|
||||
use MintyPHP\Repository\Scheduler\ScheduledJobRepositoryInterface;
|
||||
use MintyPHP\Repository\Scheduler\ScheduledJobRunRepositoryInterface;
|
||||
use MintyPHP\Repository\Scheduler\SchedulerRuntimeRepositoryInterface;
|
||||
use MintyPHP\Repository\Support\DatabaseSessionRepository;
|
||||
use MintyPHP\Repository\Support\RepoQuery;
|
||||
use MintyPHP\Service\Audit\SystemAuditService;
|
||||
use MintyPHP\Service\Audit\AuditRecorderInterface;
|
||||
|
||||
/**
|
||||
* Orchestrates the execution of scheduled jobs.
|
||||
@@ -33,7 +32,7 @@ class SchedulerRunService
|
||||
private readonly ScheduledJobRegistry $scheduledJobRegistry,
|
||||
private readonly ScheduleCalculator $scheduleCalculator,
|
||||
private readonly DatabaseSessionRepository $databaseSessionRepository,
|
||||
private readonly SystemAuditService $systemAuditService
|
||||
private readonly AuditRecorderInterface $systemAuditService
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -423,13 +422,13 @@ class SchedulerRunService
|
||||
private function auditOutcomeForRunStatus(string $status): string
|
||||
{
|
||||
if ($status === ScheduledJobRunStatus::Success->value) {
|
||||
return SystemAuditOutcome::Success->value;
|
||||
return 'success';
|
||||
}
|
||||
if ($status === ScheduledJobRunStatus::Failed->value) {
|
||||
return SystemAuditOutcome::Failed->value;
|
||||
return 'failed';
|
||||
}
|
||||
// "skipped" is an operational non-error state (e.g. already running),
|
||||
// not an authorization denial.
|
||||
return SystemAuditOutcome::Success->value;
|
||||
return 'success';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,7 @@ use MintyPHP\Repository\Scheduler\ScheduledJobRepositoryInterface;
|
||||
use MintyPHP\Repository\Scheduler\ScheduledJobRunRepositoryInterface;
|
||||
use MintyPHP\Repository\Scheduler\SchedulerRuntimeRepositoryInterface;
|
||||
use MintyPHP\Repository\Support\DatabaseSessionRepository;
|
||||
use MintyPHP\Service\Audit\AuditServicesFactory;
|
||||
use MintyPHP\Service\Audit\SystemAuditService;
|
||||
use MintyPHP\Service\Audit\AuditRecorderInterface;
|
||||
use MintyPHP\Service\User\UserLifecycleService;
|
||||
use MintyPHP\Service\User\UserServicesFactory;
|
||||
|
||||
@@ -21,7 +20,7 @@ class SchedulerServicesFactory
|
||||
|
||||
public function __construct(
|
||||
private readonly UserServicesFactory $userServicesFactory,
|
||||
private readonly AuditServicesFactory $auditServicesFactory,
|
||||
private readonly AuditRecorderInterface $auditRecorder,
|
||||
private readonly DatabaseSessionRepository $databaseSessionRepository,
|
||||
private readonly SchedulerRepositoryFactory $schedulerRepositoryFactory,
|
||||
private readonly AppContainer $appContainer
|
||||
@@ -48,7 +47,7 @@ class SchedulerServicesFactory
|
||||
$this->getScheduledJobRegistry(),
|
||||
$this->getScheduleCalculator(),
|
||||
$this->getDatabaseSessionRepository(),
|
||||
$this->getSystemAuditService()
|
||||
$this->getAuditRecorder()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -86,7 +85,6 @@ class SchedulerServicesFactory
|
||||
{
|
||||
return $this->scheduledJobRegistry ??= new ScheduledJobRegistry(
|
||||
$this->getUserLifecycleService(),
|
||||
$this->getSystemAuditService(),
|
||||
$this->appContainer
|
||||
);
|
||||
}
|
||||
@@ -96,9 +94,9 @@ class SchedulerServicesFactory
|
||||
return $this->databaseSessionRepository;
|
||||
}
|
||||
|
||||
private function getSystemAuditService(): SystemAuditService
|
||||
private function getAuditRecorder(): AuditRecorderInterface
|
||||
{
|
||||
return $this->auditServicesFactory->createSystemAuditService();
|
||||
return $this->auditRecorder;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace MintyPHP\Service\Settings;
|
||||
use MintyPHP\Repository\Auth\ApiTokenRepository;
|
||||
use MintyPHP\Repository\Auth\RememberTokenRepository;
|
||||
use MintyPHP\Service\Access\RoleService;
|
||||
use MintyPHP\Service\Audit\SystemAuditService;
|
||||
use MintyPHP\Service\Audit\AuditRecorderInterface;
|
||||
use MintyPHP\Service\Org\DepartmentService;
|
||||
use MintyPHP\Service\Tenant\TenantService;
|
||||
|
||||
@@ -29,7 +29,7 @@ class AdminSettingsService
|
||||
private readonly DepartmentService $departmentService,
|
||||
private readonly RememberTokenRepository $rememberTokenRepository,
|
||||
private readonly ApiTokenRepository $apiTokenRepository,
|
||||
private readonly SystemAuditService $systemAuditService
|
||||
private readonly AuditRecorderInterface $systemAuditService
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
|
||||
namespace MintyPHP\Service\Tenant;
|
||||
|
||||
use MintyPHP\Domain\Taxonomy\SystemAuditOutcome;
|
||||
use MintyPHP\Domain\Taxonomy\TenantStatus;
|
||||
use MintyPHP\Repository\Org\DepartmentRepositoryInterface;
|
||||
use MintyPHP\Repository\Tenant\TenantRepositoryInterface;
|
||||
use MintyPHP\Service\Audit\SystemAuditService;
|
||||
use MintyPHP\Service\Audit\AuditRecorderInterface;
|
||||
use MintyPHP\Service\Directory\DirectorySettingsGateway;
|
||||
|
||||
class TenantService
|
||||
@@ -15,7 +14,7 @@ class TenantService
|
||||
private readonly TenantRepositoryInterface $tenantRepository,
|
||||
private readonly DepartmentRepositoryInterface $departmentRepository,
|
||||
private readonly DirectorySettingsGateway $settingsGateway,
|
||||
private readonly SystemAuditService $systemAuditService
|
||||
private readonly AuditRecorderInterface $systemAuditService
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -91,7 +90,7 @@ class TenantService
|
||||
$this->settingsGateway->setDefaultTenantId((int) $createdId);
|
||||
}
|
||||
|
||||
$this->systemAuditService->record('admin.tenants.create', SystemAuditOutcome::Success->value, [
|
||||
$this->systemAuditService->record('admin.tenants.create', 'success', [
|
||||
'actor_user_id' => $currentUserId > 0 ? $currentUserId : null,
|
||||
'target_type' => 'tenant',
|
||||
'target_id' => (int) $createdId,
|
||||
@@ -155,7 +154,7 @@ class TenantService
|
||||
return ['ok' => false, 'errors' => [t('Tenant can not be updated')], 'form' => $form];
|
||||
}
|
||||
|
||||
$this->systemAuditService->record('admin.tenants.update', SystemAuditOutcome::Success->value, [
|
||||
$this->systemAuditService->record('admin.tenants.update', 'success', [
|
||||
'actor_user_id' => $currentUserId > 0 ? $currentUserId : null,
|
||||
'target_type' => 'tenant',
|
||||
'target_id' => $tenantId,
|
||||
@@ -194,7 +193,7 @@ class TenantService
|
||||
return ['ok' => false, 'status' => 500, 'error' => 'delete_failed'];
|
||||
}
|
||||
|
||||
$this->systemAuditService->record('admin.tenants.delete', SystemAuditOutcome::Success->value, [
|
||||
$this->systemAuditService->record('admin.tenants.delete', 'success', [
|
||||
'target_type' => 'tenant',
|
||||
'target_id' => $tenantId,
|
||||
'target_uuid' => (string) ($tenant['uuid'] ?? ''),
|
||||
|
||||
@@ -8,7 +8,7 @@ use MintyPHP\Repository\Support\DatabaseSessionRepository;
|
||||
use MintyPHP\Repository\User\UserListQueryRepositoryInterface;
|
||||
use MintyPHP\Repository\User\UserReadRepositoryInterface;
|
||||
use MintyPHP\Repository\User\UserWriteRepositoryInterface;
|
||||
use MintyPHP\Service\Audit\SystemAuditService;
|
||||
use MintyPHP\Service\Audit\AuditRecorderInterface;
|
||||
use MintyPHP\Service\Tenant\TenantScopeService;
|
||||
|
||||
/**
|
||||
@@ -36,7 +36,7 @@ class UserAccountService
|
||||
private readonly UserSettingsGateway $settingsGateway,
|
||||
private readonly TenantScopeService $scopeGateway,
|
||||
private readonly UserDirectoryGateway $directoryGateway,
|
||||
private readonly SystemAuditService $systemAuditService,
|
||||
private readonly AuditRecorderInterface $systemAuditService,
|
||||
private readonly DatabaseSessionRepository $databaseSessionRepository,
|
||||
private readonly ?ModuleEventDispatcher $eventDispatcher = null
|
||||
) {
|
||||
|
||||
@@ -6,14 +6,14 @@ use MintyPHP\Repository\Support\DatabaseSessionRepository;
|
||||
use MintyPHP\Repository\Support\RepoQuery;
|
||||
use MintyPHP\Repository\User\UserReadRepositoryInterface;
|
||||
use MintyPHP\Repository\User\UserWriteRepositoryInterface;
|
||||
use MintyPHP\Service\Audit\UserLifecycleAuditService;
|
||||
use MintyPHP\Service\Audit\UserLifecycleAuditInterface;
|
||||
|
||||
class UserLifecycleRestoreService
|
||||
{
|
||||
public function __construct(
|
||||
private readonly UserReadRepositoryInterface $userReadRepository,
|
||||
private readonly UserWriteRepositoryInterface $userWriteRepository,
|
||||
private readonly UserLifecycleAuditService $userLifecycleAuditService,
|
||||
private readonly UserLifecycleAuditInterface $userLifecycleAuditService,
|
||||
private readonly DatabaseSessionRepository $databaseSessionRepository,
|
||||
private readonly UserSettingsGateway $userSettingsGateway
|
||||
) {
|
||||
|
||||
@@ -7,7 +7,7 @@ use MintyPHP\Repository\Support\RepoQuery;
|
||||
use MintyPHP\Repository\User\UserReadRepositoryInterface;
|
||||
use MintyPHP\Repository\User\UserWriteRepositoryInterface;
|
||||
use MintyPHP\Service\Access\PermissionService;
|
||||
use MintyPHP\Service\Audit\UserLifecycleAuditService;
|
||||
use MintyPHP\Service\Audit\UserLifecycleAuditInterface;
|
||||
|
||||
class UserLifecycleService
|
||||
{
|
||||
@@ -18,7 +18,7 @@ class UserLifecycleService
|
||||
private readonly UserReadRepositoryInterface $userReadRepository,
|
||||
private readonly UserWriteRepositoryInterface $userWriteRepository,
|
||||
private readonly UserSettingsGateway $settingsGateway,
|
||||
private readonly UserLifecycleAuditService $userLifecycleAuditService,
|
||||
private readonly UserLifecycleAuditInterface $userLifecycleAuditService,
|
||||
private readonly DatabaseSessionRepository $databaseSessionRepository
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@ use MintyPHP\Repository\User\UserListQueryRepositoryInterface;
|
||||
use MintyPHP\Repository\User\UserReadRepositoryInterface;
|
||||
use MintyPHP\Repository\User\UserWriteRepositoryInterface;
|
||||
use MintyPHP\Service\Access\AssignableRoleService;
|
||||
use MintyPHP\Service\Audit\AuditServicesFactory;
|
||||
use MintyPHP\Service\Audit\UserLifecycleAuditService;
|
||||
use MintyPHP\Service\Audit\AuditRecorderInterface;
|
||||
use MintyPHP\Service\Audit\UserLifecycleAuditInterface;
|
||||
|
||||
class UserServicesFactory
|
||||
{
|
||||
@@ -24,13 +24,13 @@ class UserServicesFactory
|
||||
private ?UserAccountService $userAccountService = null;
|
||||
private ?UserLifecycleService $userLifecycleService = null;
|
||||
private ?UserLifecycleRestoreService $userLifecycleRestoreService = null;
|
||||
private ?UserLifecycleAuditService $userLifecycleAuditService = null;
|
||||
|
||||
/**
|
||||
* @param \Closure(): AssignableRoleService $assignableRoleServiceFactory Lazy resolver to break circular dependency
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly AuditServicesFactory $auditServicesFactory,
|
||||
private readonly AuditRecorderInterface $auditRecorder,
|
||||
private readonly UserLifecycleAuditInterface $userLifecycleAudit,
|
||||
private readonly UserRepositoryFactory $userRepositoryFactory,
|
||||
private readonly UserGatewayFactory $userGatewayFactory,
|
||||
private readonly DatabaseSessionRepository $databaseSessionRepository,
|
||||
@@ -50,7 +50,7 @@ class UserServicesFactory
|
||||
$this->createUserSettingsGateway(),
|
||||
$this->userGatewayFactory->getTenantScopeService(),
|
||||
$this->createUserDirectoryGateway(),
|
||||
$this->auditServicesFactory->createSystemAuditService(),
|
||||
$this->auditRecorder,
|
||||
$this->databaseSessionRepository,
|
||||
$this->eventDispatcher
|
||||
);
|
||||
@@ -145,7 +145,7 @@ class UserServicesFactory
|
||||
$this->createUserReadRepository(),
|
||||
$this->createUserWriteRepository(),
|
||||
$this->createUserSettingsGateway(),
|
||||
$this->createUserLifecycleAuditService(),
|
||||
$this->userLifecycleAudit,
|
||||
$this->databaseSessionRepository
|
||||
);
|
||||
}
|
||||
@@ -155,14 +155,9 @@ class UserServicesFactory
|
||||
return $this->userLifecycleRestoreService ??= new UserLifecycleRestoreService(
|
||||
$this->createUserReadRepository(),
|
||||
$this->createUserWriteRepository(),
|
||||
$this->createUserLifecycleAuditService(),
|
||||
$this->userLifecycleAudit,
|
||||
$this->databaseSessionRepository,
|
||||
$this->createUserSettingsGateway()
|
||||
);
|
||||
}
|
||||
|
||||
private function createUserLifecycleAuditService(): UserLifecycleAuditService
|
||||
{
|
||||
return $this->userLifecycleAuditService ??= $this->auditServicesFactory->createUserLifecycleAuditService();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ class SearchSqlResourceProvider
|
||||
[
|
||||
'key' => 'api-audit',
|
||||
'label' => t('API audit'),
|
||||
'permission' => PermissionService::API_AUDIT_VIEW,
|
||||
'permission' => 'api_audit.view',
|
||||
'countSql' => "select count(*) from api_audit_log where (request_id like ? escape '\\\\' or path like ? escape '\\\\' or error_code like ? escape '\\\\' or ip like ? escape '\\\\')",
|
||||
'countParams' => [$like, $like, $like, $like],
|
||||
'previewSql' => "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 '\\\\') order by created_at desc limit ?",
|
||||
@@ -124,7 +124,7 @@ class SearchSqlResourceProvider
|
||||
[
|
||||
'key' => 'system-audit',
|
||||
'label' => t('System audit'),
|
||||
'permission' => PermissionService::SYSTEM_AUDIT_VIEW,
|
||||
'permission' => 'system_audit.view',
|
||||
'countSql' => "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 '\\\\')",
|
||||
'countParams' => [$like, $like, $like, $like],
|
||||
'previewSql' => "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 ?",
|
||||
|
||||
@@ -41,16 +41,24 @@ function setAppContainer(\MintyPHP\App\AppContainer $container): void
|
||||
);
|
||||
|
||||
\MintyPHP\Http\ApiBootstrap::configure(
|
||||
static fn (): \MintyPHP\Service\Audit\ApiAuditService => $container->get(\MintyPHP\Service\Audit\ApiAuditService::class),
|
||||
$container->has(\MintyPHP\Module\Audit\Service\ApiAuditService::class)
|
||||
? static fn () => $container->get(\MintyPHP\Module\Audit\Service\ApiAuditService::class)
|
||||
: null,
|
||||
static fn (): \MintyPHP\Service\Settings\SettingsApiPolicyGateway => $container->get(\MintyPHP\Service\Settings\SettingsApiPolicyGateway::class),
|
||||
static fn (): \MintyPHP\Service\Security\RateLimiterService => $container->get(\MintyPHP\Service\Security\RateLimiterService::class),
|
||||
static fn (): \MintyPHP\Http\ApiSystemAuditReporter => $container->get(\MintyPHP\Http\ApiSystemAuditReporter::class)
|
||||
$container->has(\MintyPHP\Module\Audit\Http\ApiSystemAuditReporter::class)
|
||||
? static fn () => $container->get(\MintyPHP\Module\Audit\Http\ApiSystemAuditReporter::class)
|
||||
: null
|
||||
);
|
||||
|
||||
\MintyPHP\Http\ApiResponse::configure(
|
||||
static fn (): \MintyPHP\Service\Audit\ApiAuditService => $container->get(\MintyPHP\Service\Audit\ApiAuditService::class),
|
||||
$container->has(\MintyPHP\Module\Audit\Service\ApiAuditService::class)
|
||||
? static fn () => $container->get(\MintyPHP\Module\Audit\Service\ApiAuditService::class)
|
||||
: null,
|
||||
static fn (): \MintyPHP\Service\Access\AuthorizationService => $container->get(\MintyPHP\Service\Access\AuthorizationService::class),
|
||||
static fn (): \MintyPHP\Http\ApiSystemAuditReporter => $container->get(\MintyPHP\Http\ApiSystemAuditReporter::class)
|
||||
$container->has(\MintyPHP\Module\Audit\Http\ApiSystemAuditReporter::class)
|
||||
? static fn () => $container->get(\MintyPHP\Module\Audit\Http\ApiSystemAuditReporter::class)
|
||||
: null
|
||||
);
|
||||
|
||||
\MintyPHP\Support\Guard::configure(
|
||||
|
||||
97
modules/audit/db/updates/2026-03-25-audit-tables.sql
Normal file
97
modules/audit/db/updates/2026-03-25-audit-tables.sql
Normal file
@@ -0,0 +1,97 @@
|
||||
-- Audit module: idempotent migration for existing audit tables.
|
||||
-- These tables may already exist in deployments that had audit in core.
|
||||
-- All statements use IF NOT EXISTS to be safe for both fresh installs and upgrades.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `system_audit_log` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`event_uuid` CHAR(36) NOT NULL,
|
||||
`request_id` CHAR(36) NULL,
|
||||
`channel` VARCHAR(20) NOT NULL DEFAULT 'web',
|
||||
`event_type` VARCHAR(64) NOT NULL,
|
||||
`outcome` VARCHAR(20) NOT NULL DEFAULT 'success',
|
||||
`error_code` VARCHAR(100) NULL,
|
||||
`actor_user_id` INT UNSIGNED NULL,
|
||||
`actor_tenant_id` INT UNSIGNED NULL,
|
||||
`target_type` VARCHAR(64) NULL,
|
||||
`target_id` INT UNSIGNED NULL,
|
||||
`target_uuid` CHAR(36) NULL,
|
||||
`method` VARCHAR(8) NULL,
|
||||
`path` VARCHAR(255) NULL,
|
||||
`ip_hash` CHAR(64) NULL,
|
||||
`user_agent_hash` CHAR(64) NULL,
|
||||
`metadata_json` JSON NULL,
|
||||
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_sal_event_type_created` (`event_type`, `created` DESC),
|
||||
KEY `idx_sal_actor_user_id` (`actor_user_id`),
|
||||
KEY `idx_sal_created` (`created` DESC)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `api_audit_log` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`request_id` CHAR(36) NOT NULL,
|
||||
`method` VARCHAR(8) NOT NULL,
|
||||
`path` VARCHAR(255) NOT NULL,
|
||||
`query_json` JSON NULL,
|
||||
`status_code` SMALLINT UNSIGNED NOT NULL,
|
||||
`duration_ms` INT UNSIGNED NULL,
|
||||
`error_code` VARCHAR(100) NULL,
|
||||
`user_id` INT UNSIGNED NULL,
|
||||
`tenant_id` INT UNSIGNED NULL,
|
||||
`api_token_id` INT UNSIGNED NULL,
|
||||
`token_tenant_id` INT UNSIGNED NULL,
|
||||
`ip` VARCHAR(45) NULL,
|
||||
`user_agent` VARCHAR(255) NULL,
|
||||
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_aal_created` (`created` DESC),
|
||||
KEY `idx_aal_path_created` (`path`(64), `created` DESC),
|
||||
KEY `idx_aal_user_id` (`user_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `import_audit_runs` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`run_uuid` CHAR(36) NOT NULL,
|
||||
`profile_key` VARCHAR(64) NOT NULL,
|
||||
`status` VARCHAR(20) NOT NULL DEFAULT 'running',
|
||||
`source_filename` VARCHAR(255) NULL,
|
||||
`mapped_targets_csv` VARCHAR(500) NULL,
|
||||
`rows_total` INT UNSIGNED NOT NULL DEFAULT 0,
|
||||
`created_count` INT UNSIGNED NOT NULL DEFAULT 0,
|
||||
`skipped_count` INT UNSIGNED NOT NULL DEFAULT 0,
|
||||
`failed_count` INT UNSIGNED NOT NULL DEFAULT 0,
|
||||
`error_codes_json` JSON NULL,
|
||||
`duration_ms` INT UNSIGNED NULL,
|
||||
`user_id` INT UNSIGNED NULL,
|
||||
`current_tenant_id` INT UNSIGNED NULL,
|
||||
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`finished_at` DATETIME NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_iar_created` (`created` DESC),
|
||||
KEY `idx_iar_profile_key` (`profile_key`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `user_lifecycle_audit_log` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`run_uuid` CHAR(36) NOT NULL,
|
||||
`action` VARCHAR(20) NOT NULL,
|
||||
`trigger_type` VARCHAR(20) NOT NULL,
|
||||
`status` VARCHAR(20) NOT NULL DEFAULT 'success',
|
||||
`reason_code` VARCHAR(100) NULL,
|
||||
`policy_deactivate_days` INT UNSIGNED NOT NULL DEFAULT 0,
|
||||
`policy_delete_days` INT UNSIGNED NOT NULL DEFAULT 0,
|
||||
`actor_user_id` INT UNSIGNED NULL,
|
||||
`target_user_id` INT UNSIGNED NULL,
|
||||
`target_user_uuid` CHAR(36) NULL,
|
||||
`target_user_email` VARCHAR(255) NULL,
|
||||
`snapshot_enc` MEDIUMTEXT NULL,
|
||||
`snapshot_version` TINYINT UNSIGNED NOT NULL DEFAULT 1,
|
||||
`restored_at` DATETIME NULL,
|
||||
`restored_by_user_id` INT UNSIGNED NULL,
|
||||
`restored_user_id` INT UNSIGNED NULL,
|
||||
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_ulal_action_created` (`action`, `created` DESC),
|
||||
KEY `idx_ulal_target_user_id` (`target_user_id`),
|
||||
KEY `idx_ulal_created` (`created` DESC)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
71
modules/audit/i18n/default_de.json
Normal file
71
modules/audit/i18n/default_de.json
Normal file
@@ -0,0 +1,71 @@
|
||||
{
|
||||
"Telemetry helps detect recurring UI issues and failed requests in production.": "Telemetry hilft, wiederkehrende UI-Probleme und fehlgeschlagene Requests in Produktion zu erkennen.",
|
||||
"perm.user_lifecycle_audit.view": "Benutzer-Lifecycle-Protokolle anzeigen",
|
||||
"perm.users.lifecycle_restore": "Benutzer aus Lifecycle-Protokoll wiederherstellen",
|
||||
"User lifecycle": "Benutzer-Lebenszyklus",
|
||||
"Audit": "Audit",
|
||||
"API audit": "API-Protokolle",
|
||||
"API audit logs": "API-Protokolle",
|
||||
"Purge API audit logs": "API-Protokolle bereinigen",
|
||||
"Purge entries older than 90 days?": "Einträge älter als 90 Tage bereinigen?",
|
||||
"%d API audit entries purged": "%d API-Protokoll-Einträge bereinigt",
|
||||
"API audit entry not found": "API-Protokoll-Eintrag nicht gefunden",
|
||||
"View API audit entry": "API-Protokoll-Eintrag anzeigen",
|
||||
"Import audit logs": "Import-Protokolle",
|
||||
"Can view import audit logs": "Kann Import-Protokolle anzeigen",
|
||||
"Purge import logs": "Import-Protokolle bereinigen",
|
||||
"%d import audit entries purged": "%d Import-Protokoll-Einträge bereinigt",
|
||||
"Import audit entry not found": "Import-Protokoll-Eintrag nicht gefunden",
|
||||
"View import audit entry": "Import-Protokoll-Eintrag anzeigen",
|
||||
"User lifecycle policy": "Benutzer-Lifecycle-Regel",
|
||||
"User lifecycle defines when inactive users are deactivated or deleted.": "Hier wird festgelegt, wann inaktive Benutzer deaktiviert oder gelöscht werden.",
|
||||
"System audit controls whether security events are logged and how long they are kept.": "Hier wird gesteuert, ob Sicherheitsereignisse protokolliert werden und wie lange sie aufbewahrt werden.",
|
||||
"Privileged admins are excluded from automatic lifecycle actions": "Privilegierte Admins sind von automatischen Lifecycle-Aktionen ausgenommen",
|
||||
"Run user lifecycle now?": "Benutzer-Lifecycle jetzt ausführen?",
|
||||
"Run user lifecycle now": "Benutzer-Lifecycle jetzt ausführen",
|
||||
"User lifecycle already running": "Benutzer-Lifecycle läuft bereits",
|
||||
"User lifecycle failed": "Benutzer-Lifecycle fehlgeschlagen",
|
||||
"User lifecycle completed: %d deactivated, %d deleted": "Benutzer-Lifecycle abgeschlossen: %d deaktiviert, %d gelöscht",
|
||||
"Purge scheduler logs": "Scheduler-Protokolle bereinigen",
|
||||
"Purge run logs older than 90 days?": "Laufprotokolle älter als 90 Tage bereinigen?",
|
||||
"%d scheduler run logs purged": "%d Scheduler-Laufprotokolle bereinigt",
|
||||
"User lifecycle logs": "Benutzer-Lifecycle-Protokolle",
|
||||
"View user lifecycle audit entry": "Benutzer-Lifecycle-Eintrag anzeigen",
|
||||
"Lifecycle audit entry not found": "Benutzer-Lifecycle-Eintrag nicht gefunden",
|
||||
"Purge user lifecycle logs": "Benutzer-Lifecycle-Protokolle bereinigen",
|
||||
"Purge entries older than 365 days?": "Einträge älter als 365 Tage bereinigen?",
|
||||
"%d user lifecycle audit entries purged": "%d Benutzer-Lifecycle-Protokoll-Einträge bereinigt",
|
||||
"Lifecycle event": "Lifecycle-Ereignis",
|
||||
"Restore this user from lifecycle snapshot?": "Diesen Benutzer aus dem Lifecycle-Snapshot wiederherstellen?",
|
||||
"Lifecycle event was already restored": "Lifecycle-Ereignis wurde bereits wiederhergestellt",
|
||||
"Lifecycle snapshot unavailable": "Lifecycle-Snapshot nicht verfügbar",
|
||||
"System audit": "System-Protokolle",
|
||||
"System audit logs": "System-Protokolle",
|
||||
"View system audit entry": "System-Protokoll-Eintrag anzeigen",
|
||||
"System audit entry not found": "System-Protokoll-Eintrag nicht gefunden",
|
||||
"Enable system audit log": "System-Protokolle aktivieren",
|
||||
"Purge system audit logs": "System-Protokolle bereinigen",
|
||||
"Purge expired system audit entries?": "Abgelaufene System-Protokoll-Einträge bereinigen?",
|
||||
"%d system audit entries purged": "%d System-Protokoll-Einträge bereinigt",
|
||||
"Frontend telemetry": "Frontend-Telemetrie",
|
||||
"Enable frontend telemetry": "Frontend-Telemetrie aktivieren",
|
||||
"Allowed telemetry events": "Erlaubte Telemetrie-Ereignisse",
|
||||
"Telemetry": "Telemetrie",
|
||||
"Telemetry helps us find UI problems faster.": "Telemetrie hilft uns, UI-Probleme schneller zu finden.",
|
||||
"The percentage of users who will have telemetry enabled.": "Der Anteil der Nutzer, für die Telemetrie aktiv ist.",
|
||||
"Advanced telemetry options": "Erweiterte Telemetrie-Optionen",
|
||||
"setting.system_audit_enabled": "System-Protokolle aktivieren/deaktivieren",
|
||||
"setting.system_audit_retention_days": "Aufbewahrungsdauer in Tagen für System-Protokoll-Einträge",
|
||||
"setting.frontend_telemetry_enabled": "Sichere Frontend-UX- und Fehler-Telemetrie aktivieren/deaktivieren",
|
||||
"setting.frontend_telemetry_sample_rate": "Sampling-Rate zwischen 0 und 1 für akzeptierte Frontend-Telemetrie-Ereignisse",
|
||||
"setting.frontend_telemetry_allowed_events": "Erlaubte Frontend-Telemetrie-Ereignisgruppen (warn_once, ajax_error)",
|
||||
"System audit events (24h)": "System-Protokoll-Ereignisse (24h)",
|
||||
"System audit risks (24h)": "System-Protokoll-Risiken (24h)",
|
||||
"Top system audit event types (24h)": "Top-System-Protokoll-Ereignistypen (24h)",
|
||||
"Recent system audit risks (24h)": "Aktuelle System-Protokoll-Risiken (24h)",
|
||||
"User lifecycle audit": "Benutzer-Lifecycle-Protokoll",
|
||||
"Lifecycle runs (7d)": "Lifecycle-Läufe (7d)",
|
||||
"Lifecycle risks (7d)": "Lifecycle-Risiken (7d)",
|
||||
"Top lifecycle reason codes (7d)": "Top-Lifecycle-Fehlercodes (7d)",
|
||||
"Recent lifecycle risks (7d)": "Aktuelle Lifecycle-Risiken (7d)"
|
||||
}
|
||||
71
modules/audit/i18n/default_en.json
Normal file
71
modules/audit/i18n/default_en.json
Normal file
@@ -0,0 +1,71 @@
|
||||
{
|
||||
"Telemetry helps detect recurring UI issues and failed requests in production.": "Telemetry helps detect recurring UI issues and failed requests in production.",
|
||||
"perm.user_lifecycle_audit.view": "View user lifecycle logs",
|
||||
"perm.users.lifecycle_restore": "Restore users from lifecycle audit",
|
||||
"User lifecycle": "User lifecycle",
|
||||
"Audit": "Audit",
|
||||
"API audit": "API logs",
|
||||
"API audit logs": "API logs",
|
||||
"Purge API audit logs": "Purge API logs",
|
||||
"Purge entries older than 90 days?": "Purge entries older than 90 days?",
|
||||
"%d API audit entries purged": "%d API log entries purged",
|
||||
"API audit entry not found": "API log entry not found",
|
||||
"View API audit entry": "View API log entry",
|
||||
"Import audit logs": "Import audit logs",
|
||||
"Can view import audit logs": "Can view import audit logs",
|
||||
"Purge import logs": "Purge import logs",
|
||||
"%d import audit entries purged": "%d import audit entries purged",
|
||||
"Import audit entry not found": "Import audit entry not found",
|
||||
"View import audit entry": "View import audit entry",
|
||||
"User lifecycle policy": "User lifecycle policy",
|
||||
"User lifecycle defines when inactive users are deactivated or deleted.": "User lifecycle defines when inactive users are deactivated or deleted.",
|
||||
"System audit controls whether security events are logged and how long they are kept.": "System audit controls whether security events are logged and how long they are kept.",
|
||||
"Privileged admins are excluded from automatic lifecycle actions": "Privileged admins are excluded from automatic lifecycle actions",
|
||||
"Run user lifecycle now?": "Run user lifecycle now?",
|
||||
"Run user lifecycle now": "Run user lifecycle now",
|
||||
"User lifecycle already running": "User lifecycle already running",
|
||||
"User lifecycle failed": "User lifecycle failed",
|
||||
"User lifecycle completed: %d deactivated, %d deleted": "User lifecycle completed: %d deactivated, %d deleted",
|
||||
"Purge scheduler logs": "Purge scheduler logs",
|
||||
"Purge run logs older than 90 days?": "Purge run logs older than 90 days?",
|
||||
"%d scheduler run logs purged": "%d scheduler run logs purged",
|
||||
"User lifecycle logs": "User lifecycle logs",
|
||||
"View user lifecycle audit entry": "View user lifecycle audit entry",
|
||||
"Lifecycle audit entry not found": "Lifecycle audit entry not found",
|
||||
"Purge user lifecycle logs": "Purge user lifecycle logs",
|
||||
"Purge entries older than 365 days?": "Purge entries older than 365 days?",
|
||||
"%d user lifecycle audit entries purged": "%d user lifecycle audit entries purged",
|
||||
"Lifecycle event": "Lifecycle event",
|
||||
"Restore this user from lifecycle snapshot?": "Restore this user from lifecycle snapshot?",
|
||||
"Lifecycle event was already restored": "Lifecycle event was already restored",
|
||||
"Lifecycle snapshot unavailable": "Lifecycle snapshot unavailable",
|
||||
"System audit": "System audit",
|
||||
"System audit logs": "System audit logs",
|
||||
"View system audit entry": "View system audit entry",
|
||||
"System audit entry not found": "System audit entry not found",
|
||||
"Enable system audit log": "Enable system audit log",
|
||||
"Purge system audit logs": "Purge system audit logs",
|
||||
"Purge expired system audit entries?": "Purge expired system audit entries?",
|
||||
"%d system audit entries purged": "%d system audit entries purged",
|
||||
"Frontend telemetry": "Frontend telemetry",
|
||||
"Enable frontend telemetry": "Enable frontend telemetry",
|
||||
"Allowed telemetry events": "Allowed telemetry events",
|
||||
"Telemetry": "Telemetry",
|
||||
"Telemetry helps us find UI problems faster.": "Telemetry helps us find UI problems faster.",
|
||||
"The percentage of users who will have telemetry enabled.": "The percentage of users who will have telemetry enabled.",
|
||||
"Advanced telemetry options": "Advanced telemetry options",
|
||||
"setting.system_audit_enabled": "Enable/disable system audit logging",
|
||||
"setting.system_audit_retention_days": "Retention in days for system audit entries",
|
||||
"setting.frontend_telemetry_enabled": "Enable/disable secure frontend UX and error telemetry",
|
||||
"setting.frontend_telemetry_sample_rate": "Sampling rate between 0 and 1 for accepted frontend telemetry events",
|
||||
"setting.frontend_telemetry_allowed_events": "Allowed frontend telemetry event groups (warn_once, ajax_error)",
|
||||
"System audit events (24h)": "System audit events (24h)",
|
||||
"System audit risks (24h)": "System audit risks (24h)",
|
||||
"Top system audit event types (24h)": "Top system audit event types (24h)",
|
||||
"Recent system audit risks (24h)": "Recent system audit risks (24h)",
|
||||
"User lifecycle audit": "User lifecycle audit",
|
||||
"Lifecycle runs (7d)": "Lifecycle runs (7d)",
|
||||
"Lifecycle risks (7d)": "Lifecycle risks (7d)",
|
||||
"Top lifecycle reason codes (7d)": "Top lifecycle reason codes (7d)",
|
||||
"Recent lifecycle risks (7d)": "Recent lifecycle risks (7d)"
|
||||
}
|
||||
55
modules/audit/lib/Module/Audit/AuditAuthorizationPolicy.php
Normal file
55
modules/audit/lib/Module/Audit/AuditAuthorizationPolicy.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Module\Audit;
|
||||
|
||||
use MintyPHP\Service\Access\AuthorizationDecision;
|
||||
use MintyPHP\Service\Access\AuthorizationPolicyInterface;
|
||||
use MintyPHP\Service\Access\PermissionService;
|
||||
|
||||
final class AuditAuthorizationPolicy implements AuthorizationPolicyInterface
|
||||
{
|
||||
public const ABILITY_API_AUDIT_VIEW = 'audit.api.view';
|
||||
public const ABILITY_SYSTEM_AUDIT_VIEW = 'audit.system.view';
|
||||
public const ABILITY_SYSTEM_AUDIT_PURGE = 'audit.system.purge';
|
||||
public const ABILITY_IMPORTS_AUDIT_VIEW = 'audit.imports.view';
|
||||
public const ABILITY_USER_LIFECYCLE_VIEW = 'audit.user_lifecycle.view';
|
||||
public const ABILITY_USER_LIFECYCLE_RESTORE = 'audit.user_lifecycle.restore';
|
||||
|
||||
private const ABILITY_PERMISSION_MAP = [
|
||||
self::ABILITY_API_AUDIT_VIEW => 'api_audit.view',
|
||||
self::ABILITY_SYSTEM_AUDIT_VIEW => 'system_audit.view',
|
||||
self::ABILITY_SYSTEM_AUDIT_PURGE => 'system_audit.purge',
|
||||
self::ABILITY_IMPORTS_AUDIT_VIEW => 'imports.audit.view',
|
||||
self::ABILITY_USER_LIFECYCLE_VIEW => 'user_lifecycle_audit.view',
|
||||
self::ABILITY_USER_LIFECYCLE_RESTORE => 'users.lifecycle_restore',
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
private readonly PermissionService $permissionService
|
||||
) {
|
||||
}
|
||||
|
||||
public function supports(string $ability): bool
|
||||
{
|
||||
return isset(self::ABILITY_PERMISSION_MAP[$ability]);
|
||||
}
|
||||
|
||||
public function authorize(string $ability, array $context = []): AuthorizationDecision
|
||||
{
|
||||
$actorUserId = (int) ($context['actor_user_id'] ?? 0);
|
||||
if ($actorUserId <= 0) {
|
||||
return AuthorizationDecision::deny(403, 'forbidden');
|
||||
}
|
||||
|
||||
$permissionKey = self::ABILITY_PERMISSION_MAP[$ability] ?? null;
|
||||
if ($permissionKey === null) {
|
||||
return AuthorizationDecision::deny(403, 'forbidden');
|
||||
}
|
||||
|
||||
if (!$this->permissionService->userHas($actorUserId, $permissionKey)) {
|
||||
return AuthorizationDecision::deny(403, 'forbidden');
|
||||
}
|
||||
|
||||
return AuthorizationDecision::allow();
|
||||
}
|
||||
}
|
||||
100
modules/audit/lib/Module/Audit/AuditContainerRegistrar.php
Normal file
100
modules/audit/lib/Module/Audit/AuditContainerRegistrar.php
Normal file
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Module\Audit;
|
||||
|
||||
use MintyPHP\App\AppContainer;
|
||||
use MintyPHP\App\Container\ContainerRegistrar;
|
||||
use MintyPHP\Http\RequestRuntimeInterface;
|
||||
use MintyPHP\Http\SessionStoreInterface;
|
||||
use MintyPHP\Module\Audit\Handler\ApiAuditPurgeJobHandler;
|
||||
use MintyPHP\Module\Audit\Handler\ImportAuditPurgeJobHandler;
|
||||
use MintyPHP\Module\Audit\Handler\SystemAuditPurgeJobHandler;
|
||||
use MintyPHP\Module\Audit\Handler\UserLifecycleAuditPurgeJobHandler;
|
||||
use MintyPHP\Module\Audit\Http\ApiSystemAuditReporter;
|
||||
use MintyPHP\Module\Audit\Providers\AuditLayoutProvider;
|
||||
use MintyPHP\Module\Audit\Service\AuditRepositoryFactory;
|
||||
use MintyPHP\Module\Audit\Service\AuditServicesFactory;
|
||||
use MintyPHP\Module\Audit\Service\ApiAuditService;
|
||||
use MintyPHP\Module\Audit\Service\AuditMetadataEnricher;
|
||||
use MintyPHP\Module\Audit\Service\FrontendTelemetryIngestService;
|
||||
use MintyPHP\Module\Audit\Service\ImportAuditService;
|
||||
use MintyPHP\Module\Audit\Service\SystemAuditService;
|
||||
use MintyPHP\Module\Audit\Service\UserLifecycleAuditService;
|
||||
use MintyPHP\Repository\User\UserReadRepository;
|
||||
use MintyPHP\Service\Access\PermissionService;
|
||||
use MintyPHP\Service\Audit\AuditRecorderInterface;
|
||||
use MintyPHP\Service\Audit\ImportAuditInterface;
|
||||
use MintyPHP\Service\Audit\UserLifecycleAuditInterface;
|
||||
use MintyPHP\Service\Security\RateLimiterService;
|
||||
use MintyPHP\Service\Settings\SettingServicesFactory;
|
||||
use MintyPHP\Service\Settings\SettingsFrontendTelemetryGateway;
|
||||
|
||||
final class AuditContainerRegistrar implements ContainerRegistrar
|
||||
{
|
||||
public function register(AppContainer $container): void
|
||||
{
|
||||
// Repository factory
|
||||
$container->set(AuditRepositoryFactory::class, static fn (): AuditRepositoryFactory => new AuditRepositoryFactory());
|
||||
|
||||
// Services factory
|
||||
$container->set(AuditServicesFactory::class, static fn (AppContainer $c): AuditServicesFactory => new AuditServicesFactory(
|
||||
$c->get(AuditRepositoryFactory::class),
|
||||
$c->get(SettingServicesFactory::class)->createSettingsSystemAuditGateway(),
|
||||
$c->get(RequestRuntimeInterface::class),
|
||||
$c->get(SessionStoreInterface::class)
|
||||
));
|
||||
|
||||
// Concrete audit services
|
||||
$container->set(SystemAuditService::class, static fn (AppContainer $c): SystemAuditService => $c->get(AuditServicesFactory::class)->createSystemAuditService());
|
||||
$container->set(ApiAuditService::class, static fn (AppContainer $c): ApiAuditService => $c->get(AuditServicesFactory::class)->createApiAuditService());
|
||||
$container->set(UserLifecycleAuditService::class, static fn (AppContainer $c): UserLifecycleAuditService => $c->get(AuditServicesFactory::class)->createUserLifecycleAuditService());
|
||||
$container->set(ImportAuditService::class, static fn (AppContainer $c): ImportAuditService => new ImportAuditService(
|
||||
$c->get(AuditRepositoryFactory::class)->createImportAuditRunRepository()
|
||||
));
|
||||
|
||||
// Core interface bindings — override null implementations
|
||||
$container->set(AuditRecorderInterface::class, static fn (AppContainer $c): AuditRecorderInterface => $c->get(SystemAuditService::class));
|
||||
$container->set(UserLifecycleAuditInterface::class, static fn (AppContainer $c): UserLifecycleAuditInterface => $c->get(UserLifecycleAuditService::class));
|
||||
$container->set(ImportAuditInterface::class, static fn (AppContainer $c): ImportAuditInterface => $c->get(ImportAuditService::class));
|
||||
|
||||
// Frontend telemetry
|
||||
$container->set(FrontendTelemetryIngestService::class, static fn (AppContainer $c): FrontendTelemetryIngestService => new FrontendTelemetryIngestService(
|
||||
$c->get(SystemAuditService::class),
|
||||
$c->get(SettingsFrontendTelemetryGateway::class),
|
||||
$c->get(RateLimiterService::class),
|
||||
$c->get(SessionStoreInterface::class)
|
||||
));
|
||||
|
||||
// API system audit reporter
|
||||
$container->set(ApiSystemAuditReporter::class, static fn (AppContainer $c): ApiSystemAuditReporter => new ApiSystemAuditReporter(
|
||||
$c->get(SystemAuditService::class)
|
||||
));
|
||||
|
||||
// Metadata enricher
|
||||
$container->set(AuditMetadataEnricher::class, static fn (AppContainer $c): AuditMetadataEnricher => new AuditMetadataEnricher(
|
||||
$c->get(UserReadRepository::class)
|
||||
));
|
||||
|
||||
// Authorization policy
|
||||
$container->set(AuditAuthorizationPolicy::class, static fn (AppContainer $c): AuditAuthorizationPolicy => new AuditAuthorizationPolicy(
|
||||
$c->get(PermissionService::class)
|
||||
));
|
||||
|
||||
// Scheduler job handlers
|
||||
$container->set(SystemAuditPurgeJobHandler::class, static fn (AppContainer $c): SystemAuditPurgeJobHandler => new SystemAuditPurgeJobHandler(
|
||||
$c->get(SystemAuditService::class)
|
||||
));
|
||||
$container->set(ApiAuditPurgeJobHandler::class, static fn (AppContainer $c): ApiAuditPurgeJobHandler => new ApiAuditPurgeJobHandler(
|
||||
$c->get(ApiAuditService::class)
|
||||
));
|
||||
$container->set(ImportAuditPurgeJobHandler::class, static fn (AppContainer $c): ImportAuditPurgeJobHandler => new ImportAuditPurgeJobHandler(
|
||||
$c->get(ImportAuditService::class)
|
||||
));
|
||||
$container->set(UserLifecycleAuditPurgeJobHandler::class, static fn (AppContainer $c): UserLifecycleAuditPurgeJobHandler => new UserLifecycleAuditPurgeJobHandler(
|
||||
$c->get(UserLifecycleAuditService::class)
|
||||
));
|
||||
|
||||
// Layout provider
|
||||
$container->set(AuditLayoutProvider::class, static fn (AppContainer $c): AuditLayoutProvider => new AuditLayoutProvider());
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Domain\Taxonomy;
|
||||
namespace MintyPHP\Module\Audit\Domain;
|
||||
|
||||
use MintyPHP\Domain\Taxonomy\SupportsStringTaxonomy;
|
||||
|
||||
enum ImportAuditStatus: string
|
||||
{
|
||||
@@ -1,6 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Domain\Taxonomy;
|
||||
namespace MintyPHP\Module\Audit\Domain;
|
||||
|
||||
use MintyPHP\Domain\Taxonomy\SupportsStringTaxonomy;
|
||||
|
||||
enum SystemAuditChannel: string
|
||||
{
|
||||
@@ -1,6 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Domain\Taxonomy;
|
||||
namespace MintyPHP\Module\Audit\Domain;
|
||||
|
||||
use MintyPHP\Domain\Taxonomy\SupportsStringTaxonomy;
|
||||
|
||||
enum SystemAuditOutcome: string
|
||||
{
|
||||
@@ -1,6 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Domain\Taxonomy;
|
||||
namespace MintyPHP\Module\Audit\Domain;
|
||||
|
||||
use MintyPHP\Domain\Taxonomy\SupportsStringTaxonomy;
|
||||
|
||||
enum UserLifecycleAction: string
|
||||
{
|
||||
@@ -1,6 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Domain\Taxonomy;
|
||||
namespace MintyPHP\Module\Audit\Domain;
|
||||
|
||||
use MintyPHP\Domain\Taxonomy\SupportsStringTaxonomy;
|
||||
|
||||
enum UserLifecycleStatus: string
|
||||
{
|
||||
@@ -1,6 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Domain\Taxonomy;
|
||||
namespace MintyPHP\Module\Audit\Domain;
|
||||
|
||||
use MintyPHP\Domain\Taxonomy\SupportsStringTaxonomy;
|
||||
|
||||
enum UserLifecycleTriggerType: string
|
||||
{
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Module\Audit\Handler;
|
||||
|
||||
use MintyPHP\Module\Audit\Service\ApiAuditService;
|
||||
use MintyPHP\Service\Scheduler\Handler\ScheduledJobHandlerInterface;
|
||||
|
||||
class ApiAuditPurgeJobHandler implements ScheduledJobHandlerInterface
|
||||
{
|
||||
public function __construct(private readonly ApiAuditService $apiAuditService)
|
||||
{
|
||||
}
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'label' => 'API audit log cleanup',
|
||||
'description' => 'Purges API audit log entries older than 90 days',
|
||||
'default_enabled' => 1,
|
||||
'default_timezone' => 'UTC',
|
||||
'default_schedule_type' => 'daily',
|
||||
'default_schedule_interval' => 1,
|
||||
'default_schedule_time' => '03:15',
|
||||
'default_schedule_weekdays_csv' => null,
|
||||
'default_catchup_once' => 1,
|
||||
'allowed_schedule_types' => ['daily', 'weekly'],
|
||||
];
|
||||
}
|
||||
|
||||
public function execute(?int $actorUserId): array
|
||||
{
|
||||
$deleted = $this->apiAuditService->purgeExpired();
|
||||
|
||||
return [
|
||||
'status' => 'success',
|
||||
'error_code' => null,
|
||||
'error_message' => null,
|
||||
'result' => ['deleted_count' => $deleted],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Module\Audit\Handler;
|
||||
|
||||
use MintyPHP\Module\Audit\Service\ImportAuditService;
|
||||
use MintyPHP\Service\Scheduler\Handler\ScheduledJobHandlerInterface;
|
||||
|
||||
class ImportAuditPurgeJobHandler implements ScheduledJobHandlerInterface
|
||||
{
|
||||
public function __construct(private readonly ImportAuditService $importAuditService)
|
||||
{
|
||||
}
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'label' => 'Import audit log cleanup',
|
||||
'description' => 'Purges import audit run records older than 90 days',
|
||||
'default_enabled' => 1,
|
||||
'default_timezone' => 'UTC',
|
||||
'default_schedule_type' => 'daily',
|
||||
'default_schedule_interval' => 1,
|
||||
'default_schedule_time' => '03:30',
|
||||
'default_schedule_weekdays_csv' => null,
|
||||
'default_catchup_once' => 1,
|
||||
'allowed_schedule_types' => ['daily', 'weekly'],
|
||||
];
|
||||
}
|
||||
|
||||
public function execute(?int $actorUserId): array
|
||||
{
|
||||
$deleted = $this->importAuditService->purgeExpired();
|
||||
|
||||
return [
|
||||
'status' => 'success',
|
||||
'error_code' => null,
|
||||
'error_message' => null,
|
||||
'result' => ['deleted_count' => $deleted],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Service\Scheduler\Handler;
|
||||
namespace MintyPHP\Module\Audit\Handler;
|
||||
|
||||
use MintyPHP\Service\Audit\SystemAuditService;
|
||||
use MintyPHP\Module\Audit\Service\SystemAuditService;
|
||||
use MintyPHP\Service\Scheduler\Handler\ScheduledJobHandlerInterface;
|
||||
|
||||
class SystemAuditPurgeJobHandler implements ScheduledJobHandlerInterface
|
||||
{
|
||||
@@ -34,9 +35,7 @@ class SystemAuditPurgeJobHandler implements ScheduledJobHandlerInterface
|
||||
'status' => 'success',
|
||||
'error_code' => null,
|
||||
'error_message' => null,
|
||||
'result' => [
|
||||
'deleted_count' => $deleted,
|
||||
],
|
||||
'result' => ['deleted_count' => $deleted],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Module\Audit\Handler;
|
||||
|
||||
use MintyPHP\Module\Audit\Service\UserLifecycleAuditService;
|
||||
use MintyPHP\Service\Scheduler\Handler\ScheduledJobHandlerInterface;
|
||||
|
||||
class UserLifecycleAuditPurgeJobHandler implements ScheduledJobHandlerInterface
|
||||
{
|
||||
public function __construct(private readonly UserLifecycleAuditService $userLifecycleAuditService)
|
||||
{
|
||||
}
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'label' => 'User lifecycle audit log cleanup',
|
||||
'description' => 'Purges user lifecycle audit entries older than 365 days',
|
||||
'default_enabled' => 1,
|
||||
'default_timezone' => 'UTC',
|
||||
'default_schedule_type' => 'daily',
|
||||
'default_schedule_interval' => 1,
|
||||
'default_schedule_time' => '03:45',
|
||||
'default_schedule_weekdays_csv' => null,
|
||||
'default_catchup_once' => 1,
|
||||
'allowed_schedule_types' => ['daily', 'weekly'],
|
||||
];
|
||||
}
|
||||
|
||||
public function execute(?int $actorUserId): array
|
||||
{
|
||||
$deleted = $this->userLifecycleAuditService->purgeExpired();
|
||||
|
||||
return [
|
||||
'status' => 'success',
|
||||
'error_code' => null,
|
||||
'error_message' => null,
|
||||
'result' => ['deleted_count' => $deleted],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Http;
|
||||
namespace MintyPHP\Module\Audit\Http;
|
||||
|
||||
use MintyPHP\Domain\Taxonomy\SystemAuditOutcome;
|
||||
use MintyPHP\Service\Audit\SystemAuditService;
|
||||
use MintyPHP\Http\ApiAuth;
|
||||
use MintyPHP\Http\RequestContext;
|
||||
use MintyPHP\Module\Audit\Domain\SystemAuditOutcome;
|
||||
use MintyPHP\Module\Audit\Service\SystemAuditService;
|
||||
|
||||
class ApiSystemAuditReporter
|
||||
{
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Module\Audit\Providers;
|
||||
|
||||
use MintyPHP\App\AppContainer;
|
||||
use MintyPHP\App\Module\Contracts\LayoutContextProvider;
|
||||
|
||||
final class AuditLayoutProvider implements LayoutContextProvider
|
||||
{
|
||||
public function provide(array $session, AppContainer $container): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Repository\Audit;
|
||||
namespace MintyPHP\Module\Audit\Repository;
|
||||
|
||||
use MintyPHP\DB;
|
||||
use MintyPHP\Repository\Support\RepoQuery;
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Repository\Audit;
|
||||
namespace MintyPHP\Module\Audit\Repository;
|
||||
|
||||
/** Contract for API request audit log creation, pagination, and retention purge. */
|
||||
interface ApiAuditLogRepositoryInterface
|
||||
@@ -1,9 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Repository\Audit;
|
||||
namespace MintyPHP\Module\Audit\Repository;
|
||||
|
||||
use MintyPHP\DB;
|
||||
use MintyPHP\Domain\Taxonomy\ImportAuditStatus;
|
||||
use MintyPHP\Module\Audit\Domain\ImportAuditStatus;
|
||||
use MintyPHP\Repository\Support\RepoQuery;
|
||||
use MintyPHP\Repository\Support\RepositoryArrayHelper;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Repository\Audit;
|
||||
namespace MintyPHP\Module\Audit\Repository;
|
||||
|
||||
/** Contract for CSV import run audit trail creation, completion, and purge. */
|
||||
interface ImportAuditRunRepositoryInterface
|
||||
@@ -1,10 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Repository\Audit;
|
||||
namespace MintyPHP\Module\Audit\Repository;
|
||||
|
||||
use MintyPHP\DB;
|
||||
use MintyPHP\Domain\Taxonomy\SystemAuditChannel;
|
||||
use MintyPHP\Domain\Taxonomy\SystemAuditOutcome;
|
||||
use MintyPHP\Module\Audit\Domain\SystemAuditChannel;
|
||||
use MintyPHP\Module\Audit\Domain\SystemAuditOutcome;
|
||||
use MintyPHP\Repository\Support\RepoQuery;
|
||||
use MintyPHP\Repository\Support\RepositoryArrayHelper;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Repository\Audit;
|
||||
namespace MintyPHP\Module\Audit\Repository;
|
||||
|
||||
/** Contract for system-wide security event logging with channel and outcome filtering. */
|
||||
interface SystemAuditLogRepositoryInterface
|
||||
@@ -1,11 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Repository\Audit;
|
||||
namespace MintyPHP\Module\Audit\Repository;
|
||||
|
||||
use MintyPHP\DB;
|
||||
use MintyPHP\Domain\Taxonomy\UserLifecycleAction;
|
||||
use MintyPHP\Domain\Taxonomy\UserLifecycleStatus;
|
||||
use MintyPHP\Domain\Taxonomy\UserLifecycleTriggerType;
|
||||
use MintyPHP\Module\Audit\Domain\UserLifecycleAction;
|
||||
use MintyPHP\Module\Audit\Domain\UserLifecycleStatus;
|
||||
use MintyPHP\Module\Audit\Domain\UserLifecycleTriggerType;
|
||||
use MintyPHP\Repository\Support\RepoQuery;
|
||||
use MintyPHP\Repository\Support\RepositoryArrayHelper;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Repository\Audit;
|
||||
namespace MintyPHP\Module\Audit\Repository;
|
||||
|
||||
/** Contract for tracking user deactivation/deletion lifecycle events and restore eligibility. */
|
||||
interface UserLifecycleAuditRepositoryInterface
|
||||
@@ -1,11 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Service\Audit;
|
||||
namespace MintyPHP\Module\Audit\Service;
|
||||
|
||||
use MintyPHP\Http\ApiAuth;
|
||||
use MintyPHP\Http\RequestContext;
|
||||
use MintyPHP\Http\RequestRuntimeInterface;
|
||||
use MintyPHP\Repository\Audit\ApiAuditLogRepositoryInterface;
|
||||
use MintyPHP\Module\Audit\Repository\ApiAuditLogRepositoryInterface;
|
||||
|
||||
class ApiAuditService
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Service\Audit;
|
||||
namespace MintyPHP\Module\Audit\Service;
|
||||
|
||||
use MintyPHP\Repository\User\UserReadRepositoryInterface;
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Module\Audit\Service;
|
||||
|
||||
use MintyPHP\Module\Audit\Repository\ApiAuditLogRepository;
|
||||
use MintyPHP\Module\Audit\Repository\ApiAuditLogRepositoryInterface;
|
||||
use MintyPHP\Module\Audit\Repository\ImportAuditRunRepository;
|
||||
use MintyPHP\Module\Audit\Repository\ImportAuditRunRepositoryInterface;
|
||||
use MintyPHP\Module\Audit\Repository\SystemAuditLogRepository;
|
||||
use MintyPHP\Module\Audit\Repository\SystemAuditLogRepositoryInterface;
|
||||
use MintyPHP\Module\Audit\Repository\UserLifecycleAuditRepository;
|
||||
use MintyPHP\Module\Audit\Repository\UserLifecycleAuditRepositoryInterface;
|
||||
|
||||
class AuditRepositoryFactory
|
||||
{
|
||||
private ?ApiAuditLogRepository $apiAuditLogRepository = null;
|
||||
private ?ImportAuditRunRepository $importAuditRunRepository = null;
|
||||
private ?UserLifecycleAuditRepository $userLifecycleAuditRepository = null;
|
||||
private ?SystemAuditLogRepository $systemAuditLogRepository = null;
|
||||
|
||||
public function createApiAuditLogRepository(): ApiAuditLogRepositoryInterface
|
||||
{
|
||||
return $this->apiAuditLogRepository ??= new ApiAuditLogRepository();
|
||||
}
|
||||
|
||||
public function createUserLifecycleAuditRepository(): UserLifecycleAuditRepositoryInterface
|
||||
{
|
||||
return $this->userLifecycleAuditRepository ??= new UserLifecycleAuditRepository();
|
||||
}
|
||||
|
||||
public function createSystemAuditLogRepository(): SystemAuditLogRepositoryInterface
|
||||
{
|
||||
return $this->systemAuditLogRepository ??= new SystemAuditLogRepository();
|
||||
}
|
||||
|
||||
public function createImportAuditRunRepository(): ImportAuditRunRepositoryInterface
|
||||
{
|
||||
return $this->importAuditRunRepository ??= new ImportAuditRunRepository();
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Service\Audit;
|
||||
namespace MintyPHP\Module\Audit\Service;
|
||||
|
||||
use MintyPHP\Http\RequestRuntimeInterface;
|
||||
use MintyPHP\Http\SessionStoreInterface;
|
||||
use MintyPHP\Repository\Audit\ApiAuditLogRepositoryInterface;
|
||||
use MintyPHP\Repository\Audit\SystemAuditLogRepositoryInterface;
|
||||
use MintyPHP\Repository\Audit\UserLifecycleAuditRepositoryInterface;
|
||||
use MintyPHP\Module\Audit\Repository\ApiAuditLogRepositoryInterface;
|
||||
use MintyPHP\Module\Audit\Repository\SystemAuditLogRepositoryInterface;
|
||||
use MintyPHP\Module\Audit\Repository\UserLifecycleAuditRepositoryInterface;
|
||||
use MintyPHP\Service\Settings\SettingsSystemAuditGateway;
|
||||
|
||||
class AuditServicesFactory
|
||||
@@ -1,10 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Service\Audit;
|
||||
namespace MintyPHP\Module\Audit\Service;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use DateTimeZone;
|
||||
use MintyPHP\Domain\Taxonomy\SystemAuditOutcome;
|
||||
use MintyPHP\Module\Audit\Domain\SystemAuditOutcome;
|
||||
use MintyPHP\Http\RequestContext;
|
||||
use MintyPHP\Http\SessionStoreInterface;
|
||||
use MintyPHP\Service\Security\RateLimiterService;
|
||||
@@ -1,12 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Service\Audit;
|
||||
namespace MintyPHP\Module\Audit\Service;
|
||||
|
||||
use MintyPHP\Domain\Taxonomy\ImportAuditStatus;
|
||||
use MintyPHP\Repository\Audit\ImportAuditRunRepositoryInterface;
|
||||
use MintyPHP\Module\Audit\Domain\ImportAuditStatus;
|
||||
use MintyPHP\Module\Audit\Repository\ImportAuditRunRepositoryInterface;
|
||||
use MintyPHP\Repository\Support\RepoQuery;
|
||||
use MintyPHP\Service\Audit\ImportAuditInterface;
|
||||
|
||||
class ImportAuditService
|
||||
class ImportAuditService implements ImportAuditInterface
|
||||
{
|
||||
private const RETENTION_DAYS = 90;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Service\Audit;
|
||||
namespace MintyPHP\Module\Audit\Service;
|
||||
|
||||
use MintyPHP\Http\RequestContext;
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Service\Audit;
|
||||
namespace MintyPHP\Module\Audit\Service;
|
||||
|
||||
use MintyPHP\Domain\Taxonomy\SystemAuditChannel;
|
||||
use MintyPHP\Domain\Taxonomy\SystemAuditOutcome;
|
||||
use MintyPHP\Module\Audit\Domain\SystemAuditChannel;
|
||||
use MintyPHP\Module\Audit\Domain\SystemAuditOutcome;
|
||||
use MintyPHP\Http\ApiAuth;
|
||||
use MintyPHP\Http\RequestContext;
|
||||
use MintyPHP\Http\SessionStoreInterface;
|
||||
use MintyPHP\Repository\Audit\SystemAuditLogRepositoryInterface;
|
||||
use MintyPHP\Module\Audit\Repository\SystemAuditLogRepositoryInterface;
|
||||
use MintyPHP\Repository\Support\RepoQuery;
|
||||
use MintyPHP\Service\Audit\AuditRecorderInterface;
|
||||
use MintyPHP\Service\Settings\SettingsSystemAuditGateway;
|
||||
|
||||
class SystemAuditService
|
||||
class SystemAuditService implements AuditRecorderInterface
|
||||
{
|
||||
private const RETENTION_DAYS_MIN = 30;
|
||||
private const RETENTION_DAYS_MAX = 1095;
|
||||
@@ -1,14 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Service\Audit;
|
||||
namespace MintyPHP\Module\Audit\Service;
|
||||
|
||||
use MintyPHP\Domain\Taxonomy\UserLifecycleAction;
|
||||
use MintyPHP\Domain\Taxonomy\UserLifecycleStatus;
|
||||
use MintyPHP\Domain\Taxonomy\UserLifecycleTriggerType;
|
||||
use MintyPHP\Repository\Audit\UserLifecycleAuditRepositoryInterface;
|
||||
use MintyPHP\Module\Audit\Domain\UserLifecycleAction;
|
||||
use MintyPHP\Module\Audit\Domain\UserLifecycleStatus;
|
||||
use MintyPHP\Module\Audit\Domain\UserLifecycleTriggerType;
|
||||
use MintyPHP\Module\Audit\Repository\UserLifecycleAuditRepositoryInterface;
|
||||
use MintyPHP\Service\Audit\UserLifecycleAuditInterface;
|
||||
use MintyPHP\Support\Crypto;
|
||||
|
||||
class UserLifecycleAuditService
|
||||
class UserLifecycleAuditService implements UserLifecycleAuditInterface
|
||||
{
|
||||
private const RETENTION_DAYS = 365;
|
||||
private const SNAPSHOT_VERSION = 1;
|
||||
185
modules/audit/module.php
Normal file
185
modules/audit/module.php
Normal file
@@ -0,0 +1,185 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Audit module manifest.
|
||||
*
|
||||
* Provides four audit streams: system audit, API audit, import audit,
|
||||
* and user lifecycle audit. Includes frontend telemetry ingestion,
|
||||
* PII redaction, encrypted snapshots, and retention-based cleanup.
|
||||
*/
|
||||
return [
|
||||
'id' => 'audit',
|
||||
'version' => '1.0.0',
|
||||
'enabled_by_default' => true,
|
||||
'load_order' => 5,
|
||||
'requires' => [],
|
||||
|
||||
'routes' => [
|
||||
// API audit pages
|
||||
['path' => 'admin/api-audit', 'target' => 'admin/api-audit/index'],
|
||||
['path' => 'admin/api-audit/view', 'target' => 'admin/api-audit/view'],
|
||||
['path' => 'admin/api-audit/data', 'target' => 'admin/api-audit/data'],
|
||||
['path' => 'admin/api-audit/purge', 'target' => 'admin/api-audit/purge'],
|
||||
// System audit pages
|
||||
['path' => 'admin/system-audit', 'target' => 'admin/system-audit/index'],
|
||||
['path' => 'admin/system-audit/view', 'target' => 'admin/system-audit/view'],
|
||||
['path' => 'admin/system-audit/data', 'target' => 'admin/system-audit/data'],
|
||||
['path' => 'admin/system-audit/purge', 'target' => 'admin/system-audit/purge'],
|
||||
// Import audit pages
|
||||
['path' => 'admin/import-audit', 'target' => 'admin/import-audit/index'],
|
||||
['path' => 'admin/import-audit/view', 'target' => 'admin/import-audit/view'],
|
||||
['path' => 'admin/import-audit/data', 'target' => 'admin/import-audit/data'],
|
||||
['path' => 'admin/import-audit/purge', 'target' => 'admin/import-audit/purge'],
|
||||
// User lifecycle audit pages
|
||||
['path' => 'admin/user-lifecycle-audit', 'target' => 'admin/user-lifecycle-audit/index'],
|
||||
['path' => 'admin/user-lifecycle-audit/view', 'target' => 'admin/user-lifecycle-audit/view'],
|
||||
['path' => 'admin/user-lifecycle-audit/data', 'target' => 'admin/user-lifecycle-audit/data'],
|
||||
['path' => 'admin/user-lifecycle-audit/purge', 'target' => 'admin/user-lifecycle-audit/purge'],
|
||||
['path' => 'admin/user-lifecycle-audit/restore', 'target' => 'admin/user-lifecycle-audit/restore'],
|
||||
// Frontend telemetry
|
||||
['path' => 'admin/frontend-telemetry/ingest', 'target' => 'admin/frontend-telemetry/ingest'],
|
||||
],
|
||||
'public_paths' => [],
|
||||
|
||||
'container_registrars' => [
|
||||
\MintyPHP\Module\Audit\AuditContainerRegistrar::class,
|
||||
],
|
||||
|
||||
'ui_slots' => [
|
||||
'sidebar.admin_nav_item' => [
|
||||
[
|
||||
'key' => 'audit-api-audit',
|
||||
'group' => 'admin-logs',
|
||||
'label' => 'API audit',
|
||||
'path' => 'admin/api-audit',
|
||||
'permission' => 'audit.api.view',
|
||||
'order' => 200,
|
||||
],
|
||||
[
|
||||
'key' => 'audit-system-audit',
|
||||
'group' => 'admin-logs',
|
||||
'label' => 'System audit logs',
|
||||
'path' => 'admin/system-audit',
|
||||
'permission' => 'audit.system.view',
|
||||
'order' => 210,
|
||||
],
|
||||
[
|
||||
'key' => 'audit-import-audit',
|
||||
'group' => 'admin-logs',
|
||||
'label' => 'Import logs',
|
||||
'path' => 'admin/import-audit',
|
||||
'permission' => 'audit.imports.view',
|
||||
'order' => 220,
|
||||
],
|
||||
[
|
||||
'key' => 'audit-user-lifecycle-audit',
|
||||
'group' => 'admin-logs',
|
||||
'label' => 'User lifecycle logs',
|
||||
'path' => 'admin/user-lifecycle-audit',
|
||||
'permission' => 'audit.user_lifecycle.view',
|
||||
'order' => 230,
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
'authorization_policies' => [
|
||||
\MintyPHP\Module\Audit\AuditAuthorizationPolicy::class,
|
||||
],
|
||||
|
||||
'permissions' => [
|
||||
[
|
||||
'key' => 'audit.api.view',
|
||||
'description' => 'View API audit logs',
|
||||
],
|
||||
[
|
||||
'key' => 'audit.system.view',
|
||||
'description' => 'View system audit logs',
|
||||
],
|
||||
[
|
||||
'key' => 'audit.system.purge',
|
||||
'description' => 'Purge system audit logs',
|
||||
],
|
||||
[
|
||||
'key' => 'audit.imports.view',
|
||||
'description' => 'View import audit logs',
|
||||
],
|
||||
[
|
||||
'key' => 'audit.user_lifecycle.view',
|
||||
'description' => 'View user lifecycle audit logs',
|
||||
],
|
||||
[
|
||||
'key' => 'audit.user_lifecycle.restore',
|
||||
'description' => 'Restore deleted users from lifecycle audit',
|
||||
],
|
||||
],
|
||||
|
||||
'scheduler_jobs' => [
|
||||
[
|
||||
'job_key' => 'audit_system_purge',
|
||||
'handler' => \MintyPHP\Module\Audit\Handler\SystemAuditPurgeJobHandler::class,
|
||||
'label' => 'System audit log cleanup',
|
||||
'description' => 'Purges system audit log entries older than the configured retention period',
|
||||
'default_enabled' => 1,
|
||||
'default_timezone' => 'UTC',
|
||||
'default_schedule_type' => 'daily',
|
||||
'default_schedule_interval' => 1,
|
||||
'default_schedule_time' => '03:00',
|
||||
'default_schedule_weekdays_csv' => null,
|
||||
'default_catchup_once' => 1,
|
||||
'allowed_schedule_types' => ['daily', 'weekly'],
|
||||
],
|
||||
[
|
||||
'job_key' => 'audit_api_purge',
|
||||
'handler' => \MintyPHP\Module\Audit\Handler\ApiAuditPurgeJobHandler::class,
|
||||
'label' => 'API audit log cleanup',
|
||||
'description' => 'Purges API audit log entries older than 90 days',
|
||||
'default_enabled' => 1,
|
||||
'default_timezone' => 'UTC',
|
||||
'default_schedule_type' => 'daily',
|
||||
'default_schedule_interval' => 1,
|
||||
'default_schedule_time' => '03:15',
|
||||
'default_schedule_weekdays_csv' => null,
|
||||
'default_catchup_once' => 1,
|
||||
'allowed_schedule_types' => ['daily', 'weekly'],
|
||||
],
|
||||
[
|
||||
'job_key' => 'audit_import_purge',
|
||||
'handler' => \MintyPHP\Module\Audit\Handler\ImportAuditPurgeJobHandler::class,
|
||||
'label' => 'Import audit log cleanup',
|
||||
'description' => 'Purges import audit run records older than 90 days',
|
||||
'default_enabled' => 1,
|
||||
'default_timezone' => 'UTC',
|
||||
'default_schedule_type' => 'daily',
|
||||
'default_schedule_interval' => 1,
|
||||
'default_schedule_time' => '03:30',
|
||||
'default_schedule_weekdays_csv' => null,
|
||||
'default_catchup_once' => 1,
|
||||
'allowed_schedule_types' => ['daily', 'weekly'],
|
||||
],
|
||||
[
|
||||
'job_key' => 'audit_user_lifecycle_purge',
|
||||
'handler' => \MintyPHP\Module\Audit\Handler\UserLifecycleAuditPurgeJobHandler::class,
|
||||
'label' => 'User lifecycle audit log cleanup',
|
||||
'description' => 'Purges user lifecycle audit entries older than 365 days',
|
||||
'default_enabled' => 1,
|
||||
'default_timezone' => 'UTC',
|
||||
'default_schedule_type' => 'daily',
|
||||
'default_schedule_interval' => 1,
|
||||
'default_schedule_time' => '03:45',
|
||||
'default_schedule_weekdays_csv' => null,
|
||||
'default_catchup_once' => 1,
|
||||
'allowed_schedule_types' => ['daily', 'weekly'],
|
||||
],
|
||||
],
|
||||
|
||||
'layout_context_providers' => [
|
||||
\MintyPHP\Module\Audit\Providers\AuditLayoutProvider::class,
|
||||
],
|
||||
'session_providers' => [],
|
||||
|
||||
'event_listeners' => [],
|
||||
'search_resources' => [],
|
||||
'asset_groups' => [],
|
||||
'migrations_path' => 'db/updates',
|
||||
'i18n_path' => 'i18n',
|
||||
];
|
||||
@@ -1,15 +1,16 @@
|
||||
<?php
|
||||
|
||||
use MintyPHP\Service\Access\OperationsAuthorizationPolicy;
|
||||
use MintyPHP\Module\Audit\AuditAuthorizationPolicy;
|
||||
use MintyPHP\Module\Audit\Service\ApiAuditService;
|
||||
use MintyPHP\Support\Guard;
|
||||
|
||||
Guard::requireLogin();
|
||||
Guard::requireAbilityOrForbidden(OperationsAuthorizationPolicy::ABILITY_ADMIN_API_AUDIT_VIEW);
|
||||
Guard::requireAbilityOrForbidden(AuditAuthorizationPolicy::ABILITY_API_AUDIT_VIEW);
|
||||
gridRequireGetRequest();
|
||||
|
||||
$filters = gridParseFiltersFromSchemaFile(__DIR__ . '/filter-schema.php');
|
||||
|
||||
$result = app(\MintyPHP\Service\Audit\ApiAuditService::class)->listPaged($filters);
|
||||
$result = app(ApiAuditService::class)->listPaged($filters);
|
||||
|
||||
$rows = [];
|
||||
foreach ($result['rows'] as $row) {
|
||||
@@ -2,17 +2,18 @@
|
||||
|
||||
use MintyPHP\Buffer;
|
||||
use MintyPHP\Http\SessionStoreInterface;
|
||||
use MintyPHP\Module\Audit\AuditAuthorizationPolicy;
|
||||
use MintyPHP\Module\Audit\Service\ApiAuditService;
|
||||
use MintyPHP\Service\Access\SettingsAuthorizationPolicy;
|
||||
use MintyPHP\Service\Access\UiAccessService;
|
||||
use MintyPHP\Service\Access\UiCapabilityMap;
|
||||
use MintyPHP\Service\Audit\ApiAuditService;
|
||||
use MintyPHP\Support\Guard;
|
||||
|
||||
$session = app(SessionStoreInterface::class)->all();
|
||||
Guard::requireLogin();
|
||||
Guard::requireAbility(\MintyPHP\Service\Access\OperationsAuthorizationPolicy::ABILITY_ADMIN_API_AUDIT_VIEW);
|
||||
Guard::requireAbility(AuditAuthorizationPolicy::ABILITY_API_AUDIT_VIEW);
|
||||
$viewAuth['page'] = app(UiAccessService::class)->pageCapabilities(
|
||||
(int) ($session['user']['id'] ?? 0),
|
||||
UiCapabilityMap::PAGE_AUDIT_PURGE
|
||||
['can_purge' => SettingsAuthorizationPolicy::ABILITY_ADMIN_SETTINGS_UPDATE]
|
||||
);
|
||||
|
||||
Buffer::set('title', t('API audit logs'));
|
||||
@@ -66,4 +66,4 @@ $pageConfig = [
|
||||
?>
|
||||
<script src="<?php e(assetVersion('vendor/gridjs/gridjs.umd.js')); ?>"></script>
|
||||
<script type="application/json" id="page-config-admin-api-audit-index"><?php gridJsonForJs($pageConfig); ?></script>
|
||||
<script type="module" src="<?php e(assetVersion('js/pages/admin-api-audit-index.js')); ?>"></script>
|
||||
<script type="module" src="<?php e(assetVersion('modules/audit/js/pages/admin-api-audit-index.js')); ?>"></script>
|
||||
@@ -18,6 +18,6 @@ if (!Session::checkCsrfToken()) {
|
||||
Router::redirect('admin/api-audit');
|
||||
}
|
||||
|
||||
$deleted = app(\MintyPHP\Service\Audit\ApiAuditService::class)->purgeExpired();
|
||||
$deleted = app(\MintyPHP\Module\Audit\Service\ApiAuditService::class)->purgeExpired();
|
||||
Flash::success(sprintf(t('%d API audit entries purged'), $deleted), 'admin/api-audit', 'api_audit_purged');
|
||||
Router::redirect('admin/api-audit');
|
||||
@@ -6,10 +6,10 @@ use MintyPHP\Support\Flash;
|
||||
use MintyPHP\Support\Guard;
|
||||
|
||||
Guard::requireLogin();
|
||||
Guard::requireAbility(\MintyPHP\Service\Access\OperationsAuthorizationPolicy::ABILITY_ADMIN_API_AUDIT_VIEW);
|
||||
Guard::requireAbility(\MintyPHP\Module\Audit\AuditAuthorizationPolicy::ABILITY_API_AUDIT_VIEW);
|
||||
|
||||
$auditId = (int) ($id ?? 0);
|
||||
$auditLog = $auditId > 0 ? app(\MintyPHP\Service\Audit\ApiAuditService::class)->find($auditId) : null;
|
||||
$auditLog = $auditId > 0 ? app(\MintyPHP\Module\Audit\Service\ApiAuditService::class)->find($auditId) : null;
|
||||
if (!$auditLog) {
|
||||
Flash::error('API audit entry not found', 'admin/api-audit', 'api_audit_not_found');
|
||||
Router::redirect('admin/api-audit');
|
||||
@@ -3,7 +3,7 @@
|
||||
use MintyPHP\Http\RequestContext;
|
||||
use MintyPHP\Http\SessionStoreInterface;
|
||||
use MintyPHP\Router;
|
||||
use MintyPHP\Service\Audit\FrontendTelemetryIngestService;
|
||||
use MintyPHP\Module\Audit\Service\FrontendTelemetryIngestService;
|
||||
use MintyPHP\Session;
|
||||
|
||||
if ((requestInput()->method()) !== 'POST') {
|
||||
@@ -1,12 +1,12 @@
|
||||
<?php
|
||||
|
||||
use MintyPHP\Domain\Taxonomy\ImportAuditStatus;
|
||||
use MintyPHP\Service\Access\OperationsAuthorizationPolicy;
|
||||
use MintyPHP\Service\Audit\ImportAuditService;
|
||||
use MintyPHP\Module\Audit\AuditAuthorizationPolicy;
|
||||
use MintyPHP\Module\Audit\Domain\ImportAuditStatus;
|
||||
use MintyPHP\Module\Audit\Service\ImportAuditService;
|
||||
use MintyPHP\Support\Guard;
|
||||
|
||||
Guard::requireLogin();
|
||||
Guard::requireAbilityOrForbidden(OperationsAuthorizationPolicy::ABILITY_ADMIN_IMPORTS_AUDIT_VIEW);
|
||||
Guard::requireAbilityOrForbidden(AuditAuthorizationPolicy::ABILITY_IMPORTS_AUDIT_VIEW);
|
||||
gridRequireGetRequest();
|
||||
|
||||
$importAuditService = app(ImportAuditService::class);
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
use MintyPHP\Domain\Taxonomy\ImportAuditStatus;
|
||||
use MintyPHP\Module\Audit\Domain\ImportAuditStatus;
|
||||
|
||||
$importAuditStatusItems = array_map(
|
||||
static fn (ImportAuditStatus $status): array => [
|
||||
@@ -2,17 +2,18 @@
|
||||
|
||||
use MintyPHP\Buffer;
|
||||
use MintyPHP\Http\SessionStoreInterface;
|
||||
use MintyPHP\Module\Audit\AuditAuthorizationPolicy;
|
||||
use MintyPHP\Module\Audit\Service\ImportAuditService;
|
||||
use MintyPHP\Service\Access\SettingsAuthorizationPolicy;
|
||||
use MintyPHP\Service\Access\UiAccessService;
|
||||
use MintyPHP\Service\Access\UiCapabilityMap;
|
||||
use MintyPHP\Service\Audit\ImportAuditService;
|
||||
use MintyPHP\Support\Guard;
|
||||
|
||||
$session = app(SessionStoreInterface::class)->all();
|
||||
Guard::requireLogin();
|
||||
Guard::requireAbility(\MintyPHP\Service\Access\OperationsAuthorizationPolicy::ABILITY_ADMIN_IMPORTS_AUDIT_VIEW);
|
||||
Guard::requireAbility(AuditAuthorizationPolicy::ABILITY_IMPORTS_AUDIT_VIEW);
|
||||
$viewAuth['page'] = app(UiAccessService::class)->pageCapabilities(
|
||||
(int) ($session['user']['id'] ?? 0),
|
||||
UiCapabilityMap::PAGE_AUDIT_PURGE
|
||||
['can_purge' => SettingsAuthorizationPolicy::ABILITY_ADMIN_SETTINGS_UPDATE]
|
||||
);
|
||||
|
||||
Buffer::set('title', t('Import audit logs'));
|
||||
@@ -69,4 +69,4 @@ $pageConfig = [
|
||||
?>
|
||||
<script src="<?php e(assetVersion('vendor/gridjs/gridjs.umd.js')); ?>"></script>
|
||||
<script type="application/json" id="page-config-admin-import-audit-index"><?php gridJsonForJs($pageConfig); ?></script>
|
||||
<script type="module" src="<?php e(assetVersion('js/pages/admin-import-audit-index.js')); ?>"></script>
|
||||
<script type="module" src="<?php e(assetVersion('modules/audit/js/pages/admin-import-audit-index.js')); ?>"></script>
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
|
||||
use MintyPHP\Module\Audit\Service\ImportAuditService;
|
||||
use MintyPHP\Router;
|
||||
use MintyPHP\Service\Audit\ImportAuditService;
|
||||
use MintyPHP\Session;
|
||||
use MintyPHP\Support\Flash;
|
||||
use MintyPHP\Support\Guard;
|
||||
@@ -6,10 +6,10 @@ use MintyPHP\Support\Flash;
|
||||
use MintyPHP\Support\Guard;
|
||||
|
||||
Guard::requireLogin();
|
||||
Guard::requireAbility(\MintyPHP\Service\Access\OperationsAuthorizationPolicy::ABILITY_ADMIN_IMPORTS_AUDIT_VIEW);
|
||||
Guard::requireAbility(\MintyPHP\Module\Audit\AuditAuthorizationPolicy::ABILITY_IMPORTS_AUDIT_VIEW);
|
||||
|
||||
$runId = (int) ($id ?? 0);
|
||||
$auditRun = $runId > 0 ? app(\MintyPHP\Service\Audit\ImportAuditService::class)->find($runId) : null;
|
||||
$auditRun = $runId > 0 ? app(\MintyPHP\Module\Audit\Service\ImportAuditService::class)->find($runId) : null;
|
||||
if (!$auditRun) {
|
||||
Flash::error(t('Import audit entry not found'), 'admin/import-audit', 'import_audit_not_found');
|
||||
Router::redirect('admin/import-audit');
|
||||
@@ -1,13 +1,13 @@
|
||||
<?php
|
||||
|
||||
use MintyPHP\Domain\Taxonomy\SystemAuditChannel;
|
||||
use MintyPHP\Domain\Taxonomy\SystemAuditOutcome;
|
||||
use MintyPHP\Service\Access\OperationsAuthorizationPolicy;
|
||||
use MintyPHP\Service\Audit\SystemAuditService;
|
||||
use MintyPHP\Module\Audit\AuditAuthorizationPolicy;
|
||||
use MintyPHP\Module\Audit\Domain\SystemAuditChannel;
|
||||
use MintyPHP\Module\Audit\Domain\SystemAuditOutcome;
|
||||
use MintyPHP\Module\Audit\Service\SystemAuditService;
|
||||
use MintyPHP\Support\Guard;
|
||||
|
||||
Guard::requireLogin();
|
||||
Guard::requireAbilityOrForbidden(OperationsAuthorizationPolicy::ABILITY_ADMIN_SYSTEM_AUDIT_VIEW);
|
||||
Guard::requireAbilityOrForbidden(AuditAuthorizationPolicy::ABILITY_SYSTEM_AUDIT_VIEW);
|
||||
gridRequireGetRequest();
|
||||
|
||||
$filters = gridParseFiltersFromSchemaFile(__DIR__ . '/filter-schema.php');
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
|
||||
use MintyPHP\Domain\Taxonomy\SystemAuditChannel;
|
||||
use MintyPHP\Domain\Taxonomy\SystemAuditOutcome;
|
||||
use MintyPHP\Module\Audit\Domain\SystemAuditChannel;
|
||||
use MintyPHP\Module\Audit\Domain\SystemAuditOutcome;
|
||||
|
||||
$outcomeItems = array_map(
|
||||
static fn (SystemAuditOutcome $outcome): array => [
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user