Skip to content

Commit

Permalink
[Feature] Add ability to sanitize post fields (#13)
Browse files Browse the repository at this point in the history
  • Loading branch information
snags88 authored Apr 11, 2020
1 parent b8f8899 commit 59f253b
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 6 deletions.
45 changes: 45 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ VCRCleaner::enable(array(
return preg_replace('/<password.*<\/password>/', 'hunter2', $body);
}
),
'postFieldScrubbers' => array(
function(array $postFields) {
$postFields['Secret'] = 'REDACTED';
return $postFields;
}
),
),
'response' => array(
'ignoreHeaders' => array(),
Expand All @@ -74,6 +80,7 @@ This library allows your sanitize both the Request and Response sections of your
- `request.ignoreQueryFields` - Define which GET parameters in your URL to completely strip out of your recordings.
- `request.ignoreHeaders` - Define the headers in your recording that will automatically be set to null in your recordings
- `request.bodyScrubbers` - An array of callbacks that will have the request body available as a string. Each callback **must** return the modified body. The callbacks are called consecutively in the order they appear in this array and the value from one callback propagates to the next.
- `request.postFieldScrubbers` - An array of callbacks that will have the request post fields available as an array. Each callback **must** return the modified post fields array. The callbacks are called consecutively in the order they appear in this array and the value from one callback propagates to the next.

#### Sanitizing Responses

Expand Down Expand Up @@ -197,6 +204,44 @@ VCRCleaner::enable(array(
body: '...response body...'
```
### Post Field Content
When making POST requests, your VCR will sometimes record the data inside of a `post_fields` parameter rather than the `body`; e.g. when `CURLOPT_POSTFIELDS` is used in cURL and you do not set `CURLOPT_POST` to `true`. In those cases, this option can be used to sanitize sensitive content. Note that unlike the `body` field, `post_fields` is an array:

```php
VCRCleaner::enable(array(
'request' => array(
'postFieldScrubber' => array(
function (array $postFields) {
$postFields['Secret_Key'] = '';
return $postFields;
},
),
),
));
```

```yaml
# You POST request to `https://www.example.com/search` with a post field of
# `['data'=> 'hello world', 'Secret_Key' => 'abc']` gets recorded like so,
-
request:
method: POST
url: 'https://www.example.com/search'
headers:
Host: www.example.com
post_fields:
data: 'hello world'
Secret_Key: ''
response:
status:
http_version: '1.1'
code: '404'
message: 'Not Found'
headers: ~
body: '...response body...'
```
## License
[MIT](/LICENSE.md)
6 changes: 6 additions & 0 deletions src/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ public static function getReqBodyScrubbers()
return self::$options['request']['bodyScrubbers'];
}

public static function getReqPostFieldScrubbers()
{
return self::$options['request']['postFieldScrubbers'];
}

public static function getResIgnoredHeaders()
{
return self::$options['response']['ignoreHeaders'];
Expand All @@ -59,6 +64,7 @@ private static function defaultConfig()
'ignoreQueryFields' => array(),
'ignoreHeaders' => array(),
'bodyScrubbers' => array(),
'postFieldScrubbers'=> array(),
),
'response' => array(
'ignoreHeaders' => array(),
Expand Down
9 changes: 5 additions & 4 deletions src/VCRCleaner.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@ abstract class VCRCleaner
* ```
* $options = [
* 'request' => [
* 'ignoreHostname' => boolean
* 'ignoreQueryFields' => string[]
* 'ignoreHeaders' => string[]
* 'bodyScrubbers' => Array<(string $body): string>
* 'ignoreHostname' => boolean
* 'ignoreQueryFields' => string[]
* 'ignoreHeaders' => string[]
* 'bodyScrubbers' => Array<(string $body): string>
* 'postFieldScrubbers' => Array<(array $postFields): array>
* ],
* 'response' => [
* 'ignoreHeaders' => string[]
Expand Down
16 changes: 14 additions & 2 deletions src/VCRCleanerEventSubscriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public function onBeforeRecord(BeforeRecordEvent $event)
$this->sanitizeRequestUrl($event->getRequest());
$this->sanitizeRequestHeaders($event->getRequest());
$this->sanitizeRequestBody($event->getRequest());
$this->sanitizeRequestPostFields($event->getRequest());

$originalRes = $event->getResponse();

Expand All @@ -71,7 +72,7 @@ public function onBeforeRecord(BeforeRecordEvent $event)

private function sanitizeRequestHeaders(Request $request)
{
$caseInsensitiveKeys = [];
$caseInsensitiveKeys = array();

foreach ($request->getHeaders() as $key => $value) {
$caseInsensitiveKeys[strtolower($key)] = $key;
Expand Down Expand Up @@ -139,11 +140,22 @@ private function sanitizeRequestBody(Request $request)
$request->setBody($body);
}

private function sanitizeRequestPostFields(Request $request)
{
$postFields = $request->getPostFields();

foreach (Config::getReqPostFieldScrubbers() as $scrubber) {
$postFields = $scrubber($postFields);
}

$request->setPostFields($postFields);
}

private function sanitizeResponseHeaders(array &$workspace)
{
// To avoid breaking case-sensitivity in cassettes, keep a record of the
// mapping between lowercase to original casing.
$caseInsensitiveKeys = [];
$caseInsensitiveKeys = array();

foreach ($workspace['headers'] as $key => $value) {
$caseInsensitiveKeys[strtolower($key)] = $key;
Expand Down
32 changes: 32 additions & 0 deletions tests/VCR/VCRCleanerEventSubscriberTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,38 @@ public function testCurlCallWithSensitiveBody()
$this->assertContains('REDACTED', $vcrFile);
}

public function testCurlCallWithSensitivePostField()
{
$cb = function (array $postFields) {
$postFields['VerySecret'] = 'REDACTED';

return $postFields;
};

VCRCleaner::enable(array(
'request' => array(
'postFieldScrubbers' => array(
$cb,
),
),
));

$curl = new Curl();
$secret = 'Do not tell anyone this secret';
$postFields = array(
'SomethingPublic' => 'Not a secret',
'VerySecret' => $secret,
);
$curl->setOpt(CURLOPT_POSTFIELDS, $postFields);
$curl->post('https://www.example.com/search');
$curl->close();

$vcrFile = $this->getCassetteContent();

$this->assertNotContains($secret, $vcrFile);
$this->assertContains('REDACTED', $vcrFile);
}

public function testCurlCallWithRedactedHostname()
{
VCRCleaner::enable(array(
Expand Down

0 comments on commit 59f253b

Please sign in to comment.