Skip to content

Commit

Permalink
Merge pull request #393 from HubSpot/feature/SignatureUtil
Browse files Browse the repository at this point in the history
add Sinature Util and Webhooks Util is deprecated
  • Loading branch information
ksvirkou-hubspot authored Aug 11, 2022
2 parents a9abbf7 + 0e4a374 commit bd2355e
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 1 deletion.
94 changes: 94 additions & 0 deletions src/Utils/Signature.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<?php

namespace SevenShores\Hubspot\Utils;

use UnexpectedValueException;

class Signature
{
/**
* Reject the request if the timestamp is older than 5 minutes. Otherwise, proceed with validating the signature.
* 5 minutes in milliseconds.
*/
public const MAX_ALLOWED_TIMESTAMP = 300000;

/**
* Validation of Hubspot Signature.
*
* @param array<signature: string, secret: string, requestBody: string, httpUri: string, httpMethod: string, timestamp?: string, signatureVersion?: string, checkTimestamp?: string> $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<signature: string, secret: string, requestBody: string, httpUri: string, httpMethod: string, timestamp?: string, signatureVersion: string> $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}");
}
}
4 changes: 3 additions & 1 deletion src/Utils/Webhooks.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
45 changes: 45 additions & 0 deletions tests/unit/Utils/SignatureTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

namespace SevenShores\Hubspot\Tests\Unit\Utils;

use SevenShores\Hubspot\Utils;

/**
* @internal
* @coversNothing
*/
class SignatureTest extends \PHPUnit\Framework\TestCase
{
protected $secret = 'clientSecret';
protected $requestBody = 'SomeBody';

/** @test */
public function validationHubspotSignatureValidData()
{
$result = Utils\Signature::isValid([
'signature' => 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
);
}
}

0 comments on commit bd2355e

Please sign in to comment.