refactor to use regex as well as plain strings, removed variable replacement lengths
Cette révision appartient à :
Parent
40f439f1c4
révision
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)
|
||||
|
||||
## [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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
Chargement…
x
Référencer dans un nouveau ticket
Bloquer un utilisateur