WebhookRequestValidator.php 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Signifyd\Model\SignifydGateway\Response;
  7. use Magento\Framework\Json\DecoderInterface;
  8. use Magento\Signifyd\Model\Config;
  9. /**
  10. * Validates webhook request.
  11. *
  12. */
  13. class WebhookRequestValidator
  14. {
  15. /**
  16. * Allowed topic identifiers which will be sent in the X-SIGNIFYD-TOPIC header of the webhook.
  17. *
  18. * @var array
  19. */
  20. private $allowedTopicValues = [
  21. 'cases/creation',
  22. 'cases/rescore',
  23. 'cases/review',
  24. 'guarantees/completion',
  25. 'cases/test'
  26. ];
  27. /**
  28. * @var Config
  29. */
  30. private $config;
  31. /**
  32. * @var DecoderInterface
  33. */
  34. private $decoder;
  35. /**
  36. * @param Config $config
  37. * @param DecoderInterface $decoder
  38. */
  39. public function __construct(
  40. Config $config,
  41. DecoderInterface $decoder
  42. ) {
  43. $this->config = $config;
  44. $this->decoder = $decoder;
  45. }
  46. /**
  47. * Validates webhook request.
  48. *
  49. * @param WebhookRequest $webhookRequest
  50. * @return bool
  51. */
  52. public function validate(WebhookRequest $webhookRequest)
  53. {
  54. $body = $webhookRequest->getBody();
  55. $eventTopic = $webhookRequest->getEventTopic();
  56. $hash = $webhookRequest->getHash();
  57. return $this->isValidTopic($eventTopic)
  58. && $this->isValidBody($body)
  59. && $this->isValidHash($eventTopic, $body, $hash);
  60. }
  61. /**
  62. * Checks if value of topic identifier is in allowed list
  63. *
  64. * @param string $topic topic identifier.
  65. * @return bool
  66. */
  67. private function isValidTopic($topic)
  68. {
  69. return in_array($topic, $this->allowedTopicValues);
  70. }
  71. /**
  72. * Verifies a webhook request body is valid JSON and not empty.
  73. *
  74. * @param string $body
  75. * @return bool
  76. */
  77. private function isValidBody($body)
  78. {
  79. try {
  80. $decodedBody = $this->decoder->decode($body);
  81. } catch (\Exception $e) {
  82. return false;
  83. }
  84. return !empty($decodedBody);
  85. }
  86. /**
  87. * Verifies a webhook request has in fact come from SIGNIFYD.
  88. *
  89. * @param string $eventTopic
  90. * @param string $body
  91. * @param string $hash
  92. * @return bool
  93. */
  94. private function isValidHash($eventTopic, $body, $hash)
  95. {
  96. // In the case that this is a webhook test, the encoding ABCDE is allowed
  97. $apiKey = $eventTopic == 'cases/test' ? 'ABCDE' : $this->config->getApiKey();
  98. $actualHash = base64_encode(hash_hmac('sha256', $body, $apiKey, true));
  99. return $hash === $actualHash;
  100. }
  101. }