diff --git a/.github/assets/easyadmin_integration_dark.png b/.github/assets/easyadmin_integration_dark.png new file mode 100644 index 0000000..653ecc4 Binary files /dev/null and b/.github/assets/easyadmin_integration_dark.png differ diff --git a/README.md b/README.md index b1f87b9..07fe70c 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,61 @@ AuditTrailBundle is a modern, lightweight bundle that automatically tracks and s --- +## Why AuditTrailBundle? + +Most audit bundles capture changes synchronously, which can significantly slow down your application's write performance. AuditTrailBundle solves this by separating the **Capture** and **Persistence** phases. + +### Split-Phase Architecture + +```text + Application Doctrine ORM AuditTrailBundle Queue / Storage + | | | | + | flush() | | | + |----------------->| | | + | | onFlush (Capture) | | + | |------------------->| | + | | | Compute Diffs | + | | | Cache Payload | + | |<-------------------| | + | | | | + | | Execute SQL | | + | | (Transaction) | | + | | | | + | | postFlush | | + | |------------------->| | + | | | Dispatch Audit | + | | |-------------------->| + | flush() returns | | | + |<-----------------| | | + | Async Save +``` + +- **Non-blocking**: Audit capture happens during the flush, but storage is offloaded to a background process. +- **Data Integrity**: Cryptographic signing ensures logs cannot be tampered with. +- **Developer First**: Simple PHP 8 attributes, zero boilerplate. + +--- + +## Enterprise-Ready UI + +Native integration with **EasyAdmin** provides a professional dashboard for your audit logs out of the box. + +![EasyAdmin Integration Showcase](.github/assets/easyadmin_integration_dark.png) + +--- + +## Security & Compliance + +Track not just what changed, but who did it and where they were. + +- **Sensitive Data Masking**: Native support for `#[SensitiveParameter]` and custom `#[Sensitive]` attributes. +- **HMAC Signatures**: Every audit log is signed to prevent database tampering. +- **Integrity Verification**: Command-line tools to audit your audit logs. + +![Integrity Check CLI](.github/assets/audit_integrity_check.png) + +--- + ## Documentation | Topic | Description | @@ -32,17 +87,6 @@ AuditTrailBundle is a modern, lightweight bundle that automatically tracks and s --- -## Key Features - -- **High Performance**: Non-blocking audits using a **Split-Phase Architecture** (capture in `onFlush`, dispatch in `postFlush`). -- **Multiple Transports**: Doctrine, HTTP (ELK/Splunk), Queue (RabbitMQ/Redis). -- **Deep Collection Tracking**: Tracks Many-to-Many and One-to-Many changes with precision. -- **Sensitive Data Masking**: Native support for `#[SensitiveParameter]` and custom `#[Sensitive]` attributes. -- **Safe Revert Support**: Easily roll back entities to any point in history. -- **Conditional Auditing**: Skip logs based on runtime conditions. -- **Access Auditing**: Track entity read operations (GET requests) with high-performance caching and configurable cooldowns. -- **Rich Context**: Tracks IP, User Agent, Impersonation, and custom metadata. - ## Quick Start ### 1. Installation @@ -51,79 +95,41 @@ AuditTrailBundle is a modern, lightweight bundle that automatically tracks and s composer require rcsofttech/audit-trail-bundle ``` -### 2. Database Setup (Doctrine Transport) - -If you are using the **Doctrine Transport** (default), update your database schema: - -```bash -php bin/console make:migration -php bin/console doctrine:migrations:migrate -``` - -### 3. Basic Usage +### 2. Basic Usage Add the `#[Auditable]` attribute to any Doctrine entity you want to track. ```php + [!NOTE] -> `#[AuditAccess]` does not require `#[Auditable]` — they are independent attributes. -> However, if `#[AuditCondition]` is present on the same entity, it **is** respected for access logs. -> The expression receives `action = "access"` for fine-grained control. - -#### Cache Configuration - -To use the `cooldown` feature, you must specify a PSR-6 cache pool in your configuration: - -```yaml -# config/packages/audit_trail.yaml -audit_trail: - cache_pool: 'cache.app' # Use any available PSR-6 cache pool -``` +- **PHP**: 8.4+ +- **Symfony**: 7.4+ or 8.0+ +- **Doctrine ORM**: 3.0+ --- -## Requirements +## Community & Support -- PHP 8.4+ -- Symfony 7.4+ -- Doctrine ORM 3.0+ +- **Bugs & Features**: Please use the [GitHub Issue Tracker](https://github.com/rcsofttech85/AuditTrailBundle/issues). +- **Contributing**: Check out our [Contributing Guide](https://github.com/rcsofttech85/AuditTrailBundle/blob/main/CONTRIBUTING.md). ## License diff --git a/docs/advanced-usage.md b/docs/advanced-usage.md index 583b39c..3a1697b 100644 --- a/docs/advanced-usage.md +++ b/docs/advanced-usage.md @@ -5,6 +5,12 @@ You can ignore specific properties or enable/disable auditing per entity via the class-level attribute. ```php + 100")] class Product { - public function getPrice(): int { ... } + public function getPrice(): int + { + // ... + } } ``` @@ -44,6 +57,10 @@ class Product For complex logic, implement the `AuditVoterInterface`. Your voter will be automatically discovered if it's registered as a service. ```php +getAuditLog(); - + // Add custom metadata $context = $log->getContext(); $context['server_id'] = 'node-01'; diff --git a/docs/integrations.md b/docs/integrations.md index 54bbafb..991da84 100644 --- a/docs/integrations.md +++ b/docs/integrations.md @@ -5,6 +5,10 @@ Add the `AuditLogCrudController` to your `DashboardController`: ```php +revert($log, false, false, [ 'reason' => 'Accidental deletion', - 'ticket_id' => 'TICKET-456' + 'ticket_id' => 'TICKET-456', ]); ``` @@ -33,6 +37,10 @@ By default, `AuditReverter` **silences** the standard `AuditSubscriber` during a For scenarios requiring full technical transparency (e.g., strict forensic compliance), you can disable this silencing: ```php +revert($log, false, false, [], false); ``` diff --git a/docs/security-integrity.md b/docs/security-integrity.md index 7549aee..91a89e8 100644 --- a/docs/security-integrity.md +++ b/docs/security-integrity.md @@ -7,14 +7,24 @@ Sensitive data is automatically masked in audit logs. ### Option 1: Use PHP's `#[SensitiveParameter]` ```php +find(123); +find(123); $history = $this->auditReader->getHistoryFor($user); foreach ($history as $entry) { @@ -48,8 +56,13 @@ foreach ($history as $entry) { ## Building Custom Queries ```php -//Find all updates to User entities in the last 30 days +auditReader ->forEntity(User::class) ->updates() @@ -57,10 +70,7 @@ $recentUpdates = $this->auditReader ->limit(50) ->getResults(); -//Find all deletions by a specific admin - -use Rcsofttech\AuditTrailBundle\Entity\AuditLog; - +// Find all deletions by a specific admin $deletions = $this->auditReader ->byUser(adminUserId: 1) ->action( @@ -73,6 +83,10 @@ $deletions = $this->auditReader ## Find everything that happened in a single transaction ```php +auditReader ->byTransaction('019b5aca-60ed-70bf-b139-255aa96c96cb') ->getResults(); @@ -81,6 +95,10 @@ $transactionAudits = $this->auditReader ## Find updates where a specific field was changed ```php +auditReader ->forEntity(User::class) ->updates() @@ -91,6 +109,10 @@ $emailChanges = $this->auditReader ## Working with Diffs ```php +auditReader ->forEntity(User::class, '123') ->updates() @@ -120,6 +142,10 @@ if ($entry) { ## Working with Collections ```php +auditReader ->forEntity(Order::class) ->between( @@ -132,6 +158,10 @@ $audits = $this->auditReader ## Filter results in memory ```php +filter( fn ($entry) => $entry->hasFieldChanged('price') ); @@ -140,6 +170,10 @@ $priceChanges = $audits->filter( ## Grouping results ```php +groupByAction(); // ['create' => [...], 'update' => [...], 'delete' => [...] @@ -150,8 +184,12 @@ $byEntity = $audits->groupByEntity(); ## Collection utilities ```php +first(); -$last = $audits->last(); +$last = $audits->last(); $count = $audits->count(); $empty = $audits->isEmpty(); ``` @@ -159,6 +197,10 @@ $empty = $audits->isEmpty(); ## Pagination ```php +auditReader ->forEntity(Product::class) ->limit(25) @@ -169,6 +211,10 @@ $page1 = $this->auditReader ## Check if matching records exist ```php +auditReader ->forEntity(User::class) ->deletes() diff --git a/docs/symfony-audit-transports.md b/docs/symfony-audit-transports.md index 13bc333..00fe80e 100644 --- a/docs/symfony-audit-transports.md +++ b/docs/symfony-audit-transports.md @@ -75,11 +75,15 @@ You can pass Messenger stamps (like `DelayStamp` or `DispatchAfterCurrentBusStam Pass them via the `$context` array when creating an audit log or performing a revert. ```php +createAuditLog($entity, 'custom_action', null, null, [ - 'messenger_stamps' => [new DelayStamp(5000)] + 'messenger_stamps' => [new DelayStamp(5000)], ]); ``` @@ -88,6 +92,10 @@ $auditService->createAuditLog($entity, 'custom_action', null, null, [ Use the `AuditMessageStampEvent` to add stamps to the message right before it is dispatched to the bus. This is the recommended way to add transport-specific stamps. ```php +