diff --git a/src/Utils/Signature.php b/src/Utils/Signature.php new file mode 100644 index 00000000..2cfbf26b --- /dev/null +++ b/src/Utils/Signature.php @@ -0,0 +1,94 @@ + $options + * @param string $secret['signature'] hubspot signarute + * @param string $secret['secret'] the Secret of your app + * @param string $secret['requestBody'] the request body include the payload of the request as a JSON string + * @param string $secret['httpUri'] the full URL of the incoming request, including the http:// prefix, the path of your endpoint, and any query parameters + * @param string $secret['httpMethod'] the method of the incoming request, such as GET or POST + * @param string $secret['timestamp'] a unix timestamp of the request, provided in the X-HubSpot-Request-Timestamp header (Reject the request if the timestamp is older than 5 minutes) + * @param string $secret['signatureVersion'] signature version (V1, V2 or V3) + * @param bool $secret['checkTimestamp'] check timestamp or not (default value true) + */ + public static function isValid( + array $options + ): bool { + $signatureVersion = static::getOptionOrThrow($options, 'signatureVersion', 'v1'); + + if ('v3' === $signatureVersion && static::getOptionOrThrow($options, 'checkTimestamp', true)) { + $currentTimestamp = Timestamp::getCurrentTimestampWithMilliseconds(); + $timestamp = (int) static::getOptionOrThrow($options, 'timestamp'); + if (($currentTimestamp - $timestamp) > static::MAX_ALLOWED_TIMESTAMP) { + return false; + } + } + + $hash = static::getHashedSignature($signatureVersion, $options); + + return hash_equals(static::getOptionOrThrow($options, 'signature'), $hash); + } + + /** + * Get hashed signature. + * + * @param string $signatureVersion signature version (V1, V2 or V3) + * @param array $options + * @param string $secret['secret'] the Secret of your app + * @param string $secret['requestBody'] the request body include the payload of the request as a JSON string + * @param string $secret['httpUri'] the full URL of the incoming request, including the http:// prefix, the path of your endpoint, and any query parameters + * @param string $secret['httpMethod'] the method of the incoming request, such as GET or POST + * @param string $secret['timestamp'] a unix timestamp of the request, provided in the X-HubSpot-Request-Timestamp header + */ + public static function getHashedSignature( + string $signatureVersion, + array $options + ): string { + switch ($signatureVersion) { + case 'v1': + $sourceString = static::getOptionOrThrow($options, 'secret').static::getOptionOrThrow($options, 'requestBody'); + + return hash('sha256', $sourceString); + + case 'v2': + $sourceString = static::getOptionOrThrow($options, 'secret').static::getOptionOrThrow($options, 'httpMethod').static::getOptionOrThrow($options, 'httpUri').static::getOptionOrThrow($options, 'requestBody'); + + return hash('sha256', $sourceString); + + case 'v3': + $sourceString = static::getOptionOrThrow($options, 'httpMethod').static::getOptionOrThrow($options, 'httpUri').static::getOptionOrThrow($options, 'requestBody').static::getOptionOrThrow($options, 'timestamp'); + + return base64_encode(hash_hmac('sha256', $sourceString, static::getOptionOrThrow($options, 'secret'), true)); + + default: + throw new UnexpectedValueException("Not supported signature version: {$signatureVersion}"); + } + } + + protected static function getOptionOrThrow(array $options, string $name, $default = null) + { + if (array_key_exists($name, $options)) { + return $options[$name]; + } + + if (null !== $default) { + return $default; + } + + throw new UnexpectedValueException("Not provided {$name}"); + } +} diff --git a/src/Utils/Webhooks.php b/src/Utils/Webhooks.php index 932c1a1a..f085110a 100644 --- a/src/Utils/Webhooks.php +++ b/src/Utils/Webhooks.php @@ -6,7 +6,9 @@ class Webhooks { /** * Validation of Hubspot Signature. - * + * + * @deprecated + * * @param string $signature hubspot signarute * @param string $secret the Secret of your app * @param string $requestBody a set of scopes that your app will need access to diff --git a/tests/unit/Utils/SignatureTest.php b/tests/unit/Utils/SignatureTest.php new file mode 100644 index 00000000..653c1c4b --- /dev/null +++ b/tests/unit/Utils/SignatureTest.php @@ -0,0 +1,45 @@ + hash('sha256', $this->secret.$this->requestBody), + 'secret' => $this->secret, + 'requestBody' => $this->requestBody + ]); + + $this->assertEquals( + true, + $result + ); + } + + /** @test */ + public function validationHubspotSignatureInvalidData() + { + $result = Utils\Signature::isValid([ + 'signature' => hash('sha256', $this->secret.$this->requestBody.'1'), + 'secret' => $this->secret, + 'requestBody' => $this->requestBody + ]); + + $this->assertEquals( + false, + $result + ); + } +}