lorenzo/audit-stash

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

安装次数: 363 172

依赖: 4

建议者: 0

安全: 0

星标: 77

关注者: 17

分支: 39

开放性问题: 7

类型:cakephp-plugin

4.2.1 2024-08-30 09:24 UTC

This package is auto-updated.

Last update: 2024-08-30 12:07:50 UTC


README

Build Status Coverage Status Total Downloads License

此插件为您的应用中任何 Table 类实现了 "审计跟踪",即记录任何特定表的实体创建、修改或删除的能力。

默认情况下,此插件将审计日志存储到 Elasticsearch,因为我们发现它是一个出色的追加数据流存储引擎,并为查找历史数据中的更改提供了真正强大的功能。

尽管我们建议将日志存储在 Elasticsearch 中,但此插件足够通用,您可以根据需要实现自己的持久化策略。

安装

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

composer require lorenzo/audit-stash
bin/cake plugin load AuditStash

要使用默认存储引擎(ElasticSearch),您需要安装官方的 elastic-search 插件,执行以下行

composer require cakephp/elastic-search
bin/cake plugin load Cake/ElasticSearch

配置

Elastic Search

现在您需要将数据源配置添加到您的 config/app.php 文件中

'Datasources' => [
    'auditlog_elastic' => [
        'className' => 'Cake\ElasticSearch\Datasource\Connection',
        'driver' => 'Cake\ElasticSearch\Datasource\Connection',
        'host' => '127.0.0.1', // server where elasticsearch is running
        'port' => 9200
    ],
    ...
]

表格 / 正规数据库

如果您想使用常规数据库,或者可以使用 CakePHP ORM API 使用的引擎,则可以使用此插件提供的表格持久化。

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

'AuditStash' => [
    'persister' => \AuditStash\Persister\TablePersister::class,
],

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

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

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

表格持久化配置

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

$this->addBehavior('AuditStash.AuditLog');
$this->behaviors()->get('AuditLog')->persister()->setConfig([
    'extractMetaFields' => [
        'user.id' => 'user_id'
    ]
]);

使用 AuditStash

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

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

请记住,每次您更改表格的架构时都要执行命令行命令!

配置行为

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

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

如果您更喜欢,您可以使用 whitelist。这意味着只有该数组中列出的字段将被行为跟踪。

public function initialize(array $config = []): void
{
    ...
    $this->addBehavior('AuditStash.AuditLog', [
        'whitelist' => ['title', 'description', 'author_id']
    ]);
}

如果您有包含敏感信息但仍然想跟踪其更改的字段,您可以使用 sensitive 配置。

public function initialize(array $config = []): void
{
    ...
    $this->addBehavior('AuditStash.AuditLog', [
        'sensitive' => ['body']
    ]);
}

存储已登录用户

将触发特定表格更改的用户标识符存储在表中通常很有用。为此目的,AuditStash 提供了 RequestMetadata 监听器类,它可以存储当前 URL、IP 和已登录用户。您需要在应用程序的 AppController::beforeFilter() 方法中添加此监听器。

use AuditStash\Meta\RequestMetadata;
...

class AppController extends Controller
{
    public function beforeFilter(EventInterface $event)
    {
        ...
        $eventManager = $this->fetchTable()->getEventManager();
        $eventManager->on(
            new RequestMetadata(
                request: $this->getRequest(),
                user: $this->getRequest()->getAttribute('identity')?->getIdentifier()
            )
        );
    }
}

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

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

class AppController extends Controller
{
    public function beforeFilter(EventInterface $event)
    {
        ...
        EventManager::instance()->on(
            new RequestMetadata(
                request: $this->getRequest(),
                user: $this->getRequest()->getAttribute('identity')?->getIdentifier()
            )
        );
    }
}

在日志中存储额外信息

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 (EventInterface $event, array $logs): void {
    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): void
    {
        foreach ($auditLogs as $log) {
            $eventType = $log->getEventType();
            $data = [
                'timestamp' => $log->getTimestamp(),
                'transaction' => $log->getTransactionId(),
                'type' => $log->getEventType(),
                'primary_key' => $log->getId(),
                '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');

配置包含您持久化器完全命名空间类名的类。

处理事务性查询

有时,您可能希望将多个数据库更改包裹在一个事务中,以便在过程中某个部分失败时可以回滚。有两种实现方式。最简单的是将您的保存策略更改为使用afterSave而不是afterCommit。在您的应用程序配置中,例如在config/app.php

'AuditStash' => [
    'saveType' => 'afterSave',
]

如果使用afterSave,这样就完成了。您应该了解这两个之间的区别,因为存在一些缺点:https://book.cakephp.com.cn/4/en/orm/table-objects.html#aftersave

如果您使用的是默认的afterCommit,为了在事务中创建审计日志,需要进行一些额外的设置。首先创建文件src/Model/Audit/AuditTrail.php,内容如下

<?php
declare(strict_types=1);

namespace App\Model\Audit;

use Cake\Utility\Text;
use SplObjectStorage;

class AuditTrail
{
    protected SplObjectStorage $_auditQueue;
    protected string $_auditTransaction;

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

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

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

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

然后,您的交易应该看起来类似于以下示例中的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());
});

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

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

测试

默认情况下,测试套件不会运行Elastic。如果您是Elastic用户并希望针对本地实例进行测试,则需要设置环境变量

elastic_dsn="Cake\ElasticSearch\Datasource\Connection://127.0.0.1:9200?driver=Cake\ElasticSearch\Datasource\Connection" vendor/bin/phpunit