refactor to use regex as well as plain strings, removed variable replacement lengths
Dieser Commit ist enthalten in:
Ursprung
40f439f1c4
Commit
4fcef7d244
13
CHANGELOG.md
13
CHANGELOG.md
@ -6,16 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased](https://git.d3data.de/D3Public/sensitiveMessageFormatter/compare/1.1.0...rel_1.x)
|
## [Unreleased](https://git.d3data.de/D3Public/sensitiveMessageFormatter/compare/1.1.0...rel_1.x)
|
||||||
|
|
||||||
## [1.1.0](https://git.d3data.de/D3Public/sensitiveMessageFormatter/compare/1.0.1...1.1.0) - 2025-01-07
|
|
||||||
### Added
|
|
||||||
- optional fixed length replacement
|
|
||||||
|
|
||||||
## [1.0.1](https://git.d3data.de/D3Public/sensitiveMessageFormatter/compare/1.0.0...1.0.1) - 2025-01-01
|
|
||||||
### Changed
|
|
||||||
- Guzzle dependency from 7.0
|
|
||||||
|
|
||||||
## [1.0.0](https://git.d3data.de/D3Public/sensitiveMessageFormatter/releases/tag/1.0.0) - 2024-12-23
|
## [1.0.0](https://git.d3data.de/D3Public/sensitiveMessageFormatter/releases/tag/1.0.0) - 2024-12-23
|
||||||
### Added
|
### Added
|
||||||
- initial implementation
|
- initial implementation
|
||||||
- replace defined fixed strings by replacement characters
|
- replace regex
|
||||||
- search for urlencoded strings too
|
- replace plain string (+ urlencoded if required)
|
||||||
|
- optional replacement string configuration
|
||||||
|
@ -25,7 +25,7 @@ $stack->push(
|
|||||||
$myLogger,
|
$myLogger,
|
||||||
new sensitiveMessageFormatter(
|
new sensitiveMessageFormatter(
|
||||||
'{method} {uri} {req_body} - RESPONSE: {code} - {res_body}',
|
'{method} {uri} {req_body} - RESPONSE: {code} - {res_body}',
|
||||||
['myUsername', 'mySecretPassword']
|
['myUsername', '/my.*Password/i']
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
Logger::INFO
|
Logger::INFO
|
||||||
|
@ -27,15 +27,15 @@ class sensitiveMessageFormatter extends MessageFormatter
|
|||||||
/**
|
/**
|
||||||
* @param string|null $template
|
* @param string|null $template
|
||||||
* @param string[] $anonymizations
|
* @param string[] $anonymizations
|
||||||
* @param string|null $replaceChar
|
* @param string|null $replacement
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
?string $template = self::CLF,
|
?string $template = self::CLF,
|
||||||
protected array $anonymizations = [],
|
protected array $anonymizations = [],
|
||||||
protected ?string $replaceChar = null,
|
protected ?string $replacement = null
|
||||||
protected ?int $fixedReplacementLength = null
|
|
||||||
) {
|
) {
|
||||||
$this->createReplacements($this->anonymizations);
|
$this->replacement ??= '*****';
|
||||||
|
$this->convertStringsToRegex($this->anonymizations);
|
||||||
|
|
||||||
parent::__construct($template);
|
parent::__construct($template);
|
||||||
}
|
}
|
||||||
@ -44,27 +44,23 @@ class sensitiveMessageFormatter extends MessageFormatter
|
|||||||
* @param string[] $search
|
* @param string[] $search
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
protected function createReplacements(array $search = []): void
|
protected function convertStringsToRegex(array $search = []): void
|
||||||
{
|
{
|
||||||
$replacements = [];
|
|
||||||
|
|
||||||
$this->replaceChar ??= '*';
|
|
||||||
|
|
||||||
array_map(
|
array_map(
|
||||||
function ($search) use (&$replacements) {
|
function ($search) use (&$searchStrings) {
|
||||||
$replacements[$search] = str_repeat(
|
if (!$this->stringIsRegexp($search)) {
|
||||||
$this->replaceChar,
|
$searchStrings[] = '/'.preg_quote($search, '/').'/i';
|
||||||
$this->fixedReplacementLength ?? strlen($search)
|
if (urlencode($search) !== $search) {
|
||||||
);
|
$searchStrings[] = '/' . preg_quote(urlencode($search), '/') . '/i';
|
||||||
$replacements[urlencode($search)] = str_repeat(
|
}
|
||||||
$this->replaceChar,
|
} else {
|
||||||
$this->fixedReplacementLength ?? strlen($search)
|
$searchStrings[] = $search;
|
||||||
);
|
}
|
||||||
},
|
},
|
||||||
$search
|
$search
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->anonymizations = $replacements;
|
$this->anonymizations = $searchStrings;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function format(
|
public function format(
|
||||||
@ -75,13 +71,18 @@ class sensitiveMessageFormatter extends MessageFormatter
|
|||||||
$result = parent::format($request, $response, $error);
|
$result = parent::format($request, $response, $error);
|
||||||
|
|
||||||
if (count($this->anonymizations)) {
|
if (count($this->anonymizations)) {
|
||||||
$result = str_replace(
|
$result = preg_replace(
|
||||||
array_keys($this->anonymizations),
|
$this->anonymizations,
|
||||||
array_values($this->anonymizations),
|
$this->replacement,
|
||||||
$result
|
$result
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function stringIsRegexp(string $string): bool
|
||||||
|
{
|
||||||
|
return @preg_match($string, '') !== false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ namespace D3\SensitiveMessageFormatter\tests;
|
|||||||
|
|
||||||
use D3\SensitiveMessageFormatter\sensitiveMessageFormatter;
|
use D3\SensitiveMessageFormatter\sensitiveMessageFormatter;
|
||||||
use Generator;
|
use Generator;
|
||||||
|
use GuzzleHttp\MessageFormatter;
|
||||||
use GuzzleHttp\Psr7\Request;
|
use GuzzleHttp\Psr7\Request;
|
||||||
use GuzzleHttp\Psr7\Response;
|
use GuzzleHttp\Psr7\Response;
|
||||||
use ReflectionException;
|
use ReflectionException;
|
||||||
@ -28,90 +29,74 @@ use ReflectionException;
|
|||||||
*/
|
*/
|
||||||
class sensitiveMessageFormatterTest extends ApiTestCase
|
class sensitiveMessageFormatterTest extends ApiTestCase
|
||||||
{
|
{
|
||||||
protected sensitiveMessageFormatter $sut;
|
|
||||||
|
|
||||||
public function setUp(): void
|
|
||||||
{
|
|
||||||
parent::setUp();
|
|
||||||
|
|
||||||
$this->sut = new sensitiveMessageFormatter(
|
|
||||||
'{method} {uri} HTTP/{version} {req_headers} {req_body} -- RESPONSE: {code} - {res_headers} {res_body}'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @test
|
* @test
|
||||||
* @throws ReflectionException
|
* @throws ReflectionException
|
||||||
* @covers \D3\SensitiveMessageFormatter\sensitiveMessageFormatter::__construct
|
* @covers \D3\SensitiveMessageFormatter\sensitiveMessageFormatter::__construct
|
||||||
|
* @dataProvider constructDataProvider
|
||||||
*/
|
*/
|
||||||
public function testConstruct(): void
|
public function testConstruct(?string $replacement, string $expected): void
|
||||||
{
|
{
|
||||||
$sutMock = $this->getMockBuilder(sensitiveMessageFormatter::class)
|
$sutMock = $this->getMockBuilder(sensitiveMessageFormatter::class)
|
||||||
->disableOriginalConstructor()
|
->disableOriginalConstructor()
|
||||||
->onlyMethods(['createReplacements'])
|
->onlyMethods(['convertStringsToRegex'])
|
||||||
->getMock();
|
->getMock();
|
||||||
$sutMock->expects($this->once())->method('createReplacements');
|
$sutMock->expects($this->once())->method('convertStringsToRegex');
|
||||||
|
|
||||||
$sutMock->__construct();
|
$sutMock->__construct(MessageFormatter::CLF, [], $replacement);
|
||||||
|
|
||||||
$this->assertSame(
|
$this->assertSame(
|
||||||
'*',
|
$expected,
|
||||||
$this->getValue(
|
$this->getValue(
|
||||||
$this->sut,
|
$sutMock,
|
||||||
'replaceChar'
|
'replacement'
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function constructDataProvider(): Generator
|
||||||
|
{
|
||||||
|
yield 'without custom replacement' => [null, '*****'];
|
||||||
|
yield 'with custom replacement' => ['-.-', '-.-'];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @test
|
* @test
|
||||||
* @throws ReflectionException
|
* @throws ReflectionException
|
||||||
* @dataProvider createReplacementsDataProvider
|
* @dataProvider convertStringsToRegexDataProvider
|
||||||
* @covers \D3\SensitiveMessageFormatter\sensitiveMessageFormatter::createReplacements
|
* @covers \D3\SensitiveMessageFormatter\sensitiveMessageFormatter::convertStringsToRegex
|
||||||
*/
|
*/
|
||||||
public function testCreateReplacements(
|
public function testConvertStringsToRegex(
|
||||||
array $input,
|
array $input,
|
||||||
array $expected,
|
array $expected
|
||||||
?string $replacement = null,
|
): void {
|
||||||
?int $fixedReplacementLenth = null
|
$sutMock = $this->getMockBuilder(sensitiveMessageFormatter::class)
|
||||||
): void
|
->disableOriginalConstructor()
|
||||||
{
|
->getMock();
|
||||||
if ($replacement) {
|
|
||||||
$this->setValue(
|
|
||||||
$this->sut,
|
|
||||||
'replaceChar',
|
|
||||||
$replacement
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->setValue(
|
|
||||||
$this->sut,
|
|
||||||
'fixedReplacementLength',
|
|
||||||
$fixedReplacementLenth
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->callMethod(
|
$this->callMethod(
|
||||||
$this->sut,
|
$sutMock,
|
||||||
'createReplacements',
|
'convertStringsToRegex',
|
||||||
[$input]
|
[$input]
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->assertSame(
|
$this->assertSame(
|
||||||
$expected,
|
$expected,
|
||||||
$this->getValue(
|
$this->getValue(
|
||||||
$this->sut,
|
$sutMock,
|
||||||
'anonymizations'
|
'anonymizations'
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function createReplacementsDataProvider(): Generator
|
public static function convertStringsToRegexDataProvider(): Generator
|
||||||
{
|
{
|
||||||
yield 'simple' => [['abc'], ['abc' => '***']];
|
yield 'simple' => [['abc'], ['/abc/i']];
|
||||||
yield 'multiple' => [['def', 'def'], ['def' => '***']];
|
yield 'multiple' => [['def', 'ghi'], ['/def/i', '/ghi/i']];
|
||||||
yield 'urlencoded' => [['1&c'], ['1&c' => '***', '1%26c' => '***' ]];
|
yield 'urlencoded' => [['1&c'], ['/1&c/i', '/1%26c/i']];
|
||||||
yield 'different replace char' => [['abcd'], ['abcd' => '####'], '#'];
|
yield 'delimiter' => [['de/fg'], ['/de\/fg/i', '/de%2Ffg/i']];
|
||||||
yield 'fixed replacement lenght' => [['abcd'], ['abcd' => '*******'], '*', 7];
|
yield 'regex' => [['/abc/mi'], ['/abc/mi']];
|
||||||
|
yield 'mixed' => [['/abc/mi', 'def', '/ghi/mi'], ['/abc/mi', '/def/i', '/ghi/mi']];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -122,17 +107,16 @@ class sensitiveMessageFormatterTest extends ApiTestCase
|
|||||||
*/
|
*/
|
||||||
public function testFormat(array $replacements, $expected): void
|
public function testFormat(array $replacements, $expected): void
|
||||||
{
|
{
|
||||||
$this->callMethod(
|
$sut = new sensitiveMessageFormatter(
|
||||||
$this->sut,
|
'{method} {uri} HTTP/{version} {req_headers} {req_body} -- RESPONSE: {code} - {res_headers} {res_body}',
|
||||||
'createReplacements',
|
$replacements
|
||||||
[$replacements]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
$request = new Request(
|
$request = new Request(
|
||||||
'POST',
|
'POST',
|
||||||
'google.com',
|
'google.com',
|
||||||
['header1' => 'value1', 'header2' => 'val%26ue2'],
|
['header1' => 'value1', 'header2' => 'val%26ue2'],
|
||||||
'Body value1 + value2'
|
'Body value1 + value2, Body value1 + value2 aBodyb'
|
||||||
);
|
);
|
||||||
$response = new Response(
|
$response = new Response(
|
||||||
200,
|
200,
|
||||||
@ -146,7 +130,7 @@ class sensitiveMessageFormatterTest extends ApiTestCase
|
|||||||
'@(\r\n|\r|\n)@',
|
'@(\r\n|\r|\n)@',
|
||||||
'==',
|
'==',
|
||||||
$this->callMethod(
|
$this->callMethod(
|
||||||
$this->sut,
|
$sut,
|
||||||
'format',
|
'format',
|
||||||
[$request, $response]
|
[$request, $response]
|
||||||
)
|
)
|
||||||
@ -156,11 +140,23 @@ class sensitiveMessageFormatterTest extends ApiTestCase
|
|||||||
|
|
||||||
public static function formatDataProvider(): Generator
|
public static function formatDataProvider(): Generator
|
||||||
{
|
{
|
||||||
yield [
|
yield 'plain' => [
|
||||||
['value1', 'val&ue2'],
|
['value1', 'val&ue2'],
|
||||||
'POST google.com HTTP/1.1 POST google.com HTTP/1.1=='.
|
'POST google.com HTTP/1.1 POST google.com HTTP/1.1=='.
|
||||||
'header1: ******==header2: ******* Body ****** + value2 -- RESPONSE: 200 - HTTP/1.1 200 OK=='.
|
'header1: *****==header2: ***** Body ***** + value2, Body ***** + value2 aBodyb -- RESPONSE: 200 - HTTP/1.1 200 OK=='.
|
||||||
'header1: ******==header2: value2 Body ****** + value2',
|
'header1: *****==header2: value2 Body ***** + value2',
|
||||||
|
];
|
||||||
|
yield 'regex placeholders' => [
|
||||||
|
['/go.?gle/i'],
|
||||||
|
'POST *****.com HTTP/1.1 POST *****.com HTTP/1.1=='.
|
||||||
|
'header1: value1==header2: val%26ue2 Body value1 + value2, Body value1 + value2 aBodyb -- RESPONSE: 200 - HTTP/1.1 200 OK=='.
|
||||||
|
'header1: value1==header2: value2 Body value1 + value2',
|
||||||
|
];
|
||||||
|
yield 'regex control chars + look behind' => [
|
||||||
|
['/\bbody\b/i', '/(?<=RESPONSE:\s)200/i'],
|
||||||
|
'POST google.com HTTP/1.1 POST google.com HTTP/1.1=='.
|
||||||
|
'header1: value1==header2: val%26ue2 ***** value1 + value2, ***** value1 + value2 aBodyb -- RESPONSE: ***** - HTTP/1.1 200 OK=='.
|
||||||
|
'header1: value1==header2: value2 ***** value1 + value2',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Laden…
x
In neuem Issue referenzieren
Einen Benutzer sperren