Skip to content

Commit

Permalink
Merge pull request #50 from spira/feature/decode-base64-by-fields-list
Browse files Browse the repository at this point in the history
Decode request values by Base64-Encoded-Fields
  • Loading branch information
zakhenry committed Apr 12, 2016
2 parents 3cae42a + 27dad5f commit 47b8fc8
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 47 deletions.
38 changes: 23 additions & 15 deletions Middleware/TransformInputDataMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@ class TransformInputDataMiddleware
*/
public function handle($request, Closure $next)
{
$this->transformRequestInput($request, $request->query);
$this->transformRequestInput($request, $request->json());
$fields = $this->getFieldsForDecoding($request);

$this->transformRequestInput($request, $request->query, $fields);
$this->transformRequestInput($request, $request->json(), $fields);

return $next($request);
}
Expand All @@ -45,7 +47,7 @@ protected function renameKeys(array $array)
foreach ($array as $key => $value) {

// Recursively check if the value is an array that needs parsing too
$value = (is_array($value)) ? $this->renameKeys($value) : $value;
$value = is_array($value) ? $this->renameKeys($value) : $value;

// Convert camelCase to snake_case
if (is_string($key) && ! ctype_lower($key)) {
Expand All @@ -63,29 +65,35 @@ protected function renameKeys(array $array)
* @param Request $request
* @param ParameterBag $input
*/
protected function transformRequestInput(Request $request, ParameterBag $input)
protected function transformRequestInput(Request $request, ParameterBag $input, $fieldsForDecode)
{
foreach ($input as $key => $value) {
$value = $this->extractEncodedJson($input, $key, $value);
$snake_key = snake_case($key);

if (in_array($snake_key, $fieldsForDecode)) {
$value = $this->extractEncodedJson($input, $key, $value);
}

// Handle snakecase conversion in sub arrays
if (is_array($value)) {
$value = $this->renameKeys($value);
$input->set($key, $value);
}

// Find any potential camelCase keys in the 'root' array, and convert
// them to snake_case
if (! ctype_lower($key)) {
// Only convert if the key will change
if ($key != snake_case($key)) {
$input->set(snake_case($key), $value);
$input->remove($key);
}
// Only convert camelCase keys in the 'root' array if the key will change
if ($key != $snake_key) {
$input->set($snake_key, $value);
$input->remove($key);
}
}
}

/** Return normalized array of snake-cased fields for base64 decoding */
protected function getFieldsForDecoding(Request $request)
{
return array_map('snake_case', explode(',', $request->headers->get('Base64-Encoded-Fields')));
}

/**
* If the input is both base64 encoded and json encoded extract it to array.
* @param ParameterBag $input
Expand All @@ -110,9 +118,9 @@ private function extractEncodedJson(ParameterBag $input, $key, $value)

$jsonParsed = json_decode($decoded, true);

//if value couldn't be json decoded, it wasn't valid json, return the original value
//if value couldn't be json decoded, it wasn't valid json, return the decoded value
if (! $jsonParsed) {
return $value;
return $decoded;
}

$input->set($key, $jsonParsed);
Expand Down
69 changes: 41 additions & 28 deletions tests/MiddlewareTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,54 +15,67 @@

class MiddlewareTest extends TestCase
{
public function testBase64Decode()
{
$enc = base64_encode(json_encode(['will_not_be_decoded']));

$request = new Request();
$request->headers->add([
'Base64-Encoded-Fields' => 'plainArray,some_string',
]);
$request->offsetSet('plain_array', base64_encode(json_encode(['ololo' => 123])));
$request->offsetSet('someString', base64_encode('abc'));
$request->offsetSet('not_encoded', $enc);

$result = $this->executeRequestWithMiddleware($request)->all();

$this->assertEquals(123, array_get($result, 'plain_array.ololo'));
$this->assertEquals('abc', array_get($result, 'some_string'));
$this->assertEquals($enc, array_get($result, 'not_encoded'));
}

/**
* Test TransformInputData middleware.
*
* @return void
*/
public function testTransformInputData()
{
$mw = new TransformInputDataMiddleware();

// Create a request object to test
$request = new Request();
$request->offsetSet('firstName', 'foo');
$request->offsetSet('lastname', 'bar');
$request = new Request([
'firstName' => 'foo',
'lastname' => 'bar',
]);

// And a next closure
$next = function ($request) { return $request; };
$result = $this->executeRequestWithMiddleware($request)->all();

// Execute
$request = $mw->handle($request, $next);

// Assert
$this->assertArrayHasKey('first_name', $request->all());
$this->assertArrayNotHasKey('firstName', $request->all());
$this->assertArrayHasKey('lastname', $request->all());
$this->assertArrayEquals(['first_name', 'lastname'], array_keys($result));
}

public function testTransformInputDataNested()
{
$mw = new TransformInputDataMiddleware();

// Create a request object to test
$request = new Request();
$request->offsetSet('firstName', 'foo');
$request->offsetSet('lastname', 'bar');
$request->offsetSet('nestedArray', ['fooBar' => 'bar', 'foo' => 'bar', 'oneMore' => ['andThis' => true]]);

// And a next closure
$next = function ($request) { return $request; };
$result = $this->executeRequestWithMiddleware($request)->all();

$this->assertArrayEquals(['first_name', 'lastname', 'nested_array'], array_keys($result));
$this->assertEquals('bar', array_get($result, 'nested_array.foo_bar'));
$this->assertTrue(array_get($result, 'nested_array.one_more.and_this'));
}

// Execute
$request = $mw->handle($request, $next);
/** @return Request */
protected function executeRequestWithMiddleware(Request $request)
{
$mw = new TransformInputDataMiddleware();

// Assert
$this->assertArrayHasKey('first_name', $request->all());
$this->assertArrayNotHasKey('firstName', $request->all());
$this->assertArrayHasKey('lastname', $request->all());
$this->assertArrayHasKey('nested_array', $request->all());
$this->assertArrayHasKey('foo_bar', $request->nested_array);
$this->assertArrayHasKey('and_this', $request->nested_array['one_more']);
return $mw->handle(
$request,
function ($request) {
return $request;
}
);
}
}
14 changes: 10 additions & 4 deletions tests/integration/EntityTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,10 @@ public function testCustomSearchApplied($arr_path, $query, $value = 'foobar')
'q' => base64_encode(json_encode($query)),
'custom_search' => 1,
];
$this->getJson('/test/entities/search?'.http_build_query($params), ['Range' => 'entities=0-']);
$this->getJson(
'/test/entities/search?'.http_build_query($params),
['Range' => 'entities=0-', 'Base64-Encoded-Fields' => 'q']
);

$this->assertResponseStatus(404);
}
Expand Down Expand Up @@ -258,7 +261,10 @@ public function testGetAllPaginatedSimpleSearch($path)

$this->app->instance(TestEntity::class, $mockModel);

$this->getJson($path.'?q='.base64_encode(json_encode('foobar')), ['Range' => 'entities=0-']);
$this->getJson(
$path.'?q='.base64_encode(json_encode('foobar')),
['Range' => 'entities=0-', 'Base64-Encoded-Fields' => 'q']
);

$this->assertResponseStatus(404);
}
Expand Down Expand Up @@ -297,7 +303,7 @@ public function testGetAllPaginatedComplexSearch($path)
],
];

$this->getJson($path.'?q='.base64_encode(json_encode($query)), ['Range' => 'entities=0-']);
$this->getJson($path.'?q='.base64_encode(json_encode($query)), ['Range' => 'entities=0-', 'Base64-Encoded-Fields' => 'q']);

$this->assertResponseStatus(404);
}
Expand Down Expand Up @@ -343,7 +349,7 @@ public function testGetAllPaginatedComplexSearchMatchAll($path)
'authorId' => [''],
];

$this->getJson($path.'?q='.base64_encode(json_encode($query)), ['Range' => 'entities=0-']);
$this->getJson($path.'?q='.base64_encode(json_encode($query)), ['Range' => 'entities=0-', 'Base64-Encoded-Fields' => 'q']);

$this->assertResponseStatus(206);
}
Expand Down

0 comments on commit 47b8fc8

Please sign in to comment.