diff --git a/composer.json b/composer.json index a0df8d5..1e92fd5 100644 --- a/composer.json +++ b/composer.json @@ -89,6 +89,7 @@ "analyse:phpstan": "Run static analysis" }, "require-dev": { + "flarum/audit": "2.x-dev", "flarum/testing": "^2.0.0", "flarum/phpstan": "^2.0.0", "flarum/gdpr": "^2.0.0" diff --git a/extend.php b/extend.php index 91ce54e..09ad239 100644 --- a/extend.php +++ b/extend.php @@ -17,6 +17,7 @@ use Flarum\Api\Resource\PostResource; use Flarum\Api\Resource\UserResource; use Flarum\Api\Schema; +use Flarum\Audit\Extend\Audit; use Flarum\Extend; use Flarum\Gdpr\Extend\UserData; use Flarum\Post\Post; @@ -191,4 +192,19 @@ (new UserData()) ->addType(Data\BannedIPData::class), ]), + + (new Extend\Conditional()) + ->whenExtensionEnabled('flarum-audit', fn () => [ + (new Audit()) + ->group('fof-ban-ips') + ->listen(IPWasBanned::class, 'fof_ban_ips.banned', fn ($e) => array_filter([ + 'ip' => $e->bannedIP->address, + 'reason' => $e->bannedIP->reason, + 'user_id' => $e->bannedIP->user_id ?: null, + ], fn ($v) => $v !== null)) + ->listen(IPWasUnbanned::class, 'fof_ban_ips.unbanned', fn ($e) => array_filter([ + 'ip' => $e->unbannedIP->address, + 'user_id' => $e->unbannedIP->user_id ?: null, + ], fn ($v) => $v !== null)), + ]), ]; diff --git a/resources/locale/en.yml b/resources/locale/en.yml index 2c7e6e7..6e8acb9 100644 --- a/resources/locale/en.yml +++ b/resources/locale/en.yml @@ -81,3 +81,13 @@ flarum-gdpr: bannedipdata: export_description: "If an IP ban record is associated with a user (i.e. the user was banned by IP), the user's IP address will be included in the export, along with the reason for the ban and the creation date." anonymize_description: "Any IP addresses linked to the user are decoupled, but the IP itself will remain banned, along with the reason. Ensure no PII data is stored in the 'reason' field." + +# Labels for the audit log entries this extension produces, rendered by the (optional) +# flarum/audit browser. Defined under the flarum-audit namespace so the audit frontend +# resolves them, but shipped here so they travel with the extension that owns the actions. +flarum-audit: + lib: + browser: + fof_ban_ips: + banned: Banned ip {ip} + unbanned: Unbanned ip {ip} diff --git a/tests/integration/AuditTest.php b/tests/integration/AuditTest.php new file mode 100644 index 0000000..160b882 --- /dev/null +++ b/tests/integration/AuditTest.php @@ -0,0 +1,138 @@ +extension('flarum-audit', 'fof-ban-ips'); + + $this->prepareDatabase([ + 'audit_log' => [], + User::class => [ + [ + 'id' => 3, + 'username' => 'user3', + 'email' => 'user3@example.com', + ], + ], + ]); + } + + #[Test] + public function banned() + { + $response = $this->send($this->request('POST', '/api/banned_ips', [ + 'authenticatedAs' => 1, + 'json' => [ + 'data' => [ + 'attributes' => [ + 'address' => '192.168.2.2', + 'reason' => 'Because', + ], + ], + ], + ])); + + $this->assertEquals(201, $response->getStatusCode(), $response->getBody()->getContents()); + + $log = AuditLog::query()->where('action', 'fof_ban_ips.banned')->first(); + $this->assertNotNull($log); + $this->assertEquals(1, $log->actor_id); + $this->assertEquals([ + 'ip' => '192.168.2.2', + 'reason' => 'Because', + ], $log->payload); + $this->assertEquals('127.0.0.1', $log->ip_address); + } + + #[Test] + public function banned_user() + { + $response = $this->send($this->request('POST', '/api/banned_ips', [ + 'authenticatedAs' => 1, + 'json' => [ + 'data' => [ + 'attributes' => [ + 'userId' => 3, + 'address' => '192.168.2.3', + 'reason' => 'Because', + ], + ], + ], + ])); + + $this->assertEquals(201, $response->getStatusCode(), $response->getBody()->getContents()); + + $log = AuditLog::query()->where('action', 'fof_ban_ips.banned')->first(); + $this->assertNotNull($log); + $this->assertEquals(1, $log->actor_id); + $this->assertEquals([ + 'ip' => '192.168.2.3', + 'reason' => 'Because', + 'user_id' => 3, + ], $log->payload); + $this->assertEquals('127.0.0.1', $log->ip_address); + } + + // We can't test unbanned without user because the event is not dispatched + // https://github.com/FriendsOfFlarum/ban-ips/issues/4 + + #[Test] + public function unbanned_user() + { + $response = $this->send($this->request('POST', '/api/banned_ips', [ + 'authenticatedAs' => 1, + 'json' => [ + 'data' => [ + 'attributes' => [ + 'userId' => 3, + 'address' => '192.168.2.4', + 'reason' => 'Because', + ], + ], + ], + ])); + + $this->assertEquals(201, $response->getStatusCode(), $response->getBody()->getContents()); + + $response = $this->send($this->request('POST', '/api/users/3/unban', [ + 'authenticatedAs' => 1, + ])); + + $this->assertEquals(200, $response->getStatusCode(), $response->getBody()->getContents()); + + $log = AuditLog::query()->where('action', 'fof_ban_ips.unbanned')->first(); + $this->assertNotNull($log); + $this->assertEquals(1, $log->actor_id); + $this->assertEquals([ + 'ip' => '192.168.2.4', + 'user_id' => 3, + ], $log->payload); + $this->assertEquals('127.0.0.1', $log->ip_address); + } +}