From dc60258cc8c3bfdfa9d2aa3bbc776c0ec5cf3bd7 Mon Sep 17 00:00:00 2001 From: jf-cbd Date: Wed, 11 Jun 2025 14:31:34 +0200 Subject: [PATCH 1/6] Immplement method signature verification --- datamodel.combodo-webhook-integration.xml | 98 +++++++++++++++++++ .../Notification/Action/_ActionWebhook.php | 82 ++++++++++++++++ 2 files changed, 180 insertions(+) diff --git a/datamodel.combodo-webhook-integration.xml b/datamodel.combodo-webhook-integration.xml index 92b665a..e215407 100644 --- a/datamodel.combodo-webhook-integration.xml +++ b/datamodel.combodo-webhook-integration.xml @@ -856,11 +856,13 @@ if(stripos($sCallbackFQCN, '$this->') !== false) { $sMethodName = str_ireplace('$this->', '', $sCallbackFQCN); + $this->CheckCallbackSignature($sCallbackFQCN, $oTriggeringObject); $payload = $oTriggeringObject->$sMethodName($aContextArgs, $oLog, $this); } // Otherwise, check if callback is callable as a static method elseif(is_callable($sCallbackFQCN)) { + $this->CheckCallbackSignature($sCallbackFQCN); $payload = call_user_func($sCallbackFQCN, $oTriggeringObject, $aContextArgs, $oLog, $this); } // Otherwise, there is a problem @@ -875,6 +877,102 @@ ]]> + + false + private + Overload-DBObject + + + + ') !== false) { + $sCallbackMethodName = str_ireplace('$this->', '', $sResponseCallback); + $sCallBackClassName = get_class($oTriggeringObject); + $oReflector = new ReflectionClass($sCallBackClassName); + $aCallbackParameters = $oReflector->getMethod($sCallbackMethodName)->getParameters(); + if (count($aCallbackParameters) !== 3) { + $sErrorMessage = "The callback method '$sCallbackMethodName' of class '$sCallBackClassName' must have exactly 3 parameters: the ContextArgs array, the EventNotification object and the ActionWebhook object."; + IssueLog::Error($sErrorMessage); + throw new SecurityException($sErrorMessage); + } + foreach ($aCallbackParameters as $param) { + if ($param->getType() === null) { + $sErrorMessage = "The callback method '$sCallbackMethodName' of class '$sCallBackClassName' must have type-hinted parameters, but parameter {$param->getName()} is not type-hinted."; + IssueLog::Error($sErrorMessage); + throw new SecurityException($sErrorMessage); + } + if ($iParamCount === 0 && $param->getType()->getName() !== 'array') { + $sErrorMessage = "The callback method '$sCallbackMethodName' of class '$sCallBackClassName' must have a first parameter of type 'array', but it has {$param->getType()->getName()} instead."; + IssueLog::Error($sErrorMessage); + throw new SecurityException($sErrorMessage); + } elseif ($iParamCount === 1 && $param->getType()->getName() !== 'EventNotification') { + $sErrorMessage = "The callback method '$sCallbackMethodName' of class '$sCallBackClassName' must have a second parameter of type 'EventNotification', but it has {$param->getType()->getName()} instead."; + IssueLog::Error($sErrorMessage); + throw new SecurityException($sErrorMessage); + } + elseif ($iParamCount === 2 && $param->getType()->getName() !== 'ActionWebhook') { + $sErrorMessage = "The callback method '$sCallbackMethodName' of class '$sCallBackClassName' must have a third parameter of type 'ActionWebhook', but it has {$param->getType()->getName()} instead."; + IssueLog::Error($sErrorMessage); + throw new SecurityException($sErrorMessage); + } + $iParamCount++; + } + } else { + $pos = strrpos($sResponseCallback, '::'); + if ($pos !== false) { + $sCallBackClassName = substr($sResponseCallback, 0, $pos); + $sCallbackMethodName = substr($sResponseCallback, $pos + 2); + } else { + $sErrorMessage = "The callback '$sResponseCallback' is not a valid static method (missing '::' separator)."; + IssueLog::Error($sErrorMessage); + throw new SecurityException($sErrorMessage); + } + $oReflector = new ReflectionClass($sCallBackClassName); + $aCallbackParameters = $oReflector->getMethod($sCallbackMethodName)->getParameters(); + if (count($aCallbackParameters) !== 4) { + $sErrorMessage = "The callback method '$sCallbackMethodName' of class '$sCallBackClassName' must have exactly 4 parameters: the DBObject, the ContextArgs array, the EventNotification object and the ActionWebhook object."; + IssueLog::Error($sErrorMessage); + throw new SecurityException($sErrorMessage); + } + foreach ($aCallbackParameters as $param) { + if ($param->getType() === null) { + $sErrorMessage = "The callback method '$sCallbackMethodName' of class '$sCallBackClassName' must have type-hinted parameters, but parameter {$param->getName()} is not type-hinted."; + IssueLog::Error($sErrorMessage); + throw new SecurityException($sErrorMessage); + } + if ($iParamCount === 0 && $param->getType()->getName() !== 'DBObject') { + $sErrorMessage = "The callback method '$sCallbackMethodName' of class '$sCallBackClassName' must have a first parameter of type 'DBObject', but it has {$param->getType()->getName()} instead."; + IssueLog::Error($sErrorMessage); + throw new SecurityException($sErrorMessage); + } elseif ($iParamCount === 1 && $param->getType()->getName() !== 'array') { + $sErrorMessage = "The callback method '$sCallbackMethodName' of class '$sCallBackClassName' must have a first parameter of type 'array', but it has {$param->getType()->getName()} instead."; + IssueLog::Error($sErrorMessage); + throw new SecurityException($sErrorMessage); + } elseif ($iParamCount === 2 && $param->getType()->getName() !== 'EventNotification') { + $sErrorMessage = "The callback method '$sCallbackMethodName' of class '$sCallBackClassName' must have a second parameter of type 'EventNotification', but it has {$param->getType()->getName()} instead."; + IssueLog::Error($sErrorMessage); + throw new SecurityException($sErrorMessage); + } + elseif ($iParamCount === 3 && $param->getType()->getName() !== 'ActionWebhook') { + $sErrorMessage = "The callback method '$sCallbackMethodName' of class '$sCallBackClassName' must have a third parameter of type 'ActionWebhook', but it has {$param->getType()->getName()} instead."; + IssueLog::Error($sErrorMessage); + throw new SecurityException($sErrorMessage); + } + $iParamCount++; + } + } + } + ]]> + + false protected diff --git a/src/Core/Notification/Action/_ActionWebhook.php b/src/Core/Notification/Action/_ActionWebhook.php index e437d8e..932d240 100644 --- a/src/Core/Notification/Action/_ActionWebhook.php +++ b/src/Core/Notification/Action/_ActionWebhook.php @@ -11,6 +11,10 @@ use Exception; use IssueLog; use MetaModel; +use ReflectionClass; +use ReflectionException; +use ReflectionMethod; +use SecurityException; use UserRights; use utils; @@ -30,6 +34,7 @@ abstract class _ActionWebhook extends ActionNotification * * @throws \ArchivedObjectException * @throws \CoreException + * @throws ReflectionException */ public static function ExecuteResponseHandler(WebResponse $oResponse, array $aParams) { @@ -52,11 +57,13 @@ public static function ExecuteResponseHandler(WebResponse $oResponse, array $aPa if(stripos($sResponseCallback, '$this->') !== false) { $sMethodName = str_ireplace('$this->', '', $sResponseCallback); + self::CheckCallbackSignature($sResponseCallback, $oTriggeringObject); $oTriggeringObject->$sMethodName($oResponse, $oActionWebhook); } // Otherwise, check if callback is callable as a static method elseif(is_callable($sResponseCallback)) { + self::CheckCallbackSignature($sResponseCallback); call_user_func($sResponseCallback, $oTriggeringObject, $oResponse, $oActionWebhook); } // Otherwise, there is a problem, we cannot call the callback @@ -66,6 +73,81 @@ public static function ExecuteResponseHandler(WebResponse $oResponse, array $aPa } } + /** + * @throws ReflectionException + * @throws SecurityException + */ + private static function CheckCallbackSignature(string $sResponseCallback, $oTriggeringObject = null): void + { + $iParamCount = 0; + if (stripos($sResponseCallback, '$this->') !== false) { + $sCallbackMethodName = str_ireplace('$this->', '', $sResponseCallback); + $sCallBackClassName = get_class($oTriggeringObject); + $oReflector = new ReflectionClass($sCallBackClassName); + $aCallbackParameters = $oReflector->getMethod($sCallbackMethodName)->getParameters(); + if (count($aCallbackParameters) !== 2) { + $sErrorMessage = "The callback method '$sCallbackMethodName' of class '$sCallBackClassName' must have exactly 2 parameters: the WebResponse and the ActionWebhook object."; + IssueLog::Error($sErrorMessage); + throw new SecurityException($sErrorMessage); + } + foreach ($aCallbackParameters as $param) { + if ($param->getType() === null) { + $sErrorMessage = "The callback method '$sCallbackMethodName' of class '$sCallBackClassName' must have type-hinted parameters, but parameter {$param->getName()} is not type-hinted."; + IssueLog::Error($sErrorMessage); + throw new SecurityException($sErrorMessage); + } + if ($iParamCount === 0 && $param->getType()->getName() !== 'Combodo\iTop\Core\WebResponse') { + $sErrorMessage = "The callback method '$sCallbackMethodName' of class '$sCallBackClassName' must have a first parameter of type 'Combodo\iTop\Core\WebResponse', but it has {$param->getType()->getName()} instead."; + IssueLog::Error($sErrorMessage); + throw new SecurityException($sErrorMessage); + } elseif ($iParamCount === 1 && $param->getType()->getName() !== 'ActionWebhook') { + $sErrorMessage = "The callback method '$sCallbackMethodName' of class '$sCallBackClassName' must have a second parameter of type 'ActionWebhook', but it has {$param->getType()->getName()} instead."; + IssueLog::Error($sErrorMessage); + throw new SecurityException($sErrorMessage); + } + $iParamCount++; + } + } else { + $pos = strrpos($sResponseCallback, '::'); + if ($pos !== false) { + $sCallBackClassName = substr($sResponseCallback, 0, $pos); + $sCallbackMethodName = substr($sResponseCallback, $pos + 2); + } else { + $sErrorMessage = "The callback '$sResponseCallback' is not a valid static method (missing '::' separator)."; + IssueLog::Error($sErrorMessage); + throw new SecurityException($sErrorMessage); + } + $oReflector = new ReflectionClass($sCallBackClassName); + $aCallbackParameters = $oReflector->getMethod($sCallbackMethodName)->getParameters(); + if (count($aCallbackParameters) !== 3) { + $sErrorMessage = "The callback method '$sCallbackMethodName' of class '$sCallBackClassName' must have exactly 3 parameters: the DBObject, the WebResponse and the ActionWebhook object."; + IssueLog::Error($sErrorMessage); + throw new SecurityException($sErrorMessage); + } + foreach ($aCallbackParameters as $param) { + if ($param->getType() === null) { + $sErrorMessage = "The callback method '$sCallbackMethodName' of class '$sCallBackClassName' must have type-hinted parameters, but parameter {$param->getName()} is not type-hinted."; + IssueLog::Error($sErrorMessage); + throw new SecurityException($sErrorMessage); + } + if ($iParamCount === 0 && $param->getType()->getName() !== 'DBObject') { + $sErrorMessage = "The callback method '$sCallbackMethodName' of class '$sCallBackClassName' must have a first parameter of type 'DBObject', but it has {$param->getType()->getName()} instead."; + IssueLog::Error($sErrorMessage); + throw new SecurityException($sErrorMessage); + } elseif ($iParamCount === 1 && $param->getType()->getName() !== 'Combodo\iTop\Core\WebResponse') { + $sErrorMessage = "The callback method '$sCallbackMethodName' of class '$sCallBackClassName' must have a second parameter of type 'Combodo\iTop\Core\WebResponse', but it has {$param->getType()->getName()} instead."; + IssueLog::Error($sErrorMessage); + throw new SecurityException($sErrorMessage); + } elseif ($iParamCount === 2 && $param->getType()->getName() !== 'ActionWebhook') { + $sErrorMessage = "The callback method '$sCallbackMethodName' of class '$sCallBackClassName' must have a third parameter of type 'ActionWebhook', but it has {$param->getType()->getName()} instead."; + IssueLog::Error($sErrorMessage); + throw new SecurityException($sErrorMessage); + } + $iParamCount++; + } + } + } + /** * @inheritDoc * @since 1.4.0 From b44c139819c720a3515a05892f6edd4361cd5de0 Mon Sep 17 00:00:00 2001 From: jf-cbd Date: Wed, 11 Jun 2025 14:46:24 +0200 Subject: [PATCH 2/6] Fix error message --- datamodel.combodo-webhook-integration.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/datamodel.combodo-webhook-integration.xml b/datamodel.combodo-webhook-integration.xml index e215407..9e3dd3f 100644 --- a/datamodel.combodo-webhook-integration.xml +++ b/datamodel.combodo-webhook-integration.xml @@ -953,11 +953,11 @@ IssueLog::Error($sErrorMessage); throw new SecurityException($sErrorMessage); } elseif ($iParamCount === 1 && $param->getType()->getName() !== 'array') { - $sErrorMessage = "The callback method '$sCallbackMethodName' of class '$sCallBackClassName' must have a first parameter of type 'array', but it has {$param->getType()->getName()} instead."; + $sErrorMessage = "The callback method '$sCallbackMethodName' of class '$sCallBackClassName' must have a second parameter of type 'array', but it has {$param->getType()->getName()} instead."; IssueLog::Error($sErrorMessage); throw new SecurityException($sErrorMessage); } elseif ($iParamCount === 2 && $param->getType()->getName() !== 'EventNotification') { - $sErrorMessage = "The callback method '$sCallbackMethodName' of class '$sCallBackClassName' must have a second parameter of type 'EventNotification', but it has {$param->getType()->getName()} instead."; + $sErrorMessage = "The callback method '$sCallbackMethodName' of class '$sCallBackClassName' must have a third parameter of type 'EventNotification', but it has {$param->getType()->getName()} instead."; IssueLog::Error($sErrorMessage); throw new SecurityException($sErrorMessage); } From 99da1c4d6f7738289f1046be82472265dc1b2b14 Mon Sep 17 00:00:00 2001 From: jf-cbd Date: Wed, 11 Jun 2025 16:22:23 +0200 Subject: [PATCH 3/6] Add tests --- datamodel.combodo-webhook-integration.xml | 6 +- .../Notification/Action/_ActionWebhook.php | 2 +- tests/php-unit-tests/_ActionWebhookTest.php | 90 +++++++++++++++++++ 3 files changed, 94 insertions(+), 4 deletions(-) diff --git a/datamodel.combodo-webhook-integration.xml b/datamodel.combodo-webhook-integration.xml index 9e3dd3f..2ed05ca 100644 --- a/datamodel.combodo-webhook-integration.xml +++ b/datamodel.combodo-webhook-integration.xml @@ -878,8 +878,8 @@ - false - private + true + public Overload-DBObject ') !== false) { diff --git a/src/Core/Notification/Action/_ActionWebhook.php b/src/Core/Notification/Action/_ActionWebhook.php index 932d240..8a5fc2c 100644 --- a/src/Core/Notification/Action/_ActionWebhook.php +++ b/src/Core/Notification/Action/_ActionWebhook.php @@ -77,7 +77,7 @@ public static function ExecuteResponseHandler(WebResponse $oResponse, array $aPa * @throws ReflectionException * @throws SecurityException */ - private static function CheckCallbackSignature(string $sResponseCallback, $oTriggeringObject = null): void + public static function CheckCallbackSignature(string $sResponseCallback, $oTriggeringObject = null): void { $iParamCount = 0; if (stripos($sResponseCallback, '$this->') !== false) { diff --git a/tests/php-unit-tests/_ActionWebhookTest.php b/tests/php-unit-tests/_ActionWebhookTest.php index 13f35bc..5d90947 100644 --- a/tests/php-unit-tests/_ActionWebhookTest.php +++ b/tests/php-unit-tests/_ActionWebhookTest.php @@ -8,7 +8,10 @@ use Combodo\iTop\Core\Notification\Action\_ActionWebhook; use Combodo\iTop\Core\Notification\Action\Webhook\Exception\WebhookInvalidJsonValueException; +use Combodo\iTop\Core\WebResponse; +use Combodo\iTop\Core\ActionWebhook; use Combodo\iTop\Test\UnitTest\ItopTestCase; +use DBObject; class _ActionWebhookTest extends ItopTestCase { protected function setUp(): void { @@ -58,4 +61,91 @@ public function IsJsonValidProvider() { 'invalid array' => ['{foo:bar}', true], ]; } + + /** + * @return void + * @dataProvider CheckCallbackSignatureTestProvider + */ + public function testCheckCallbackSignature(string $sResponseCallback, $oTriggeringObject, $bExpectedException, $sErrorMessage) + { + if ($bExpectedException) { + $this->expectException(\SecurityException::class); + $this->expectExceptionMessage($sErrorMessage); + } else { + $this->expectNotToPerformAssertions(); + } + _ActionWebhook::CheckCallbackSignature($sResponseCallback, $oTriggeringObject); + } + + public function CheckCallbackSignatureTestProvider() + { + return [ + 'Check callback signature with no parameters' => [ + 'sResponseCallback' => 'Combodo\iTop\Test\UnitTest\Core\_ActionWebhookTest::CallBackWebhookNoParams', + 'oTriggeringObject' => null, + 'expectedException' => true, + 'sErrorMessage' => 'The callback method \'CallBackWebhookNoParams\' of class \'Combodo\iTop\Test\UnitTest\Core\_ActionWebhookTest\' must have exactly 3 parameters: the DBObject, the WebResponse and the ActionWebhook object.', + ], + 'Check callback signature with one parameter' => [ + 'sResponseCallback' => 'Combodo\iTop\Test\UnitTest\Core\_ActionWebhookTest::CallBackWebhookOneParam', + 'oTriggeringObject' => null, + 'expectedException' => true, + 'sErrorMessage' => 'The callback method \'CallBackWebhookOneParam\' of class \'Combodo\iTop\Test\UnitTest\Core\_ActionWebhookTest\' must have exactly 3 parameters: the DBObject, the WebResponse and the ActionWebhook object.', + ], + 'Check callback signature with no type' => [ + 'sResponseCallback' => 'Combodo\iTop\Test\UnitTest\Core\_ActionWebhookTest::CallBackWebhookThreeUntypedParams', + 'oTriggeringObject' => null, + 'expectedException' => true, + 'sErrorMessage' => "The callback method 'CallBackWebhookThreeUntypedParams' of class 'Combodo\iTop\Test\UnitTest\Core\_ActionWebhookTest' must have type-hinted parameters, but parameter oDBObject is not type-hinted.", + ], + 'Check callback signature with wrong type' => [ + 'sResponseCallback' => 'Combodo\iTop\Test\UnitTest\Core\_ActionWebhookTest::CallBackWebhookThreeParamsWithWrongType', + 'oTriggeringObject' => null, + 'expectedException' => true, + 'sErrorMessage' => "The callback method 'CallBackWebhookThreeParamsWithWrongType' of class 'Combodo\iTop\Test\UnitTest\Core\_ActionWebhookTest' must have a first parameter of type 'DBObject', but it has int instead.", + ], + 'Check callback signature with correct type' => [ + 'sResponseCallback' => 'Combodo\iTop\Test\UnitTest\Core\_ActionWebhookTest::CallBackWebhookThreeParamsCorrect', + 'oTriggeringObject' => null, + 'expectedException' => false, + 'sErrorMessage' => '', + ], +/* 'Check callback signature with correct type and namespace' => [ // commenting because it's not working; but maybe it's intended + 'sResponseCallback' => 'Combodo\iTop\Test\UnitTest\Core\_ActionWebhookTest::CallBackWebhookThreeParamsCorrectWithNamespace', + 'oTriggeringObject' => null, + 'expectedException' => false, + 'sErrorMessage' => '', + ],*/ + ]; + } + + public static function CallBackWebhookNoParams() + { + // This method is intentionally left empty to test the callback signature + } + + public static function CallBackWebhookOneParam() + { + // This method is intentionally left empty to test the callback signature + } + + public static function CallBackWebhookThreeUntypedParams($oDBObject, $oWebResponse, $oActionWebhook) + { + // This method is intentionally left empty to test the callback signature + } + + public static function CallBackWebhookThreeParamsWithWrongType(int $oDBObject, $oWebResponse, $oActionWebhook) + { + // This method is intentionally left empty to test the callback signature + } + + public static function CallBackWebhookThreeParamsCorrect(DBObject $oDBObject, WebResponse $oWebResponse, \ActionWebhook $oActionWebhook) + { + // This method is intentionally left empty to test the callback signature + } + + public static function CallBackWebhookThreeParamsCorrectWithNamespace(\DBObject $oDBObject, Combodo\iTop\Core\WebResponse $oWebResponse, Combodo\iTop\Core\ActionWebhook $oActionWebhook) + { + // This method is intentionally left empty to test the callback signature + } } From 76213ab3202673ab2d0ae1482baecd983cfa5f6e Mon Sep 17 00:00:00 2001 From: jf-cbd Date: Wed, 18 Jun 2025 13:21:05 +0200 Subject: [PATCH 4/6] Refactor Callback functions in a service and adap tests --- datamodel.combodo-webhook-integration.xml | 117 +------------- .../Notification/Action/_ActionWebhook.php | 114 ++----------- src/Service/CallbackService.php | 104 ++++++++++++ tests/php-unit-tests/_ActionWebhookTest.php | 151 ++++++++++++++---- vendor/autoload.php | 18 +++ vendor/composer/ClassLoader.php | 137 ++++++++-------- vendor/composer/autoload_classmap.php | 3 +- vendor/composer/autoload_namespaces.php | 2 +- vendor/composer/autoload_psr4.php | 2 +- vendor/composer/autoload_real.php | 15 +- vendor/composer/autoload_static.php | 1 + 11 files changed, 334 insertions(+), 330 deletions(-) create mode 100644 src/Service/CallbackService.php diff --git a/datamodel.combodo-webhook-integration.xml b/datamodel.combodo-webhook-integration.xml index 2ed05ca..68f6501 100644 --- a/datamodel.combodo-webhook-integration.xml +++ b/datamodel.combodo-webhook-integration.xml @@ -852,24 +852,9 @@ /** @var \DBObject $oTriggeringObject */ $oTriggeringObject = $aContextArgs['this->object()']; - // Check if callback is on the object itself - if(stripos($sCallbackFQCN, '$this->') !== false) - { - $sMethodName = str_ireplace('$this->', '', $sCallbackFQCN); - $this->CheckCallbackSignature($sCallbackFQCN, $oTriggeringObject); - $payload = $oTriggeringObject->$sMethodName($aContextArgs, $oLog, $this); - } - // Otherwise, check if callback is callable as a static method - elseif(is_callable($sCallbackFQCN)) - { - $this->CheckCallbackSignature($sCallbackFQCN); - $payload = call_user_func($sCallbackFQCN, $oTriggeringObject, $aContextArgs, $oLog, $this); - } - // Otherwise, there is a problem - else - { - throw new Exception('Prepare payload callback is not callable ('.$sCallbackFQCN.')'); - } + $oCallBack = new CallbackService($sCallbackFQCN); + $oCallBack->CheckCallbackSignature('DBObject', ['array', 'EventNotification', 'ActionWebhook']); + $oCallBack->Invoke($oTriggeringObject, [$aContextArgs, $oLog, $this]); } return $payload; @@ -877,102 +862,6 @@ ]]> - - true - public - Overload-DBObject - - - - ') !== false) { - $sCallbackMethodName = str_ireplace('$this->', '', $sResponseCallback); - $sCallBackClassName = get_class($oTriggeringObject); - $oReflector = new ReflectionClass($sCallBackClassName); - $aCallbackParameters = $oReflector->getMethod($sCallbackMethodName)->getParameters(); - if (count($aCallbackParameters) !== 3) { - $sErrorMessage = "The callback method '$sCallbackMethodName' of class '$sCallBackClassName' must have exactly 3 parameters: the ContextArgs array, the EventNotification object and the ActionWebhook object."; - IssueLog::Error($sErrorMessage); - throw new SecurityException($sErrorMessage); - } - foreach ($aCallbackParameters as $param) { - if ($param->getType() === null) { - $sErrorMessage = "The callback method '$sCallbackMethodName' of class '$sCallBackClassName' must have type-hinted parameters, but parameter {$param->getName()} is not type-hinted."; - IssueLog::Error($sErrorMessage); - throw new SecurityException($sErrorMessage); - } - if ($iParamCount === 0 && $param->getType()->getName() !== 'array') { - $sErrorMessage = "The callback method '$sCallbackMethodName' of class '$sCallBackClassName' must have a first parameter of type 'array', but it has {$param->getType()->getName()} instead."; - IssueLog::Error($sErrorMessage); - throw new SecurityException($sErrorMessage); - } elseif ($iParamCount === 1 && $param->getType()->getName() !== 'EventNotification') { - $sErrorMessage = "The callback method '$sCallbackMethodName' of class '$sCallBackClassName' must have a second parameter of type 'EventNotification', but it has {$param->getType()->getName()} instead."; - IssueLog::Error($sErrorMessage); - throw new SecurityException($sErrorMessage); - } - elseif ($iParamCount === 2 && $param->getType()->getName() !== 'ActionWebhook') { - $sErrorMessage = "The callback method '$sCallbackMethodName' of class '$sCallBackClassName' must have a third parameter of type 'ActionWebhook', but it has {$param->getType()->getName()} instead."; - IssueLog::Error($sErrorMessage); - throw new SecurityException($sErrorMessage); - } - $iParamCount++; - } - } else { - $pos = strrpos($sResponseCallback, '::'); - if ($pos !== false) { - $sCallBackClassName = substr($sResponseCallback, 0, $pos); - $sCallbackMethodName = substr($sResponseCallback, $pos + 2); - } else { - $sErrorMessage = "The callback '$sResponseCallback' is not a valid static method (missing '::' separator)."; - IssueLog::Error($sErrorMessage); - throw new SecurityException($sErrorMessage); - } - $oReflector = new ReflectionClass($sCallBackClassName); - $aCallbackParameters = $oReflector->getMethod($sCallbackMethodName)->getParameters(); - if (count($aCallbackParameters) !== 4) { - $sErrorMessage = "The callback method '$sCallbackMethodName' of class '$sCallBackClassName' must have exactly 4 parameters: the DBObject, the ContextArgs array, the EventNotification object and the ActionWebhook object."; - IssueLog::Error($sErrorMessage); - throw new SecurityException($sErrorMessage); - } - foreach ($aCallbackParameters as $param) { - if ($param->getType() === null) { - $sErrorMessage = "The callback method '$sCallbackMethodName' of class '$sCallBackClassName' must have type-hinted parameters, but parameter {$param->getName()} is not type-hinted."; - IssueLog::Error($sErrorMessage); - throw new SecurityException($sErrorMessage); - } - if ($iParamCount === 0 && $param->getType()->getName() !== 'DBObject') { - $sErrorMessage = "The callback method '$sCallbackMethodName' of class '$sCallBackClassName' must have a first parameter of type 'DBObject', but it has {$param->getType()->getName()} instead."; - IssueLog::Error($sErrorMessage); - throw new SecurityException($sErrorMessage); - } elseif ($iParamCount === 1 && $param->getType()->getName() !== 'array') { - $sErrorMessage = "The callback method '$sCallbackMethodName' of class '$sCallBackClassName' must have a second parameter of type 'array', but it has {$param->getType()->getName()} instead."; - IssueLog::Error($sErrorMessage); - throw new SecurityException($sErrorMessage); - } elseif ($iParamCount === 2 && $param->getType()->getName() !== 'EventNotification') { - $sErrorMessage = "The callback method '$sCallbackMethodName' of class '$sCallBackClassName' must have a third parameter of type 'EventNotification', but it has {$param->getType()->getName()} instead."; - IssueLog::Error($sErrorMessage); - throw new SecurityException($sErrorMessage); - } - elseif ($iParamCount === 3 && $param->getType()->getName() !== 'ActionWebhook') { - $sErrorMessage = "The callback method '$sCallbackMethodName' of class '$sCallBackClassName' must have a third parameter of type 'ActionWebhook', but it has {$param->getType()->getName()} instead."; - IssueLog::Error($sErrorMessage); - throw new SecurityException($sErrorMessage); - } - $iParamCount++; - } - } - } - ]]> - - false protected diff --git a/src/Core/Notification/Action/_ActionWebhook.php b/src/Core/Notification/Action/_ActionWebhook.php index 8a5fc2c..a37bdcb 100644 --- a/src/Core/Notification/Action/_ActionWebhook.php +++ b/src/Core/Notification/Action/_ActionWebhook.php @@ -6,15 +6,14 @@ use ApplicationContext; use Combodo\iTop\Core\Notification\Action\Webhook\Exception\WebhookInvalidJsonValueException; use Combodo\iTop\Core\WebResponse; +use Combodo\iTop\Service\CallbackService; use Combodo\iTop\Service\WebRequestSender; +use DBObject; use EventWebhook; use Exception; use IssueLog; use MetaModel; -use ReflectionClass; use ReflectionException; -use ReflectionMethod; -use SecurityException; use UserRights; use utils; @@ -35,117 +34,28 @@ abstract class _ActionWebhook extends ActionNotification * @throws \ArchivedObjectException * @throws \CoreException * @throws ReflectionException + * @throws \Exception */ public static function ExecuteResponseHandler(WebResponse $oResponse, array $aParams) { // Retrieve objects from params - if (! array_key_exists('oTriggeringObject', $aParams) || ! array_key_exists('oActionWebhook', $aParams)) { + if (!array_key_exists('oTriggeringObject', $aParams) || !array_key_exists('oActionWebhook', $aParams)) { IssueLog::Error('Missing parameters in response handler. Expecting at least oTriggeringObject and oActionWebhook', 'console', [ 'oResponse' => $oResponse, 'aParams' => $aParams, ]); throw new Exception('Missing parameters in response handler. See error log for details.'); } - /** @var \DBObject $oTriggeringObject */ - $oTriggeringObject = is_object($aParams['oTriggeringObject']) ? - $aParams['oTriggeringObject'] : - MetaModel::GetObject($aParams['oTriggeringObject']['class'], $aParams['oTriggeringObject']['id'], true, true); + /** @var DBObject $oTriggeringObject */ + $oTriggeringObject = is_object($aParams['oTriggeringObject']) ? + $aParams['oTriggeringObject'] : + MetaModel::GetObject($aParams['oTriggeringObject']['class'], $aParams['oTriggeringObject']['id'], true, true); $oActionWebhook = MetaModel::GetObject($aParams['oActionWebhook']['class'], $aParams['oActionWebhook']['id'], true, true); + $sResponseCallback = $oActionWebhook->Get('process_response_callback'); - // Check if callback is on the object itself - $sResponseCallback = $oActionWebhook->Get('process_response_callback'); - if(stripos($sResponseCallback, '$this->') !== false) - { - $sMethodName = str_ireplace('$this->', '', $sResponseCallback); - self::CheckCallbackSignature($sResponseCallback, $oTriggeringObject); - $oTriggeringObject->$sMethodName($oResponse, $oActionWebhook); - } - // Otherwise, check if callback is callable as a static method - elseif(is_callable($sResponseCallback)) - { - self::CheckCallbackSignature($sResponseCallback); - call_user_func($sResponseCallback, $oTriggeringObject, $oResponse, $oActionWebhook); - } - // Otherwise, there is a problem, we cannot call the callback - elseif(empty($sResponseCallback) === false) - { - throw new Exception('Process response callback is not callable ('.$sResponseCallback.')'); - } - } - - /** - * @throws ReflectionException - * @throws SecurityException - */ - public static function CheckCallbackSignature(string $sResponseCallback, $oTriggeringObject = null): void - { - $iParamCount = 0; - if (stripos($sResponseCallback, '$this->') !== false) { - $sCallbackMethodName = str_ireplace('$this->', '', $sResponseCallback); - $sCallBackClassName = get_class($oTriggeringObject); - $oReflector = new ReflectionClass($sCallBackClassName); - $aCallbackParameters = $oReflector->getMethod($sCallbackMethodName)->getParameters(); - if (count($aCallbackParameters) !== 2) { - $sErrorMessage = "The callback method '$sCallbackMethodName' of class '$sCallBackClassName' must have exactly 2 parameters: the WebResponse and the ActionWebhook object."; - IssueLog::Error($sErrorMessage); - throw new SecurityException($sErrorMessage); - } - foreach ($aCallbackParameters as $param) { - if ($param->getType() === null) { - $sErrorMessage = "The callback method '$sCallbackMethodName' of class '$sCallBackClassName' must have type-hinted parameters, but parameter {$param->getName()} is not type-hinted."; - IssueLog::Error($sErrorMessage); - throw new SecurityException($sErrorMessage); - } - if ($iParamCount === 0 && $param->getType()->getName() !== 'Combodo\iTop\Core\WebResponse') { - $sErrorMessage = "The callback method '$sCallbackMethodName' of class '$sCallBackClassName' must have a first parameter of type 'Combodo\iTop\Core\WebResponse', but it has {$param->getType()->getName()} instead."; - IssueLog::Error($sErrorMessage); - throw new SecurityException($sErrorMessage); - } elseif ($iParamCount === 1 && $param->getType()->getName() !== 'ActionWebhook') { - $sErrorMessage = "The callback method '$sCallbackMethodName' of class '$sCallBackClassName' must have a second parameter of type 'ActionWebhook', but it has {$param->getType()->getName()} instead."; - IssueLog::Error($sErrorMessage); - throw new SecurityException($sErrorMessage); - } - $iParamCount++; - } - } else { - $pos = strrpos($sResponseCallback, '::'); - if ($pos !== false) { - $sCallBackClassName = substr($sResponseCallback, 0, $pos); - $sCallbackMethodName = substr($sResponseCallback, $pos + 2); - } else { - $sErrorMessage = "The callback '$sResponseCallback' is not a valid static method (missing '::' separator)."; - IssueLog::Error($sErrorMessage); - throw new SecurityException($sErrorMessage); - } - $oReflector = new ReflectionClass($sCallBackClassName); - $aCallbackParameters = $oReflector->getMethod($sCallbackMethodName)->getParameters(); - if (count($aCallbackParameters) !== 3) { - $sErrorMessage = "The callback method '$sCallbackMethodName' of class '$sCallBackClassName' must have exactly 3 parameters: the DBObject, the WebResponse and the ActionWebhook object."; - IssueLog::Error($sErrorMessage); - throw new SecurityException($sErrorMessage); - } - foreach ($aCallbackParameters as $param) { - if ($param->getType() === null) { - $sErrorMessage = "The callback method '$sCallbackMethodName' of class '$sCallBackClassName' must have type-hinted parameters, but parameter {$param->getName()} is not type-hinted."; - IssueLog::Error($sErrorMessage); - throw new SecurityException($sErrorMessage); - } - if ($iParamCount === 0 && $param->getType()->getName() !== 'DBObject') { - $sErrorMessage = "The callback method '$sCallbackMethodName' of class '$sCallBackClassName' must have a first parameter of type 'DBObject', but it has {$param->getType()->getName()} instead."; - IssueLog::Error($sErrorMessage); - throw new SecurityException($sErrorMessage); - } elseif ($iParamCount === 1 && $param->getType()->getName() !== 'Combodo\iTop\Core\WebResponse') { - $sErrorMessage = "The callback method '$sCallbackMethodName' of class '$sCallBackClassName' must have a second parameter of type 'Combodo\iTop\Core\WebResponse', but it has {$param->getType()->getName()} instead."; - IssueLog::Error($sErrorMessage); - throw new SecurityException($sErrorMessage); - } elseif ($iParamCount === 2 && $param->getType()->getName() !== 'ActionWebhook') { - $sErrorMessage = "The callback method '$sCallbackMethodName' of class '$sCallBackClassName' must have a third parameter of type 'ActionWebhook', but it has {$param->getType()->getName()} instead."; - IssueLog::Error($sErrorMessage); - throw new SecurityException($sErrorMessage); - } - $iParamCount++; - } - } + $oCallBack = new CallbackService($sResponseCallback); + $oCallBack->CheckCallbackSignature(get_class($oTriggeringObject), ['Combodo\iTop\Core\WebResponse', 'ActionWebhook']); + $oCallBack->Invoke($oTriggeringObject, [$oResponse, $oActionWebhook]); } /** diff --git a/src/Service/CallbackService.php b/src/Service/CallbackService.php new file mode 100644 index 0000000..3af4104 --- /dev/null +++ b/src/Service/CallbackService.php @@ -0,0 +1,104 @@ +sCallBackDefinition = $sCallBackDefinition; + if (stripos($sCallBackDefinition, '$this->') !== false) { + $this->sCallBackMethodName = str_ireplace('$this->', '', $sCallBackDefinition); + $this->bIsStatic = false; + $this->sCallBackClassName = ''; // we don't need it for non-static methods + } else { + if (!is_callable($sCallBackDefinition)) { + $sMessageError = "The callback '$sCallBackDefinition' is not valid."; + IssueLog::Error($sMessageError, 'console'); + throw new Exception($sMessageError); + } + $iPos = strrpos($sCallBackDefinition, '::'); + if ($iPos !== false && $iPos > 0) { + $this->sCallBackClassName = substr($sCallBackDefinition, 0, $iPos); + $this->sCallBackMethodName = substr($sCallBackDefinition, $iPos + 2); + $this->bIsStatic = true; + } else { + $sMessageError = "The callback '$sCallBackDefinition' is not a valid static method."; + IssueLog::Error($sMessageError, 'console'); + throw new Exception($sMessageError); + } + } + } + + public function IsStatic(): bool + { + return $this->bIsStatic; + } + /** + * @throws ReflectionException + * @throws SecurityException + */ + public function CheckCallbackSignature(string $sTriggeringObjectType, array $aParamsType): void + { + $iParamCount = 0; + if ($this->bIsStatic) { // If the callback is static, we expect the first parameter to be the triggering object type + array_unshift($aParamsType, $sTriggeringObjectType); + } else { + $this->sCallBackClassName = $sTriggeringObjectType; + } + $oReflector = new ReflectionClass($this->sCallBackClassName); + $aCallbackParameters = $oReflector->getMethod($this->sCallBackMethodName)->getParameters(); + $iRequiredNumberOfParams = count($aParamsType); + if (count($aCallbackParameters) !== $iRequiredNumberOfParams) { + $sErrorMessage = "The callback method '$this->sCallBackMethodName' of class '$this->sCallBackClassName' must have exactly $iRequiredNumberOfParams parameters."; + IssueLog::Error($sErrorMessage); + throw new SecurityException($sErrorMessage); + } + foreach ($aCallbackParameters as $oParam) { + if ($oParam->getType() === null) { + $sErrorMessage = "The callback method '$this->sCallBackMethodName' of class '$this->sCallBackClassName' must have type-hinted parameters, but parameter {$oParam->getName()} is not type-hinted."; + IssueLog::Error($sErrorMessage); + throw new SecurityException($sErrorMessage); + } + if ($oParam->getType()->getName() !== $aParamsType[$iParamCount]) { + $sErrorMessage = "The callback method '$this->sCallBackMethodName' of class '$this->sCallBackClassName' must have a first parameter of type '$aParamsType[$iParamCount]', but it has {$oParam->getType()->getName()} instead."; + IssueLog::Error($sErrorMessage); + throw new SecurityException($sErrorMessage); + } + $iParamCount++; + } + } + + /** + * @param $oTriggeringObject + * @param array $aParams + * + * @return mixed + */ + public function Invoke($oTriggeringObject, array $aParams): mixed + { + if ($this->bIsStatic) { + return call_user_func_array([$this->sCallBackClassName, $this->sCallBackMethodName], array_merge([$oTriggeringObject], $aParams)); + + } else { + return call_user_func_array([$oTriggeringObject, $this->sCallBackMethodName], $aParams); + } + } +} \ No newline at end of file diff --git a/tests/php-unit-tests/_ActionWebhookTest.php b/tests/php-unit-tests/_ActionWebhookTest.php index 5d90947..ac6d354 100644 --- a/tests/php-unit-tests/_ActionWebhookTest.php +++ b/tests/php-unit-tests/_ActionWebhookTest.php @@ -9,17 +9,19 @@ use Combodo\iTop\Core\Notification\Action\_ActionWebhook; use Combodo\iTop\Core\Notification\Action\Webhook\Exception\WebhookInvalidJsonValueException; use Combodo\iTop\Core\WebResponse; -use Combodo\iTop\Core\ActionWebhook; +use ActionWebhook; +use Combodo\iTop\Service\CallbackService; use Combodo\iTop\Test\UnitTest\ItopTestCase; use DBObject; +use Exception; +use ReflectionException; -class _ActionWebhookTest extends ItopTestCase { - protected function setUp(): void { +class _ActionWebhookTest extends ItopTestCase +{ + protected function setUp(): void + { parent::setUp(); - - // no datamodel loaded so we need to include module file manually - $this->RequireOnceItopFile('env-production/combodo-webhook-integration/src/Core/Notification/Action/_ActionWebhook.php'); - $this->RequireOnceItopFile('env-production/combodo-webhook-integration/src/Core/Notification/Action/Webhook/Exception/WebhookInvalidJsonValueException.php'); + $this->RequireOnceItopFile('env-production/combodo-webhook-integration/vendor/autoload.php'); } /** @@ -65,8 +67,10 @@ public function IsJsonValidProvider() { /** * @return void * @dataProvider CheckCallbackSignatureTestProvider + * @throws ReflectionException + * @throws Exception */ - public function testCheckCallbackSignature(string $sResponseCallback, $oTriggeringObject, $bExpectedException, $sErrorMessage) + public function testCheckCallbackSignature(string $sResponseCallback, $bExpectedException, $sErrorMessage) { if ($bExpectedException) { $this->expectException(\SecurityException::class); @@ -74,48 +78,87 @@ public function testCheckCallbackSignature(string $sResponseCallback, $oTriggeri } else { $this->expectNotToPerformAssertions(); } - _ActionWebhook::CheckCallbackSignature($sResponseCallback, $oTriggeringObject); + $oCallBackService = new CallbackService($sResponseCallback); + if ($oCallBackService->IsStatic()) { + $oCallBackService->CheckCallbackSignature(DBObject::class, ['Combodo\iTop\Core\WebResponse', 'ActionWebhook']); + } else { + $oCallBackService->CheckCallbackSignature($this::class, ['Combodo\iTop\Core\WebResponse', 'ActionWebhook']); + } } - public function CheckCallbackSignatureTestProvider() + public function CheckCallbackSignatureTestProvider(): array { return [ - 'Check callback signature with no parameters' => [ - 'sResponseCallback' => 'Combodo\iTop\Test\UnitTest\Core\_ActionWebhookTest::CallBackWebhookNoParams', - 'oTriggeringObject' => null, + 'Check callback signature with no parameters' => [ + 'sResponseCallback' => _ActionWebhookTest::class.'::CallBackWebhookNoParams', + 'expectedException' => true, + 'sErrorMessage' => 'The callback method \'CallBackWebhookNoParams\' of class \'Combodo\iTop\Test\UnitTest\Core\_ActionWebhookTest\' must have exactly 3 parameters.', + ], + 'Check callback signature with no parameters on object' => [ + 'sResponseCallback' => '$this->CallBackWebhookNoParamsOnObject', + 'expectedException' => true, + 'sErrorMessage' => 'The callback method \'CallBackWebhookNoParamsOnObject\' of class \'Combodo\iTop\Test\UnitTest\Core\_ActionWebhookTest\' must have exactly 2 parameters.', + ], + 'Check callback signature with one parameter' => [ + 'sResponseCallback' => _ActionWebhookTest::class.'::CallBackWebhookOneParam', 'expectedException' => true, - 'sErrorMessage' => 'The callback method \'CallBackWebhookNoParams\' of class \'Combodo\iTop\Test\UnitTest\Core\_ActionWebhookTest\' must have exactly 3 parameters: the DBObject, the WebResponse and the ActionWebhook object.', + 'sErrorMessage' => 'The callback method \'CallBackWebhookOneParam\' of class \'Combodo\iTop\Test\UnitTest\Core\_ActionWebhookTest\' must have exactly 3 parameters.', ], - 'Check callback signature with one parameter' => [ - 'sResponseCallback' => 'Combodo\iTop\Test\UnitTest\Core\_ActionWebhookTest::CallBackWebhookOneParam', - 'oTriggeringObject' => null, + 'Check callback signature with one parameter on object' => [ + 'sResponseCallback' => '$this->CallBackWebhookOneParamOnObject', 'expectedException' => true, - 'sErrorMessage' => 'The callback method \'CallBackWebhookOneParam\' of class \'Combodo\iTop\Test\UnitTest\Core\_ActionWebhookTest\' must have exactly 3 parameters: the DBObject, the WebResponse and the ActionWebhook object.', + 'sErrorMessage' => 'The callback method \'CallBackWebhookOneParamOnObject\' of class \'Combodo\iTop\Test\UnitTest\Core\_ActionWebhookTest\' must have exactly 2 parameters.', ], - 'Check callback signature with no type' => [ - 'sResponseCallback' => 'Combodo\iTop\Test\UnitTest\Core\_ActionWebhookTest::CallBackWebhookThreeUntypedParams', - 'oTriggeringObject' => null, + 'Check callback signature with no type' => [ + 'sResponseCallback' => _ActionWebhookTest::class.'::CallBackWebhookThreeUntypedParams', 'expectedException' => true, 'sErrorMessage' => "The callback method 'CallBackWebhookThreeUntypedParams' of class 'Combodo\iTop\Test\UnitTest\Core\_ActionWebhookTest' must have type-hinted parameters, but parameter oDBObject is not type-hinted.", ], - 'Check callback signature with wrong type' => [ - 'sResponseCallback' => 'Combodo\iTop\Test\UnitTest\Core\_ActionWebhookTest::CallBackWebhookThreeParamsWithWrongType', - 'oTriggeringObject' => null, + 'Check callback signature with no type on object' => [ + 'sResponseCallback' => '$this->CallBackWebhookTwoUntypedParamsOnObject', + 'expectedException' => true, + 'sErrorMessage' => "The callback method 'CallBackWebhookTwoUntypedParamsOnObject' of class 'Combodo\iTop\Test\UnitTest\Core\_ActionWebhookTest' must have type-hinted parameters, but parameter oWebResponse is not type-hinted.", + ], + 'Check callback signature with wrong type' => [ + 'sResponseCallback' => _ActionWebhookTest::class.'::CallBackWebhookThreeParamsWithWrongType', 'expectedException' => true, 'sErrorMessage' => "The callback method 'CallBackWebhookThreeParamsWithWrongType' of class 'Combodo\iTop\Test\UnitTest\Core\_ActionWebhookTest' must have a first parameter of type 'DBObject', but it has int instead.", ], - 'Check callback signature with correct type' => [ - 'sResponseCallback' => 'Combodo\iTop\Test\UnitTest\Core\_ActionWebhookTest::CallBackWebhookThreeParamsCorrect', - 'oTriggeringObject' => null, + 'Check callback signature with wrong type on object' => [ + 'sResponseCallback' => '$this->CallBackWebhookTwoParamsWithWrongTypeOnObject', + 'expectedException' => true, + 'sErrorMessage' => "The callback method 'CallBackWebhookTwoParamsWithWrongTypeOnObject' of class 'Combodo\iTop\Test\UnitTest\Core\_ActionWebhookTest' must have a first parameter of type 'Combodo\iTop\Core\WebResponse', but it has int instead.", + ], + 'Check callback signature with correct type but incorrect order' => [ + 'sResponseCallback' => _ActionWebhookTest::class.'::CallBackWebhookThreeParamsCorrectButWrongOrder', + 'expectedException' => true, + 'sErrorMessage' => "Combodo\iTop\Test\UnitTest\Core\_ActionWebhookTest' must have a first parameter of type 'DBObject', but it has Combodo\iTop\Core\WebResponse instead.", + ], + 'Check callback signature with correct type but incorrect order on object' => [ + 'sResponseCallback' => '$this->CallBackWebhookTwoParamsCorrectButWrongOrderOnObject', + 'expectedException' => true, + 'sErrorMessage' => "Combodo\iTop\Test\UnitTest\Core\_ActionWebhookTest' must have a first parameter of type 'Combodo\iTop\Core\WebResponse', but it has ActionWebhook instead.", + ], + 'Check callback signature with correct type' => [ + 'sResponseCallback' => _ActionWebhookTest::class.'::CallBackWebhookThreeParamsCorrect', + 'expectedException' => false, + 'sErrorMessage' => '', + ], + 'Check callback signature with correct type on object' => [ + 'sResponseCallback' => '$this->CallBackWebhookTwoParamsCorrectOnObject', + 'expectedException' => false, + 'sErrorMessage' => '', + ], + 'Check callback signature with correct type and namespace' => [ + 'sResponseCallback' => _ActionWebhookTest::class.'::CallBackWebhookThreeParamsCorrectWithNamespace', 'expectedException' => false, 'sErrorMessage' => '', ], -/* 'Check callback signature with correct type and namespace' => [ // commenting because it's not working; but maybe it's intended - 'sResponseCallback' => 'Combodo\iTop\Test\UnitTest\Core\_ActionWebhookTest::CallBackWebhookThreeParamsCorrectWithNamespace', - 'oTriggeringObject' => null, + 'Check callback signature with correct type and namespace on object' => [ + 'sResponseCallback' => '$this->CallBackWebhookTwoParamsCorrectWithNamespaceOnObject', 'expectedException' => false, 'sErrorMessage' => '', - ],*/ + ], ]; } @@ -124,7 +167,7 @@ public static function CallBackWebhookNoParams() // This method is intentionally left empty to test the callback signature } - public static function CallBackWebhookOneParam() + public static function CallBackWebhookOneParam(int $i) { // This method is intentionally left empty to test the callback signature } @@ -139,12 +182,52 @@ public static function CallBackWebhookThreeParamsWithWrongType(int $oDBObject, $ // This method is intentionally left empty to test the callback signature } - public static function CallBackWebhookThreeParamsCorrect(DBObject $oDBObject, WebResponse $oWebResponse, \ActionWebhook $oActionWebhook) + public static function CallBackWebhookThreeParamsCorrectButWrongOrder(WebResponse $oWebResponse, DBObject $oDBObject, ActionWebhook $oActionWebhook) + { + // This method is intentionally left empty to test the callback signature + } + + public static function CallBackWebhookThreeParamsCorrect(DBObject $oDBObject, WebResponse $oWebResponse, ActionWebhook $oActionWebhook) + { + // This method is intentionally left empty to test the callback signature + } + + public static function CallBackWebhookThreeParamsCorrectWithNamespace(\DBObject $oDBObject, \Combodo\iTop\Core\WebResponse $oWebResponse, \ActionWebhook $oActionWebhook) + { + // This method is intentionally left empty to test the callback signature + } + + private function CallBackWebhookNoParamsOnObject() + { + // This method is intentionally left empty to test the callback signature + } + + private function CallBackWebhookOneParamOnObject(int $i) + { + // This method is intentionally left empty to test the callback signature + } + + private function CallBackWebhookTwoUntypedParamsOnObject($oWebResponse, $oActionWebhook) + { + // This method is intentionally left empty to test the callback signature + } + + private function CallBackWebhookTwoParamsWithWrongTypeOnObject(int $oWebResponse, $oActionWebhook) + { + // This method is intentionally left empty to test the callback signature + } + + private function CallBackWebhookTwoParamsCorrectButWrongOrderOnObject(ActionWebhook $oActionWebhook, WebResponse $oWebResponse) + { + // This method is intentionally left empty to test the callback signature + } + + private function CallBackWebhookTwoParamsCorrectOnObject( WebResponse $oWebResponse, ActionWebhook $oActionWebhook) { // This method is intentionally left empty to test the callback signature } - public static function CallBackWebhookThreeParamsCorrectWithNamespace(\DBObject $oDBObject, Combodo\iTop\Core\WebResponse $oWebResponse, Combodo\iTop\Core\ActionWebhook $oActionWebhook) + private function CallBackWebhookTwoParamsCorrectWithNamespaceOnObject(\Combodo\iTop\Core\WebResponse $oWebResponse, \ActionWebhook $oActionWebhook) { // This method is intentionally left empty to test the callback signature } diff --git a/vendor/autoload.php b/vendor/autoload.php index 550ec59..dc1c8d0 100644 --- a/vendor/autoload.php +++ b/vendor/autoload.php @@ -2,6 +2,24 @@ // autoload.php @generated by Composer +if (PHP_VERSION_ID < 50600) { + if (!headers_sent()) { + header('HTTP/1.1 500 Internal Server Error'); + } + $err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL; + if (!ini_get('display_errors')) { + if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { + fwrite(STDERR, $err); + } elseif (!headers_sent()) { + echo $err; + } + } + trigger_error( + $err, + E_USER_ERROR + ); +} + require_once __DIR__ . '/composer/autoload_real.php'; return ComposerAutoloaderInit5c648c5cddb3c29c87aec82f31f7e556::getLoader(); diff --git a/vendor/composer/ClassLoader.php b/vendor/composer/ClassLoader.php index 0cd6055..7824d8f 100644 --- a/vendor/composer/ClassLoader.php +++ b/vendor/composer/ClassLoader.php @@ -42,35 +42,37 @@ */ class ClassLoader { - /** @var ?string */ + /** @var \Closure(string):void */ + private static $includeFile; + + /** @var string|null */ private $vendorDir; // PSR-4 /** - * @var array[] - * @psalm-var array> + * @var array> */ private $prefixLengthsPsr4 = array(); /** - * @var array[] - * @psalm-var array> + * @var array> */ private $prefixDirsPsr4 = array(); /** - * @var array[] - * @psalm-var array + * @var list */ private $fallbackDirsPsr4 = array(); // PSR-0 /** - * @var array[] - * @psalm-var array> + * List of PSR-0 prefixes + * + * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2'))) + * + * @var array>> */ private $prefixesPsr0 = array(); /** - * @var array[] - * @psalm-var array + * @var list */ private $fallbackDirsPsr0 = array(); @@ -78,8 +80,7 @@ class ClassLoader private $useIncludePath = false; /** - * @var string[] - * @psalm-var array + * @var array */ private $classMap = array(); @@ -87,29 +88,29 @@ class ClassLoader private $classMapAuthoritative = false; /** - * @var bool[] - * @psalm-var array + * @var array */ private $missingClasses = array(); - /** @var ?string */ + /** @var string|null */ private $apcuPrefix; /** - * @var self[] + * @var array */ private static $registeredLoaders = array(); /** - * @param ?string $vendorDir + * @param string|null $vendorDir */ public function __construct($vendorDir = null) { $this->vendorDir = $vendorDir; + self::initializeIncludeClosure(); } /** - * @return string[] + * @return array> */ public function getPrefixes() { @@ -121,8 +122,7 @@ public function getPrefixes() } /** - * @return array[] - * @psalm-return array> + * @return array> */ public function getPrefixesPsr4() { @@ -130,8 +130,7 @@ public function getPrefixesPsr4() } /** - * @return array[] - * @psalm-return array + * @return list */ public function getFallbackDirs() { @@ -139,8 +138,7 @@ public function getFallbackDirs() } /** - * @return array[] - * @psalm-return array + * @return list */ public function getFallbackDirsPsr4() { @@ -148,8 +146,7 @@ public function getFallbackDirsPsr4() } /** - * @return string[] Array of classname => path - * @psalm-var array + * @return array Array of classname => path */ public function getClassMap() { @@ -157,8 +154,7 @@ public function getClassMap() } /** - * @param string[] $classMap Class to filename map - * @psalm-param array $classMap + * @param array $classMap Class to filename map * * @return void */ @@ -175,24 +171,25 @@ public function addClassMap(array $classMap) * Registers a set of PSR-0 directories for a given prefix, either * appending or prepending to the ones previously set for this prefix. * - * @param string $prefix The prefix - * @param string[]|string $paths The PSR-0 root directories - * @param bool $prepend Whether to prepend the directories + * @param string $prefix The prefix + * @param list|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories * * @return void */ public function add($prefix, $paths, $prepend = false) { + $paths = (array) $paths; if (!$prefix) { if ($prepend) { $this->fallbackDirsPsr0 = array_merge( - (array) $paths, + $paths, $this->fallbackDirsPsr0 ); } else { $this->fallbackDirsPsr0 = array_merge( $this->fallbackDirsPsr0, - (array) $paths + $paths ); } @@ -201,19 +198,19 @@ public function add($prefix, $paths, $prepend = false) $first = $prefix[0]; if (!isset($this->prefixesPsr0[$first][$prefix])) { - $this->prefixesPsr0[$first][$prefix] = (array) $paths; + $this->prefixesPsr0[$first][$prefix] = $paths; return; } if ($prepend) { $this->prefixesPsr0[$first][$prefix] = array_merge( - (array) $paths, + $paths, $this->prefixesPsr0[$first][$prefix] ); } else { $this->prefixesPsr0[$first][$prefix] = array_merge( $this->prefixesPsr0[$first][$prefix], - (array) $paths + $paths ); } } @@ -222,9 +219,9 @@ public function add($prefix, $paths, $prepend = false) * Registers a set of PSR-4 directories for a given namespace, either * appending or prepending to the ones previously set for this namespace. * - * @param string $prefix The prefix/namespace, with trailing '\\' - * @param string[]|string $paths The PSR-4 base directories - * @param bool $prepend Whether to prepend the directories + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories * * @throws \InvalidArgumentException * @@ -232,17 +229,18 @@ public function add($prefix, $paths, $prepend = false) */ public function addPsr4($prefix, $paths, $prepend = false) { + $paths = (array) $paths; if (!$prefix) { // Register directories for the root namespace. if ($prepend) { $this->fallbackDirsPsr4 = array_merge( - (array) $paths, + $paths, $this->fallbackDirsPsr4 ); } else { $this->fallbackDirsPsr4 = array_merge( $this->fallbackDirsPsr4, - (array) $paths + $paths ); } } elseif (!isset($this->prefixDirsPsr4[$prefix])) { @@ -252,18 +250,18 @@ public function addPsr4($prefix, $paths, $prepend = false) throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); } $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; - $this->prefixDirsPsr4[$prefix] = (array) $paths; + $this->prefixDirsPsr4[$prefix] = $paths; } elseif ($prepend) { // Prepend directories for an already registered namespace. $this->prefixDirsPsr4[$prefix] = array_merge( - (array) $paths, + $paths, $this->prefixDirsPsr4[$prefix] ); } else { // Append directories for an already registered namespace. $this->prefixDirsPsr4[$prefix] = array_merge( $this->prefixDirsPsr4[$prefix], - (array) $paths + $paths ); } } @@ -272,8 +270,8 @@ public function addPsr4($prefix, $paths, $prepend = false) * Registers a set of PSR-0 directories for a given prefix, * replacing any others previously set for this prefix. * - * @param string $prefix The prefix - * @param string[]|string $paths The PSR-0 base directories + * @param string $prefix The prefix + * @param list|string $paths The PSR-0 base directories * * @return void */ @@ -290,8 +288,8 @@ public function set($prefix, $paths) * Registers a set of PSR-4 directories for a given namespace, * replacing any others previously set for this namespace. * - * @param string $prefix The prefix/namespace, with trailing '\\' - * @param string[]|string $paths The PSR-4 base directories + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list|string $paths The PSR-4 base directories * * @throws \InvalidArgumentException * @@ -425,7 +423,8 @@ public function unregister() public function loadClass($class) { if ($file = $this->findFile($class)) { - includeFile($file); + $includeFile = self::$includeFile; + $includeFile($file); return true; } @@ -476,9 +475,9 @@ public function findFile($class) } /** - * Returns the currently registered loaders indexed by their corresponding vendor directories. + * Returns the currently registered loaders keyed by their corresponding vendor directories. * - * @return self[] + * @return array */ public static function getRegisteredLoaders() { @@ -555,18 +554,26 @@ private function findFileWithExtension($class, $ext) return false; } -} -/** - * Scope isolated include. - * - * Prevents access to $this/self from included files. - * - * @param string $file - * @return void - * @private - */ -function includeFile($file) -{ - include $file; + /** + * @return void + */ + private static function initializeIncludeClosure() + { + if (self::$includeFile !== null) { + return; + } + + /** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + * + * @param string $file + * @return void + */ + self::$includeFile = \Closure::bind(static function($file) { + include $file; + }, null, null); + } } diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php index 8b22278..b01cb33 100644 --- a/vendor/composer/autoload_classmap.php +++ b/vendor/composer/autoload_classmap.php @@ -2,7 +2,7 @@ // autoload_classmap.php @generated by Composer -$vendorDir = dirname(dirname(__FILE__)); +$vendorDir = dirname(__DIR__); $baseDir = dirname($vendorDir); return array( @@ -10,6 +10,7 @@ 'Combodo\\iTop\\Core\\Notification\\Action\\_ActionWebhook' => $baseDir . '/src/Core/Notification/Action/_ActionWebhook.php', 'Combodo\\iTop\\Core\\WebRequest' => $baseDir . '/src/Core/WebRequest.php', 'Combodo\\iTop\\Core\\WebResponse' => $baseDir . '/src/Core/WebResponse.php', + 'Combodo\\iTop\\Service\\CallbackService' => $baseDir . '/src/Service/CallbackService.php', 'Combodo\\iTop\\Service\\WebRequestSender' => $baseDir . '/src/Service/WebRequestSender.php', 'Combodo\\iTop\\Service\\WebRequestService' => $baseDir . '/src/Service/WebRequestService.php', 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', diff --git a/vendor/composer/autoload_namespaces.php b/vendor/composer/autoload_namespaces.php index b7fc012..15a2ff3 100644 --- a/vendor/composer/autoload_namespaces.php +++ b/vendor/composer/autoload_namespaces.php @@ -2,7 +2,7 @@ // autoload_namespaces.php @generated by Composer -$vendorDir = dirname(dirname(__FILE__)); +$vendorDir = dirname(__DIR__); $baseDir = dirname($vendorDir); return array( diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php index 51ca060..6d2ed79 100644 --- a/vendor/composer/autoload_psr4.php +++ b/vendor/composer/autoload_psr4.php @@ -2,7 +2,7 @@ // autoload_psr4.php @generated by Composer -$vendorDir = dirname(dirname(__FILE__)); +$vendorDir = dirname(__DIR__); $baseDir = dirname($vendorDir); return array( diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php index b5d4a4b..2d077eb 100644 --- a/vendor/composer/autoload_real.php +++ b/vendor/composer/autoload_real.php @@ -23,20 +23,11 @@ public static function getLoader() } spl_autoload_register(array('ComposerAutoloaderInit5c648c5cddb3c29c87aec82f31f7e556', 'loadClassLoader'), true, true); - self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__))); + self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__)); spl_autoload_unregister(array('ComposerAutoloaderInit5c648c5cddb3c29c87aec82f31f7e556', 'loadClassLoader')); - $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); - if ($useStaticLoader) { - require __DIR__ . '/autoload_static.php'; - - call_user_func(\Composer\Autoload\ComposerStaticInit5c648c5cddb3c29c87aec82f31f7e556::getInitializer($loader)); - } else { - $classMap = require __DIR__ . '/autoload_classmap.php'; - if ($classMap) { - $loader->addClassMap($classMap); - } - } + require __DIR__ . '/autoload_static.php'; + call_user_func(\Composer\Autoload\ComposerStaticInit5c648c5cddb3c29c87aec82f31f7e556::getInitializer($loader)); $loader->setClassMapAuthoritative(true); $loader->register(true); diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index 3867bd8..f0c76e3 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -25,6 +25,7 @@ class ComposerStaticInit5c648c5cddb3c29c87aec82f31f7e556 'Combodo\\iTop\\Core\\Notification\\Action\\_ActionWebhook' => __DIR__ . '/../..' . '/src/Core/Notification/Action/_ActionWebhook.php', 'Combodo\\iTop\\Core\\WebRequest' => __DIR__ . '/../..' . '/src/Core/WebRequest.php', 'Combodo\\iTop\\Core\\WebResponse' => __DIR__ . '/../..' . '/src/Core/WebResponse.php', + 'Combodo\\iTop\\Service\\CallbackService' => __DIR__ . '/../..' . '/src/Service/CallbackService.php', 'Combodo\\iTop\\Service\\WebRequestSender' => __DIR__ . '/../..' . '/src/Service/WebRequestSender.php', 'Combodo\\iTop\\Service\\WebRequestService' => __DIR__ . '/../..' . '/src/Service/WebRequestService.php', 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', From d220fa4b3e991038800ab5669b826a58a52f582a Mon Sep 17 00:00:00 2001 From: jf-cbd <121934370+jf-cbd@users.noreply.github.com> Date: Wed, 18 Jun 2025 14:26:58 +0200 Subject: [PATCH 5/6] Use ::class Co-authored-by: Thomas Casteleyn --- datamodel.combodo-webhook-integration.xml | 2 +- src/Core/Notification/Action/_ActionWebhook.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/datamodel.combodo-webhook-integration.xml b/datamodel.combodo-webhook-integration.xml index 68f6501..b42755f 100644 --- a/datamodel.combodo-webhook-integration.xml +++ b/datamodel.combodo-webhook-integration.xml @@ -853,7 +853,7 @@ $oTriggeringObject = $aContextArgs['this->object()']; $oCallBack = new CallbackService($sCallbackFQCN); - $oCallBack->CheckCallbackSignature('DBObject', ['array', 'EventNotification', 'ActionWebhook']); + $oCallBack->CheckCallbackSignature(DBObject::class, ['array', EventNotification::class, ActionWebhook::class]); $oCallBack->Invoke($oTriggeringObject, [$aContextArgs, $oLog, $this]); } diff --git a/src/Core/Notification/Action/_ActionWebhook.php b/src/Core/Notification/Action/_ActionWebhook.php index a37bdcb..5a721d1 100644 --- a/src/Core/Notification/Action/_ActionWebhook.php +++ b/src/Core/Notification/Action/_ActionWebhook.php @@ -54,7 +54,7 @@ public static function ExecuteResponseHandler(WebResponse $oResponse, array $aPa $sResponseCallback = $oActionWebhook->Get('process_response_callback'); $oCallBack = new CallbackService($sResponseCallback); - $oCallBack->CheckCallbackSignature(get_class($oTriggeringObject), ['Combodo\iTop\Core\WebResponse', 'ActionWebhook']); + $oCallBack->CheckCallbackSignature(get_class($oTriggeringObject), [Combodo\iTop\Core\WebResponse::class, ActionWebhook::class]); $oCallBack->Invoke($oTriggeringObject, [$oResponse, $oActionWebhook]); } From e6b786b4866cd638030917644673e1086ce49364 Mon Sep 17 00:00:00 2001 From: jf-cbd Date: Wed, 18 Jun 2025 16:50:53 +0200 Subject: [PATCH 6/6] Fix CI test + import Hipska's change --- datamodel.combodo-webhook-integration.xml | 2 +- .../Notification/Action/_ActionWebhook.php | 2 +- src/Service/CallbackService.php | 25 +++++++++++-------- tests/php-unit-tests/_ActionWebhookTest.php | 8 +++--- 4 files changed, 21 insertions(+), 16 deletions(-) diff --git a/datamodel.combodo-webhook-integration.xml b/datamodel.combodo-webhook-integration.xml index b42755f..9c7ccc7 100644 --- a/datamodel.combodo-webhook-integration.xml +++ b/datamodel.combodo-webhook-integration.xml @@ -853,7 +853,7 @@ $oTriggeringObject = $aContextArgs['this->object()']; $oCallBack = new CallbackService($sCallbackFQCN); - $oCallBack->CheckCallbackSignature(DBObject::class, ['array', EventNotification::class, ActionWebhook::class]); + $oCallBack->CheckCallbackSignature(get_class($oTriggeringObject), ['array', EventNotification::class, ActionWebhook::class]); $oCallBack->Invoke($oTriggeringObject, [$aContextArgs, $oLog, $this]); } diff --git a/src/Core/Notification/Action/_ActionWebhook.php b/src/Core/Notification/Action/_ActionWebhook.php index 5a721d1..d9c849d 100644 --- a/src/Core/Notification/Action/_ActionWebhook.php +++ b/src/Core/Notification/Action/_ActionWebhook.php @@ -54,7 +54,7 @@ public static function ExecuteResponseHandler(WebResponse $oResponse, array $aPa $sResponseCallback = $oActionWebhook->Get('process_response_callback'); $oCallBack = new CallbackService($sResponseCallback); - $oCallBack->CheckCallbackSignature(get_class($oTriggeringObject), [Combodo\iTop\Core\WebResponse::class, ActionWebhook::class]); + $oCallBack->CheckCallbackSignature(get_class($oTriggeringObject), [WebResponse::class, \ActionWebhook::class]); $oCallBack->Invoke($oTriggeringObject, [$oResponse, $oActionWebhook]); } diff --git a/src/Service/CallbackService.php b/src/Service/CallbackService.php index 3af4104..54937a7 100644 --- a/src/Service/CallbackService.php +++ b/src/Service/CallbackService.php @@ -2,6 +2,7 @@ namespace Combodo\iTop\Service; +use DBObject; use Exception; use IssueLog; use ReflectionClass; @@ -9,11 +10,15 @@ use SecurityException; /** - * A service that will perform checks on the callback signature of a webhook action. + * A service that will perform checks on the signature of a method. + * Usage: + * $callbackService = new CallbackService('ClassName::methodName'); + * $callbackService->CheckCallbackSignature(get_class($oTriggeringObject), [DBObject::class, 'string', 'int']); + * $callbackService->Invoke($oTriggeringObject, ['stringValue', 42]); + * */ class CallbackService { - private string $sCallBackDefinition; private string $sCallBackClassName; private string $sCallBackMethodName; private bool $bIsStatic; @@ -23,7 +28,6 @@ class CallbackService */ public function __construct(string $sCallBackDefinition) { - $this->sCallBackDefinition = $sCallBackDefinition; if (stripos($sCallBackDefinition, '$this->') !== false) { $this->sCallBackMethodName = str_ireplace('$this->', '', $sCallBackDefinition); $this->bIsStatic = false; @@ -51,17 +55,18 @@ public function IsStatic(): bool { return $this->bIsStatic; } + /** * @throws ReflectionException * @throws SecurityException */ - public function CheckCallbackSignature(string $sTriggeringObjectType, array $aParamsType): void + public function CheckCallbackSignature(string $sTriggeringObjectClass, array $aParamsType): void { $iParamCount = 0; if ($this->bIsStatic) { // If the callback is static, we expect the first parameter to be the triggering object type - array_unshift($aParamsType, $sTriggeringObjectType); + array_unshift($aParamsType, DBObject::class); } else { - $this->sCallBackClassName = $sTriggeringObjectType; + $this->sCallBackClassName = $sTriggeringObjectClass; } $oReflector = new ReflectionClass($this->sCallBackClassName); $aCallbackParameters = $oReflector->getMethod($this->sCallBackMethodName)->getParameters(); @@ -71,18 +76,18 @@ public function CheckCallbackSignature(string $sTriggeringObjectType, array $aPa IssueLog::Error($sErrorMessage); throw new SecurityException($sErrorMessage); } - foreach ($aCallbackParameters as $oParam) { + foreach ($aParamsType as $iParamOrder => $sParamType) { + $oParam = $aCallbackParameters[$iParamOrder]; if ($oParam->getType() === null) { $sErrorMessage = "The callback method '$this->sCallBackMethodName' of class '$this->sCallBackClassName' must have type-hinted parameters, but parameter {$oParam->getName()} is not type-hinted."; IssueLog::Error($sErrorMessage); throw new SecurityException($sErrorMessage); } - if ($oParam->getType()->getName() !== $aParamsType[$iParamCount]) { - $sErrorMessage = "The callback method '$this->sCallBackMethodName' of class '$this->sCallBackClassName' must have a first parameter of type '$aParamsType[$iParamCount]', but it has {$oParam->getType()->getName()} instead."; + if ($oParam->getType()->getName() !== $sParamType) { + $sErrorMessage = "The callback method '$this->sCallBackMethodName' of class '$this->sCallBackClassName' must have a parameter of type '$sParamType', but it has {$oParam->getType()->getName()} instead."; IssueLog::Error($sErrorMessage); throw new SecurityException($sErrorMessage); } - $iParamCount++; } } diff --git a/tests/php-unit-tests/_ActionWebhookTest.php b/tests/php-unit-tests/_ActionWebhookTest.php index ac6d354..90e7c4d 100644 --- a/tests/php-unit-tests/_ActionWebhookTest.php +++ b/tests/php-unit-tests/_ActionWebhookTest.php @@ -122,22 +122,22 @@ public function CheckCallbackSignatureTestProvider(): array 'Check callback signature with wrong type' => [ 'sResponseCallback' => _ActionWebhookTest::class.'::CallBackWebhookThreeParamsWithWrongType', 'expectedException' => true, - 'sErrorMessage' => "The callback method 'CallBackWebhookThreeParamsWithWrongType' of class 'Combodo\iTop\Test\UnitTest\Core\_ActionWebhookTest' must have a first parameter of type 'DBObject', but it has int instead.", + 'sErrorMessage' => "The callback method 'CallBackWebhookThreeParamsWithWrongType' of class 'Combodo\iTop\Test\UnitTest\Core\_ActionWebhookTest' must have a parameter of type 'DBObject', but it has int instead.", ], 'Check callback signature with wrong type on object' => [ 'sResponseCallback' => '$this->CallBackWebhookTwoParamsWithWrongTypeOnObject', 'expectedException' => true, - 'sErrorMessage' => "The callback method 'CallBackWebhookTwoParamsWithWrongTypeOnObject' of class 'Combodo\iTop\Test\UnitTest\Core\_ActionWebhookTest' must have a first parameter of type 'Combodo\iTop\Core\WebResponse', but it has int instead.", + 'sErrorMessage' => "The callback method 'CallBackWebhookTwoParamsWithWrongTypeOnObject' of class 'Combodo\iTop\Test\UnitTest\Core\_ActionWebhookTest' must have a parameter of type 'Combodo\iTop\Core\WebResponse', but it has int instead.", ], 'Check callback signature with correct type but incorrect order' => [ 'sResponseCallback' => _ActionWebhookTest::class.'::CallBackWebhookThreeParamsCorrectButWrongOrder', 'expectedException' => true, - 'sErrorMessage' => "Combodo\iTop\Test\UnitTest\Core\_ActionWebhookTest' must have a first parameter of type 'DBObject', but it has Combodo\iTop\Core\WebResponse instead.", + 'sErrorMessage' => "Combodo\iTop\Test\UnitTest\Core\_ActionWebhookTest' must have a parameter of type 'DBObject', but it has Combodo\iTop\Core\WebResponse instead.", ], 'Check callback signature with correct type but incorrect order on object' => [ 'sResponseCallback' => '$this->CallBackWebhookTwoParamsCorrectButWrongOrderOnObject', 'expectedException' => true, - 'sErrorMessage' => "Combodo\iTop\Test\UnitTest\Core\_ActionWebhookTest' must have a first parameter of type 'Combodo\iTop\Core\WebResponse', but it has ActionWebhook instead.", + 'sErrorMessage' => "Combodo\iTop\Test\UnitTest\Core\_ActionWebhookTest' must have a parameter of type 'Combodo\iTop\Core\WebResponse', but it has ActionWebhook instead.", ], 'Check callback signature with correct type' => [ 'sResponseCallback' => _ActionWebhookTest::class.'::CallBackWebhookThreeParamsCorrect',