Display audit logs

This commit is contained in:
James Cole
2022-10-02 14:37:50 +02:00
parent 06cd75ba74
commit ca8a65af60
21 changed files with 360 additions and 100 deletions

View File

@@ -23,6 +23,7 @@ namespace FireflyIII\Handlers\Events;
use FireflyIII\Events\TriggeredAuditLog;
use FireflyIII\Models\AuditLogEntry;
use FireflyIII\Repositories\AuditLogEntry\ALERepositoryInterface;
class AuditEventHandler
{
@@ -33,13 +34,16 @@ class AuditEventHandler
*/
public function storeAuditEvent(TriggeredAuditLog $event)
{
$auditLogEntry = new AuditLogEntry;
$auditLogEntry->auditable()->associate($event->auditable);
$auditLogEntry->changer()->associate($event->changer);
$auditLogEntry->action = $event->field;
$auditLogEntry->before = $event->before;
$auditLogEntry->after = $event->after;
$auditLogEntry->save();
$array = [
'auditable' => $event->auditable,
'changer' => $event->changer,
'action' => $event->field,
'before' => $event->before,
'after' => $event->after,
];
/** @var ALERepositoryInterface $repository */
$repository = app(ALERepositoryInterface::class);
$repository->store($array);
}
}

View File

@@ -27,12 +27,12 @@ use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\TransactionGroup;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\AuditLogEntry\ALERepositoryInterface;
use FireflyIII\Repositories\TransactionGroup\TransactionGroupRepositoryInterface;
use FireflyIII\Transformers\TransactionGroupTransformer;
use Illuminate\Contracts\View\Factory;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\View\View;
use Symfony\Component\HttpFoundation\ParameterBag;
@@ -43,6 +43,7 @@ use Symfony\Component\HttpFoundation\ParameterBag;
class ShowController extends Controller
{
private TransactionGroupRepositoryInterface $repository;
private ALERepositoryInterface $ALERepository;
/**
* ShowController constructor.
@@ -54,7 +55,8 @@ class ShowController extends Controller
// some useful repositories:
$this->middleware(
function ($request, $next) {
$this->repository = app(TransactionGroupRepositoryInterface::class);
$this->repository = app(TransactionGroupRepositoryInterface::class);
$this->ALERepository = app(ALERepositoryInterface::class);
app('view')->share('title', (string) trans('firefly.transactions'));
app('view')->share('mainTitleIcon', 'fa-exchange');
@@ -108,10 +110,17 @@ class ShowController extends Controller
$groupArray['transactions'][$index]['tags'] = $this->repository->getTagObjects($groupArray['transactions'][$index]['transaction_journal_id']);
}
// get audit log entries:
$logEntries = [];
foreach($transactionGroup->transactionJournals as $journal) {
$logEntries[$journal->id] = $this->ALERepository->getForObject($journal);
}
$events = $this->repository->getPiggyEvents($transactionGroup);
$attachments = $this->repository->getAttachments($transactionGroup);
$links = $this->repository->getLinks($transactionGroup);
return view(
'transactions.show',
compact(
@@ -119,6 +128,7 @@ class ShowController extends Controller
'amounts',
'first',
'type',
'logEntries',
'subTitle',
'splits',
'groupArray',

View File

@@ -33,8 +33,8 @@ class AuditLogEntry extends Model
use SoftDeletes;
protected $casts = [
'before' => 'array',
'after' => 'array',
'before' => 'array',
'after' => 'array',
'created_at' => 'datetime',
'updated_at' => 'datetime',
'deleted_at' => 'datetime',

View File

@@ -40,6 +40,8 @@ use FireflyIII\Helpers\Report\ReportHelper;
use FireflyIII\Helpers\Report\ReportHelperInterface;
use FireflyIII\Helpers\Webhook\Sha3SignatureGenerator;
use FireflyIII\Helpers\Webhook\SignatureGeneratorInterface;
use FireflyIII\Repositories\AuditLogEntry\ALERepository;
use FireflyIII\Repositories\AuditLogEntry\ALERepositoryInterface;
use FireflyIII\Repositories\ObjectGroup\ObjectGroupRepository;
use FireflyIII\Repositories\ObjectGroup\ObjectGroupRepositoryInterface;
use FireflyIII\Repositories\TransactionType\TransactionTypeRepository;
@@ -171,6 +173,8 @@ class FireflyServiceProvider extends ServiceProvider
$this->app->bind(TransactionTypeRepositoryInterface::class, TransactionTypeRepository::class);
$this->app->bind(AttachmentHelperInterface::class, AttachmentHelper::class);
$this->app->bind(ALERepositoryInterface::class, ALERepository::class);
$this->app->bind(
ObjectGroupRepositoryInterface::class,
static function (Application $app) {

View File

@@ -0,0 +1,56 @@
<?php
/*
* ALERepository.php
* Copyright (c) 2022 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Repositories\AuditLogEntry;
use FireflyIII\Models\AuditLogEntry;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
/**
* Class ALERepository
*/
class ALERepository implements ALERepositoryInterface
{
/**
* @inheritDoc
*/
public function store(array $data): AuditLogEntry
{
$auditLogEntry = new AuditLogEntry;
$auditLogEntry->auditable()->associate($data['auditable']);
$auditLogEntry->changer()->associate($data['changer']);
$auditLogEntry->action = $data['field'];
$auditLogEntry->before = $data['before'];
$auditLogEntry->after = $data['after'];
$auditLogEntry->save();
return $auditLogEntry;
}
/**
* @inheritDoc
*/
public function getForObject(Model $model): Collection
{
return AuditLogEntry::where('auditable_id', $model->id)->where('auditable_type', get_class($model))->get();
}
}

View File

@@ -0,0 +1,44 @@
<?php
/*
* ALERepositoryInterface.php
* Copyright (c) 2022 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Repositories\AuditLogEntry;
use FireflyIII\Models\AuditLogEntry;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
/**
* Interface ALERepositoryInterface
*/
interface ALERepositoryInterface
{
/**
* @param array $data
* @return AuditLogEntry
*/
public function store(array $data): AuditLogEntry;
/**
* @param Model $model
* @return Collection
*/
public function getForObject(Model $model): Collection;
}

View File

@@ -55,7 +55,7 @@ class AppendDescription implements ActionInterface
// event for audit log entry
/** @var TransactionJournal $journal */
$journal = TransactionJournal::where('user_id', $journal['user_id'])->find($journal['transaction_journal_id']);
event(new TriggeredAuditLog($this->action->rule, $journal, 'update_description', null, $description));
event(new TriggeredAuditLog($this->action->rule, $journal, 'update_description', $journal['description'], $description));
return true;
}

View File

@@ -61,7 +61,7 @@ class ClearBudget implements ActionInterface
DB::table('budget_transaction_journal')->where('transaction_journal_id', '=', $journal['transaction_journal_id'])->delete();
event(new TriggeredAuditLog($this->action->rule, $journal, 'remove_budget', $budget->name, null));
event(new TriggeredAuditLog($this->action->rule, $journal, 'clear_budget', $budget->name, null));
Log::debug(sprintf('RuleAction ClearBudget removed all budgets from journal #%d.', $journal['transaction_journal_id']));

View File

@@ -60,7 +60,7 @@ class ClearCategory implements ActionInterface
DB::table('category_transaction_journal')->where('transaction_journal_id', '=', $journal['transaction_journal_id'])->delete();
event(new TriggeredAuditLog($this->action->rule, $journal, 'removed_category', $category->name, null));
event(new TriggeredAuditLog($this->action->rule, $journal, 'clear_category', $category->name, null));
Log::debug(sprintf('RuleAction ClearCategory removed all categories from journal #%d.', $journal['transaction_journal_id']));

View File

@@ -64,7 +64,7 @@ class ClearNotes implements ActionInterface
->delete();
Log::debug(sprintf('RuleAction ClearNotes removed all notes from journal #%d.', $journal['transaction_journal_id']));
event(new TriggeredAuditLog($this->action->rule, $journal, 'remove_notes', $before, null));
event(new TriggeredAuditLog($this->action->rule, $journal, 'clear_notes', $before, null));
return true;
}

View File

@@ -75,13 +75,13 @@ class ConvertToDeposit implements ActionInterface
if (TransactionType::WITHDRAWAL === $type) {
Log::debug('Going to transform a withdrawal to a deposit.');
$object = TransactionJournal::where('user_id', $journal['user_id'])->find($journal['transaction_journal_id']);
event(new TriggeredAuditLog($this->action->rule, $object, 'change_transaction_type', TransactionType::WITHDRAWAL, TransactionType::DEPOSIT));
event(new TriggeredAuditLog($this->action->rule, $object, 'update_transaction_type', TransactionType::WITHDRAWAL, TransactionType::DEPOSIT));
return $this->convertWithdrawalArray($journal);
}
if (TransactionType::TRANSFER === $type) {
$object = TransactionJournal::where('user_id', $journal['user_id'])->find($journal['transaction_journal_id']);
event(new TriggeredAuditLog($this->action->rule, $object, 'change_transaction_type', TransactionType::TRANSFER, TransactionType::DEPOSIT));
event(new TriggeredAuditLog($this->action->rule, $object, 'update_transaction_type', TransactionType::TRANSFER, TransactionType::DEPOSIT));
Log::debug('Going to transform a transfer to a deposit.');
return $this->convertTransferArray($journal);

View File

@@ -92,7 +92,7 @@ class ConvertToTransfer implements ActionInterface
if (TransactionType::WITHDRAWAL === $type) {
Log::debug('Going to transform a withdrawal to a transfer.');
$object = TransactionJournal::where('user_id', $journal['user_id'])->find($journal['transaction_journal_id']);
event(new TriggeredAuditLog($this->action->rule, $object, 'change_transaction_type', TransactionType::WITHDRAWAL, TransactionType::TRANSFER));
event(new TriggeredAuditLog($this->action->rule, $object, 'update_transaction_type', TransactionType::WITHDRAWAL, TransactionType::TRANSFER));
return $this->convertWithdrawalArray($journal, $asset);
}
@@ -100,7 +100,7 @@ class ConvertToTransfer implements ActionInterface
Log::debug('Going to transform a deposit to a transfer.');
$object = TransactionJournal::where('user_id', $journal['user_id'])->find($journal['transaction_journal_id']);
event(new TriggeredAuditLog($this->action->rule, $object, 'change_transaction_type', TransactionType::DEPOSIT, TransactionType::TRANSFER));
event(new TriggeredAuditLog($this->action->rule, $object, 'update_transaction_type', TransactionType::DEPOSIT, TransactionType::TRANSFER));
return $this->convertDepositArray($journal, $asset);
}

View File

@@ -73,14 +73,14 @@ class ConvertToWithdrawal implements ActionInterface
if (TransactionType::DEPOSIT === $type) {
Log::debug('Going to transform a deposit to a withdrawal.');
$object = TransactionJournal::where('user_id', $journal['user_id'])->find($journal['transaction_journal_id']);
event(new TriggeredAuditLog($this->action->rule, $object, 'change_transaction_type', TransactionType::DEPOSIT, TransactionType::WITHDRAWAL));
event(new TriggeredAuditLog($this->action->rule, $object, 'update_transaction_type', TransactionType::DEPOSIT, TransactionType::WITHDRAWAL));
return $this->convertDepositArray($journal);
}
if (TransactionType::TRANSFER === $type) {
Log::debug('Going to transform a transfer to a withdrawal.');
$object = TransactionJournal::where('user_id', $journal['user_id'])->find($journal['transaction_journal_id']);
event(new TriggeredAuditLog($this->action->rule, $object, 'change_transaction_type', TransactionType::TRANSFER, TransactionType::WITHDRAWAL));
event(new TriggeredAuditLog($this->action->rule, $object, 'update_transaction_type', TransactionType::TRANSFER, TransactionType::WITHDRAWAL));
return $this->convertTransferArray($journal);
}

View File

@@ -63,6 +63,14 @@ class LinkToBill implements ActionInterface
$bill = $repository->findByName($billName);
if (null !== $bill && $journal['transaction_type_type'] === TransactionType::WITHDRAWAL) {
$count = DB::table('transaction_journals')->where('id', '=', $journal['transaction_journal_id'])
->where('bill_id', $bill->id)->count();
if (0 !== $count) {
Log::error(sprintf('RuleAction LinkToBill could not set the bill of journal #%d to bill "%s": already set.', $journal['transaction_journal_id'], $billName));
return false;
}
DB::table('transaction_journals')
->where('id', '=', $journal['transaction_journal_id'])
->update(['bill_id' => $bill->id]);
@@ -71,7 +79,7 @@ class LinkToBill implements ActionInterface
);
$journal = TransactionJournal::where('user_id', $journal['user_id'])->find($journal['transaction_journal_id']);
event(new TriggeredAuditLog($this->action->rule, $journal, 'change_bill', null, $bill->id));
event(new TriggeredAuditLog($this->action->rule, $journal, 'set_bill', null, $bill->name));
return true;
}

View File

@@ -74,7 +74,7 @@ class MoveNotesToDescription implements ActionInterface
$note->delete();
event(new TriggeredAuditLog($this->action->rule, $journal, 'update_description', $before, $journal->description));
event(new TriggeredAuditLog($this->action->rule, $journal, 'remove_notes', $beforeNote, null));
event(new TriggeredAuditLog($this->action->rule, $journal, 'clear_notes', $beforeNote, null));
return true;
}

View File

@@ -50,14 +50,19 @@ class RemoveAllTags implements ActionInterface
*/
public function actOnArray(array $journal): bool
{
Log::debug(sprintf('RuleAction ClearCategory removed all tags from journal %d.', $journal['transaction_journal_id']));
DB::table('tag_transaction_journal')->where('transaction_journal_id', $journal['transaction_journal_id'])->delete();
$count = DB::table('tag_transaction_journal')->where('transaction_journal_id', $journal['transaction_journal_id'])->count();
if (0 === $count) {
Log::debug(sprintf('RuleAction RemoveAllTags, journal #%d has no tags.', $journal['transaction_journal_id']));
return false;
}
Log::debug(sprintf('RuleAction RemoveAllTags removed all tags from journal %d.', $journal['transaction_journal_id']));
/** @var TransactionJournal $journal */
$journal = TransactionJournal::where('user_id', $journal['user_id'])->find($journal['transaction_journal_id']);
// audit log
event(new TriggeredAuditLog($this->action->rule, $journal, 'remove_all_tags', null, null));
event(new TriggeredAuditLog($this->action->rule, $journal, 'clear_all_tags', null, null));
return true;
}

View File

@@ -62,6 +62,11 @@ class RemoveTag implements ActionInterface
);
return false;
}
$count = DB::table('tag_transaction_journal')->where('transaction_journal_id', $journal['transaction_journal_id'])->where('tag_id', $tag->id)->count();
if(0 === $count) {
Log::debug(sprintf('RuleAction RemoveTag tried to remove tag "%s" from journal #%d but no such tag is linked.', $name, $journal['transaction_journal_id']));
return false;
}
Log::debug(sprintf('RuleAction RemoveTag removed tag #%d ("%s") from journal #%d.', $tag->id, $tag->tag, $journal['transaction_journal_id']));
DB::table('tag_transaction_journal')
@@ -71,7 +76,7 @@ class RemoveTag implements ActionInterface
/** @var TransactionJournal $journal */
$journal = TransactionJournal::where('user_id', $journal['user_id'])->find($journal['transaction_journal_id']);
event(new TriggeredAuditLog($this->action->rule, $journal, 'remove_tag', $tag->tag, null));
event(new TriggeredAuditLog($this->action->rule, $journal, 'clear_tag', $tag->tag, null));
return true;
}

View File

@@ -28,6 +28,7 @@ use FireflyIII\Events\TriggeredAuditLog;
use FireflyIII\Models\PiggyBank;
use FireflyIII\Models\RuleAction;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
use FireflyIII\User;
@@ -61,6 +62,7 @@ class UpdatePiggybank implements ActionInterface
// refresh the transaction type.
$user = User::find($journal['user_id']);
/** @var TransactionJournal $journalObj */
$journalObj = $user->transactionJournals()->find($journal['transaction_journal_id']);
$type = TransactionType::find((int) $journalObj->transaction_type_id);
$journal['transaction_type_type'] = $type->type;
@@ -91,7 +93,7 @@ class UpdatePiggybank implements ActionInterface
Log::debug('Piggy bank account is linked to source, so remove amount.');
$this->removeAmount($journal, $piggyBank, $destination->amount);
event(new TriggeredAuditLog($this->action->rule, $journalObj, 'remove_from_piggy', null, ['amount' => $destination->amount, 'piggy' => $piggyBank->name]));
event(new TriggeredAuditLog($this->action->rule, $journalObj, 'remove_from_piggy', null, ['currency_symbol' => $journalObj->transactionCurrency->symbol, 'decimal_places' => $journalObj->transactionCurrency->decimal_places, 'amount' => $destination->amount, 'piggy' => $piggyBank->name]));
return true;
}
@@ -99,7 +101,7 @@ class UpdatePiggybank implements ActionInterface
Log::debug('Piggy bank account is linked to source, so add amount.');
$this->addAmount($journal, $piggyBank, $destination->amount);
event(new TriggeredAuditLog($this->action->rule, $journalObj, 'add_to_piggy', null, ['amount' => $destination->amount, 'piggy' => $piggyBank->name]));
event(new TriggeredAuditLog($this->action->rule, $journalObj, 'add_to_piggy', null, ['currency_symbol' => $journalObj->transactionCurrency->symbol, 'decimal_places' => $journalObj->transactionCurrency->decimal_places, 'amount' => $destination->amount, 'piggy' => $piggyBank->name]));
return true;
}