refactor to use regex as well as plain strings, removed variable replacement lengths

Cette révision appartient à :
Daniel Seifert 2025-01-09 23:33:01 +01:00
Parent 40f439f1c4
révision 4fcef7d244
Signé par: DanielS
ID de la clé GPG: 6A513E13AEE66170
4 fichiers modifiés avec 80 ajouts et 90 suppressions

Voir le fichier

@ -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)
## [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
### Added
- initial implementation
- replace defined fixed strings by replacement characters
- search for urlencoded strings too
- replace regex
- replace plain string (+ urlencoded if required)
- optional replacement string configuration

Voir le fichier

@ -25,7 +25,7 @@ $stack->push(
$myLogger,
new sensitiveMessageFormatter(
'{method} {uri} {req_body} - RESPONSE: {code} - {res_body}',
['myUsername', 'mySecretPassword']
['myUsername', '/my.*Password/i']
)
),
Logger::INFO

Voir le fichier

@ -27,15 +27,15 @@ class sensitiveMessageFormatter extends MessageFormatter
/**
* @param string|null $template
* @param string[] $anonymizations
* @param string|null $replaceChar
* @param string|null $replacement
*/
public function __construct(
?string $template = self::CLF,
protected array $anonymizations = [],
protected ?string $replaceChar = null,
protected ?int $fixedReplacementLength = null
protected ?string $replacement = null
) {
$this->createReplacements($this->anonymizations);
$this->replacement ??= '*****';
$this->convertStringsToRegex($this->anonymizations);
parent::__construct($template);
}
@ -44,27 +44,23 @@ class sensitiveMessageFormatter extends MessageFormatter
* @param string[] $search
* @return void
*/
protected function createReplacements(array $search = []): void
protected function convertStringsToRegex(array $search = []): void
{
$replacements = [];
$this->replaceChar ??= '*';
array_map(
function ($search) use (&$replacements) {
$replacements[$search] = str_repeat(
$this->replaceChar,
$this->fixedReplacementLength ?? strlen($search)
);
$replacements[urlencode($search)] = str_repeat(
$this->replaceChar,
$this->fixedReplacementLength ?? strlen($search)
);
function ($search) use (&$searchStrings) {
if (!$this->stringIsRegexp($search)) {
$searchStrings[] = '/'.preg_quote($search, '/').'/i';
if (urlencode($search) !== $search) {
$searchStrings[] = '/' . preg_quote(urlencode($search), '/') . '/i';
}
} else {
$searchStrings[] = $search;
}
},
$search
);
$this->anonymizations = $replacements;
$this->anonymizations = $searchStrings;
}
public function format(
@ -75,13 +71,18 @@ class sensitiveMessageFormatter extends MessageFormatter
$result = parent::format($request, $response, $error);
if (count($this->anonymizations)) {
$result = str_replace(
array_keys($this->anonymizations),
array_values($this->anonymizations),
$result = preg_replace(
$this->anonymizations,
$this->replacement,
$result
);
}
return $result;
}
protected function stringIsRegexp(string $string): bool
{
return @preg_match($string, '') !== false;
}
}

Voir le fichier

@ -19,6 +19,7 @@ namespace D3\SensitiveMessageFormatter\tests;
use D3\SensitiveMessageFormatter\sensitiveMessageFormatter;
use Generator;
use GuzzleHttp\MessageFormatter;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;
use ReflectionException;
@ -28,90 +29,74 @@ use ReflectionException;
*/
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
* @throws ReflectionException
* @covers \D3\SensitiveMessageFormatter\sensitiveMessageFormatter::__construct
* @dataProvider constructDataProvider
*/
public function testConstruct(): void
public function testConstruct(?string $replacement, string $expected): void
{
$sutMock = $this->getMockBuilder(sensitiveMessageFormatter::class)
->disableOriginalConstructor()
->onlyMethods(['createReplacements'])
->onlyMethods(['convertStringsToRegex'])
->getMock();
$sutMock->expects($this->once())->method('createReplacements');
$sutMock->expects($this->once())->method('convertStringsToRegex');
$sutMock->__construct();
$sutMock->__construct(MessageFormatter::CLF, [], $replacement);
$this->assertSame(
'*',
$expected,
$this->getValue(
$this->sut,
'replaceChar'
$sutMock,
'replacement'
)
);
}
public static function constructDataProvider(): Generator
{
yield 'without custom replacement' => [null, '*****'];
yield 'with custom replacement' => ['-.-', '-.-'];
}
/**
* @test
* @throws ReflectionException
* @dataProvider createReplacementsDataProvider
* @covers \D3\SensitiveMessageFormatter\sensitiveMessageFormatter::createReplacements
* @dataProvider convertStringsToRegexDataProvider
* @covers \D3\SensitiveMessageFormatter\sensitiveMessageFormatter::convertStringsToRegex
*/
public function testCreateReplacements(
public function testConvertStringsToRegex(
array $input,
array $expected,
?string $replacement = null,
?int $fixedReplacementLenth = null
): void
{
if ($replacement) {
$this->setValue(
$this->sut,
'replaceChar',
$replacement
);
}
$this->setValue(
$this->sut,
'fixedReplacementLength',
$fixedReplacementLenth
);
array $expected
): void {
$sutMock = $this->getMockBuilder(sensitiveMessageFormatter::class)
->disableOriginalConstructor()
->getMock();
$this->callMethod(
$this->sut,
'createReplacements',
$sutMock,
'convertStringsToRegex',
[$input]
);
$this->assertSame(
$expected,
$this->getValue(
$this->sut,
$sutMock,
'anonymizations'
)
);
}
public static function createReplacementsDataProvider(): Generator
public static function convertStringsToRegexDataProvider(): Generator
{
yield 'simple' => [['abc'], ['abc' => '***']];
yield 'multiple' => [['def', 'def'], ['def' => '***']];
yield 'urlencoded' => [['1&c'], ['1&c' => '***', '1%26c' => '***' ]];
yield 'different replace char' => [['abcd'], ['abcd' => '####'], '#'];
yield 'fixed replacement lenght' => [['abcd'], ['abcd' => '*******'], '*', 7];
yield 'simple' => [['abc'], ['/abc/i']];
yield 'multiple' => [['def', 'ghi'], ['/def/i', '/ghi/i']];
yield 'urlencoded' => [['1&c'], ['/1&c/i', '/1%26c/i']];
yield 'delimiter' => [['de/fg'], ['/de\/fg/i', '/de%2Ffg/i']];
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
{
$this->callMethod(
$this->sut,
'createReplacements',
[$replacements]
$sut = new sensitiveMessageFormatter(
'{method} {uri} HTTP/{version} {req_headers} {req_body} -- RESPONSE: {code} - {res_headers} {res_body}',
$replacements
);
$request = new Request(
'POST',
'google.com',
['header1' => 'value1', 'header2' => 'val%26ue2'],
'Body value1 + value2'
'Body value1 + value2, Body value1 + value2 aBodyb'
);
$response = new Response(
200,
@ -146,7 +130,7 @@ class sensitiveMessageFormatterTest extends ApiTestCase
'@(\r\n|\r|\n)@',
'==',
$this->callMethod(
$this->sut,
$sut,
'format',
[$request, $response]
)
@ -156,11 +140,23 @@ class sensitiveMessageFormatterTest extends ApiTestCase
public static function formatDataProvider(): Generator
{
yield [
yield 'plain' => [
['value1', 'val&ue2'],
'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: value2 Body ****** + value2',
'header1: *****==header2: ***** Body ***** + value2, Body ***** + value2 aBodyb -- RESPONSE: 200 - HTTP/1.1 200 OK=='.
'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',
];
}
}