From dcdce526089c83bbe8f28769c3b97e4c4ac9ed0d Mon Sep 17 00:00:00 2001 From: Molkobain Date: Fri, 4 Nov 2022 16:21:08 +0100 Subject: [PATCH] =?UTF-8?q?N=C2=B05655=20-=20Add=20new=20sanitization=20fi?= =?UTF-8?q?lters=20for=20routing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * utils::ENUM_SANITIZATION_FILTER_OPERATION filter to enable operation "namespaces" in current operations for better reading * utils::ENUM_SANITIZATION_FILTER_ROUTE filter for upcoming auto routing --- application/utils.inc.php | 40 +++++++++++----- pages/ajax.render.php | 2 +- test/application/UtilsTest.php | 87 +++++++++++++++++++++++++++------- 3 files changed, 100 insertions(+), 29 deletions(-) diff --git a/application/utils.inc.php b/application/utils.inc.php index f04f8971b..9c8d57f60 100644 --- a/application/utils.inc.php +++ b/application/utils.inc.php @@ -69,6 +69,16 @@ class utils * @since 3.0.0 */ public const ENUM_SANITIZATION_FILTER_CONTEXT_PARAM = 'context_param'; + /** + * @var string To filter routes passed to back-end router before being redirected to corresponding controller / method + * @since 3.1.0 + */ + public const ENUM_SANITIZATION_FILTER_ROUTE = 'route'; + /** + * @var string To filter operation codes passed to back-end router before being redirected to corresponding controller (/ business logic in case of legacy operations) + * @since 3.1.0 + */ + public const ENUM_SANITIZATION_FILTER_OPERATION = 'operation'; /** * @var string * @since 3.0.0 @@ -406,6 +416,8 @@ class utils break; case static::ENUM_SANITIZATION_FILTER_CONTEXT_PARAM: + case static::ENUM_SANITIZATION_FILTER_ROUTE: + case static::ENUM_SANITIZATION_FILTER_OPERATION: case static::ENUM_SANITIZATION_FILTER_PARAMETER: case static::ENUM_SANITIZATION_FILTER_FIELD_NAME: case static::ENUM_SANITIZATION_FILTER_TRANSACTION_ID: @@ -427,27 +439,31 @@ class utils switch ($sSanitizationFilter) { case static::ENUM_SANITIZATION_FILTER_TRANSACTION_ID: - // same as parameter type but keep the dot character - // see N°1835 : when using file transaction_id on Windows you get *.tmp tokens - // it must be included at the regexp beginning otherwise you'll get an invalid character error - $retValue = filter_var($value, FILTER_VALIDATE_REGEXP, - array("options" => array("regexp" => '/^[\. A-Za-z0-9_=-]*$/'))); + // Same as parameter type but keep the dot character + // transaction_id, the dot is mostly for Windows servers when using file storage as the tokens are named *.tmp + // - See N°1835 + // - Note: It must be included at the regexp beginning otherwise you'll get an invalid character error + $retValue = filter_var($value, FILTER_VALIDATE_REGEXP, array("options" => array("regexp" => '/^[\. A-Za-z0-9_=-]*$/'))); + break; + + case static::ENUM_SANITIZATION_FILTER_ROUTE: + case static::ENUM_SANITIZATION_FILTER_OPERATION: + // - Routes should be of the "controller_namespace_code.controller_method_name" form + // - Operations should be allowed to be namespaced as well even though then don't have dedicated controller yet + $retValue = filter_var($value, FILTER_VALIDATE_REGEXP, array("options" => array("regexp" => '/^[\.A-Za-z0-9_-]*$/'))); break; case static::ENUM_SANITIZATION_FILTER_PARAMETER: - $retValue = filter_var($value, FILTER_VALIDATE_REGEXP, - array("options" => array("regexp" => '/^[ A-Za-z0-9_=-]*$/'))); // the '=', '%3D, '%2B', '%2F' - // characters are used in serialized filters (starting 2.5, only the url encoded versions are presents, but the "=" is kept for BC) + $retValue = filter_var($value, FILTER_VALIDATE_REGEXP, array("options" => array("regexp" => '/^[ A-Za-z0-9_=-]*$/'))); // the '=', '%3D, '%2B', '%2F' + // Characters are used in serialized filters (starting 2.5, only the url encoded versions are presents, but the "=" is kept for BC) break; case static::ENUM_SANITIZATION_FILTER_FIELD_NAME: - $retValue = filter_var($value, FILTER_VALIDATE_REGEXP, - array("options" => array("regexp" => '/^[A-Za-z0-9_]+(->[A-Za-z0-9_]+)*$/'))); // att_code or att_code->name or AttCode->Name or AttCode->Key2->Name + $retValue = filter_var($value, FILTER_VALIDATE_REGEXP, array("options" => array("regexp" => '/^[A-Za-z0-9_]+(->[A-Za-z0-9_]+)*$/'))); // att_code or att_code->name or AttCode->Name or AttCode->Key2->Name break; case static::ENUM_SANITIZATION_FILTER_CONTEXT_PARAM: - $retValue = filter_var($value, FILTER_VALIDATE_REGEXP, - array("options" => array("regexp" => '/^[ A-Za-z0-9_=%:+-]*$/'))); + $retValue = filter_var($value, FILTER_VALIDATE_REGEXP, array("options" => array("regexp" => '/^[ A-Za-z0-9_=%:+-]*$/'))); break; } diff --git a/pages/ajax.render.php b/pages/ajax.render.php index 360ec2617..9b44ac332 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -34,7 +34,7 @@ try $oKPI = new ExecutionKPI(); require_once(APPROOT.'/application/loginwebpage.class.inc.php'); - $operation = utils::ReadParam('operation', ''); + $operation = utils::ReadParam('operation', '', false, utils::ENUM_SANITIZATION_FILTER_OPERATION); // Only allow export functions to portal users switch ($operation) { diff --git a/test/application/UtilsTest.php b/test/application/UtilsTest.php index 1e207d980..d0e660cdf 100644 --- a/test/application/UtilsTest.php +++ b/test/application/UtilsTest.php @@ -420,6 +420,55 @@ class UtilsTest extends ItopTestCase ]; } + /** + * @dataProvider ToCamelCaseProvider + * @covers utils::ToCamelCase + * + * @param string $sInput + * @param string $sExpectedOutput + * + * @return void + */ + public function testToCamelCase(string $sInput, string $sExpectedOutput) + { + $sTestedOutput = utils::ToCamelCase($sInput); + $this->assertEquals($sExpectedOutput, $sTestedOutput, "Camel case transformation for '$sInput' doesn't match. Got '$sTestedOutput', expected '$sExpectedOutput'."); + } + + /** + * @since 3.1.0 + * @return \string[][] + */ + public function ToCamelCaseProvider(): array + { + return [ + 'One word' => [ + 'hello', + 'Hello', + ], + 'Two words separated with space' => [ + 'hello world', + 'HelloWorld', + ], + 'Two words separated with underscore' => [ + 'hello_world', + 'HelloWorld', + ], + 'Two words separated with dash' => [ + 'hello-world', + 'HelloWorld', + ], + 'Two words separated with dot' => [ + 'hello.world', + 'Hello.world', + ], + 'Three words separated with underscore and space' => [ + 'hello_there world', + 'HelloThereWorld', + ], + ]; + } + /** * @dataProvider ToAcronymProvider * @covers utils::ToAcronym @@ -654,8 +703,8 @@ class UtilsTest extends ItopTestCase public function sanitizerDataProvider() { return [ - 'good integer' => ['integer', '2565', '2565'], - 'bad integer' => ['integer', 'a2656', '2656'], + 'good integer' => [utils::ENUM_SANITIZATION_FILTER_INTEGER, '2565', '2565'], + 'bad integer' => [utils::ENUM_SANITIZATION_FILTER_INTEGER, 'a2656', '2656'], /** * 'class' filter needs a loaded datamodel... and is only an indirection to \MetaModel::IsValidClass so might very important to test ! * If we switch this class to ItopDataTestCase then we are seeing : @@ -665,20 +714,26 @@ class UtilsTest extends ItopTestCase */ // 'good class' => ['class', 'UserRequest', 'UserRequest'], // 'bad class' => ['class', 'MyUserRequest',null], - 'good string' => ['string', 'Is Peter smart and funny?', 'Is Peter smart and funny?'], - 'bad string' => ['string', 'Is Peter & funny?', 'Is Peter <smart> & funny?'], - 'good transaction_id' => ['transaction_id', '8965.-dd', '8965.-dd'], - 'bad transaction_id' => ['transaction_id', '8965.-dd+', null], - 'good parameter' => ['parameter', 'JU8965-dd=_', 'JU8965-dd=_'], - 'bad parameter' => ['parameter', '8965.-dd+', null], - 'good field_name' => ['field_name', 'Name->bUzz38', 'Name->bUzz38'], - 'bad field_name' => ['field_name', 'name-buzz', null], - 'good context_param' => ['context_param', '%dssD25_=%:+-', '%dssD25_=%:+-'], - 'bad context_param' => ['context_param', '%dssD,25_=%:+-', null], - 'good element_identifier' => ['element_identifier', 'AD05nb', 'AD05nb'], - 'bad element_identifier' => ['element_identifier', 'AD05nb+', 'AD05nb'], - 'good url' => ['url', 'https://www.w3schools.com', 'https://www.w3schools.com'], - 'bad url' => ['url', 'https://www.w3schoo��ls.co�m', 'https://www.w3schools.com'], + 'good string' => [utils::ENUM_SANITIZATION_FILTER_STRING, 'Is Peter smart and funny?', 'Is Peter smart and funny?'], + 'bad string' => [utils::ENUM_SANITIZATION_FILTER_STRING, 'Is Peter & funny?', 'Is Peter <smart> & funny?'], + 'good transaction_id' => [utils::ENUM_SANITIZATION_FILTER_TRANSACTION_ID, '8965.-dd', '8965.-dd'], + 'bad transaction_id' => [utils::ENUM_SANITIZATION_FILTER_TRANSACTION_ID, '8965.-dd+', null], + 'good route' => [utils::ENUM_SANITIZATION_FILTER_ROUTE, 'object.modify', 'object.modify'], + 'good route with underscore' => [utils::ENUM_SANITIZATION_FILTER_ROUTE, 'object.apply_modify', 'object.apply_modify'], + 'bad route with space' => [utils::ENUM_SANITIZATION_FILTER_ROUTE, 'object modify', null], + 'good operation' => [utils::ENUM_SANITIZATION_FILTER_OPERATION, 'modify', 'modify'], + 'good operation with underscore' => [utils::ENUM_SANITIZATION_FILTER_OPERATION, 'apply_modify', 'apply_modify'], + 'bad operation with space' => [utils::ENUM_SANITIZATION_FILTER_OPERATION, 'apply modify', null], + 'good parameter' => [utils::ENUM_SANITIZATION_FILTER_PARAMETER, 'JU8965-dd=_', 'JU8965-dd=_'], + 'bad parameter' => [utils::ENUM_SANITIZATION_FILTER_PARAMETER, '8965.-dd+', null], + 'good field_name' => [utils::ENUM_SANITIZATION_FILTER_FIELD_NAME, 'Name->bUzz38', 'Name->bUzz38'], + 'bad field_name' => [utils::ENUM_SANITIZATION_FILTER_FIELD_NAME, 'name-buzz', null], + 'good context_param' => [utils::ENUM_SANITIZATION_FILTER_CONTEXT_PARAM, '%dssD25_=%:+-', '%dssD25_=%:+-'], + 'bad context_param' => [utils::ENUM_SANITIZATION_FILTER_CONTEXT_PARAM, '%dssD,25_=%:+-', null], + 'good element_identifier' => [utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER, 'AD05nb', 'AD05nb'], + 'bad element_identifier' => [utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER, 'AD05nb+', 'AD05nb'], + 'good url' => [utils::ENUM_SANITIZATION_FILTER_URL, 'https://www.w3schools.com', 'https://www.w3schools.com'], + 'bad url' => [utils::ENUM_SANITIZATION_FILTER_URL, 'https://www.w3schoo��ls.co�m', 'https://www.w3schools.com'], 'raw_data' => ['raw_data', '\s😃😃😃', '\s😃😃😃'], ]; }