kdesilva/audit-trail

灵活且稳固的cakephp审计日志跟踪插件

安装: 641

依赖项: 0

建议者: 0

安全: 0

星级: 2

关注者: 2

分支: 37

类型:cakephp-plugin

4.4.2 2022-02-14 09:59 UTC

This package is auto-updated.

Last update: 2024-09-14 15:27:16 UTC


README

此插件是从lorenzo/audit-stash分叉而来

虽然上述插件非常适合存储更改历史,但它有以下问题或没有我需要的所有功能。

  • 在删除事件中未记录原始数据。

    • 如果您在添加了一些数据后添加此插件,这将很有用。
  • 关联表记录未正确保存(在这种情况下,我考虑了两个具有“hasMany”关系的模型)

    • 目前,在CakePHP(4.x)ORM中,当存在“hasMany”关系(例如,考虑两个数据库表:items和item_attributes)时,EntityTrait::extractOriginal(array $fields)不返回关联表(item_attributes)原始数据的原始值,而是返回修改后的值。
    • 此外,在CakePHP(4.x)ORM中还有一个错误,即使没有对关联表数据进行更改,它也会将关联的('hasMany')实体标记为脏的。
    • 无法仅记录关联表的更改数据列
  • 当调用saveMany()来保存多个实体时,无法记录审计日志

  • 无法通过app.php设置AuditLog行为和/或Table Persister的一些常见配置

  • 无法记录外键的友好数据字段

  • 创建事件将相同的数据添加到'original'和'changed'列中

  • 'id'(主键)字段添加到'original'和'changed'数据中,除非您在每个模型类中将它列入黑名单。(主键作为单独的字段记录)

  • 无法在数据库中存储用户友好的字段值。这对于轻松识别记录很有用;特别是,当数据库仅存储更改数据时。因此,我使用了CakePHP Model::setDisplayField()来检索用户友好的字段值。

因此,我决定从原始项目分叉并改进它以支持上述缺失的功能。

安装

您可以使用composer将此插件安装到您的CakePHP应用程序中,并在应用程序的根目录中执行以下命令。

composer require kdesilva/audit-trail
bin/cake plugin load AuditStash

如果您计划使用ElasticSearch作为存储引擎,请参阅lorenzo/audit-stash

配置

表/常规数据库

如果您想使用常规数据库,然后通过CakePHP ORM API使用引擎,则可以使用此插件提供的表持久化器。

为此,您需要相应地配置AuditStash.persister选项。在您的config/app.php文件中添加以下配置

'AuditStash' => [
    'persister' => 'AuditStash\Persister\TablePersister'
]

默认情况下,插件将尝试将日志存储在名为audit_logs的表中,通过具有别名AuditLogs的表类。您可以在应用程序中创建/覆盖它,如果您需要的话。

您可以在本插件的config/migration文件夹中找到一个迁移,您可以将它应用到您的数据库中,这将添加一个名为audit_logs的表,其中包含所有默认列。或者,您可以自己烘焙迁移来创建表。之后,您可以为相应的表类进行迁移。

如果您使用插件默认的迁移,可以使用以下命令创建表和模型类。

bin/cake migrations migrate -p AuditStash -t 20171018185609
bin/cake bake model AuditLogs

表持久化器配置

表格持久化支持各种配置选项,请参阅其API文档以获取更多信息。通常,配置可以通过其config()(或setConfig())方法应用

$this->addBehavior('AuditStash.AuditLog');
$this->behaviors()->get('AuditLog')->persister()->config([
    'extractMetaFields' => [
        'user.name' => 'username'
    ]
]);

此外,您还可以通过app.php设置一些常用配置。目前,插件支持'extractMetaFields'和'blacklist'

...
'AuditStash' => [
    'persister' => 'AuditStash\Persister\TablePersister',
    'extractMetaFields' => [
            'user.username' => 'username',
            'user.customer_id' => 'customer_id',
        ],
    'blacklist' => ['customer_id'],
],

使用AuditStash

在您的任何表类中启用审计日志就像在initialize()函数中添加一个行为一样简单

class ArticlesTable extends Table
{
    public function initialize(array $config = [])
    {
        $this->setDisplayField('article_name');
        ...
        $this->addBehavior('AuditStash.AuditLog');
    }
}

配置行为

AuditLog行为可以配置为忽略您的表中的某些字段,默认情况下,它忽略idcreatedmodified字段

class ArticlesTable extends Table
{
    public function initialize(array $config = [])
    {
        $this->setDisplayField('article_name');
        ...
        $this->addBehavior('AuditStash.AuditLog', [
            'blacklist' => ['created', 'modified', 'another_field_name']
        ]);
    }
}

如果您喜欢,可以使用whitelist代替。这意味着只有列在该数组中的字段才会被行为跟踪

class ArticlesTable extends Table
{
    public function initialize(array $config = [])
    {
        $this->setDisplayField('article_name');
        ...
        $this->addBehavior('AuditStash.AuditLog', [
            'whitelist' => ['title', 'description', 'author_id']
        ]);
    }
}

如果您需要从相关表(即具有外键的表)检索人类友好的数据字段,您可以设置如下所示的foreignKeys

public function initialize(array $config = [])
{
    $this->setDisplayField('article_name');
    ...
    $this->addBehavior('AuditStash.AuditLog', [
        'blacklist' => ['customer_id', 'product_id'],
        'foreignKeys' => [
            'Categories' => 'name', // foreign key Model => human-friendly field name
            'ProductStatuses' => 'status',
        ],
        'unsetAssociatedEntityFieldsNotDirtyByFieldName' => [
            'associated_table_name' => 'field_name_in_associated_table'
        ]
    ]);
}

如上项目描述中所述,CakePHP (4.x) ORM即使在关联数据没有发生变化的情况下也会返回所有关联数据。因此,如果您需要从关联实体中删除未更改的数据,您需要设置unsetAssociatedEntityFieldsNotDirtyByFieldName,如上述示例所示。

存储登录用户

通常,将触发某些表更改的用户标识存储起来非常有用。为此,AuditStash提供了一个能够存储当前URL、IP和登录用户的RequestMetadata监听器类。您需要将此监听器添加到您的应用程序中的AppController::beforeFilter()方法

use AuditStash\Meta\RequestMetadata;
...

class AppController extends Controller
{
    public function beforeFilter(Event $event)
    {
        ...
        $eventManager = $this->loadModel()->eventManager();
        $identity = $this->request->getAttribute('identity');
        if ($identity != null) {
            $eventManager->on(
                 new RequestMetadata($this->request, [
                    'username' => $identity['username'],
                    'customer_id' => $identity['customer_id'],
                ])
            );
        }
    }
}

上述代码假设您将从控制器触发表操作,使用控制器的默认Table类。如果您计划在同一个控制器中使用其他Table类进行保存或删除,建议您全局附加监听器

use AuditStash\Meta\RequestMetadata;
use Cake\Event\EventManager;
...

class AppController extends Controller
{
    public function beforeFilter(Event $event)
    {
        ...
        $identity = $this->request->getAttribute('identity');
        if ($identity != null) {
            EventManager::instance()->on(
                new RequestMetadata($this->request, [
                    'username' => $identity['username'],
                    'customer_id' => $identity['customer_id'],
                ])
            );
        }
    }
}

在日志中存储额外信息

AuditStash还能够为每个已记录的事件存储任意数据。您可以使用ApplicationMetadata监听器或创建自己的。如果您选择使用ApplicationMetadata,您的日志将包含存储的app_name键和您提供的任何额外信息。您可以在应用程序的任何位置配置此监听器,例如在bootstrap.php文件中,或者在AppController中直接配置。

use AuditStash\Meta\ApplicationMetadata;
use Cake\Event\EventManager;

EventManager::instance()->on(new ApplicationMetadata('my_blog_app', [
    'server' => $theServerID,
    'extra' => $somExtraInformation,
    'moon_phase' => $currentMoonPhase
]));

实现自己的元数据监听器就像将监听器附加到AuditStash.beforeLog事件一样简单。例如

EventManager::instance()->on('AuditStash.beforeLog', function ($event, array $logs) {
    foreach ($logs as $log) {
        $log->setMetaInfo($log->getMetaInfo() + ['extra' => 'This is extra data to be stored']);
    }
});

实现您自己的持久化策略

有合理的理由想要为审计日志使用不同的持久化引擎。幸运的是,此插件允许您实现自己的存储引擎。这就像实现PersisterInterface接口一样简单

use AuditStash\PersisterInterface;

class MyPersister implements PersisterInterface
{
    public function logEvents(array $auditLogs)
    {
        foreach ($auditLogs as $log) {
            $eventType = $log->getEventType();
            $data = [
                'timestamp' => $log->getTimestamp(),
                'transaction' => $log->getTransactionId(),
                'type' => $log->getEventType(),
                'primary_key' => $log->getId(),
                'display_value' => $event->getDisplayValue(),
                'source' => $log->getSourceName(),
                'parent_source' => $log->getParentSourceName(),
                'original' => json_encode($log->getOriginal()),
                'changed' => $eventType === 'delete' ? null : json_encode($log->getChanged()),
                'meta' => json_encode($log->getMetaInfo())
            ];
            $storage = new MyStorage();
            $storage->save($data);
        }
    }
}

最后,您需要配置AuditStash以使用您的新持久化器。在config/app.php文件中添加以下行

'AuditStash' => [
    'persister' => 'App\Namespace\For\Your\Persister'
]

或者如果您作为独立插件使用

\Cake\Core\Configure::write('AuditStash.persister', 'App\Namespace\For\Your\DatabasePersister');

配置包含您持久化器的完全限定类名。

处理事务性查询

偶尔,您可能希望将多个数据库更改包装在一个事务中,以便在过程的一部分失败时可以回滚。为了在事务中创建审计日志,需要进行一些额外的设置。首先创建文件src/Model/Audit/AuditTrail.php,内容如下

<?php
namespace App\Model\Audit;

use Cake\Utility\Text;
use SplObjectStorage;

class AuditTrail
{
    protected $_auditQueue;
    protected $_auditTransaction;

    public function __construct()
    {
        $this->_auditQueue = new SplObjectStorage;
        $this->_auditTransaction = Text::uuid();
    }

    public function toSaveOptions()
    {
        return [
            '_auditQueue' => $this->_auditQueue,
            '_auditTransaction' => $this->_auditTransaction
        ];
    }
}

在任何您希望使用Connection::transactional()的地方,您需要在文件顶部包含以下内容

use App\Model\Audit\AuditTrail;
use Cake\Event\Event;

然后,您的交易应类似于以下示例中的BookmarksController

$trail = new AuditTrail();
$success = $this->Bookmarks->connection()->transactional(function () use ($trail) {
    $bookmark = $this->Bookmarks->newEntity();
    $bookmark1->save($data1, $trail->toSaveOptions());
    $bookmark2 = $this->Bookmarks->newEntity();
    $bookmark2->save($data2, $trail->toSaveOptions());
    ...
    $bookmarkN = $this->Bookmarks->newEntity();
    $bookmarkN->save($dataN, $trail->toSaveOptions());

    return true;
});

if ($success) {
    $event = new Event('Model.afterCommit', $this->Bookmarks);
    $table->behaviors()->get('AuditLog')->afterCommit($event, $result, $auditTrail->toSaveOptions());
}

这将保存您的对象的所有审计信息,以及任何相关数据的审计信息。请注意,$result必须是一个Object实例。不要更改文本“Model.afterCommit”。

保存多个实体

创建文件src/Model/Audit/AuditTrail.php,如上节所示

...
$auditTrail = new AuditTrail();

if ($this->Bookmarks->saveMany($entities, $auditTrail->toSaveOptions())) {
    ...                   
}