mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-11-25 08:53:55 +08:00
Added ability to adjust stored IP address precision
Included tests to cover. For #3560
This commit is contained in:
parent
67d12cc1df
commit
4e8995c3d0
|
@ -357,3 +357,11 @@ API_REQUESTS_PER_MIN=180
|
|||
# user identifier (Username or email).
|
||||
LOG_FAILED_LOGIN_MESSAGE=false
|
||||
LOG_FAILED_LOGIN_CHANNEL=errorlog_plain_webserver
|
||||
|
||||
# Alter the precision of IP addresses stored by BookStack.
|
||||
# Should be a number between 0 and 4, where 4 retains the full IP address
|
||||
# and 0 completely hides the IP address. As an examples, a value of 2 for the
|
||||
# IP address '146.191.42.4' would result in '146.191.x.x' being logged.
|
||||
# For the IP address '2001:db8:85a3:8d3:1319:8a2e:370:7348' this would result as:
|
||||
# '2001:db8:85a3:8d3:x:x:x:x'
|
||||
IP_ADDRESS_PRECISION=4
|
|
@ -40,12 +40,10 @@ class ActivityLogger
|
|||
*/
|
||||
protected function newActivityForUser(string $type): Activity
|
||||
{
|
||||
$ip = request()->ip() ?? '';
|
||||
|
||||
return (new Activity())->forceFill([
|
||||
'type' => strtolower($type),
|
||||
'user_id' => user()->id,
|
||||
'ip' => config('app.env') === 'demo' ? '127.0.0.1' : $ip,
|
||||
'ip' => IpFormatter::fromCurrentRequest()->format(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
81
app/Actions/IpFormatter.php
Normal file
81
app/Actions/IpFormatter.php
Normal file
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
namespace BookStack\Actions;
|
||||
|
||||
class IpFormatter
|
||||
{
|
||||
protected string $ip;
|
||||
protected int $precision;
|
||||
|
||||
public function __construct(string $ip, int $precision)
|
||||
{
|
||||
$this->ip = trim($ip);
|
||||
$this->precision = max(0, min($precision, 4));
|
||||
}
|
||||
|
||||
public function format(): string
|
||||
{
|
||||
if (empty($this->ip) || $this->precision === 4) {
|
||||
return $this->ip;
|
||||
}
|
||||
|
||||
return $this->isIpv6() ? $this->maskIpv6() : $this->maskIpv4();
|
||||
}
|
||||
|
||||
protected function maskIpv4(): string
|
||||
{
|
||||
$exploded = $this->explodeAndExpandIp('.', 4);
|
||||
$maskGroupCount = min( 4 - $this->precision, count($exploded));
|
||||
|
||||
for ($i = 0; $i < $maskGroupCount; $i++) {
|
||||
$exploded[3 - $i] = 'x';
|
||||
}
|
||||
|
||||
return implode('.', $exploded);
|
||||
}
|
||||
|
||||
protected function maskIpv6(): string
|
||||
{
|
||||
$exploded = $this->explodeAndExpandIp(':', 8);
|
||||
$maskGroupCount = min(8 - ($this->precision * 2), count($exploded));
|
||||
|
||||
for ($i = 0; $i < $maskGroupCount; $i++) {
|
||||
$exploded[7 - $i] = 'x';
|
||||
}
|
||||
|
||||
return implode(':', $exploded);
|
||||
}
|
||||
|
||||
protected function isIpv6(): bool
|
||||
{
|
||||
return strpos($this->ip, ':') !== false;
|
||||
}
|
||||
|
||||
protected function explodeAndExpandIp(string $separator, int $targetLength): array
|
||||
{
|
||||
$exploded = explode($separator, $this->ip);
|
||||
|
||||
while (count($exploded) < $targetLength) {
|
||||
$emptyIndex = array_search('', $exploded) ?: count($exploded) - 1;
|
||||
array_splice($exploded, $emptyIndex, 0, '0');
|
||||
}
|
||||
|
||||
$emptyIndex = array_search('', $exploded);
|
||||
if ($emptyIndex !== false) {
|
||||
$exploded[$emptyIndex] = '0';
|
||||
}
|
||||
|
||||
return $exploded;
|
||||
}
|
||||
|
||||
public static function fromCurrentRequest(): self
|
||||
{
|
||||
$ip = request()->ip() ?? '';
|
||||
|
||||
if (config('app.env') === 'demo') {
|
||||
$ip = '127.0.0.1';
|
||||
}
|
||||
|
||||
return new self($ip, config('app.ip_address_precision'));
|
||||
}
|
||||
}
|
|
@ -64,6 +64,10 @@ return [
|
|||
// Current host and source for the "DRAWIO" setting will be auto-appended to the sources configured.
|
||||
'iframe_sources' => env('ALLOWED_IFRAME_SOURCES', 'https://*.draw.io https://*.youtube.com https://*.youtube-nocookie.com https://*.vimeo.com'),
|
||||
|
||||
// Alter the precision of IP addresses stored by BookStack.
|
||||
// Integer value between 0 (IP hidden) to 4 (Full IP usage)
|
||||
'ip_address_precision' => env('IP_ADDRESS_PRECISION', 4),
|
||||
|
||||
// Application timezone for back-end date functions.
|
||||
'timezone' => env('APP_TIMEZONE', 'UTC'),
|
||||
|
||||
|
|
|
@ -57,5 +57,6 @@
|
|||
<server name="LOG_FAILED_LOGIN_CHANNEL" value="testing"/>
|
||||
<server name="WKHTMLTOPDF" value="false"/>
|
||||
<server name="APP_DEFAULT_DARK_MODE" value="false"/>
|
||||
<server name="IP_ADDRESS_PRECISION" value="4"/>
|
||||
</php>
|
||||
</phpunit>
|
||||
|
|
|
@ -218,4 +218,27 @@ class AuditLogTest extends TestCase
|
|||
'entity_id' => $page->id,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_ip_address_respects_precision_setting()
|
||||
{
|
||||
config()->set('app.proxies', '*');
|
||||
config()->set('app.ip_address_precision', 2);
|
||||
$editor = $this->getEditor();
|
||||
/** @var Page $page */
|
||||
$page = Page::query()->first();
|
||||
|
||||
$this->actingAs($editor)->put($page->getUrl(), [
|
||||
'name' => 'Updated page',
|
||||
'html' => '<p>Updated content</p>',
|
||||
], [
|
||||
'X-Forwarded-For' => '192.123.45.1',
|
||||
])->assertRedirect($page->refresh()->getUrl());
|
||||
|
||||
$this->assertDatabaseHas('activities', [
|
||||
'type' => ActivityType::PAGE_UPDATE,
|
||||
'ip' => '192.123.x.x',
|
||||
'user_id' => $editor->id,
|
||||
'entity_id' => $page->id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
32
tests/Unit/IpFormatterTest.php
Normal file
32
tests/Unit/IpFormatterTest.php
Normal file
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use BookStack\Actions\IpFormatter;
|
||||
use Tests\TestCase;
|
||||
|
||||
class IpFormatterTest extends TestCase
|
||||
{
|
||||
public function test_ips_formatted_as_expected()
|
||||
{
|
||||
$this->assertEquals('192.123.45.5', (new IpFormatter('192.123.45.5', 4))->format());
|
||||
$this->assertEquals('192.123.45.x', (new IpFormatter('192.123.45.5', 3))->format());
|
||||
$this->assertEquals('192.123.x.x', (new IpFormatter('192.123.45.5', 2))->format());
|
||||
$this->assertEquals('192.x.x.x', (new IpFormatter('192.123.45.5', 1))->format());
|
||||
$this->assertEquals('x.x.x.x', (new IpFormatter('192.123.45.5', 0))->format());
|
||||
|
||||
$ipv6 = '2001:db8:85a3:8d3:1319:8a2e:370:7348';
|
||||
$this->assertEquals($ipv6, (new IpFormatter($ipv6, 4))->format());
|
||||
$this->assertEquals('2001:db8:85a3:8d3:1319:8a2e:x:x', (new IpFormatter($ipv6, 3))->format());
|
||||
$this->assertEquals('2001:db8:85a3:8d3:x:x:x:x', (new IpFormatter($ipv6, 2))->format());
|
||||
$this->assertEquals('2001:db8:x:x:x:x:x:x', (new IpFormatter($ipv6, 1))->format());
|
||||
$this->assertEquals('x:x:x:x:x:x:x:x', (new IpFormatter($ipv6, 0))->format());
|
||||
}
|
||||
|
||||
public function test_shortened_ipv6_addresses_expands_as_expected()
|
||||
{
|
||||
$this->assertEquals('2001:0:0:0:0:0:x:x', (new IpFormatter('2001::370:7348', 3))->format());
|
||||
$this->assertEquals('2001:0:0:0:0:85a3:x:x', (new IpFormatter('2001::85a3:370:7348', 3))->format());
|
||||
$this->assertEquals('2001:0:x:x:x:x:x:x', (new IpFormatter('2001::', 1))->format());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user