Flexible and rock solid audit log tracking for CakePHP 3

Overview

AuditStash Plugin For CakePHP

Build Status Coverage Status Total Downloads License

This plugin implements an "audit trail" for any of your Table classes in your application, that is, the ability of recording any creation, modification or delete of the entities of any particular table.

By default, this plugin stores the audit logs into Elasticsearch, as we have found that it is a fantastic storage engine for append-only streams of data and provides really powerful features for finding changes in the historic data.

Even though we suggest storing the logs in Elasticsearch, this plugin is generic enough so you can implement your own persisting strategies, if so you wish.

Installation

You can install this plugin into your CakePHP application using composer and executing the following lines in the root of your application.

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

For using the default storage engine (ElasticSearch) you need to install the official elastic-search plugin, by executing the following lines:

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

Configuration

Elastic Search

You now need to add the datasource configuration to your config/app.php file:

'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
    ],
    ...
]

Tables / Regular Databases

If you want to use a regular database, respectively an engine that can be used via the CakePHP ORM API, then you can use the table persister that ships with this plugin.

To do so you need to configure the AuditStash.persister option accordingly. In your config/app.php file add the following configuration:

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

The plugin will then by default try to store the logs in a table named audit_logs, via a table class with the alias AuditLogs, which you could create/overwrite in your application if you need.

You can find a migration in the config/migration folder of this plugin which you can apply to your database, this will add a table named audit_logs with all the default columns - alternatively create the table manually. After that you can bake the corresponding table class.

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

Table Persister Configuration

The table persister supports various configuration options, please refer to its API documentation for further information. Generally configuration can be applied via its config() (or setConfig()) method:

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

Using AuditStash

Enabling the Audit Log in any of your table classes is as simple as adding a behavior in the initialize() function:

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

When using the Elasticserch persister, it is recommended that you tell Elasticsearch about the schema of your table. You can do this automatically by executing the following command:

bin/cake elastic_mapping Articles

If you are using one index per day, save yourself some time and add the --use-templates option. This will create a schema template so any new index will inherit this configuration:

bin/cake elastic_mapping Articles --use-templates

Remember to execute the command line each time you change the schema of your table!

Configuring The Behavior

The AuditLog behavior can be configured to ignore certain fields of your table, by default it ignores the created and modified fields:

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

If you prefer, you can use a whitelist instead. This means that only the fields listed in that array will be tracked by the behavior:

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

Storing The Logged In User

It is often useful to store the identifier of the user that is triggering the changes in a certain table. For this purpose, AuditStash provides the RequestMetadata listener class, that is capable of storing the current URL, IP and logged in user. You need to add this listener to your application in the AppController::beforeFilter() method:

use AuditStash\Meta\RequestMetadata;
...

class AppController extends Controller
{
    public function beforeFilter(Event $event)
    {
        ...
        $eventManager = $this->loadModel()->eventManager();
        $eventManager->on(new RequestMetadata($this->request, $this->Auth->user('id')));
    }
}

The above code assumes that you will trigger the table operations from the controller, using the default Table class for the controller. If you plan to use other Table classes for saving or deleting inside the same controller, it is advised that you attach the listener globally:

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

class AppController extends Controller
{
    public function beforeFilter(Event $event)
    {
        ...
        EventManager::instance()->on(new RequestMetadata($this->request, $this->Auth->user('id')));
    }
}

Storing Extra Information In Logs

AuditStash is also capable of storing arbitrary data for each of the logged events. You can use the ApplicationMetadata listener or create your own. If you choose to use ApplicationMetadata, your logs will contain the app_name key stored and any extra information your may have provided. You can configure this listener anywhere in your application, such as the bootstrap.php file or, again, directly in your AppController.

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

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

Implementing your own metadata listeners is as simple as attaching the listener to the AuditStash.beforeLog event. For example:

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']);
    }
});

Implementing Your Own Persister Strategies

There are valid reasons for wanting to use a different persist engine for your audit logs. Luckily, this plugin allows you to implement your own storage engines. It is as simple as implementing the PersisterInterface interface:

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(),
                '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);
        }
    }
}

Finally, you need to configure AuditStash to use your new persister. In the config/app.php file add the following lines:

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

or if you are using as standalone via

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

The configuration contains the fully namespaced class name of your persister.

Working With Transactional Queries

Occasionally, you may want to wrap a number of database changes in a transaction, so that it can be rolled back if one part of the process fails. In order to create audit logs during a transaction, some additional setup is required. First create the file src/Model/Audit/AuditTrail.php with the following:

<?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
        ];
    }
}

Anywhere you wish to use Connection::transactional(), you will need to first include the following at the top of the file:

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

Your transaction should then look similar to this example of a 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());
}

This will save all audit info for your objects, as well as audits for any associated data. Please note, $result must be an instance of an Object. Do not change the text "Model.afterCommit".

Comments
  • Badges, travis, etc.

    Badges, travis, etc.

    Do not merge yet, work in progress. Questions:

    • [x] what name do you want in the LICENSE file?
    • [x] could you enable this repo in travis, styleci and codecov?
    opened by bravo-kernel 13
  • Enhance table/database persisting functionality.

    Enhance table/database persisting functionality.

    This is pretty much a copy of the functionality as it lives in a plugin of mine that I regularily use. I've only made some slight changes for now to get the tests running, but it should be good enough to review whether it might be a useful additional.

    As mentioned on Slack the other day, it can be configured with custom tables (and/or locators, which requires CakePHP >= 3.1), has basic error logging, can store primary keys in different formats (plain, multi-column, JSON), and can be configured to extract any metadata as properties/columns.

    In its current state it's more or less compatible with the existing database persister (JSON encoding wont encode null, and by default no metadata is being extracted).

    opened by ndm2 11
  • Behavior doesn't apply when Table is used in a transactional() query.

    Behavior doesn't apply when Table is used in a transactional() query.

    It appears that the AuditStash behavior isn't being called when the Table is used as part of a transactional query.

    For instance:

    $schedules_table->connection()->transactional(function() use ($schedules_table, $schedules) {
        foreach ($schedules as $schedule)
        {
            $schedules_table->save($schedule, ['atomic' => false]);
        }
    }
    
    opened by ah8r 10
  • Update structures for new Elasticsearch version

    Update structures for new Elasticsearch version

    • Upgrade cakephp/elastic-search to 2.0-beta
    • Change Type to Index class
    • Update phpunit usage, for newer versions
    • Require PHP >= 7, as current stable version of Elastica
    • Minors typo fix
    opened by CauanCabral 9
  • Issue implementing CRUD ElasticLogsIndexAction - Unknown Method

    Issue implementing CRUD ElasticLogsIndexAction - Unknown Method "alias"

    Looks like ElasticLogsIndexAction::_handle() is passing Cake\ElasticSearch\Query to PaginatorComponent::paginate().

    paginate() attempts to define $alias = $object->alias() using the object it is passed but of course fails to do so.

    Before defining $alias it does check to see if the object passed is an instance of QueryInterface

    if ($object instanceof QueryInterface) {
        $query = $object;
        $object = $query->repository();
    }
    

    I am note sure if this is an issue with my implementation of the CRUD action or an issue with the action itself.

    cakephp/cakephp @ 3.1.5 cakephp/elastic-search @ 0.3.0 friendsofcake/crud @ 4.2.1 lorenzo/audit-stash @ dev-master

    opened by themogwi 9
  • Error when trying to add mapping

    Error when trying to add mapping

    Hi

    I cannot use the shell to add the mapping to Elasticsearch:

    "Fatal error: Wrong parameters for Exception([string $exception [, long $code [, Exception $previous = NULL]]]) in D:\Xampp\htdocs\ebsst_v4\vendor\ruflin\elastica\lib\Elastica\Exception\ResponseException.php on line 34"

    The command Ive used:

    "bin\cake elastic_mapping Employees --use-templates"

    Can you help please?

    Thanks, Frank

    opened by LDSign 7
  • blacklist does not over-ride default

    blacklist does not over-ride default

    I'm trying to have the modified field tracked by AuditStash, but specifying a blacklist w/out it will not include changes to modified.

    From Model/Table/SomeTable.php

    public function initialize(array $config) {
            $this->addBehavior('AuditStash.AuditLog', [
                'blacklist' => ['created']
            ]);
        ...
    }
    

    If I remove modified from the blacklist in audit-stash/src/Model/Behavior/AuditLogBehavior.php, however, the field is tracked in Audit Log changes.

    opened by scottwpage 6
  • Title change not stored in log

    Title change not stored in log

    I found that title field on my table not logged in Audit Log. After that I changed my behavior declarations like below:

        $this->addBehavior('AuditStash.AuditLog', [
            'whitelist' => ['title']
        ]);
    

    But still now not stored title change. I am using my own database persister strategies

    opened by monsurhoq 5
  • Allow option to persist deleted values on delete

    Allow option to persist deleted values on delete

    Reference issue: https://github.com/lorenzo/audit-stash/issues/19

    We should probably add tests for the new Behavior configuration option logDeletedValues.

    opened by kevinquinnyo 3
  • deprecation warning for ServerRequest::here()

    deprecation warning for ServerRequest::here()

    https://github.com/lorenzo/audit-stash/blob/79216666674714bf4eac68bad278dfd2c0bdcb9a/src/Meta/RequestMetadata.php#L64

    Deprecated (16384): ServerRequest::here() will be removed in 4.0.0. You should use getRequestTarget() instead. - /var/www/html/vendor/lorenzo/audit-stash/src/Meta/RequestMetadata.php, line: 64

    opened by mzumbadoate 3
  • AuditLogsType.php does not comply with psr-4 autoloading standard.

    AuditLogsType.php does not comply with psr-4 autoloading standard.

    Deprecation Notice: Class AuditStash\Model\Index\AuditLogsIndex located in plugins/AuditStash/src\Model\Index\AuditLogsType.php does not comply with psr-4 autoloading standard. It will not autoload anymore in Composer v2.0. in Composer/Autoload/ClassMapGenerator.php:201

    for 2 branch

    opened by gemal 2
  • Logging Cascading Deletes

    Logging Cascading Deletes

    Is there a way to log cascading deletes? Table Person has many Docarchives... Dependent is true...

    If i delete a dataset from Person it is logged correctly... but the dependent Docarchives that get deleted too are not logged...

    opened by majaMH 0
  • String datetime changes may break custom DateTimeType implementations

    String datetime changes may break custom DateTimeType implementations

    In our application we are using a custom Cake\Database\Type\DateTimeType::marshal() that extends the default CakePHP database type. Our implementation basically interprets strings with no timezone information as being in the user's local timezone.

    Upgrading to audit-stash 3.0.2 causes timezone issues with the created timestamp in our application. Previously we received a \DateTime object which we handled correctly. Now, due to the changes in #48, we only receive a string Y-m-d H:i:s with no timezone information given, and our application interprets this as the wrong timezone.

    I'm not sure if this is expected behavior and our application is simply at fault for interpreting them differently than UTC. But in any case, this looks like a behavior change which we cannot easily work around due to the tz information loss.

    Modifying the type check in #48 to handle inherited classes would solve our issue, but I don't know if this breaks the original authors intent:

    if (!is_subclass_of(Type::getMap('datetime'), DateTimeType::class)) {
    

    Or maybe this should be configurable behavior. Any ideas on how to proceed here?

    opened by cschomburg 0
  • Implement JSON field types

    Implement JSON field types

    It would be nice if this plugin implemented native MySQL JSON field types instead of mediumtext.

    Using the plugin with JSON fields, seems to introduce excessive backslash escaping.

    opened by davidyell 2
  • Multiple index guide incorrect

    Multiple index guide incorrect

    The README specify that we need to use %s in the configuration to use multiple index. However, the 2 configurations on it are the same, how should I proceed?

    opened by Moutard3 2
  • audit-stash not working when updating multiple rows together

    audit-stash not working when updating multiple rows together

    The plugin works fine with all my tables. But I have a particular function in which the user selects rows with checkboxes, and then changes a specific field to all of them. The table audits contains a fields called "primary_key" which seems not working for such case.

    in my Controller, function, I put this: `$this->request->data; $data = $this->request->data;

        if($this->request->is(['patch', 'post', 'put'])) 
        {
            $ids = $this->request->data('data.AssetsAssignations.id');
            $room_id = $this->request->data('room_id');
    
            $this->AssetsAssignations->updateAll(
                ['room_id ' => $room_id ],
                ['id IN' => $ids]
            );
    
        }`
    

    in my table, I used this: $this->addBehavior('AuditStash.AuditLog');

    Since editing multiple rows is more dangerous, can you help me fix it?

    thanks

    opened by mbenjemaa 7
Releases(3.1.0)
Owner
José Lorenzo Rodríguez
José Lorenzo Rodríguez
CakePHP plugin to allow passing currently logged in user to model layer.

Footprint This plugin allows you to pass the currently logged in user info to the model layer of a CakePHP application. It comes bundled with the Foot

Muffin 88 Nov 14, 2022
Open source medical record system on CakePHP (OMCAKE)

omcake v0.1 小さな診療所用の電子カルテ(もどき)です。 奥村晴彦先生のtwitter オープンソースの電子カルテシステムで、WebベースでクライアントOSを選ばず、 サーバは普通のLinuxで動くPHPとか、ないんだろうか。 で唐突に召喚され、それ、ウチにありますけど〜、とノコノコ出てき

古林 敬一 7 Aug 16, 2022
Log Laravel application request and response with a unique ID for easy debugging

Flexible and extendable logging of Laravel application request and responses Zero configuration logging of Requests and Responses to database or custo

Bilfeldt 37 Nov 6, 2022
A simple and beautiful laravel log reader

Laravel Log Reader A simple and beautiful laravel log reader Documentation Get full documentation of Laravel Log Reader Other Packages Laravel H - A h

Md.Harun-Ur-Rashid 356 Dec 30, 2022
Remove messages from the error log that are from a certain folder

Error-Focus A package to stay focused on relevant entries in your error-log Scope This package allows you to declare folders that are not under your d

Andreas Heigl 7 Jul 25, 2022
An effective,fast,stable log extension for PHP

SeasLog An effective,fast,stable log extension for PHP @author Chitao.Gao [[email protected]] Documentation On php.net 中文文档 Synopsis Why use seaslog What

Neeke Gao 76 Sep 28, 2022
AcLog is a simple, zero-dependency PHP package to log activity to files

AcLog is a simple, zero-dependency PHP package to log activity to files. This is not meant for logging errors, this is can be used for logging c

null 2 Sep 9, 2022
Capture and monitor detailed error logs with nice dashboard and UI

Capture and monitor detailed error logs with nice dashboard and UI Requirements Check Laravel 6 requirements Check Laravel 7 requirements Installation

Bugphix 107 Dec 12, 2022
Sends your logs to files, sockets, inboxes, databases and various web services

Monolog - Logging for PHP Monolog sends your logs to files, sockets, inboxes, databases and various web services. See the complete list of handlers be

Jordi Boggiano 20.1k Jan 8, 2023
PHP logging library that is highly extendable and simple to use.

Analog - Minimal PHP logging library Copyright: (c) 2012-Present Johnny Broadway License: MIT A minimal PHP logging package based on the idea of using

Aband*nthecar 331 Dec 21, 2022
Paste, share and analyse Minecraft server logs

mclo.gs Paste, share & analyse your Minecraft server logs About The project mclo.gs was created in 2017 by the Aternos team after more than 4 years of

Aternos 99 Jan 3, 2023
Keep your laravel logs small and tidy.

Logs can get quite out of hand. This package helps save server space and keep your Laravel log files small.

Accent Interactive 73 Nov 14, 2022
Sends your logs to files, sockets, inboxes, databases and various web services

Monolog - Logging for PHP ⚠ This is the documentation for Monolog 3.x, if you are using older releases see the documentation for Monolog 2.x or Monolo

Jordi Boggiano 20.1k Jan 7, 2023
Robust, composite logger with filtering, formatting, and PSR-3 support

laminas-log ???? Русским гражданам Мы, участники Laminas, родились и живем в разных странах. У многих из нас есть друзья, родственники и коллеги как в

Laminas Project 27 Nov 24, 2022
Revolt is a rock-solid event loop for concurrent PHP applications.

Revolt is a rock-solid event loop for concurrent PHP applications.

Revolt PHP 586 Jan 2, 2023
[READ-ONLY] CakePHP Utility classes such as Inflector, Text, Hash, Security and Xml. This repo is a split of the main code that can be found in https://github.com/cakephp/cakephp

CakePHP Utility Classes This library provides a range of utility classes that are used throughout the CakePHP framework What's in the toolbox? Hash A

CakePHP 112 Feb 15, 2022
[READ-ONLY] Collection library in CakePHP. This repo is a split of the main code that can be found in https://github.com/cakephp/cakephp

CakePHP Collection Library The collection classes provide a set of tools to manipulate arrays or Traversable objects. If you have ever used underscore

CakePHP 85 Nov 28, 2022
[READ-ONLY] The event dispatcher library for CakePHP. This repo is a split of the main code that can be found in https://github.com/cakephp/cakephp

CakePHP Event Library This library emulates several aspects of how events are triggered and managed in popular JavaScript libraries such as jQuery: An

CakePHP 21 Oct 6, 2022
[READ-ONLY] Validation library from CakePHP. This repo is a split of the main code that can be found in https://github.com/cakephp/cakephp

CakePHP Validation Library The validation library in CakePHP provides features to build validators that can validate arbitrary arrays of data with eas

CakePHP 39 Oct 11, 2022
The SOLID principles demonstrated in PHP as seen on Elaniin's SOLID Booster Session.

SOLID Principles SOLID is the mnemonic acronym that represents 5 design principles that aim to make software designs more understandable, flexible, an

Vlass Contreras 5 Aug 24, 2022