Skip to content

Commit

Permalink
Fix validation of REST array parameters.
Browse files Browse the repository at this point in the history
  • Loading branch information
aaronweeden committed Oct 6, 2023
1 parent 039c588 commit 7bfda44
Show file tree
Hide file tree
Showing 9 changed files with 615 additions and 41 deletions.
15 changes: 14 additions & 1 deletion classes/Rest/Controllers/BaseControllerProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -284,12 +284,25 @@ private function getParam(Request $request, $name, $mandatory, $default, $filter
}
}

// If the parameter is an array, throw an exception.
$invalidMessage = (
"Invalid value for $name. Must be a(n) $expectedValueType."
);
if (is_array($value)) {
throw new BadRequestHttpException($invalidMessage);
}

// Run the found parameter value through the given filter.
if (array_key_exists('flags', $filterOptions)) {
$filterOptions['flags'] |= FILTER_NULL_ON_FAILURE;
} else {
$filterOptions['flags'] = FILTER_NULL_ON_FAILURE;
}
$value = filter_var($value, $filterId, $filterOptions);

// If the value is invalid, throw an exception.
if ($value === null) {
throw new BadRequestHttpException("Invalid value for $name. Must be a(n) $expectedValueType.");
throw new BadRequestHttpException($invalidMessage);
}

// Return the filtered value.
Expand Down
6 changes: 5 additions & 1 deletion classes/Rest/XdmodApplicationFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,11 @@ public static function getInstance()
// Extracting any POST variables provided in the Request.
$post = array();
foreach($request->request->getIterator() as $key => $value) {
$post[$key] = json_decode($value, true);
$post[$key] = (
is_string($value)
? json_decode($value, true)
: null
);
}

// Calculate the amount of time that has elapsed serving this request.
Expand Down
138 changes: 105 additions & 33 deletions tests/integration/lib/BaseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,11 @@ protected static function assertRequiredKeys(
* be present for other tests here to succeed.
* - 'int_params' — array of parameters that
* will each be tested for invalid integer values.
* - 'string_params' — array of parameters that
* will each be tested for invalid string values.
* - 'unix_ts_params' — array of parameters that
* will each be tested for invalid Unix timestamp
* values.
* - 'date_params' — array of parameters that will
* each be tested for invalid ISO 8601 date values.
* @return array of arrays of test data, each of which contains a string
Expand Down Expand Up @@ -361,16 +366,23 @@ protected function provideRestEndpointTests(
$options['additional_params']
);
}
// Set up the custom error body validator.
$errorBodyValidator = null;
if (array_key_exists('error_body_validator', $options)) {
$errorBodyValidator = $options['error_body_validator'];
}
$tests = [];
$this->provideRestEndpointAuthenticationTests(
$tests,
$options,
$validInputWithAdditionalParams
$validInputWithAdditionalParams,
$errorBodyValidator
);
$runAs = $this->provideRestEndpointAuthorizationTests(
$tests,
$options,
$validInputWithAdditionalParams
$validInputWithAdditionalParams,
$errorBodyValidator
);
// Set the role for running the tests.
if (!isset($runAs)) {
Expand All @@ -394,15 +406,17 @@ protected function provideRestEndpointTests(
$validInput,
$paramSource,
$runAs,
$tokenAuth
$tokenAuth,
$errorBodyValidator
);
$this->provideRestEndpointInvalidParamTests(
$tests,
$options,
$validInputWithAdditionalParams,
$paramSource,
$runAs,
$tokenAuth
$tokenAuth,
$errorBodyValidator
);
return $tests;
}
Expand All @@ -413,13 +427,18 @@ protected function provideRestEndpointTests(
* the given name was not provided in the request.
*
* @param string $name
* @param callable|null $bodyValidator if provided, overrides the default
* body validator.
* @return array
*/
protected function validateMissingRequiredParameterResponse($name)
{
protected function validateMissingRequiredParameterResponse(
$name,
$bodyValidator = null
) {
return $this->validateBadRequestResponse(
"$name is a required parameter.",
0
0,
$bodyValidator
);
}

Expand All @@ -430,13 +449,19 @@ protected function validateMissingRequiredParameterResponse($name)
*
* @param string $name
* @param string $type
* @param callable|null $bodyValidator if provided, overrides the default
* body validator.
* @return array
*/
protected function validateInvalidParameterResponse($name, $type)
{
protected function validateInvalidParameterResponse(
$name,
$type,
$bodyValidator = null
) {
return $this->validateBadRequestResponse(
"Invalid value for $name. Must be a(n) $type.",
0
0,
$bodyValidator
);
}

Expand All @@ -447,15 +472,21 @@ protected function validateInvalidParameterResponse($name, $type)
*
* @param string $message
* @param int $code
* @param callable|null $bodyValidator if provided, overrides the default
* body validator.
* @return array
*/
protected function validateBadRequestResponse($message, $code)
{
protected function validateBadRequestResponse(
$message,
$code,
$bodyValidator = null
) {
return [
'status_code' => 400,
'body_validator' => $this->validateErrorResponseBody(
$message,
$code
$code,
$bodyValidator
)
];
}
Expand All @@ -465,18 +496,23 @@ protected function validateBadRequestResponse($message, $code)
* validates authorization error responses with the given HTTP status code.
*
* @param int $statusCode
* @param callable|null $bodyValidator if provided, overrides the default
* body validator.
* @return array
*/
protected function validateAuthorizationErrorResponse($statusCode)
{
protected function validateAuthorizationErrorResponse(
$statusCode,
$bodyValidator = null
) {
return [
'status_code' => $statusCode,
'body_validator' => $this->validateErrorResponseBody(
(
'An error was encountered while attempting to process the'
. ' requested authorization procedure.'
),
0
0,
$bodyValidator
)
];
}
Expand All @@ -488,10 +524,18 @@ protected function validateAuthorizationErrorResponse($statusCode)
*
* @param string $message
* @param int $code
* @param callable|null $bodyValidator if provided, overrides the default
* body validator.
* @return callable
*/
protected function validateErrorResponseBody($message, $code)
{
protected function validateErrorResponseBody(
$message,
$code,
$bodyValidator = null
) {
if (!is_null($bodyValidator)) {
return $bodyValidator($message, $code);
}
return function ($body, $assertMessage) use ($message, $code) {
parent::assertEquals(
[
Expand Down Expand Up @@ -662,7 +706,8 @@ private static function getEndpointTestData(
private function provideRestEndpointAuthenticationTests(
array &$tests,
array $options,
array $validInputWithAdditionalParams
array $validInputWithAdditionalParams,
$errorBodyValidator
) {
if (
array_key_exists('authentication', $options)
Expand All @@ -672,7 +717,10 @@ private function provideRestEndpointAuthenticationTests(
'unauthenticated',
'pub',
$validInputWithAdditionalParams,
$this->validateAuthorizationErrorResponse(401)
$this->validateAuthorizationErrorResponse(
401,
$errorBodyValidator
)
];
}
}
Expand All @@ -683,7 +731,8 @@ private function provideRestEndpointAuthenticationTests(
private function provideRestEndpointAuthorizationTests(
array &$tests,
array $options,
array $validInputWithAdditionalParams
array $validInputWithAdditionalParams,
$errorBodyValidator
) {
if (array_key_exists('authorization', $options)) {
foreach (self::getBaseRoles() as $role) {
Expand All @@ -692,7 +741,10 @@ private function provideRestEndpointAuthorizationTests(
'unauthorized',
$role,
$validInputWithAdditionalParams,
$this->validateAuthorizationErrorResponse(403)
$this->validateAuthorizationErrorResponse(
403,
$errorBodyValidator
)
];
}
}
Expand Down Expand Up @@ -733,7 +785,8 @@ private function provideRestEndpointMissingRequiredParamTests(
array $validInput,
$paramSource,
$runAs,
$tokenAuth
$tokenAuth,
$errorBodyValidator
) {
foreach (array_keys($validInput[$paramSource]) as $param) {
$input = $validInput;
Expand All @@ -743,7 +796,10 @@ private function provideRestEndpointMissingRequiredParamTests(
$runAs,
$tokenAuth,
$input,
$this->validateMissingRequiredParameterResponse($param)
$this->validateMissingRequiredParameterResponse(
$param,
$errorBodyValidator
)
);
}
}
Expand All @@ -757,24 +813,40 @@ private function provideRestEndpointInvalidParamTests(
array $validInputWithAdditionalParams,
$paramSource,
$runAs,
$tokenAuth
$tokenAuth,
$errorBodyValidator
) {
$types = [
'int_params' => 'integer',
'string_params' => 'string',
'unix_ts_params' => 'Unix timestamp',
'date_params' => 'ISO 8601 Date'
];
$values = [
'string' => 'foo',
'array' => ['foo' => 'bar']
];
foreach ($types as $key => $type) {
if (array_key_exists($key, $options)) {
foreach ($options[$key] as $param) {
$input = $validInputWithAdditionalParams;
$input[$paramSource][$param] = 'foo';
$tests[] = self::getEndpointTestData(
$param . '_string',
$runAs,
$tokenAuth,
$input,
$this->validateInvalidParameterResponse($param, $type)
);
foreach ($values as $id => $value) {
// Strings can be strings, so skip that test.
if ('string_params' !== $key || 'string' !== $id) {
$input[$paramSource][$param] = $value;
$tests[] = self::getEndpointTestData(
$param . '_' . $id,
$runAs,
$tokenAuth,
$input,
$this->validateInvalidParameterResponse(
$param,
$type,
$errorBodyValidator
)
);
}
}
}
}
}
Expand Down
Loading

0 comments on commit 7bfda44

Please sign in to comment.