From d6782ce67258faa13f0628be22ccc64bb425b02b Mon Sep 17 00:00:00 2001
From: Anders Bilfeldt
Date: Sat, 18 Dec 2021 19:36:14 +0100
Subject: [PATCH 1/5] Implement new MessageAccessor class
---
composer.json | 1 +
src/MessageAccessor.php | 172 ++++++++++++++++++++++++++++++++++
tests/MessageAccessorTest.php | 161 +++++++++++++++++++++++++++++++
3 files changed, 334 insertions(+)
create mode 100644 src/MessageAccessor.php
create mode 100644 tests/MessageAccessorTest.php
diff --git a/composer.json b/composer.json
index f00bd7f..218d1fa 100644
--- a/composer.json
+++ b/composer.json
@@ -21,6 +21,7 @@
],
"require": {
"php": "^7.4|^8.0",
+ "ext-json": "*",
"guzzlehttp/guzzle": "^7.2",
"illuminate/http": "^8.0",
"illuminate/support": "^8.0",
diff --git a/src/MessageAccessor.php b/src/MessageAccessor.php
new file mode 100644
index 0000000..949b82c
--- /dev/null
+++ b/src/MessageAccessor.php
@@ -0,0 +1,172 @@
+values = $values;
+ $this->queryFilters = $queryFilters;
+ $this->headersFilters = $headersFilters;
+ $this->jsonFilters = $jsonFilers;
+ $this->replace = $replace;
+ }
+
+ public function getUri(RequestInterface $request): UriInterface
+ {
+ $uri = $request->getUri();
+ parse_str($uri->getQuery(), $query);
+
+ return $uri
+ ->withUserInfo($this->replace($this->values, $this->replace, $uri->getUserInfo()))
+ ->withHost($this->replace($this->values, $this->replace, $uri->getHost()))
+ ->withPath($this->replace($this->values, $this->replace, $uri->getPath()))
+ ->withQuery(Arr::query($this->replaceParameters($query, $this->queryFilters, $this->values, $this->replace)));
+ }
+
+ public function getBase(RequestInterface $request): string
+ {
+ $uri = $this->getUri($request);
+
+ $base = '';
+ if ($uri->getScheme()) {
+ $base .= $uri->getScheme() . '://';
+ }
+ if ($uri->getUserInfo()) {
+ $base .= $uri->getUserInfo() . '@';
+ }
+ if ($uri->getHost()) {
+ $base .= $uri->getHost();
+ }
+ if ($uri->getPort()) {
+ $base .= ':' . $uri->getPort();
+ }
+
+ return $base;
+ }
+
+ public function getQuery(RequestInterface $request): array
+ {
+ parse_str($this->getUri($request)->getQuery(), $query);
+
+ return $query;
+ }
+
+ public function getHeaders(MessageInterface $message): array
+ {
+ foreach ($this->headersFilters as $headersFilter) {
+ if ($message->hasHeader($headersFilter)) {
+ $message = $message->withHeader($headersFilter, $this->replace);
+ }
+ }
+
+ // Header filter applied above as this is an array with two layers
+ return $this->replaceParameters($message->getHeaders(), [], $this->values, $this->replace, false);
+ }
+
+ /**
+ * Determine if the request is JSON.
+ *
+ * @see vendor/laravel/framework/src/Illuminate/Http/Client/Request.php
+ * @param MessageInterface $message
+ * @return bool
+ */
+ public function isJson(MessageInterface $message): bool
+ {
+ return $message->hasHeader('Content-Type') &&
+ Str::contains($message->getHeaderLine('Content-Type'), 'json');
+ }
+
+ public function getJson(MessageInterface $message): ?array
+ {
+ return $this->replaceParameters(
+ json_decode($message->getBody(), true),
+ $this->jsonFilters,
+ $this->values,
+ $this->replace
+ );
+ }
+
+ public function getContent(MessageInterface $message): string
+ {
+ if ($this->isJson($message)) {
+ $body = json_encode($this->getJson($message));
+ } else {
+ $body = $message->getBody()->getContents();
+ foreach($this->values as $value) {
+ $body = $this->replace($value, $this->replace, $body);
+ }
+ }
+
+ return $body;
+ }
+
+ public function filter(MessageInterface $message): MessageInterface
+ {
+ if ($this->isJson($message)) {
+ $body = json_encode($this->getJson($message));
+ } else {
+ $body = $message->getBody()->getContents();
+ foreach ($this->values as $value) {
+ $body = $this->replace($value, $this->replace, $body);
+ }
+ }
+
+ foreach ($this->getHeaders($message) as $header => $values) {
+ $message = $message->withHeader($header, $values);
+ }
+
+ return $message->withBody(Utils::streamFor($body));
+ }
+
+ protected function replaceParameters(array $array, array $parameters, array $values, string $replace, $strict = true): array
+ {
+ foreach ($parameters as $parameter) {
+ if (data_get($array, $parameter, null)) {
+ data_set($array, $parameter, $replace);
+ }
+ }
+
+ array_walk_recursive( $array, function (&$item, $key) use ($values, $replace, $strict) {
+ foreach ($values as $value) {
+ if (! $strict && str_contains($item, $value)) {
+ $item = str_replace($value, $replace, $item);
+ } elseif ($strict && $value === $item) {
+ $item = $replace;
+ }
+ }
+
+ return $item;
+ });
+
+ return $array;
+ }
+
+ protected function replace($search, $replace, ?string $subject): ?string
+ {
+ if (is_null($subject)) {
+ return null;
+ }
+
+ return str_replace($search, $replace, $subject);
+ }
+}
diff --git a/tests/MessageAccessorTest.php b/tests/MessageAccessorTest.php
new file mode 100644
index 0000000..32a98ea
--- /dev/null
+++ b/tests/MessageAccessorTest.php
@@ -0,0 +1,161 @@
+messageAccessor = new MessageAccessor(
+ ['secret'],
+ ['search', 'filter.field2'],
+ ['Authorization'],
+ ['data.baz.*.password']
+ );
+
+ $this->request = new Request(
+ 'POST',
+ 'https://user:secret@secret.example.com:9000/some-path/secret/should-not-be-removed?test=true&search=foo&filter[field1]=A&filter[field2]=B#anchor',
+ [
+ 'Accept' => 'application/json',
+ 'Content-Type' => 'application/json',
+ 'Authorization' => 'Bearer 1234567890',
+ ],
+ json_encode([
+ 'data' => [
+ 'foo' => 'bar',
+ 'baz' => [
+ [
+ 'field_1' => 'value1',
+ 'field_2' => 'value2',
+ 'password' => '123456',
+ 'secret' => 'this is not for everyone',
+ ]
+ ]
+ ],
+ ])
+ );
+ }
+
+ public function test_get_uri()
+ {
+ $uri = $this->messageAccessor->getUri($this->request);
+
+ $this->assertEquals('https', $uri->getScheme());
+ $this->assertEquals('user%3A********@********.example.com:9000', $uri->getAuthority());
+ $this->assertEquals('user%3A********', $uri->getUserInfo());
+ $this->assertEquals('********.example.com', $uri->getHost());
+ $this->assertEquals('9000', $uri->getPort());
+ $this->assertEquals('/some-path/********/should-not-be-removed', $uri->getPath());
+ $this->assertEquals('test=true&search=********&filter[field1]=A&filter[field2]=********', urldecode($uri->getQuery()));
+ $this->assertEquals('anchor', $uri->getFragment());
+ }
+
+ public function test_get_base()
+ {
+ $this->assertEquals('https://user:********@********.example.com:9000',
+ urldecode($this->messageAccessor->getBase($this->request)));
+ }
+
+ public function test_get_query()
+ {
+ $query = $this->messageAccessor->getQuery($this->request);
+
+ $this->assertIsArray($query);
+ $this->assertEquals([
+ 'test' => 'true',
+ 'search' => '********',
+ 'filter' => [
+ 'field1' => 'A',
+ 'field2' => '********',
+ ],
+ ], $query);
+ }
+
+ public function test_get_headers()
+ {
+ $headers = $this->messageAccessor->getHeaders($this->request);
+
+ $this->assertIsArray($headers);
+ $this->assertEquals([
+ 'Accept' => ['application/json'],
+ 'Content-Type' => ['application/json'],
+ 'Authorization' => ['********'],
+ 'Host' => ['********.example.com:9000'],
+ ], $headers);
+ }
+
+ public function test_is_json()
+ {
+ $this->assertTrue($this->messageAccessor->isJson($this->request));
+ $this->assertFalse($this->messageAccessor->isJson(new Response(200, ['Content-Type' => 'text/html'], '')));
+ }
+
+ public function test_get_json()
+ {
+ $json = $this->messageAccessor->getJson($this->request);
+
+ $this->assertIsArray($json);
+ $this->assertEquals([
+ 'data' => [
+ 'foo' => 'bar',
+ 'baz' => [
+ [
+ 'field_1' => 'value1',
+ 'field_2' => 'value2',
+ 'password' => '********',
+ 'secret' => 'this is not for everyone', // Note that keys are NOT filtered
+ ]
+ ]
+ ],
+ ], $json);
+ }
+
+ public function test_get_content()
+ {
+ $content = $this->messageAccessor->getContent($this->request);
+
+ $this->assertEquals(json_encode([
+ 'data' => [
+ 'foo' => 'bar',
+ 'baz' => [
+ [
+ 'field_1' => 'value1',
+ 'field_2' => 'value2',
+ 'password' => '********',
+ 'secret' => 'this is not for everyone', // Note that keys are NOT filtered
+ ]
+ ]
+ ],
+ ]), $content);
+ }
+
+ public function test_filter()
+ {
+ $request = $this->messageAccessor->filter($this->request);
+
+ // Note that this is require to use double quotes for the Carriage Return (\r) to work
+ $output = "POST /some-path/secret/should-not-be-removed?test=true&search=foo&filter%5Bfield1%5D=A&filter%5Bfield2%5D=B HTTP/1.1\r
+Host: ********.example.com:9000\r
+Accept: application/json\r
+Content-Type: application/json\r
+Authorization: ********\r
+\r
+{\"data\":{\"foo\":\"bar\",\"baz\":[{\"field_1\":\"value1\",\"field_2\":\"value2\",\"password\":\"********\",\"secret\":\"this is not for everyone\"}]}}";
+
+ $this->assertEquals($output, Message::toString($request));
+ }
+}
From 49010daad60c1d99cad85c546f2cb7cff89bdb75 Mon Sep 17 00:00:00 2001
From: Anders Bilfeldt
Date: Sat, 18 Dec 2021 19:43:23 +0100
Subject: [PATCH 2/5] Psalm fixes
---
src/MessageAccessor.php | 17 +++++------------
1 file changed, 5 insertions(+), 12 deletions(-)
diff --git a/src/MessageAccessor.php b/src/MessageAccessor.php
index 949b82c..72eca77 100644
--- a/src/MessageAccessor.php
+++ b/src/MessageAccessor.php
@@ -99,7 +99,7 @@ public function isJson(MessageInterface $message): bool
public function getJson(MessageInterface $message): ?array
{
return $this->replaceParameters(
- json_decode($message->getBody(), true),
+ json_decode($message->getBody()->__toString(), true),
$this->jsonFilters,
$this->values,
$this->replace
@@ -111,9 +111,9 @@ public function getContent(MessageInterface $message): string
if ($this->isJson($message)) {
$body = json_encode($this->getJson($message));
} else {
- $body = $message->getBody()->getContents();
+ $body = $message->getBody()->__toString();
foreach($this->values as $value) {
- $body = $this->replace($value, $this->replace, $body);
+ $body = str_replace($value, $this->replace, $body);
}
}
@@ -122,14 +122,7 @@ public function getContent(MessageInterface $message): string
public function filter(MessageInterface $message): MessageInterface
{
- if ($this->isJson($message)) {
- $body = json_encode($this->getJson($message));
- } else {
- $body = $message->getBody()->getContents();
- foreach ($this->values as $value) {
- $body = $this->replace($value, $this->replace, $body);
- }
- }
+ $body = $this->getContent($message);
foreach ($this->getHeaders($message) as $header => $values) {
$message = $message->withHeader($header, $values);
@@ -146,7 +139,7 @@ protected function replaceParameters(array $array, array $parameters, array $val
}
}
- array_walk_recursive( $array, function (&$item, $key) use ($values, $replace, $strict) {
+ array_walk_recursive( $array, function (&$item) use ($values, $replace, $strict) {
foreach ($values as $value) {
if (! $strict && str_contains($item, $value)) {
$item = str_replace($value, $replace, $item);
From 33741234f8b7c9fb94b7fcf986637543b469840c Mon Sep 17 00:00:00 2001
From: Bilfeldt
Date: Sat, 18 Dec 2021 18:44:43 +0000
Subject: [PATCH 3/5] Apply fixes from StyleCI
---
src/MessageAccessor.php | 24 ++++++++++--------
tests/MessageAccessorTest.php | 48 ++++++++++++++++++-----------------
2 files changed, 38 insertions(+), 34 deletions(-)
diff --git a/src/MessageAccessor.php b/src/MessageAccessor.php
index 72eca77..338791b 100644
--- a/src/MessageAccessor.php
+++ b/src/MessageAccessor.php
@@ -18,12 +18,12 @@ class MessageAccessor
private string $replace;
public function __construct(
- array $values = [],
- array $queryFilters = [],
- array $headersFilters = [],
- array $jsonFilers = [],
+ array $values = [],
+ array $queryFilters = [],
+ array $headersFilters = [],
+ array $jsonFilers = [],
string $replace = '********'
- ){
+ ) {
$this->values = $values;
$this->queryFilters = $queryFilters;
$this->headersFilters = $headersFilters;
@@ -49,16 +49,16 @@ public function getBase(RequestInterface $request): string
$base = '';
if ($uri->getScheme()) {
- $base .= $uri->getScheme() . '://';
+ $base .= $uri->getScheme().'://';
}
if ($uri->getUserInfo()) {
- $base .= $uri->getUserInfo() . '@';
+ $base .= $uri->getUserInfo().'@';
}
if ($uri->getHost()) {
$base .= $uri->getHost();
}
if ($uri->getPort()) {
- $base .= ':' . $uri->getPort();
+ $base .= ':'.$uri->getPort();
}
return $base;
@@ -87,7 +87,9 @@ public function getHeaders(MessageInterface $message): array
* Determine if the request is JSON.
*
* @see vendor/laravel/framework/src/Illuminate/Http/Client/Request.php
+ *
* @param MessageInterface $message
+ *
* @return bool
*/
public function isJson(MessageInterface $message): bool
@@ -112,7 +114,7 @@ public function getContent(MessageInterface $message): string
$body = json_encode($this->getJson($message));
} else {
$body = $message->getBody()->__toString();
- foreach($this->values as $value) {
+ foreach ($this->values as $value) {
$body = str_replace($value, $this->replace, $body);
}
}
@@ -139,9 +141,9 @@ protected function replaceParameters(array $array, array $parameters, array $val
}
}
- array_walk_recursive( $array, function (&$item) use ($values, $replace, $strict) {
+ array_walk_recursive($array, function (&$item) use ($values, $replace, $strict) {
foreach ($values as $value) {
- if (! $strict && str_contains($item, $value)) {
+ if (!$strict && str_contains($item, $value)) {
$item = str_replace($value, $replace, $item);
} elseif ($strict && $value === $item) {
$item = $replace;
diff --git a/tests/MessageAccessorTest.php b/tests/MessageAccessorTest.php
index 32a98ea..42fc704 100644
--- a/tests/MessageAccessorTest.php
+++ b/tests/MessageAccessorTest.php
@@ -30,8 +30,8 @@ public function setUp(): void
'POST',
'https://user:secret@secret.example.com:9000/some-path/secret/should-not-be-removed?test=true&search=foo&filter[field1]=A&filter[field2]=B#anchor',
[
- 'Accept' => 'application/json',
- 'Content-Type' => 'application/json',
+ 'Accept' => 'application/json',
+ 'Content-Type' => 'application/json',
'Authorization' => 'Bearer 1234567890',
],
json_encode([
@@ -39,12 +39,12 @@ public function setUp(): void
'foo' => 'bar',
'baz' => [
[
- 'field_1' => 'value1',
- 'field_2' => 'value2',
+ 'field_1' => 'value1',
+ 'field_2' => 'value2',
'password' => '123456',
- 'secret' => 'this is not for everyone',
- ]
- ]
+ 'secret' => 'this is not for everyone',
+ ],
+ ],
],
])
);
@@ -66,8 +66,10 @@ public function test_get_uri()
public function test_get_base()
{
- $this->assertEquals('https://user:********@********.example.com:9000',
- urldecode($this->messageAccessor->getBase($this->request)));
+ $this->assertEquals(
+ 'https://user:********@********.example.com:9000',
+ urldecode($this->messageAccessor->getBase($this->request))
+ );
}
public function test_get_query()
@@ -76,7 +78,7 @@ public function test_get_query()
$this->assertIsArray($query);
$this->assertEquals([
- 'test' => 'true',
+ 'test' => 'true',
'search' => '********',
'filter' => [
'field1' => 'A',
@@ -91,10 +93,10 @@ public function test_get_headers()
$this->assertIsArray($headers);
$this->assertEquals([
- 'Accept' => ['application/json'],
- 'Content-Type' => ['application/json'],
+ 'Accept' => ['application/json'],
+ 'Content-Type' => ['application/json'],
'Authorization' => ['********'],
- 'Host' => ['********.example.com:9000'],
+ 'Host' => ['********.example.com:9000'],
], $headers);
}
@@ -114,12 +116,12 @@ public function test_get_json()
'foo' => 'bar',
'baz' => [
[
- 'field_1' => 'value1',
- 'field_2' => 'value2',
+ 'field_1' => 'value1',
+ 'field_2' => 'value2',
'password' => '********',
- 'secret' => 'this is not for everyone', // Note that keys are NOT filtered
- ]
- ]
+ 'secret' => 'this is not for everyone', // Note that keys are NOT filtered
+ ],
+ ],
],
], $json);
}
@@ -133,12 +135,12 @@ public function test_get_content()
'foo' => 'bar',
'baz' => [
[
- 'field_1' => 'value1',
- 'field_2' => 'value2',
+ 'field_1' => 'value1',
+ 'field_2' => 'value2',
'password' => '********',
- 'secret' => 'this is not for everyone', // Note that keys are NOT filtered
- ]
- ]
+ 'secret' => 'this is not for everyone', // Note that keys are NOT filtered
+ ],
+ ],
],
]), $content);
}
From 392c6f7175f24adc4344b6223605e5384dd07062 Mon Sep 17 00:00:00 2001
From: Anders Bilfeldt
Date: Sat, 18 Dec 2021 19:52:27 +0100
Subject: [PATCH 4/5] Fix tests
---
tests/MessageAccessorTest.php | 10 ++--------
1 file changed, 2 insertions(+), 8 deletions(-)
diff --git a/tests/MessageAccessorTest.php b/tests/MessageAccessorTest.php
index 42fc704..267ae44 100644
--- a/tests/MessageAccessorTest.php
+++ b/tests/MessageAccessorTest.php
@@ -149,14 +149,8 @@ public function test_filter()
{
$request = $this->messageAccessor->filter($this->request);
- // Note that this is require to use double quotes for the Carriage Return (\r) to work
- $output = "POST /some-path/secret/should-not-be-removed?test=true&search=foo&filter%5Bfield1%5D=A&filter%5Bfield2%5D=B HTTP/1.1\r
-Host: ********.example.com:9000\r
-Accept: application/json\r
-Content-Type: application/json\r
-Authorization: ********\r
-\r
-{\"data\":{\"foo\":\"bar\",\"baz\":[{\"field_1\":\"value1\",\"field_2\":\"value2\",\"password\":\"********\",\"secret\":\"this is not for everyone\"}]}}";
+ // Note that it is required to use double quotes for the Carriage Return (\r) to work and have it on one line to pass on Windows
+ $output = "POST /some-path/secret/should-not-be-removed?test=true&search=foo&filter%5Bfield1%5D=A&filter%5Bfield2%5D=B HTTP/1.1\r\nHost: ********.example.com:9000\r\nAccept: application/json\r\nContent-Type: application/json\r\nAuthorization: ********\r\n\r\n{\"data\":{\"foo\":\"bar\",\"baz\":[{\"field_1\":\"value1\",\"field_2\":\"value2\",\"password\":\"********\",\"secret\":\"this is not for everyone\"}]}}";
$this->assertEquals($output, Message::toString($request));
}
From 67bb851730ffa78fdc8a0f58e24696f6f54676ec Mon Sep 17 00:00:00 2001
From: Anders Bilfeldt
Date: Sat, 18 Dec 2021 23:02:28 +0100
Subject: [PATCH 5/5] Change order of arguments
---
src/MessageAccessor.php | 4 ++--
tests/MessageAccessorTest.php | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/MessageAccessor.php b/src/MessageAccessor.php
index 338791b..7ca8a4d 100644
--- a/src/MessageAccessor.php
+++ b/src/MessageAccessor.php
@@ -18,10 +18,10 @@ class MessageAccessor
private string $replace;
public function __construct(
- array $values = [],
+ array $jsonFilers = [],
array $queryFilters = [],
array $headersFilters = [],
- array $jsonFilers = [],
+ array $values = [],
string $replace = '********'
) {
$this->values = $values;
diff --git a/tests/MessageAccessorTest.php b/tests/MessageAccessorTest.php
index 267ae44..cb11819 100644
--- a/tests/MessageAccessorTest.php
+++ b/tests/MessageAccessorTest.php
@@ -20,10 +20,10 @@ public function setUp(): void
parent::setUp();
$this->messageAccessor = new MessageAccessor(
- ['secret'],
+ ['data.baz.*.password'],
['search', 'filter.field2'],
['Authorization'],
- ['data.baz.*.password']
+ ['secret'],
);
$this->request = new Request(